/* ============================================================
   app.css — application styles, extracted from index.html.
   Loads after ui-theme.css (tokens + .ui-* system).
   ============================================================ */

/* ===== section 1 (index.html extraction order preserved) ===== */
        :root {
            --toolbar-height: 36px;
            /* Canonical accent = #64C8FF (rgba(var(--ui-accent-rgb))) — the --ui-*
               tokens from Themes.js are the source of truth; these mirrors
               exist only for pre-theme-boot paint and match it exactly. */
        }
        
        /* overscroll-behavior: none blocks pull-to-refresh AND the Chrome
           Android edge-swipe back-navigation — a pan near the screen edge must
           move the canvas, not leave the app. */
        html { overscroll-behavior: none; }
        body { margin: 0; overflow: hidden; background: #0a0f14; color: white; cursor: default; font-family: var(--ui-font); touch-action: none; font-weight: 400; overscroll-behavior: none;
            /* iOS paints a grey box over anything you tap. It is meant for
               links in a document; on a canvas app every tap flashes. The
               property inherits, so this one line covers the whole UI. */
            -webkit-tap-highlight-color: transparent; }

        /* The canvas is a gesture surface, not a document: a long press there
           is ours (multi-select, curve point editing), and iOS must not answer
           it with its own callout or selection magnifier. Images inside panels
           deliberately keep the native long-press menu — "Save image" is a
           real thing to want. */
        #gameCanvas {
            -webkit-touch-callout: none;
            -webkit-user-select: none;
            user-select: none;
        }
        
        /* Unified sub-toolbar button styles */
        .subtoolbar-btn {
            background: rgba(var(--ui-primary-rgb), 0.5);
            color: rgba(var(--ui-text-rgb), 0.85);
            border: 2px solid var(--ui-border);
            border-radius: 5px;
            padding: 6px 12px;
            font-size: 13px;
            font-weight: 500;
            cursor: pointer;
            transition: all 0.15s ease;
            font-family: var(--ui-font);
        }
        .subtoolbar-btn:hover {
            filter: brightness(1.2);
            border-color: var(--ui-border-accent);
            color: var(--ui-text-bright);
            box-shadow: 0 0 12px rgba(var(--ui-accent-rgb), 0.45);
            transform: scale(1.05);
        }
        .subtoolbar-btn:active {
            background: rgba(var(--ui-primary-rgb), 0.55);
            transform: scale(0.97);
        }
        .subtoolbar-btn.active, .subtoolbar-btn.selected {
            /* Selection = amber (one selection colour app-wide) */
            background: rgba(255, 169, 77, 0.25);
            border-color: var(--ui-selected-bright, #FFC078);
            color: var(--ui-selected-bright, #FFC078);
            box-shadow: 0 0 10px rgba(255, 169, 77, 0.3);
        }
        .subtoolbar-btn-danger {
            background: rgba(80, 30, 30, 0.5);
            color: #ff8080;
            border: 1px solid rgba(255, 100, 100, 0.25);
            border-radius: 5px;
            padding: 6px 12px;
            font-size: 13px;
            font-weight: 500;
            cursor: pointer;
            transition: all 0.15s ease;
            font-family: var(--ui-font);
        }
        .subtoolbar-btn-danger:hover {
            background: rgba(100, 30, 30, 0.6);
            border-color: rgba(255, 100, 100, 0.4);
        }
        .subtoolbar-divider {
            width: 1px;
            height: 18px;
            background: rgba(var(--ui-accent-rgb), 0.2);
            margin: 0 2px;
        }
        
        /* Draw toolbar compact icon buttons */
        .draw-tool-btn {
            background: rgba(var(--ui-primary-rgb), 0.4);
            color: rgba(255, 255, 255, 0.75);
            border: 2px solid var(--ui-border);
            border-radius: 5px;
            padding: 5px 7px;
            font-size: 12px;
            cursor: pointer;
            transition: all 0.15s ease;
            display: flex;
            align-items: center;
            justify-content: center;
            line-height: 1;
        }
        .draw-tool-btn:hover {
            background: rgba(40, 80, 100, 0.6);
            border-color: var(--ui-border-accent);
            color: #fff;
        }
        .draw-tool-btn:active {
            transform: scale(0.97);
        }
        .draw-tool-btn.active {
            background: rgba(255, 120, 0, 0.35);
            border-color: rgba(255, 160, 0, 0.8);
            color: #ff9500;
            box-shadow: 0 0 10px rgba(255, 120, 0, 0.3);
        }
        .draw-tool-btn-danger {
            color: rgba(255, 120, 120, 0.8);
        }
        .draw-tool-btn-danger:hover {
            background: rgba(100, 30, 30, 0.5);
            border-color: rgba(255, 100, 100, 0.3);
        }
        
        /* Draw mode active indicator — orange screen border */
        #drawModeOverlay {
            display: none;
            position: fixed;
            inset: 0;
            pointer-events: none;
            z-index: 99;
            border: 2px solid rgba(255, 140, 30, 0.6);
            box-shadow: inset 0 0 30px rgba(255, 140, 30, 0.08);
            border-radius: 0;
        }
        #drawModeOverlay.active {
            display: block;
        }
        
        /* Modal animation keyframes */
        @keyframes modalFadeIn {
            from { opacity: 0; }
            to { opacity: 1; }
        }
        @keyframes modalFadeOut {
            from { opacity: 1; }
            to { opacity: 0; }
        }
        @keyframes modalContentIn {
            from { opacity: 0; transform: scale(0.95) translateY(10px); }
            to { opacity: 1; transform: scale(1) translateY(0); }
        }
        @keyframes modalContentOut {
            from { opacity: 1; transform: scale(1) translateY(0); }
            to { opacity: 0; transform: scale(0.95) translateY(10px); }
        }
        @keyframes slideInRight {
            from { opacity: 0; transform: translateX(30px); }
            to { opacity: 1; transform: translateX(0); }
        }
        @keyframes slideOutRight {
            from { opacity: 1; transform: translateX(0); }
            to { opacity: 0; transform: translateX(30px); }
        }
        @keyframes slideInLeft {
            from { opacity: 0; transform: translateX(-30px); }
            to { opacity: 1; transform: translateX(0); }
        }
        @keyframes slideOutLeft {
            from { opacity: 1; transform: translateX(0); }
            to { opacity: 0; transform: translateX(-30px); }
        }


        /* ── Scrollbar, app-wide ────────────────────────────────────────────
           ONE definition. This used to be re-declared per panel (#nodeComments
           Panel, #aiChatPanel, #orgSettingsModal — three byte-identical
           copies), which meant every new scrolling surface had to remember to
           opt in, and anything that didn't got the raw OS scrollbar: the chat
           node's DOM overlay showed a white Windows bar inside a dark card.
           Theming the baseline removes the whole class of bug — a new surface
           is themed because it exists, not because someone remembered. */
        ::-webkit-scrollbar { width: 6px; height: 6px; }
        ::-webkit-scrollbar-track { background: rgba(var(--ui-surface-rgb), 0.4); border-radius: 3px; }
        ::-webkit-scrollbar-thumb { background: rgba(var(--ui-accent-rgb), 0.3); border-radius: 3px; }
        ::-webkit-scrollbar-thumb:hover { background: rgba(var(--ui-accent-rgb), 0.5); }
        ::-webkit-scrollbar-corner { background: transparent; }
        /* Firefox has no pseudo-elements for this — two properties, inherited. */
        * { scrollbar-width: thin; scrollbar-color: rgba(var(--ui-accent-rgb), 0.3) rgba(var(--ui-surface-rgb), 0.4); }

        /* AI Assistant button glow when API key is configured — a steady
           glow, not a pulse: chrome that breathes while nothing happens reads
           as a notification that is not there (maturity audit 3.10.2026). */
        /* The in-app assistant hidden (config.js FEATURES.AI_ASSISTANT): people bring their own AI. */
        .no-ai-assistant #aiAssistantBtn,
        .no-ai-assistant .mobile-menu-btn[data-action="aiAssistant"] { display: none !important; }
        /* Kuukan's own AI tools closed — no credits, no key of their own (config.js
           houseAiOn): what makes with it goes. Their keys and credits (.house-ai in
           Settings and Plans) stay: that is the way to open them. */
        .no-house-ai .qc-btn[data-type="ai"],
        .no-house-ai .qc-btn[data-type="aichat"],
        .no-house-ai .qc-btn[data-type="ai3d"],
        .no-house-ai #toolbarCreditBadge { display: none !important; }
        .house-ai-off { display: none !important; }   /* the "your own AI instead" line, from when the tools were away */
        #aiAssistantBtn img { opacity: 0.85; vertical-align: middle; }
        #aiAssistantBtn.ai-active img {
            opacity: 1;
            filter: drop-shadow(0 0 6px rgba(212,165,116,0.6));
        }
        /* AI Assistant panel — custom scrollbar */
        #aiAssistantPanel ::-webkit-scrollbar { width: 6px; }
        #aiAssistantPanel ::-webkit-scrollbar-track { background: rgba(var(--ui-surface-rgb), 0.4); border-radius: 3px; }
        #aiAssistantPanel ::-webkit-scrollbar-thumb { background: rgba(var(--ui-accent-rgb), 0.3); border-radius: 3px; }
        #aiAssistantPanel ::-webkit-scrollbar-thumb:hover { background: rgba(var(--ui-accent-rgb), 0.5); }
        #aiAssistantPanel * { scrollbar-width: thin; scrollbar-color: rgba(var(--ui-accent-rgb), 0.3) rgba(var(--ui-surface-rgb), 0.4); }

        /* Custom scrollbar for search panel */
        #searchResults::-webkit-scrollbar {
            width: 6px;
        }
        #searchResults::-webkit-scrollbar-track {
            background: rgba(var(--ui-surface-rgb), 0.4);
            border-radius: 3px;
        }
        #searchResults::-webkit-scrollbar-thumb {
            background: rgba(var(--ui-accent-rgb), 0.3);
            border-radius: 3px;
        }
        #searchResults::-webkit-scrollbar-thumb:hover {
            background: rgba(var(--ui-accent-rgb), 0.5);
        }
        /* Firefox scrollbar for search */
        #searchResults {
            scrollbar-width: thin;
            scrollbar-color: rgba(var(--ui-accent-rgb), 0.3) rgba(var(--ui-surface-rgb), 0.4);
        }

        /* Emoji picker (chat input, reactions, comments): the category LIST scrolls (so
           the search bar above it stays fixed) with a neutral, Discord-like thin scrollbar. */
        .emoji-categories-container { max-height: 240px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: rgba(255,255,255,0.14) transparent; }
        .emoji-categories-container::-webkit-scrollbar { width: 8px; }
        .emoji-categories-container::-webkit-scrollbar-track { background: transparent; }
        .emoji-categories-container::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.14); border-radius: 4px; border: 2px solid transparent; background-clip: padding-box; }
        .emoji-categories-container::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.26); background-clip: padding-box; }

        /* Chat: subtle full-row highlight on hover (Discord-style) */
        .chat-msg { border-radius: 6px; transition: background 0.1s ease; }
        .chat-msg:hover { background: rgba(255, 255, 255, 0.035); }
        /* Grouped follow-up messages: the time hides in the avatar gutter and
           surfaces on row hover (Discord-style). Right-aligned + small so it
           tucks under where the avatar would be. */
        .plain-gutter-time {
            font-family: var(--ui-font-mono, monospace);
            font-size: 10px;
            color: rgba(var(--ui-text-rgb), 0.4);
            opacity: 0;
            transition: opacity 0.1s ease;
            white-space: nowrap;
            align-self: flex-start;
            padding-top: 4px;
            -webkit-user-select: none;
            user-select: none;
        }
        .chat-msg:hover .plain-gutter-time { opacity: 1; }
        /* A >15 min pause inside a group keeps its clock visible without
           breaking the group (11a) — the pause is information, the repeated
           header would have been noise. */
        .plain-gutter-time--always { opacity: 1; }

        /* Custom scrollbar for activity panel */
        #notifList::-webkit-scrollbar { width: 6px; }
        #notifList::-webkit-scrollbar-track { background: rgba(var(--ui-surface-rgb), 0.4); border-radius: 3px; }
        #notifList::-webkit-scrollbar-thumb { background: rgba(var(--ui-accent-rgb), 0.3); border-radius: 3px; }
        #notifList::-webkit-scrollbar-thumb:hover { background: rgba(var(--ui-accent-rgb), 0.5); }
        /* Item chip for node types with no media embed (timer, poll, text…).
           Sized as a real chip, not a footnote: it's a link to a THING in the
           world, and at 12px/1px padding it read as incidental text and was a
           poor tap target on touch. */
        .chat-node-link { display: inline-flex; align-items: center; gap: 5px; vertical-align: middle; cursor: pointer; color: #D26B3F; background: rgba(210,107,63,0.12); border: 1px solid rgba(210,107,63,0.3); border-radius: 8px; padding: 5px 10px; font-size: 13px; font-weight: 500; line-height: 1.3; min-height: 28px; box-sizing: border-box; white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis; transition: background 0.15s, border-color 0.15s; }
        .chat-node-link:hover { background: rgba(210,107,63,0.28); border-color: rgba(210,107,63,0.55); }
        /* The item this link points at was deleted (it's in the trash, so this
           is recoverable — clicking offers to restore). Drained of colour so
           it reads as a tombstone rather than a live link, but still legible:
           the message it sits in is history and must stay readable. */
        .chat-node-deleted { opacity: 0.55; filter: grayscale(1); }
        .chat-node-deleted:hover { opacity: 0.8; }
        .chat-node-deleted.chat-node-link { text-decoration: line-through; }
        /* Inline image-node block: no tinted background, no padding — let the
           image breathe at its natural size against the bubble. */
        .chat-node-image-block { background: none; padding: 0; }
        /* Chat panel header buttons — share a single style for tabs, lock, close. */
        .kk-tab-btn {
            width: 40px; height: 40px; padding: 0;
            background: rgba(var(--ui-surface-rgb),0.6);
            border: 2px solid var(--ui-border);
            border-radius: 8px;
            color: var(--ui-text-muted);
            cursor: pointer;
            display: flex; align-items: center; justify-content: center;
            flex-shrink: 0;
            position: relative;
            transition: color 0.15s, background 0.15s, border-color 0.15s, box-shadow 0.15s, transform 0.1s;
        }
        .kk-tab-btn:hover {
            color: var(--ui-text-bright);
            background: var(--ui-surface-hover);
            border-color: var(--ui-border-accent);
            box-shadow: 0 0 12px rgba(var(--ui-accent-rgb), 0.45);
            transform: scale(1.05);
        }
        .kk-tab-btn.msg-tab-active {
            background: rgba(var(--ui-accent-rgb),0.22);
            color: #fff;
            border-color: var(--ui-border-accent);
        }
        .space-dropdown-list { width: 100%; max-height: 192px; overflow-y: auto; background: rgba(var(--ui-surface-rgb),0.6); border: 2px solid var(--ui-border); border-radius: 6px; margin-bottom: 16px; font-size: 14px; }
        .space-dropdown-list::-webkit-scrollbar { width: 6px; }
        .space-dropdown-list::-webkit-scrollbar-track { background: rgba(var(--ui-surface-rgb),0.4); border-radius: 3px; }
        .space-dropdown-list::-webkit-scrollbar-thumb { background: rgba(var(--ui-accent-rgb),0.3); border-radius: 3px; }
        .space-dropdown-list::-webkit-scrollbar-thumb:hover { background: rgba(var(--ui-accent-rgb),0.5); }
        .space-dropdown-placeholder { padding: 10px 12px; color: #555; font-style: italic; }
        .space-dropdown-item { padding: 8px 12px; color: var(--ui-text); cursor: pointer; transition: background 0.1s; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
        .space-dropdown-item:hover { background: rgba(var(--ui-accent-rgb),0.1); color: var(--ui-text-bright); }
        .space-dropdown-divider { padding: 5px 12px; color: var(--ui-text-muted); font-size: 11px; pointer-events: none; border-top: 1px solid var(--ui-border-faint); margin-top: 2px; }
        /* (#spaceActivityList / #myActivityList / .custom-scrollbar each
           re-declared the same scrollbar; the app-wide rule at the top of this
           file now covers them. The .custom-scrollbar class in the markup is
           harmless and left in place.) */
        
        /* Modern minimal toolbar - teal theme, light weight */
        #ui {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            background: rgba(var(--ui-surface-rgb), 0.88);
            backdrop-filter: blur(12px);
            -webkit-backdrop-filter: blur(12px);
            border-bottom: 2px solid var(--ui-border);
            padding: 7px 16px;
            pointer-events: none;
            /* Above the location HUD widget (1200) so toolbar dropdowns (burger,
               universe menu) render over it. #ui is a stacking context — a
               dropdown's own z-index only competes INSIDE it, so this root-level
               value is what actually beats the widget. Still below modals (5000+). */
            z-index: calc(var(--z-hud) + 100);
        }
        
        .toolbar-container {
            display: flex;
            gap: 5px;
            align-items: center;
            pointer-events: auto;
            flex-wrap: wrap;
        }

        /* Comfort scale — the UI chrome reads better ~10% larger (found via an
           accidental 125% browser-zoom A/B test; 110% was the pick). Applies
           to DOM FRAMES only, desktop only: the canvas and canvas-anchored
           overlays must keep 1:1 CSS px ↔ client px or every screen-space
           hit test skews. Browser page zoom stacks on top as before. */
        @media (min-width: 769px) {
            .toolbar-container,
            #usersInSpace,
            #locationContainer,
            #discoverKuukanPanel {
                zoom: 1.1;
            }
        }
        
        .toolbar-divider {
            width: 1px;
            height: 18px;
            background: rgba(var(--ui-accent-rgb), 0.2);
            margin: 0 8px;
        }
        
        .toolbar-logo {
            display: inline-flex;
            align-items: center;
            gap: 6px;
            margin-right: 12px;
        }

        .toolbar-logo img {
            height: 26px;
            width: auto;
            display: block;
        }
        
        /* Toolbar button style - clean, light */
        .toolbar-btn {
            pointer-events: auto;
            height: 30px;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            box-sizing: border-box;
            background: var(--ui-toolbar-btn-bg, rgba(var(--ui-primary-rgb), 0.5));
            color: var(--ui-btn-text);
            border: 2px solid var(--ui-border);
            border-radius: 5px;
            padding: 5px 11px;
            font-size: 12px;
            font-weight: 400;
            transition: all 0.15s ease;
            font-family: var(--ui-font);
            cursor: pointer;
        }
        .toolbar-btn:hover {
            filter: brightness(1.2);
            border-color: var(--ui-border-accent);
            color: var(--ui-text-bright);
            box-shadow: 0 0 12px rgba(var(--ui-accent-rgb), 0.45);
            transform: scale(1.05);
        }
        .toolbar-btn:active {
            background: var(--ui-toolbar-btn-bg, rgba(var(--ui-primary-rgb), 0.55));
            transform: scale(0.97);
        }
        .toolbar-btn.active {
            background: rgba(255, 120, 0, 0.35);
            border-color: rgba(255, 160, 0, 0.8);
            color: #ff9500;
            box-shadow: 0 0 10px rgba(255, 120, 0, 0.3);
        }
        
        /* Tooltip system */
        /* The tooltip is a ::after of its trigger — trapped inside the
           trigger's stacking context, so a later sibling (e.g. the search
           row under the chat tabs) could paint OVER it. Raising the
           hovered trigger itself lifts the bubble above its neighbours. */
        [data-tooltip]:hover {
            z-index: var(--z-top);
        }
        [data-tooltip] {
            position: relative;
        }
        [data-tooltip]::after {
            content: attr(data-tooltip);
            position: absolute;
            bottom: calc(100% + 8px);
            left: 50%;
            transform: translateX(-50%) translateY(4px);
            background: rgba(var(--ui-surface-rgb), 0.95);
            color: var(--ui-text);
            font-size: 11px;
            font-weight: 400;
            font-family: var(--ui-font);
            padding: 5px 10px;
            border-radius: 6px;
            white-space: nowrap;
            pointer-events: none;
            opacity: 0;
            transition: opacity 0.15s ease, transform 0.15s ease;
            z-index: var(--z-top);
            border: 2px solid var(--ui-border);
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
        }
        [data-tooltip]:hover::after {
            opacity: 1;
            transform: translateX(-50%) translateY(0);
            transition-delay: 0.15s;
        }
        /* A finger has no hover. A phone keeps :hover on the last thing
           tapped, so a tapped button's bubble stayed on screen until the next
           tap somewhere else ("Toggle minimap" hung beside the map, Mikko
           3.10.2026). Where nothing can hover, these bubbles never show. */
        @media (hover: none) {
            [data-tooltip]::after { display: none !important; }
        }
        /* Long-copy tooltips wrap instead of stretching across the screen.
           Anchored to the label's LEFT edge rather than centred on it: these
           live in dialogs that scroll, and a scroll container clips whatever
           leaves it — a 240px box centred on a left-hand label hung outside
           the panel and got cut in half. Growing rightward keeps it inside. */
        [data-tooltip-wrap]::after {
            white-space: normal;
            width: 240px;
            line-height: 1.45;
            text-align: left;
            left: 0;
            transform: translateY(4px);
        }
        [data-tooltip-wrap]:hover::after {
            transform: translateY(0);
        }
        /* Bottom tooltips for special cases */
        [data-tooltip-pos="bottom"]::after {
            bottom: auto;
            top: calc(100% + 8px);
            transform: translateX(-50%) translateY(-4px);
        }
        [data-tooltip-pos="bottom"]:hover::after {
            transform: translateX(-50%) translateY(0);
            transition-delay: 0.15s;
        }
        /* Edge anchoring. A tooltip is centred on its element, so anything
           within half a tooltip of the window edge gets cut off by it —
           common enough that it needs a name rather than a workaround each
           time. Align it to the element's near edge instead of its middle
           and it grows inwards, where there is room. */
        [data-tooltip-align="left"]::after,
        [data-tooltip-align="left"]:hover::after {
            left: 0;
            transform: translateX(0) translateY(var(--tt-y, 0));
        }
        [data-tooltip-align="right"]::after,
        [data-tooltip-align="right"]:hover::after {
            left: auto; right: 0;
            transform: translateX(0) translateY(var(--tt-y, 0));
        }
        [data-tooltip-align]::after { --tt-y: 4px; }
        [data-tooltip-align][data-tooltip-pos="bottom"]::after { --tt-y: -4px; }
        [data-tooltip-align]:hover::after { --tt-y: 0px; }
        /* Message-action tooltips sit at the right edge of a chat/comment message —
           right-align them so the rightmost button's tooltip extends LEFT instead of
           overflowing the panel's right edge (where overflow:auto would clip it). */
        .message-actions [data-tooltip]::after {
            left: auto; right: 0;
            transform: translateX(0) translateY(-4px);
        }
        .message-actions [data-tooltip]:hover::after {
            transform: translateX(0) translateY(0);
        }
        /* Theme swatches are little balls (26.8.) — the app already speaks
           State Ball, so the picker does too. Each button's gradient stays
           in its inline style; the shape and the top-left sheen live here so
           one rule rounds them all. No overflow:hidden — it would clip the
           tooltip ::after. The sheen uses ::before (tooltips own ::after). */
        .theme-btn {
            border-radius: 50% !important;
            position: relative;
        }
        .theme-btn::before {
            content: '';
            position: absolute;
            inset: 0;
            border-radius: 50%;
            background: radial-gradient(circle at 32% 28%, rgba(255,255,255,0.35), rgba(255,255,255,0.07) 40%, transparent 58%);
            pointer-events: none;
        }
        /* Swatch tooltips grow toward where the room is. The row packs left,
           so the left half grows rightwards (the data-tooltip-align="left"
           attribute) — but on the row's right end that runs past the dialog
           edge and its scroll container clips the tip. From the 5th ball on,
           flip: grow LEFT instead. The id+class+nth beats the attribute rule. */
        #themeSelector .theme-btn:nth-child(n+5)::after,
        #themeSelector .theme-btn:nth-child(n+5):hover::after {
            left: auto; right: 0;
            transform: translateX(0) translateY(var(--tt-y, 0));
        }
        /* The location widget hugs the right screen edge — same treatment:
           right-align so the tooltip grows LEFT instead of off-screen. */
        #locationContainer [data-tooltip]::after {
            left: auto; right: 0;
            transform: translateX(0) translateY(-4px);
        }
        #locationContainer [data-tooltip]:hover::after {
            transform: translateX(0) translateY(0);
        }
        /* Toolbar tooltips: show below (toolbar is at top of viewport) */
        .toolbar-container [data-tooltip]::after {
            bottom: auto;
            top: calc(100% + 8px);
            transform: translateX(-50%) translateY(-4px);
        }
        .toolbar-container [data-tooltip]:hover::after {
            transform: translateX(-50%) translateY(0);
            transition-delay: 0.15s;
        }
        /* Right tooltips for floating buttons */
        [data-tooltip-pos="right"]::after {
            bottom: auto;
            left: calc(100% + 10px);
            top: 50%;
            transform: translateY(-50%) translateX(-4px);
        }
        [data-tooltip-pos="right"]:hover::after {
            transform: translateY(-50%) translateX(0);
            transition-delay: 0.15s;
        }
        /* Left tooltips for burger menu / right-aligned dropdowns */
        [data-tooltip-pos="left"]::after {
            bottom: auto;
            left: auto;
            right: calc(100% + 10px);
            top: 50%;
            transform: translateY(-50%) translateX(4px);
        }
        [data-tooltip-pos="left"]:hover::after {
            transform: translateY(-50%) translateX(0);
            transition-delay: 0.15s;
        }

        /* People-list row buttons sit at the right edge of a scrolling
           list, which clips whatever leaves it: a tip above the button was
           cut off on the top row, a centred one past the right edge. The tip
           opens to the button's LEFT, over its own row, and wraps a long
           destination name ("Teleport to user · LongSpaceName"). */
        #friendsList [data-tooltip]::after,
        #orgMembersList [data-tooltip]::after,
        #friendRequestsList [data-tooltip]::after,
        #friendSearchResults [data-tooltip]::after {
            bottom: auto;
            left: auto;
            right: calc(100% + 8px);
            top: 50%;
            transform: translateY(-50%) translateX(4px);
            max-width: 220px;
            width: max-content;
            white-space: normal;
            text-align: left;
            line-height: 1.35;
        }
        #friendsList [data-tooltip]:hover::after,
        #orgMembersList [data-tooltip]:hover::after,
        #friendRequestsList [data-tooltip]:hover::after,
        #friendSearchResults [data-tooltip]:hover::after {
            transform: translateY(-50%) translateX(0);
        }

        /* Icon-only buttons */
        .toolbar-btn-icon {
            padding: 5px 9px;
            font-size: 14px;
        }

        /* AI-group tint: pairs AI Assistant button and credit badge visually */
        /* AI group (assistant button + credit badge) looks like every other
           toolbar button — the Claude mark and ✨ carry the identity.
           .toolbar-btn-ai kept in markup only as a grouping hook. */

        /* ── Top bar path + control weights (option 6a, 24.8.) ──────────
           Three tinted controls, three hues, one job each: accent = Map
           (navigate out), neutral raised = the space you are in (clicking
           it SHARES — the name is the share control), warm = AI. Nothing
           else in the bar carries a fill; utilities are quiet 32px icons.
           One control height (32px) with one exception: the space pill at
           40px, because it is the heading of the page. */
        .map-pill {
            /* Same height and radius as the space pill beside it — the two
               are one path and carry equal weight; that parity is what makes
               Map read as important without needing a fill. Tinted, not
               filled: a solid fill makes Map the loudest thing in the bar.
               The gradient + top inner highlight lift it proud of the bar
               surface instead. */
            height: 40px;
            padding: 0 13px 0 10px;
            gap: 8px;
            margin-right: 5px; /* container gap 5 + this = the 10px the pair wants */
            border: 1px solid rgba(var(--ui-accent-rgb), 0.38);
            background: linear-gradient(180deg, rgba(var(--ui-accent-rgb), 0.18), rgba(var(--ui-accent-rgb), 0.07));
            box-shadow: inset 0 1px 0 0 rgba(var(--ui-accent-rgb), 0.26);
            color: rgba(var(--ui-accent-rgb), 1);
            border-radius: 10px;
            font-size: 13.5px;
            font-weight: 600;
        }
        .map-pill:hover {
            background: linear-gradient(180deg, rgba(var(--ui-accent-rgb), 0.26), rgba(var(--ui-accent-rgb), 0.12));
        }
        /* Label a touch lighter than the glyph — the second line stands, the
           first paints. Falls back to plain accent where color-mix is unknown. */
        .map-pill-label {
            color: rgba(var(--ui-accent-rgb), 1);
            color: color-mix(in srgb, rgb(var(--ui-accent-rgb)) 55%, #eefcff);
        }
        .space-pill {
            height: 40px;
            padding: 0 12px;
            gap: 8px;
            border: 1px solid rgba(var(--ui-text-rgb), 0.12);
            background: rgba(var(--ui-text-rgb), 0.05);
            border-radius: 10px;
            max-width: 250px;
        }
        .space-pill:hover { background: rgba(var(--ui-text-rgb), 0.09); }
        .space-pill-text {
            display: flex;
            flex-direction: column;
            align-items: flex-start;
            gap: 3px;
            min-width: 0;
        }
        #toolbarSpaceName {
            font-size: 14px;
            font-weight: 600;
            letter-spacing: -0.01em;
            line-height: 1;
            max-width: 190px;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
        #toolbarSpaceOwner {
            font-size: 10.5px;
            line-height: 1;
            font-weight: 400;
            color: rgba(var(--ui-text-rgb), 0.62);
        }
        #toolbarSpaceOwner:empty { display: none; }
        .space-pill-share { opacity: 0.5; flex-shrink: 0; }
        /* Quiet utilities: no border, no fill until hovered — the hover
           background makes the invisible 32px hit area visible. */
        .toolbar-util {
            width: 32px;
            height: 32px;
            padding: 0;
            border: none;
            background: transparent;
            color: rgba(var(--ui-text-rgb), 0.55);
            border-radius: 8px;
        }
        .toolbar-util:hover {
            background: rgba(var(--ui-text-rgb), 0.07);
            color: rgba(var(--ui-text-rgb), 1);
        }
        /* Browse lights up while the space holds something you have not
           looked at yet (VisitTracker refreshBrowseNew): the quiet magnifier
           becomes a labelled chip in the NEW glow's green, so the place to
           flip through the new work can be seen from the bar. */
        #searchBtn .browse-new { display: none; }
        #searchBtn.has-new {
            width: auto;
            display: inline-flex;
            align-items: center;
            gap: 5px;
            padding: 0 10px 0 8px;
            color: rgb(60, 230, 110);
            background: rgba(60, 230, 110, 0.12);
            box-shadow: inset 0 0 0 1.5px rgba(60, 230, 110, 0.45);
        }
        #searchBtn.has-new:hover { background: rgba(60, 230, 110, 0.2); }
        #searchBtn.has-new .browse-new { display: inline; font: 600 12px var(--ui-font); letter-spacing: 0.2px; }
        /* The map side speaks the same language: My Spaces / Discover are
           labelled quiet triggers — no box until hovered, same as the icon
           utilities they stand beside. */
        .my-spaces-trigger {
            height: 32px;
            padding: 0 10px;
            border: none;
            background: transparent;
            color: rgba(var(--ui-text-rgb), 0.75);
            border-radius: 8px;
        }
        .my-spaces-trigger:hover,
        .my-spaces-trigger.is-open {
            background: rgba(var(--ui-text-rgb), 0.07);
            color: rgba(var(--ui-text-rgb), 1);
        }
        /* AI is the third tinted control — warm, the orange of the logo
           mark (#D26B3F), so the bar's three fills are three hues with one
           job each and nothing competes with them. */
        .toolbar-btn-ai {
            height: 32px;
            border: 1px solid rgba(210, 107, 63, 0.4);
            background: rgba(210, 107, 63, 0.12);
            border-radius: 8px;
        }
        .toolbar-btn-ai:hover { background: rgba(210, 107, 63, 0.22); }

        /* Thin vertical divider to group related toolbar items */
        .toolbar-divider {
            display: inline-block;
            width: 1px;
            height: 18px;
            background: rgba(var(--ui-accent-rgb), 0.15);
            margin: 0 4px;
            vertical-align: middle;
        }

        /* Select dropdown styling */
        select.toolbar-btn {
            appearance: none;
            -webkit-appearance: none;
            padding-right: 24px;
            background: rgba(var(--ui-primary-rgb), 0.5) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 10 10'%3E%3Cpath fill='rgba(255,255,255,0.6)' d='M5 7L1 3h8z'/%3E%3C/svg%3E") no-repeat right 9px center;
        }
        select.toolbar-btn:hover {
            background: rgba(40, 80, 100, 0.6) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 10 10'%3E%3Cpath fill='rgba(255,255,255,0.9)' d='M5 7L1 3h8z'/%3E%3C/svg%3E") no-repeat right 9px center;
        }
        select.toolbar-btn option {
            background: var(--ui-surface);
            color: var(--ui-text);
        }
        
        /* Dropdown menu items */
        .dropdown-item:hover {
            background: rgba(94, 234, 212, 0.1);
        }

        /* ── My Spaces dropdown (Kuukan style) ── */
        .my-spaces-trigger {
            display: inline-flex;
            align-items: center;
            gap: 7px;
        }
        .my-spaces-trigger-chevron {
            opacity: 0.55;
            transition: transform 0.15s ease;
        }
        .my-spaces-trigger.is-open .my-spaces-trigger-chevron {
            transform: rotate(180deg);
        }
        .my-spaces-panel {
            position: fixed;
            z-index: calc(var(--z-top) - 1);
            min-width: 240px;
            max-width: 360px;
            max-height: 70vh;
            overflow-y: auto;
            overflow-x: hidden;
            padding: 6px 0;
            background: rgba(var(--ui-surface-rgb), 0.97);
            backdrop-filter: blur(16px);
            -webkit-backdrop-filter: blur(16px);
            border: 2px solid var(--ui-border);
            border-radius: 10px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.55);
            color: rgba(var(--ui-text-rgb), 0.9);
            font-size: 13px;
            font-family: var(--ui-font);
            -webkit-user-select: none;
            user-select: none;
        }

        /* ── Kuukan Discover rail — persistent docked "lobby" panel ── */
        #discoverKuukanPanel.discover-rail {
            /* Docked, part of the map's page: below the panels that open over
               it (Messages sits at --z-panel - 1). It borrowed .my-spaces-panel,
               a popover's z-index just under the toasts, and an arriving
               message opened underneath it on the map. */
            z-index: calc(var(--z-panel) - 2);
            left: 12px;
            top: 56px;
            bottom: 14px;
            width: 294px;
            max-width: 294px;
            max-height: none;
            border-radius: 14px;
            padding: 0;
            display: flex;
            flex-direction: column;
            transition: transform 0.28s cubic-bezier(.4,0,.2,1), opacity 0.22s;
            transform: translateX(0);
        }
        #discoverKuukanPanel.discover-rail.collapsed {
            transform: translateX(-115%);
            opacity: 0;
            pointer-events: none;
        }
        .discover-rail-header {
            display: flex; align-items: center; justify-content: space-between;
            padding: 12px 14px 10px; flex: none;
            border-bottom: 1px solid var(--ui-border-faint);
        }
        .discover-rail-title { font-size: 13px; font-weight: 700; letter-spacing: 0.4px; color: rgba(185,228,255,0.96); }
        .discover-rail-collapse {
            background: none; border: none; color: rgba(150,200,235,0.6); cursor: pointer;
            font-size: 17px; padding: 2px 7px; border-radius: 6px; line-height: 1;
        }
        .discover-rail-collapse:hover { background: rgba(var(--ui-accent-rgb),0.12); color: #fff; }
        .discover-rail-body { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 6px 0 8px; }
        /* Thin reopen tab when the rail is collapsed */
        #discoverRailReopen {
            position: fixed; left: 0; top: 90px; z-index: calc(var(--z-top) - 2); display: none;
            background: rgba(var(--ui-surface-rgb),0.97); -webkit-backdrop-filter: blur(16px); backdrop-filter: blur(16px);
            border: 2px solid var(--ui-border-accent); border-left: none;
            border-radius: 0 12px 12px 0; padding: 11px 9px; cursor: pointer;
            color: rgba(185,228,255,0.96); font-size: 17px;
            box-shadow: 0 6px 24px rgba(0,0,0,0.5); transition: background 0.15s;
        }
        #discoverRailReopen:hover { background: rgba(30,46,62,0.98); }
        .my-spaces-panel::-webkit-scrollbar {
            width: 6px;
        }
        .my-spaces-panel::-webkit-scrollbar-track {
            background: rgba(var(--ui-surface-rgb), 0.4);
            border-radius: 3px;
        }
        .my-spaces-panel::-webkit-scrollbar-thumb {
            background: rgba(var(--ui-accent-rgb), 0.3);
            border-radius: 3px;
        }
        .my-spaces-panel::-webkit-scrollbar-thumb:hover {
            background: rgba(var(--ui-accent-rgb), 0.5);
        }
        .my-spaces-panel-header {
            color: rgba(var(--ui-text-rgb), 0.7);
            font-size: 10px;
            text-transform: uppercase;
            letter-spacing: 0.7px;
            padding: 9px 14px 4px;
            pointer-events: none;
        }
        .my-spaces-panel-divider {
            height: 1px;
            background: rgba(var(--ui-accent-rgb), 0.1);
            margin: 4px 8px;
        }
        .my-spaces-panel-item {
            display: flex;
            align-items: center;
            gap: 9px;
            padding: 6px 14px;
            cursor: pointer;
            line-height: 1.3;
            transition: background 0.12s, color 0.12s;
        }
        .my-spaces-panel-item:hover {
            background: rgba(var(--ui-accent-rgb), 0.13);
            color: #fff;
        }
        .my-spaces-panel-item-icon {
            flex-shrink: 0;
            font-size: 13px;
            width: 18px;
            /* SVG + PNG marks sit in one consistent box — centred both ways
               so the lock png and stroke icons align on the same baseline */
            display: inline-flex;
            align-items: center;
            justify-content: center;
        }
        .my-spaces-panel-item-label {
            flex: 1;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
        .my-spaces-panel-item-meta {
            color: rgba(var(--ui-text-rgb), 0.6);
            font-size: 11px;
            flex-shrink: 0;
            margin-left: 6px;
        }
        .my-spaces-panel-item.is-action {
            color: #4ecdc4;
        }
        .my-spaces-panel-item.is-action:hover {
            background: rgba(var(--ui-teal-rgb), 0.15);
            color: #5fdfdf;
        }
        .my-spaces-panel-empty {
            padding: 8px 14px;
            color: rgba(var(--ui-text-rgb), 0.5);
            font-size: 12px;
            font-style: italic;
        }

        /* ── GIF reaction picker (Kuukan style) ── */
        .gif-reaction-picker {
            position: fixed;
            z-index: calc(var(--z-top) - 1);
            width: 340px;
            height: 480px;
            /* Plain block layout — children use absolute positioning so the
               scroll area is reliably constrained between header and footer.
               Flex column with display:grid child kept fighting Chrome's
               height resolution and either over- or under-shot. */
            background: rgba(var(--ui-surface-rgb), 0.97);
            backdrop-filter: blur(16px);
            -webkit-backdrop-filter: blur(16px);
            border: 1px solid rgba(180, 140, 255, 0.25);
            border-radius: 12px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.55);
            color: rgba(var(--ui-text-rgb), 0.9);
            font-family: var(--ui-font);
            -webkit-user-select: none;
            user-select: none;
            overflow: hidden;
        }
        .gif-reaction-close {
            position: absolute;
            top: 6px;
            right: 6px;
            width: 26px;
            height: 26px;
            padding: 0;
            border: none;
            border-radius: 5px;
            background: transparent;
            color: rgba(var(--ui-text-rgb), 0.55);
            font-size: 14px;
            line-height: 1;
            cursor: pointer;
            transition: background 0.12s, color 0.12s;
            z-index: 1;
        }
        .gif-reaction-close:hover {
            background: rgba(180, 140, 255, 0.2);
            color: #fff;
        }
        /* Source tabs — GIFs / Stickers */
        .gif-reaction-source-tabs {
            position: absolute;
            top: 0;
            left: 0;
            right: 36px;                       /* leave space for close button */
            height: 32px;
            display: flex;
            gap: 4px;
            padding: 4px 8px;
            box-sizing: border-box;
            border-bottom: 1px solid rgba(180, 140, 255, 0.08);
        }
        .gif-reaction-source-tab {
            flex: 1;
            padding: 0 10px;
            background: transparent;
            border: 1px solid transparent;
            border-radius: 6px;
            color: rgba(180, 180, 200, 0.55);
            font-size: 11px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.12s;
            font-family: inherit;
        }
        .gif-reaction-source-tab:hover {
            color: rgba(var(--ui-text-rgb), 0.95);
            background: rgba(180, 140, 255, 0.08);
        }
        .gif-reaction-source-tab.active {
            color: var(--ui-text-bright);
            background: rgba(180, 140, 255, 0.16);
            border-color: rgba(180, 140, 255, 0.3);
        }
        .gif-reaction-search-row {
            position: absolute;
            top: 32px;                          /* below source tabs */
            left: 0;
            right: 0;
            height: 48px;
            box-sizing: border-box;
            padding: 10px 12px 8px 12px;
            border-bottom: 1px solid rgba(180, 140, 255, 0.12);
        }
        .gif-reaction-search {
            width: 100%;
            padding: 8px 10px;
            background: rgba(var(--ui-surface-rgb), 0.7);
            border: 1px solid rgba(180, 140, 255, 0.2);
            border-radius: 6px;
            color: var(--ui-text-bright);
            font-size: 13px;
            outline: none;
            box-sizing: border-box;
            transition: border-color 0.12s;
        }
        .gif-reaction-search:focus {
            border-color: rgba(180, 140, 255, 0.5);
        }
        /* Scroll container — flex child that owns the overflow/scrollbar.
           Kept separate from the inner grid so display:grid doesn't fight
           the flex height allocation (a known Chrome quirk). */
        .gif-reaction-grid-scroll {
            position: absolute;
            top: 116px;                /* tabs (32) + search-row (48) + mode-row (36) */
            bottom: 28px;              /* attribution height */
            left: 0;
            right: 0;
            overflow-y: scroll;
            overflow-x: hidden;
            scrollbar-width: thin;     /* Firefox */
            scrollbar-color: rgba(180, 140, 255, 0.6) rgba(var(--ui-surface-rgb), 0.5);
        }
        .gif-reaction-grid-scroll::-webkit-scrollbar {
            width: 12px;
        }
        .gif-reaction-grid-scroll::-webkit-scrollbar-track {
            background: rgba(40, 50, 65, 0.7);
            border-radius: 6px;
            margin: 4px 0;
        }
        .gif-reaction-grid-scroll::-webkit-scrollbar-thumb {
            background: rgba(180, 140, 255, 0.7);
            border-radius: 6px;
            border: 2px solid rgba(var(--ui-surface-rgb), 0.97);
        }
        .gif-reaction-grid-scroll::-webkit-scrollbar-thumb:hover {
            background: rgba(200, 160, 255, 0.9);
        }
        /* Inner grid — grows to content height; the scroll container above clips it. */
        .gif-reaction-grid {
            padding: 8px;
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 6px;
            align-content: start;
        }
        .gif-reaction-tile {
            position: relative;
            padding: 0;
            background: rgba(var(--ui-surface-rgb), 0.4);
            border: 1px solid rgba(180, 140, 255, 0.08);
            border-radius: 6px;
            cursor: pointer;
            overflow: hidden;
            transition: border-color 0.12s, transform 0.12s;
        }
        .gif-reaction-tile:hover {
            border-color: rgba(180, 140, 255, 0.5);
            transform: scale(1.03);
        }
        /* Sent-confirmation flash — applied briefly after a tile click so
           the user sees their pick was registered, even though the picker
           stays open for follow-up reactions. */
        .gif-reaction-tile.gif-reaction-tile-sent {
            border-color: #5fdfdf;
            box-shadow: 0 0 0 2px rgba(95, 223, 223, 0.6);
            animation: gifReactionSentFlash 0.45s ease;
        }
        @keyframes gifReactionSentFlash {
            0%   { box-shadow: 0 0 0 0 rgba(95, 223, 223, 0.9); }
            100% { box-shadow: 0 0 0 8px rgba(95, 223, 223, 0); }
        }
        .gif-reaction-tile img {
            width: 100%;
            height: 100%;
            object-fit: cover;
            display: block;
            pointer-events: none;
        }
        /* Pin-mode toggle row */
        .gif-reaction-mode-row {
            position: absolute;
            top: 80px;                          /* tabs (32) + search-row (48) */
            left: 0;
            right: 0;
            height: 36px;
            box-sizing: border-box;
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 6px 12px 8px;
            gap: 8px;
            border-bottom: 1px solid rgba(180, 140, 255, 0.08);
        }
        .gif-reaction-pin-toggle {
            display: flex;
            align-items: center;
            gap: 8px;
            cursor: pointer;
            -webkit-user-select: none;
            user-select: none;
        }
        .gif-reaction-pin-checkbox {
            position: absolute;
            opacity: 0;
            pointer-events: none;
        }
        .gif-reaction-pin-thumb {
            position: relative;
            width: 30px;
            height: 16px;
            background: rgba(60, 70, 85, 0.7);
            border-radius: 8px;
            transition: background 0.15s;
        }
        .gif-reaction-pin-thumb::after {
            content: '';
            position: absolute;
            top: 2px;
            left: 2px;
            width: 12px;
            height: 12px;
            background: rgba(var(--ui-text-rgb), 0.9);
            border-radius: 50%;
            transition: transform 0.15s;
        }
        .gif-reaction-pin-checkbox:checked + .gif-reaction-pin-thumb {
            background: rgba(180, 140, 255, 0.55);
        }
        .gif-reaction-pin-checkbox:checked + .gif-reaction-pin-thumb::after {
            transform: translateX(14px);
        }
        .gif-reaction-pin-label {
            color: rgba(var(--ui-text-rgb), 0.8);
            font-size: 12px;
        }
        .gif-reaction-mode-hint {
            color: rgba(var(--ui-text-rgb), 0.6);
            font-size: 11px;
            font-style: italic;
        }
        /* Slight visual cue when in pin mode — lavender accent */
        .gif-reaction-picker.gif-reaction-pin-mode {
            border-color: rgba(180, 140, 255, 0.55);
            box-shadow: 0 8px 32px rgba(120, 80, 200, 0.35), 0 0 0 1px rgba(180, 140, 255, 0.15);
        }
        /* Inventory (GifReactions renderItemsTab): no GIF chrome above the
           shelf — 36px leaves room for the ✕, 8px stands in for the hidden
           GIPHY footer. */
        .gif-inventory .gif-reaction-grid-scroll { top: 36px; bottom: 8px; }
        .gif-inv-tabs { grid-column: 1 / -1; display: flex; gap: 4px; margin: 0 0 6px; }
        .gif-inv-tab {
            flex: 1; display: flex; flex-direction: column; align-items: center; gap: 3px; padding: 6px 2px 5px;
            border: none; border-radius: 6px; cursor: pointer; font-family: inherit;
            background: rgba(255, 255, 255, 0.04); color: rgba(200, 220, 235, 0.55); transition: opacity 0.2s;
        }
        .gif-inv-tab.on { background: rgba(var(--ui-teal-rgb), 0.20); color: #bdeee7; }
        .gif-inv-tab span { font-size: 8.5px; letter-spacing: 0.3px; }
        .gif-inv-tile { aspect-ratio: 1 / 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px; }
        .gif-inv-emoji { font-size: 30px; line-height: 1; }
        .gif-inv-thumb { width: 42px; height: 42px; object-fit: contain; }
        .gif-inv-glyph { line-height: 1; color: rgba(180, 205, 225, 0.7); }
        .gif-inv-name {
            max-width: 100%; padding: 0 4px; box-sizing: border-box; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
            font-size: 10.5px; color: rgba(200, 220, 235, 0.75); text-align: center;
        }
        .gif-inv-count { position: absolute; top: 3px; right: 5px; font-size: 9px; color: rgba(var(--ui-accent-rgb), 0.5); }
        /* Phone: in the bottom row's column, its full width, just above the
           dock — and lower, so the canvas stays in view (Mikko 3.10.2026:
           "voisi olla matalampia ja kompaktimpia mobiililla"). Inventory packs
           four to a row with short tiles, and its tabs stay on one line beside
           the ✕ while the shelf scrolls under them. Same condition as the
           PHONE BOTTOM ROW (its --row-* and --bottom-row). */
        @media (max-width: 768px), (pointer: coarse) and (max-height: 500px) {
            .gif-reaction-picker {
                left: var(--row-left); right: var(--row-right); top: auto; bottom: calc(var(--bottom-row) + 62px);
                width: auto; height: min(58vh, 440px); height: min(58dvh, 440px);
            }
            .gif-reaction-picker.gif-inventory { height: min(44vh, 340px); height: min(44dvh, 340px); }
            .gif-reaction-close { z-index: 2; }
            .gif-inventory .gif-reaction-grid-scroll { top: 0; bottom: 0; }
            .gif-inventory .gif-reaction-grid { grid-template-columns: repeat(4, 1fr); gap: 5px; padding: 6px; }
            .gif-inv-tabs {
                position: sticky; top: 0; z-index: 1; margin: -6px -6px 2px; padding: 6px 40px 6px 6px;
                background: rgba(var(--ui-surface-rgb), 0.97);
            }
            .gif-inv-tab { flex-direction: row; justify-content: center; gap: 6px; padding: 7px 8px; }
            .gif-inv-tab span { font-size: 11px; }
            .gif-inv-tile { aspect-ratio: auto; height: 64px; gap: 3px; }
            .gif-inv-emoji { font-size: 22px; }
            .gif-inv-thumb { width: 28px; height: 28px; }
            .gif-inv-glyph svg { width: 20px; height: 20px; }
            .gif-inv-name { font-size: 10px; }
        }
        .gif-reaction-empty {
            grid-column: 1 / -1;
            padding: 20px;
            text-align: center;
            color: rgba(var(--ui-text-rgb), 0.5);
            font-size: 12px;
            font-style: italic;
        }
        /* Footer that tells the user whether more results are coming or
           we've hit the end. Sits as a full-width row in the grid. */
        .gif-reaction-sentinel {
            grid-column: 1 / -1;
            padding: 10px 14px 14px;
            text-align: center;
            color: rgba(180, 200, 220, 0.7);
            font-size: 11px;
            letter-spacing: 0.4px;
        }
        /* When there's more to load, surface it more prominently — a
           subtle lavender pill so the user can't miss the cue. */
        .gif-reaction-sentinel.gif-reaction-sentinel-more {
            color: rgba(220, 200, 255, 0.85);
            background: rgba(180, 140, 255, 0.1);
            border-radius: 6px;
            margin: 8px;
            padding: 8px 14px;
        }
        .gif-reaction-attribution {
            position: absolute;
            bottom: 0;
            left: 0;
            right: 0;
            height: 28px;
            box-sizing: border-box;
            padding: 6px 12px;
            font-size: 10px;
            text-align: center;
            color: rgba(var(--ui-text-rgb), 0.5);
            border-top: 1px solid rgba(180, 140, 255, 0.1);
            letter-spacing: 0.5px;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        /* Official GIPHY "Powered By" mark — transparent PNG, shown as-is
           on the dark footer. */
        .gif-reaction-giphy-logo {
            height: 16px;
            width: auto;
            display: block;
        }
        
        
        /* The phone sheet (ContextMenuUtils asPhoneSheet): a canvas menu on a
           phone rises from the bottom edge — full width, at most ~60% of the
           screen, 44px rows — over a dimmed canvas. !important because the
           menus place themselves inline at the finger. */
        .mobile-sheet {
            left: 0 !important; right: 0 !important; top: auto !important; bottom: 0 !important;
            width: auto !important; min-width: 0 !important; max-width: none !important;
            max-height: min(62vh, 600px) !important; overflow-y: auto !important; overscroll-behavior: contain;
            border-radius: 16px 16px 0 0 !important;
            padding: 0 4px calc(10px + env(safe-area-inset-bottom, 0px)) !important;
            font-size: 14px !important;
            animation: mobileSheetIn 0.2s cubic-bezier(0.2, 0.8, 0.2, 1) !important;
            transition: transform 0.15s ease;
        }
        .mobile-sheet.closing { animation: mobileSheetOut 0.16s ease-in forwards !important; }
        .mobile-sheet-handle {
            position: sticky; top: 0; z-index: 2; height: 22px; margin: 0 -4px 2px;
            display: flex; align-items: center; justify-content: center;
            background: inherit; background-color: var(--ui-surface, #16222a); touch-action: none;
        }
        .mobile-sheet-handle::before { content: ''; width: 40px; height: 4px; border-radius: 2px; background: rgba(255, 255, 255, 0.28); }
        .mobile-sheet .ui-menu-item, .mobile-sheet .ctx-item, .mobile-sheet .canvas-ctx-item {
            box-sizing: border-box; min-height: 44px; display: flex; align-items: center; padding-top: 6px; padding-bottom: 6px; font-size: 14px;
        }
        .mobile-sheet-backdrop {
            position: fixed; inset: 0; background: rgba(0, 0, 0, 0.35);
            z-index: calc(var(--z-lightbox) - 1); animation: mobileSheetFade 0.15s ease;
        }
        .mobile-sheet-backdrop.closing { opacity: 0; transition: opacity 0.15s ease; }
        /* The Look panel as a phone sheet (LookPanel _asSheet): no backdrop —
           the item above stays live, and a tap on another item re-points it. */
        #lookPanel.look-sheet {
            left: 0 !important; right: 0 !important; top: auto !important; bottom: 0 !important;
            width: auto !important; max-height: min(56vh, 540px) !important;
            border-radius: 16px 16px 0 0 !important; z-index: calc(var(--z-lightbox) - 2) !important;
            padding-bottom: env(safe-area-inset-bottom, 0px);
            animation: mobileSheetIn 0.2s cubic-bezier(0.2, 0.8, 0.2, 1);
        }
        #lookPanel .look-sheet-handle { display: none; }
        #lookPanel.look-sheet .look-sheet-handle { display: flex; margin: 0; flex: 0 0 auto; }
        #lookPanel.look-sheet button { min-height: 36px; min-width: 36px; }
        /* The image Edit panel as a phone sheet: it used to float over the
           middle of the picture it edits, under the lightbox's own buttons.
           Docked at the bottom edge, the picture stays in view above it.
           The same test as isPhoneSheet() (which also stops its header drag);
           !important beats the left/top a desktop drag writes inline. */
        @media (pointer: coarse) and (max-width: 700px), (pointer: coarse) and (max-height: 700px) {
            #imageEditPanel {
                position: fixed !important; left: 0 !important; right: 0 !important; top: auto !important; bottom: 0 !important;
                width: auto !important; transform: none !important; max-height: min(46vh, 440px) !important;
                border-radius: 16px 16px 0 0 !important;
                padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px)) !important;
                animation: mobileSheetIn 0.2s cubic-bezier(0.2, 0.8, 0.2, 1);
            }
            #imgEditDragHandle { cursor: default !important; position: sticky; top: 0; z-index: 1; background: var(--ui-surface); }
            .imgEditTab { min-height: 36px; }
            /* The viewer centres the picture; with the sheet open it centres
               it in the space left above the sheet. */
            #modal-image:has(#imgEditBtn[aria-pressed="true"]) { box-sizing: border-box; padding-bottom: min(46vh, 440px); }
        }
        #imgEditBtn[aria-pressed="true"] { background: rgba(var(--ui-accent-rgb), 0.25) !important; border-color: var(--ui-border-accent) !important; }
        @keyframes mobileSheetIn { from { transform: translateY(100%); } to { transform: translateY(0); } }
        @keyframes mobileSheetOut { from { transform: translateY(0); } to { transform: translateY(100%); } }
        @keyframes mobileSheetFade { from { opacity: 0; } to { opacity: 1; } }

        /* Canvas context menu (right-click to create) */
        .canvas-context-menu {
            font-family: var(--ui-font);
        }
        .canvas-ctx-item {
            display: flex;
            align-items: center;
            padding: 8px 14px;
            cursor: pointer;
            color: var(--ui-text, #e0e0e0);
            font-size: 13px;
            transition: background 0.15s;
            gap: 10px;
        }
        .canvas-ctx-item:hover {
            background: var(--ui-surface-hover, rgba(var(--ui-accent-rgb), 0.1));
        }
        .canvas-ctx-separator {
            height: 1px;
            background: var(--ui-border, #3a3a5a);
            margin: 4px 8px;
        }
        .canvas-ctx-header {
            padding: 6px 14px 4px;
            color: var(--ui-accent, rgba(var(--ui-accent-rgb), 0.5));
            font-size: 10px;
            font-weight: 700;
            text-transform: uppercase;
            letter-spacing: 0.5px;
            opacity: 0.85;
        }
        
        
        /* Lock button styles */
        .toolbar-btn-locked {
            background: rgba(248, 113, 113, 0.1);
            border-color: rgba(248, 113, 113, 0.15);
            color: rgba(248, 113, 113, 0.8);
        }
        .toolbar-btn-locked:hover {
            background: rgba(248, 113, 113, 0.18);
            border-color: rgba(248, 113, 113, 0.25);
        }
        
        .toolbar-btn-unlocked {
            background: rgba(74, 222, 128, 0.1);
            border-color: rgba(74, 222, 128, 0.15);
            color: rgba(74, 222, 128, 0.8);
        }
        .toolbar-btn-unlocked:hover {
            background: rgba(74, 222, 128, 0.18);
            border-color: rgba(74, 222, 128, 0.25);
        }
        
        .toolbar-btn-gray {
            pointer-events: auto;
            background: rgba(255, 255, 255, 0.03);
            color: rgba(255, 255, 255, 0.4);
            border: 1px solid rgba(255, 255, 255, 0.06);
            border-radius: 5px;
            padding: 5px 11px;
            font-size: 12px;
            font-weight: 400;
            transition: all 0.15s ease;
            cursor: pointer;
        }
        .toolbar-btn-gray:hover {
            background: rgba(255, 255, 255, 0.08);
            color: rgba(255, 255, 255, 0.6);
        }
        
        /* Settings/modal button styles - matches BTN_STYLE from Renderer.js */
        /* Must override .authContainer button with higher specificity */

        .settings-section {
            margin-top: 14px;
            padding: 12px;
            background: rgba(0,0,0,0.16);
            border: 1px solid var(--ui-border-faint);
            border-radius: 8px;
        }
        .settings-section-title {
            color: rgba(var(--ui-accent-rgb),0.85);
            font-size: 11px;
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 0.8px;
            margin-bottom: 10px;
        }
        .settings-tabs {
            display: flex;
            gap: 4px;
            margin-bottom: 4px;
            border-bottom: 1px solid var(--ui-border-faint);
            padding-bottom: 0;
        }
        /* Space Settings tabs use the shared .ui-tab-underline recipe
           (ui-theme.css); this only compacts the geometry — six tabs share
           a narrow modal, so they flex-fill at a smaller size. */
        .settings-tabs .ui-tab-underline {
            flex: 1;
            padding: 8px 4px;
            font-size: 12px;
            margin: 0 0 -1px 0;
        }
        .settings-tab-panel { display: none; }
        .settings-tab-panel.active { display: block; }
        .settings-hint {
            color: var(--ui-text-muted);
            font-size: 11px;
            margin: 4px 0 0 0;
            line-height: 1.4;
        }

        /* Space Settings → Portal. The preview is the point of the section
           (Mikko 26.9.: "previewistä isompi"), so it gets the full width on
           a dotted stage; the looks are tiles that show themselves. */
        .portal-stage {
            position: relative;
            height: 300px;
            border-radius: 12px;
            border: 1px solid var(--ui-border-faint);
            background:
                radial-gradient(rgba(var(--ui-text-rgb),0.08) 1px, transparent 1.3px) 0 0 / 16px 16px,
                radial-gradient(ellipse at 50% 42%, rgba(var(--ui-surface-rgb),0.55), rgba(0,0,0,0.3));
            overflow: hidden;
        }
        .portal-stage-label {
            position: absolute; top: 12px; left: 14px; z-index: 1;
            font: 11px/1 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
            color: var(--ui-text-muted);
        }
        /* The portal's own light on the floor, in the own-space green the
           preview paints it in (SpaceSettings OWN_PORTAL_COLOR). */
        .portal-stage-floor {
            position: absolute; left: 50%; bottom: 12px; width: 58%; height: 36px;
            transform: translateX(-50%);
            background: radial-gradient(ellipse at center, rgba(97,209,92,0.26), transparent 70%);
            pointer-events: none;
        }
        .portal-stage-view {
            position: absolute; inset: 0;
            display: flex; align-items: center; justify-content: center;
            color: rgba(var(--ui-text-rgb),0.4); font-size: 28px;
        }
        .portal-stage-view canvas {
            display: block; height: 100%; width: 300px; max-width: 100%;
            object-fit: contain;
        }
        .portal-stage-view img {
            width: 220px; height: 220px; max-width: 80%; border-radius: 50%; object-fit: cover;
        }
        /* Where your image comes from: a row under the tiles, with the
           Image tile. */
        .portal-image-row { display: flex; gap: 6px; margin-top: 6px; }
        .portal-image-row[hidden] { display: none; }
        .portal-image-row .ui-btn.portal-tile { flex: 1; flex-direction: row; gap: 8px; min-height: 40px; }
        .portal-image-row .ui-btn.portal-tile:last-child { flex: 0 0 auto; padding: 6px 12px; }
        .portal-image-row svg { width: 16px; height: 16px; }
        .portal-status { display: block; margin-top: 6px; color: var(--ui-text-muted); font-size: 11px; }
        .portal-status:empty { display: none; }
        .ui-btn.portal-tile {
            display: flex; flex-direction: column; align-items: center; justify-content: center;
            gap: 4px; min-height: 50px; padding: 6px 4px;
            border-radius: 8px; font-size: 11px;
            background: rgba(var(--ui-surface-rgb),0.55);
            border: 1px solid var(--ui-border-faint);
            color: var(--ui-text-muted);
        }
        .ui-btn.portal-tile:hover:not(:disabled) { border-color: var(--ui-border); color: var(--ui-text-bright); }
        .portal-sublabel {
            display: block; margin: 10px 0 5px;
            color: var(--ui-text-muted); font-size: 12px;
        }
        .portal-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(88px, 1fr)); gap: 6px; }
        .portal-tile canvas { width: 32px; height: 32px; }
        /* The Image tile's thumbnail: your image as a small ball, or an
           invitation to add one. */
        .portal-image-thumb {
            width: 32px; height: 32px; border-radius: 50%;
            display: flex; align-items: center; justify-content: center;
            background: center / cover no-repeat;
        }
        .portal-image-thumb.empty { border: 1px dashed var(--ui-border); }
        .portal-image-thumb:not(.empty) svg { display: none; }
        .portal-image-thumb svg { width: 16px; height: 16px; }
        .portal-tile svg { width: 22px; height: 22px; }
        /* Shapes are their drawings: one low row, the name on hover (the
           tile's title). A tile without a drawing keeps its words. */
        .portal-tiles-shapes { grid-template-columns: repeat(auto-fill, minmax(48px, 1fr)); }
        /* The space's colour (SpaceSettings.js): little glossy balls, as the
           theme swatches and the map spheres themselves. 44 px to touch. */
        .portal-colors { display: flex; flex-wrap: wrap; gap: 6px; }
        .portal-color { position: relative; width: 44px; height: 44px; padding: 0; border: 0; background: none; cursor: pointer; border-radius: 50%; display: grid; place-items: center; }
        .portal-color i { width: 30px; height: 30px; border-radius: 50%; display: block;
            box-shadow: inset -3px -5px 8px rgba(0, 0, 0, 0.35), 0 2px 6px rgba(0, 0, 0, 0.4); }
        .portal-color i::before { content: ''; display: block; width: 100%; height: 100%; border-radius: 50%;
            background: radial-gradient(circle at 32% 28%, rgba(255,255,255,0.5), rgba(255,255,255,0.08) 40%, transparent 58%); }
        .portal-color[aria-checked="true"] i { outline: 2px solid var(--ui-text-bright); outline-offset: 3px; }
        .portal-color:focus-visible { outline: 2px solid var(--ui-accent); outline-offset: 1px; }
        .portal-color.auto b { position: absolute; inset: 0; display: grid; place-items: center; font: 700 11px var(--ui-font); color: #0b1416; pointer-events: none; }
        .portal-tiles-shapes .ui-btn.portal-tile { min-height: 40px; padding: 4px; font-size: 10px; }
        .portal-tiles-shapes .portal-tile:has(svg) span { display: none; }

        .place-hint {
            color: rgba(255, 255, 255, 0.35);
            font-size: 10px;
            font-weight: 400;
            margin-left: 4px;
        }
        
        .toolbar-right {
            margin-left: auto;
            display: flex;
            align-items: center;
            gap: 5px;
        }
        
        /* Hamburger menu - mobile only */
        #hamburgerBtn {
            display: none;
            pointer-events: auto;
            background: var(--ui-toolbar-btn-bg, rgba(var(--ui-primary-rgb), 0.5));
            color: var(--ui-btn-text);
            border: 2px solid var(--ui-border);
            border-radius: 5px;
            padding: 5px 8px;
            font-size: 16px;
            cursor: pointer;
            margin-left: auto;
            line-height: 1;
            transition: all 0.15s ease;
        }

        /* Desktop burger menu - hide old individual buttons, show burger */
        .burger-menu-item { display: none; }
        #desktopBurgerWrap { display: inline-block; }

        .desktop-burger-item {
            display: flex;
            align-items: center;
            gap: 10px;
            width: 100%;
            padding: 10px 16px;
            background: none;
            border: none;
            color: rgba(var(--ui-text-rgb), 0.85);
            font-size: 13px;
            cursor: pointer;
            transition: background 0.12s;
            text-align: left;
            white-space: nowrap;
        }
        .desktop-burger-item:hover { background: rgba(var(--ui-accent-rgb), 0.1); }
        /* Integration rows and their back links — the same quiet lift the rest
           of the house uses: accent border and a touch of glow on hover, a
           soft press on click. The rows carry inline base styles (they were
           born in markup), so only the states live here. */
        .integration-row { transition: border-color 0.15s, background 0.15s, transform 0.1s; }
        .integration-row:hover { border-color: var(--ui-border-accent) !important; background: rgba(var(--ui-accent-rgb), 0.08) !important; }
        .integration-row:active { transform: scale(0.99); }
        .integration-back { transition: color 0.15s; }
        .integration-back:hover { color: var(--ui-text-bright) !important; }

        .desktop-burger-item:active { transform: scale(0.98); }
        .desktop-burger-item svg { flex-shrink: 0; opacity: 0.7; }
        .desktop-burger-item svg.kuukan-icon { opacity: 1; }
        .desktop-burger-item [data-ui-icon], .mobile-menu-btn [data-ui-icon] { display: inline-flex; flex-shrink: 0; align-items: center; }
        .desktop-burger-danger { color: rgba(255, 120, 120, 0.85); }
        .desktop-burger-danger:hover { background: rgba(255, 80, 80, 0.15); }
        /* Burger dropdown tooltips: left of dropdown, override toolbar rule */
        #desktopBurgerDropdown .desktop-burger-item[data-tooltip]::after {
            bottom: auto;
            left: auto;
            right: calc(100% + 8px);
            top: 50%;
            transform: translateY(-50%) translateX(4px);
        }
        #desktopBurgerDropdown .desktop-burger-item[data-tooltip]:hover::after {
            transform: translateY(-50%) translateX(0);
            transition-delay: 0.15s;
        }
        #hamburgerBtn:hover { filter: brightness(1.2); border-color: var(--ui-border-accent); box-shadow: 0 0 12px rgba(var(--ui-accent-rgb), 0.45); transform: scale(1.05); }
        #hamburgerBtn.open .hamburger-icon { display: none; }
        #hamburgerBtn.open .close-icon { display: inline; }
        #hamburgerBtn:not(.open) .close-icon { display: none; }
        
        /* The mobile menu is one panel under the toolbar over a dim
           backdrop (a tap on the backdrop closes it) — not a full-screen
           wall two screens tall. Rails of icons with short names, one row
           for this space, the rarities behind More. */
        #mobileMenu {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(4, 8, 12, 0.5);
            backdrop-filter: blur(4px);
            -webkit-backdrop-filter: blur(4px);
            /* Above the floating canvas UI (minimap, badges, the dock —
               z-index up to ~1000) so they can't render over the menu and steal
               taps; still below modals (3000+) so menu items can open them.
               The toolbar (#ui) is bumped to 2001 on mobile so the hamburger
               close button stays tappable above the open menu. */
            z-index: var(--z-nav);
            overflow-y: auto;
            padding: 58px 8px 16px;
            animation: mobileMenuIn 0.18s ease;
        }
        #mobileMenu.open { display: block; }
        .mobile-menu-panel {
            max-width: 520px;
            margin: 0 auto;
            padding: 10px;
            background: rgba(var(--ui-surface-rgb), 0.97);
            border: 1px solid var(--ui-border);
            border-radius: 14px;
            box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
        }

        @keyframes mobileMenuIn {
            from { opacity: 0; transform: translateY(-8px); }
            to { opacity: 1; transform: translateY(0); }
        }

        .mobile-menu-divider { border-top: 1px solid var(--ui-border-faint); margin: 8px 2px; }
        /* A rail: icon over a short name, five across, 58px tall. */
        .mobile-menu-rail { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 2px; }
        .mobile-menu-rail .mobile-menu-btn {
            flex-direction: column; justify-content: center; gap: 5px;
            min-height: 58px; min-width: 0; padding: 8px 2px;
            background: transparent; border-color: transparent;
            font-size: 11px; text-align: center; line-height: 1.1;
        }
        .mobile-menu-rail .mobile-menu-btn svg, .mobile-menu-rail .mobile-menu-btn img { width: 20px; height: 20px; }
        .mobile-menu-rail .mobile-menu-btn.accent { background: rgba(20, 80, 60, 0.35); border-color: rgba(45, 212, 191, 0.2); }
        /* A row: three compact buttons. */
        .mobile-menu-row { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; }
        .mobile-menu-row .mobile-menu-btn, .mobile-menu-more .mobile-menu-btn {
            min-height: 44px; min-width: 0; padding: 8px 6px; font-size: 12.5px; border-width: 1px; justify-content: center; white-space: nowrap;
        }
        /* Teams: one scrolling row of chips. */
        .mobile-menu-chips { display: flex; gap: 6px; overflow-x: auto; padding: 0 0 8px; scrollbar-width: none; }
        .mobile-menu-chips::-webkit-scrollbar { display: none; }
        .mobile-menu-chips .mobile-menu-btn {
            flex: 0 0 auto; min-height: 40px; padding: 6px 12px; border-radius: 20px; border-width: 1px;
            font-size: 12.5px; white-space: nowrap; gap: 6px;
        }
        /* More: the once-a-week actions, folded. A dot on More says one of
           them is on (snap grid). */
        .mobile-menu-more { display: none; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; margin-top: 6px; }
        .mobile-menu-panel.more-open .mobile-menu-more { display: grid; }
        .mobile-menu-panel.more-open #mobileMoreBtn { border-color: var(--ui-border-accent); }
        .mobile-menu-more-dot { display: none; width: 6px; height: 6px; border-radius: 50%; background: #4ecdc4; margin-left: 2px; }
        .mobile-menu-more-dot.on { display: inline-block; }
        .mobile-menu-btn.is-on { border-color: rgba(78, 205, 196, 0.55); color: #7dd3c8; }
        /* Rows and chips read as buttons: a faint fill and edge. */
        .mobile-menu-row .mobile-menu-btn, .mobile-menu-more .mobile-menu-btn, .mobile-menu-chips .mobile-menu-btn {
            background: rgba(255, 255, 255, 0.045); border-color: rgba(255, 255, 255, 0.08);
        }
        .mobile-menu-divider { border-top-color: rgba(255, 255, 255, 0.07); }
        .mobile-menu-btn {
            display: flex;
            align-items: center;
            gap: 8px;
            padding: 12px;
            background: rgba(var(--ui-surface-rgb), 0.4);
            border: 2px solid var(--ui-border);
            border-radius: 8px;
            color: var(--ui-text);
            font-size: 13px;
            font-family: var(--ui-font);
            cursor: pointer;
            transition: all 0.15s;
            text-align: left;
        }
        .mobile-menu-btn:hover {
            background: rgba(40, 70, 90, 0.5);
            border-color: var(--ui-border-accent);
        }
        .mobile-menu-btn:active {
            transform: scale(0.97);
        }
        .mobile-menu-btn svg {
            flex-shrink: 0;
            width: 16px;
            height: 16px;
        }
        .mobile-menu-btn.accent {
            background: rgba(20, 80, 60, 0.4);
            border-color: rgba(45, 212, 191, 0.25);
            color: rgba(125, 211, 200, 0.9);
        }
        .mobile-menu-btn.danger {
            background: rgba(60, 30, 30, 0.3);
            border-color: rgba(239, 68, 68, 0.2);
            color: rgba(248, 113, 113, 0.8);
        }
        .mobile-menu-user {
            display: flex;
            align-items: center;
            gap: 10px;
            padding: 4px 6px 10px;
        }
        .mobile-menu-user-name {
            color: var(--ui-text-bright);
            font-size: 14px;
            font-weight: 500;
        }
        .mobile-menu-user-space {
            color: var(--ui-text-muted);
            font-size: 11px;
            margin-top: 2px;
        }
        
        .toolbar-user {
            color: var(--ui-btn-text);
            font-size: 11px;
            padding: 4px 10px;
            border-radius: 5px;
            background: var(--ui-toolbar-btn-bg, rgba(var(--ui-primary-rgb), 0.5));
            border: 2px solid var(--ui-border);
            transition: all 0.15s;
            font-weight: 400;
        }
        .toolbar-user:hover {
            filter: brightness(1.2);
            color: var(--ui-text-bright);
            box-shadow: 0 0 12px rgba(var(--ui-accent-rgb), 0.45);
            transform: scale(1.05);
        }
        input { pointer-events: auto; }
        textarea { pointer-events: auto; }
        #nodeInputWrapper {
            position: fixed;
            z-index: var(--z-panel);
            pointer-events: none;
        }
        #nodeInput { 
            background: rgba(30,35,48,0.97);
            border: 2px solid var(--ui-border-accent);
            color: var(--ui-text);
            padding: 9px 8px;
            font-family: var(--ui-font);
            font-size: 13px;
            line-height: 16px;
            border-radius: 8px;
            display: block;
            resize: none;
            overflow: hidden;
            word-break: break-word;
            white-space: pre-wrap;
            box-sizing: border-box;
            outline: none;
            pointer-events: auto;
            box-shadow: 0 0 0 3px rgba(var(--ui-accent-rgb),0.1), 0 4px 20px rgba(0,0,0,0.4);
        }
        #nodeCheckboxBtn {
            position: absolute;
            bottom: 100%;
            right: 32px;
            margin-bottom: 4px;
            background: rgba(50,60,75,0.8);
            border: 2px solid var(--ui-border-accent);
            border-radius: 4px;
            font-size: 15px;
            cursor: pointer;
            padding: 2px 5px;
            transition: background 0.2s;
            pointer-events: auto;
            z-index: 1;
            color: var(--ui-text);
        }
        #nodeCheckboxBtn:hover {
            background: rgba(60,80,100,0.9);
        }
        #nodeEmojiBtn {
            position: absolute;
            bottom: 100%;
            right: 0;
            margin-bottom: 4px;
            background: rgba(50,60,75,0.8);
            border: 2px solid var(--ui-border-accent);
            border-radius: 4px;
            font-size: 16px;
            cursor: pointer;
            padding: 2px 5px;
            transition: background 0.2s;
            pointer-events: auto;
            z-index: 1;
            /* The smiley is an SVG on currentColor — without an explicit bright
               color it inherits a dim default and reads as disabled. */
            color: #e8eef4;
        }
        #nodeEmojiBtn:hover {
            background: rgba(60,80,100,0.9);
        }
        #nodeEmojiPicker {
            position: absolute;
            top: 100%;
            right: 0;
            background: rgba(var(--ui-surface-rgb),0.98);
            border: 2px solid var(--ui-border-accent);
            border-radius: 12px;
            padding: 12px;
            max-width: 320px;
            width: 320px;
            max-height: 350px;
            overflow-y: auto;
            box-shadow: 0 4px 20px rgba(0,0,0,0.5);
            pointer-events: auto;
            z-index: calc(var(--z-panel) + 1);
            -webkit-backdrop-filter: blur(12px);
            backdrop-filter: blur(12px);
        }
        #nodeEmojiPicker::-webkit-scrollbar,
        .kuukan-emoji-popup::-webkit-scrollbar,
        .ui-autocomplete-dropdown::-webkit-scrollbar { width: 8px; }
        #nodeEmojiPicker::-webkit-scrollbar-track,
        .kuukan-emoji-popup::-webkit-scrollbar-track,
        .ui-autocomplete-dropdown::-webkit-scrollbar-track { background: transparent; }
        #nodeEmojiPicker::-webkit-scrollbar-thumb,
        .kuukan-emoji-popup::-webkit-scrollbar-thumb,
        .ui-autocomplete-dropdown::-webkit-scrollbar-thumb {
            background: rgba(var(--ui-accent-rgb),0.2);
            border-radius: 4px;
        }
        #nodeEmojiPicker::-webkit-scrollbar-thumb:hover,
        .kuukan-emoji-popup::-webkit-scrollbar-thumb:hover,
        .ui-autocomplete-dropdown::-webkit-scrollbar-thumb:hover {
            background: rgba(var(--ui-accent-rgb),0.35);
        }
        .kuukan-emoji-popup .sticker-emoji-pick:hover {
            background: rgba(var(--ui-accent-rgb),0.15);
        }
        #nodeEmojiPicker .emoji-item:hover {
            background: rgba(var(--ui-accent-rgb),0.15);
        }
        /* Chat emoji picker - same style as nodeEmojiPicker */
        #chatEmojiPicker .chat-emoji-category {
            margin-bottom: 8px;
        }
        #chatEmojiPicker .chat-emoji-category-title {
            color: var(--ui-text-muted);
            font-size: 11px;
            margin-bottom: 4px;
            text-transform: uppercase;
        }
        #chatEmojiPicker .chat-emoji-grid {
            display: flex;
            flex-wrap: wrap;
            gap: 2px;
        }
        #chatEmojiPicker .chat-emoji-item {
            font-size: 20px;
            padding: 4px;
            cursor: pointer;
            border-radius: 4px;
            transition: background 0.15s;
        }
        #chatEmojiPicker .chat-emoji-item:hover {
            background: #444;
        }
        /* Chat teleport links */
        /* The violet is identity; the TEXT is the theme's. The 0.3-alpha
           background follows the ground's lightness (dark ground → dark
           violet, light ground → lavender), so a fixed pale-purple text
           vanished on light themes ("kortit jäävät vaalealla pimentoon") —
           the theme's own text color contrasts against both. */
        .chat-teleport {
            display: inline-block;
            background: rgba(100, 60, 200, 0.3);
            border: 1px solid rgba(140, 100, 255, 0.4);
            color: var(--ui-text-bright);
            padding: 2px 8px;
            border-radius: 12px;
            cursor: pointer;
            font-size: 11px;
            font-weight: 500;
            transition: background 0.15s, border-color 0.15s;
        }
        .chat-teleport:hover {
            background: rgba(130, 80, 255, 0.45);
            border-color: rgba(180, 140, 255, 0.6);
        }
        /* CommentBox emoji picker - same style as TextNode */
        .cb-emoji-picker {
            position: absolute;
            bottom: 100%;
            right: 0;
            background: var(--ui-surface-alt);
            border: 2px solid #555;
            border-radius: 8px;
            padding: 8px;
            max-width: 320px;
            max-height: 280px;
            overflow-y: auto;
            box-shadow: 0 4px 20px rgba(0,0,0,0.5);
        }
        .cb-emoji-category {
            margin-bottom: 8px;
        }
        .cb-emoji-category-title {
            color: var(--ui-text-muted);
            font-size: 11px;
            margin-bottom: 4px;
            text-transform: uppercase;
        }
        .cb-emoji-grid {
            display: flex;
            flex-wrap: wrap;
            gap: 2px;
        }
        .cb-emoji-item {
            font-size: 20px;
            padding: 4px;
            cursor: pointer;
            border-radius: 4px;
            transition: background 0.15s;
        }
        .cb-emoji-item:hover {
            background: #444;
        }

        /* ========== FLOATING ACTION BUTTONS (CSS hover replaces inline JS) ========== */
        .floating-action-btn {
            position: fixed;
            border-radius: 50%;
            color: #fff;
            cursor: pointer;
            z-index: 999;
            box-shadow: 0 4px 20px rgba(0,0,0,0.4);
            display: flex;
            align-items: center;
            justify-content: center;
            border: 2px solid transparent;
            transition: transform 0.2s ease, box-shadow 0.2s ease;
        }
        .floating-action-btn:hover {
            transform: scale(1.1);
        }
        .floating-action-btn:active {
            transform: scale(0.95);
        }
        .floating-action-btn--gold {
            bottom: 84px; left: 20px; width: 46px; height: 46px; font-size: 20px;
            background: linear-gradient(135deg, #8a6a2a, #aa8a3a);
            border-color: rgba(249,202,36,0.4);
        }
        .floating-action-btn--gold:hover {
            box-shadow: 0 6px 25px rgba(138,106,42,0.5);
        }
        .floating-action-btn--blue {
            bottom: 24px; left: 20px; width: 56px; height: 56px; font-size: 24px;
            background: linear-gradient(135deg, #2a6a8a, #3a8aaa);
            border-color: var(--ui-border-accent);
        }
        .floating-action-btn--blue:hover {
            box-shadow: 0 6px 25px rgba(var(--ui-primary-rgb),0.5);
        }

        /* ========== THE DOCK (ActionDock.js) ==========
           The doors at the bottom left (Messages · Activity · Vibes ·
           Inventory) in one glass pill of the SmartBar's family: places on
           the left, actions in the middle. Each door carries its own number.
           It replaced the action carousel (one ball cycling the modes), which
           showed three doors of four. The PHONE BOTTOM ROW block below folds
           it to the bell while the SmartBar has the row. */
        .fac {
            position: fixed; bottom: 19px; left: 20px; z-index: 999;
            height: 54px; box-sizing: border-box; padding: 0 4px;
            display: flex; align-items: center;
            border-radius: 27px; border: 2px solid var(--ui-border); background: rgba(var(--ui-surface-rgb), 0.92);
            -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); box-shadow: 0 6px 24px rgba(0, 0, 0, 0.45);
            -webkit-user-select: none; user-select: none;
        }
        .fac-door {
            position: relative; flex: none; width: 46px; height: 46px; padding: 0;
            display: flex; align-items: center; justify-content: center;
            border: 0; border-radius: 23px; background: none; cursor: pointer; -webkit-tap-highlight-color: transparent;
            transition: width 0.45s cubic-bezier(.34, 1.3, .64, 1), opacity 0.25s ease, transform 0.45s cubic-bezier(.34, 1.3, .64, 1), background 0.15s ease;
        }
        @media (hover: hover) { .fac-door:hover { background: rgba(var(--ui-accent-rgb), 0.12); } }
        .fac-door:active { transform: scale(0.92); }
        .fac-door:focus-visible { outline: 2px solid rgb(var(--ui-teal-rgb)); outline-offset: 1px; }
        .fac-door-icon { font-size: 19px; line-height: 1; pointer-events: none; }
        /* A door's own number; the bell's "everything" shows only folded. */
        .fac-count {
            position: absolute; top: -5px; right: -5px; min-width: 18px; height: 18px; padding: 0 4px; box-sizing: border-box;
            display: flex; align-items: center; justify-content: center;
            border-radius: 9px; border: 2px solid rgb(10, 20, 30); background: #e8643c;
            color: #fff; font: 700 10px/1 var(--ui-font); pointer-events: none;
        }
        .fac-count[hidden], .fac-count-all { display: none; }
        /* The hot-mic dot, on Messages (VoiceChat sets data-voice-active). */
        .fac-voice-indicator {
            position: absolute; right: -4px; bottom: -4px; width: 18px; height: 18px; box-sizing: border-box;
            display: none; align-items: center; justify-content: center;
            border-radius: 50%; border: 2px solid rgb(10, 20, 30); background: #2a8a4a;
            font-size: 9px; pointer-events: none;
        }
        .fac[data-voice-active="true"] .fac-voice-indicator { display: flex; animation: facVoicePulse 1.6s ease-in-out infinite; }
        @keyframes facVoicePulse {
            0%, 100% { box-shadow: 0 0 0 0 rgba(64,200,120,0.65); }
            50%      { box-shadow: 0 0 0 7px rgba(64,200,120,0); }
        }
        @media (prefers-reduced-motion: reduce) { .fac-door { transition: none; } }

        /* ========== MINIMAP (Minimap.js) ========== */
        #minimapContainer { position: fixed; bottom: 16px; right: 16px; z-index: 200; font-family: var(--ui-font); transition: opacity 0.3s ease; }
        #minimapWrapper {
            position: relative; width: 200px; height: 140px; overflow: hidden; cursor: crosshair;
            background: rgba(var(--ui-surface-rgb),0.85); -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
            border: 2px solid var(--ui-border); border-radius: 10px; box-shadow: 0 4px 20px var(--ui-shadow);
        }
        /* The drawing stays 200×140 (Minimap.js MW/MH); a phone shows it smaller. */
        #minimapCanvas { display: block; width: 100%; height: 100%; touch-action: none; }
        #minimapToggle {
            position: absolute; top: -10px; left: -10px; width: 24px; height: 24px; padding: 0;
            display: flex; align-items: center; justify-content: center; border-radius: 50%; cursor: pointer;
            background: rgba(var(--ui-surface-rgb),0.9); border: 2px solid var(--ui-border-accent);
            box-shadow: 0 2px 8px rgba(0,0,0,0.3); transition: all 0.2s ease;
        }
        #minimapToggle > span { display: flex; }

        #loadingIndicator { bottom: 90px; left: 50%; transform: translateX(-50%); }

        /* ========== SMARTBAR (SmartBar.js) ==========
           The bar that shows what the moment needs. This is its desktop look,
           the default: a pill at the bottom centre in the hotspot pill's place
           (the pill steps aside while the bar is up), labelled buttons,
           shortcuts shown, a grip to drag it by (DragPanel.js; the spot is
           remembered). The PHONE BOTTOM ROW block below turns it into the
           phone's bottom row, and must stay after this one. */
        body.smartbar-on #hotspotNav { display: none !important; }
        #smartBar {
            position: fixed; z-index: 999; left: 50%; bottom: 20px; transform: translateX(-50%);
            height: 52px; box-sizing: border-box; max-width: calc(100vw - 32px);
            display: flex; align-items: center; gap: 2px; padding: 0 7px 0 5px;
            border-radius: 26px; border: 2px solid var(--ui-border); background: rgba(var(--ui-surface-rgb), 0.92);
            -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); box-shadow: 0 6px 24px rgba(0, 0, 0, 0.45);
            font-family: var(--ui-font); white-space: nowrap;
            animation: smartBarIn 0.22s cubic-bezier(.3, 1.4, .5, 1);
        }
        #smartBar[hidden] { display: none; }
        .sb-grip {
            flex: none; width: 14px; height: 28px; display: grid; grid-template-columns: repeat(2, 3px); gap: 3px;
            align-content: center; justify-content: center; opacity: 0.5;
        }
        .sb-grip:hover { opacity: 0.9; }
        .sb-grip i { width: 3px; height: 3px; border-radius: 50%; background: var(--ui-text-muted); }
        .sb-body { display: flex; align-items: center; gap: 4px; min-width: 0; }
        .sb-strip { display: flex; align-items: center; gap: 2px; min-width: 0; }
        .sb-act {
            flex: none; height: 42px; padding: 0 12px; display: flex; align-items: center; gap: 7px;
            border: 0; border-radius: 21px; background: none; cursor: pointer; color: var(--ui-text-muted);
            font: 600 13.5px var(--ui-font); -webkit-tap-highlight-color: transparent;
        }
        @media (hover: hover) { .sb-act:hover { background: rgba(var(--ui-accent-rgb), 0.1); color: var(--ui-text); } }
        .sb-act:active { background: rgba(var(--ui-accent-rgb), 0.16); }
        .sb-act.danger { color: var(--ui-danger); }
        /* A tool as its icon alone, its name as the tip (a sheet's tools). */
        .sb-act.sb-icon { width: 38px; padding: 0; justify-content: center; }
        .sb-act.sb-icon .sb-lbl { display: none; }
        .sb-act.sb-icon .sb-ico { font-weight: 700; }
        .sb-sep { flex: none; width: 1px; height: 22px; margin: 0 4px; background: rgba(var(--ui-accent-rgb), 0.18); }
        /* Two rows (a sheet's tools): the bar grows to hold them. */
        #smartBar:has(.sb-body.sb-two) { height: auto; padding-top: 4px; padding-bottom: 4px; border-radius: 22px; }
        .sb-body.sb-two { flex-direction: column; align-items: flex-start; gap: 2px; }
        .sb-strip2 { border-top: 1px solid rgba(var(--ui-accent-rgb), 0.12); padding-top: 3px; }
        .sb-strip2 .sb-act { height: 36px; }
        .sb-glabel { flex: none; padding: 0 4px 0 10px; font: 700 10.5px var(--ui-font); letter-spacing: 0.06em; text-transform: uppercase; color: var(--ui-text-muted); opacity: 0.75; }
        .sb-glabel:first-child { padding-left: 6px; }
        .sb-act.on { color: #ff8fb8; }
        /* The one next step the bar has a reason for (Play a video, unread
           Comments, Edit what you just made, Show a selection that left the
           screen, Arrange what was just duplicated): lit, never pressed for
           you. One at a time; the rest stay quiet. */
        .sb-act.lead { color: #08221f; background: rgb(var(--ui-teal-rgb)); }
        @media (hover: hover) { .sb-act.lead:hover { color: #08221f; background: rgba(var(--ui-teal-rgb), 0.85); } }
        .sb-act:focus-visible, .sb-key:focus-visible { outline: 2px solid rgb(var(--ui-teal-rgb)); outline-offset: 2px; }
        /* Its panel is open: pressed, and a press closes it. */
        .sb-act.open { color: var(--ui-text-bright); background: rgba(var(--ui-accent-rgb), 0.18); }
        /* Someone else has this item open. */
        .sb-act.sb-who { color: #c8b6ff; }
        /* Dragging: the bar is a drop target, red once the item is over it. */
        .sb-strip.sb-drop { padding: 0 16px; color: var(--ui-text-muted); }
        .sb-strip.sb-drop.over { color: #ffb4aa; }
        #smartBar:has(.sb-drop.over) { border-color: rgba(255, 120, 100, 0.7); background: rgba(70, 20, 20, 0.92); }
        .sb-act.sb-step { padding: 0 8px; }
        .sb-act.sb-step .sb-lbl:empty { display: none; }
        .sb-at { padding: 0 2px; font-weight: 600; color: var(--ui-text-muted); }
        .sb-ico { position: relative; display: flex; align-items: center; justify-content: center; height: 20px; font-size: 18px; }
        .sb-badge {
            position: absolute; top: -7px; left: 10px; min-width: 15px; height: 15px; padding: 0 3px; box-sizing: border-box;
            border-radius: 8px; background: #e8643c; color: #fff; font: 700 9px/15px var(--ui-font); text-align: center;
        }
        .sb-badge.quiet { background: #3a5560; }   /* comments, all read */
        .sb-lbl { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
        .sb-kbd {
            margin-left: 2px; padding: 0 4px; border: 1px solid rgba(255, 255, 255, 0.18); border-bottom-width: 2px; border-radius: 4px;
            color: var(--ui-text-muted); font: 600 10px var(--ui-font);
        }
        .sb-count { flex: none; padding: 0 8px; font: 700 13px var(--ui-font); color: var(--ui-text-bright); font-variant-numeric: tabular-nums; }
        .sb-strip.sb-note { gap: 8px; padding: 0 10px; color: var(--ui-text); font: 600 13px var(--ui-font); }
        /* An offer's one action (Undo, Paste here, Update) is lit like the
           lead; a way out (Done, Cancel, Mark seen) is the quiet outline. */
        .sb-key {
            flex: none; height: 34px; display: flex; align-items: center; gap: 8px; padding: 0 14px; border-radius: 17px; cursor: pointer;
            border: 1px solid rgb(var(--ui-teal-rgb)); background: rgb(var(--ui-teal-rgb));
            color: #08221f; font: 700 12.5px var(--ui-font); -webkit-tap-highlight-color: transparent;
        }
        .sb-key .sb-kbd { color: #08221f; border-color: rgba(8, 34, 31, 0.35); }
        .sb-key.quiet { background: rgba(var(--ui-teal-rgb), 0.2); color: var(--ui-text-bright); }
        .sb-key.quiet .sb-kbd { color: var(--ui-text); border-color: rgba(var(--ui-teal-rgb), 0.5); }
        /* Whose the news is. */
        .sb-faces { flex: none; display: flex; }
        .sb-faces i {
            width: 22px; height: 22px; box-sizing: border-box; margin-left: -6px; border-radius: 50%; border: 2px solid rgb(10, 20, 30);
            display: grid; place-items: center; color: #fff; font: 700 10px/1 var(--ui-font); font-style: normal;
        }
        .sb-faces i:first-child { margin-left: 0; }
        /* An offer's time left, draining (SmartBar.js picks it up mid-way
           if the bar redraws). */
        .sb-body, .sb-strip { position: relative; }
        .sb-drain {
            position: absolute; left: 14px; right: 14px; bottom: -3px; height: 2px; border-radius: 1px; pointer-events: none;
            background: rgb(var(--ui-teal-rgb)); opacity: 0.8; transform-origin: left center; animation: sbDrain linear forwards;
        }
        @keyframes sbDrain { to { transform: scaleX(0); } }
        /* One object that changes: the pill springs to its new width (desktop),
           the old content fades out where it was, the new fades in. */
        #smartBar.sb-morph { overflow: hidden; justify-content: center; transition: width 0.5s cubic-bezier(.34, 1.3, .64, 1); }
        .sb-body.sb-enter { animation: sbContentIn 0.26s 0.05s both cubic-bezier(.2, .8, .2, 1); }
        .sb-leave { position: absolute; pointer-events: none; animation: sbContentOut 0.15s ease-in forwards; }
        #smartBar.sb-out { animation: smartBarOut 0.16s ease-in forwards; }
        @keyframes sbContentIn { from { opacity: 0; translate: 0 4px; filter: blur(2px); } }
        @keyframes sbContentOut { to { opacity: 0; translate: 0 -3px; filter: blur(2px); } }
        @keyframes smartBarOut { to { opacity: 0; translate: 0 8px; } }
        /* Read out to a screen reader, never seen. */
        .sb-live { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
        /* While replying with a mouse: the reply's link, from the item to the pointer. */
        #smartBarReplyLine { position: fixed; inset: 0; width: 100%; height: 100%; z-index: 998; pointer-events: none; overflow: visible; }
        #smartBarReplyLine line { stroke: rgb(var(--ui-teal-rgb)); stroke-width: 2; stroke-dasharray: 6 5; stroke-linecap: round; opacity: 0.85; }
        #smartBarReplyLine circle { fill: rgb(var(--ui-teal-rgb)); }
        #smartBarHint {
            position: fixed; z-index: 999; left: 50%; transform: translateX(-50%); bottom: 80px;
            padding: 7px 13px; border-radius: 16px; pointer-events: none;
            border: 1px solid rgb(var(--ui-teal-rgb)); background: rgba(20, 70, 66, 0.92);
            color: var(--ui-text-bright); font: 600 12px var(--ui-font); white-space: nowrap;
        }
        #smartBarHint[hidden] { display: none; }
        /* translate, not transform: the desktop bar is centred by its transform. */
        @keyframes smartBarIn { from { opacity: 0; translate: 0 10px; } }
        @media (prefers-reduced-motion: reduce) {
            #smartBar, #smartBar.sb-out, .sb-body.sb-enter, .sb-leave { animation: none; }
            #smartBar.sb-morph { transition: none; }
        }

        /* ========== PHONE BOTTOM ROW ==========
           One row along the bottom of a phone: the dock at the left end,
           the minimap's button at the right end, the hotspot pill
           between them (hotspot-nav.css), or one row up when the gap is too
           narrow for it. Each used to keep its own spot — the minimap, the
           pill and the create "+" all crowded the same band and covered one
           another (Mikko 3.10.2026). The "+" is gone: long-press makes the
           same things right where the finger is, and an item's long-press
           menu has "Add item here" for a view with no empty canvas left.

           Same condition as the phone toolbar further down: a narrow window,
           or a touch screen on its side. The row is 56px tall; the dock is
           188px wide (four 44px doors, 4px padding and a 2px border each side)
           and 56px folded to the bell. */
        @media (max-width: 768px), (pointer: coarse) and (max-height: 500px) {
            :root {
                --bottom-row: calc(16px + env(safe-area-inset-bottom, 0px));   /* clear of the gesture strip */
                --row-left: calc(10px + env(safe-area-inset-left, 0px));
                --row-right: calc(10px + env(safe-area-inset-right, 0px));
                --row-dock-end: calc(var(--row-left) + 188px);
                --row-map-start: calc(var(--row-right) + 44px);
            }
            #actionDock { bottom: calc(var(--bottom-row) + 2px); left: var(--row-left); height: 52px; }
            #actionDock .fac-door { width: 44px; height: 44px; border-radius: 22px; }
            #actionDock .fac-door-icon { font-size: 20px; }
            #actionDock .fac-door-icon svg { width: 22px; height: 22px; }
            /* The SmartBar or a tour has the row: the dock folds to the bell,
               which then carries every number (ActionDock.js). */
            body.smartbar-on #actionDock .fac-door:not([data-door="activity"]),
            body.smartbar-tour #actionDock .fac-door:not([data-door="activity"]) { width: 0; opacity: 0; transform: scale(0.5); pointer-events: none; }
            body.smartbar-on .fac-count-own, body.smartbar-tour .fac-count-own { display: none; }
            body.smartbar-on .fac-count-all:not([hidden]), body.smartbar-tour .fac-count-all:not([hidden]) { display: flex; }
            /* The minimap is a 44px button centred on the row; the map opens
               above it, smaller, and starts closed (Minimap.js). */
            #minimapContainer {
                bottom: calc(var(--bottom-row) + 6px); right: var(--row-right);
                display: flex; flex-direction: column; align-items: flex-end; gap: 8px;
            }
            #minimapWrapper { width: 160px; height: 112px; }
            #minimapToggle { position: static; order: 1; width: 44px; height: 44px; background: rgba(var(--ui-surface-rgb),0.82); border-color: var(--ui-border); }
            #minimapToggle > span { transform: scale(1.45); }
            #minimapToggle[aria-pressed="true"] { border-color: var(--ui-border-accent); }
            /* Out of the bottom altogether, just under the toolbar. */
            #loadingIndicator { top: calc(env(safe-area-inset-top, 0px) + 60px); bottom: auto; }

            /* ── The SmartBar on a phone (SmartBar.js) ── When something is
               selected the row changes over: the dock folds to the bell
               (above), the minimap steps aside, and the bar takes the rest of
               the row; multi-select takes all of it. The bar's own look is
               the SMARTBAR block above. These are the phone's differences:
               the strip is the pill, buttons stack icon over label, no grip,
               no shortcut keys. */
            body.smartbar-on #minimapContainer { display: none !important; }
            body[data-smartbar="multi"] #actionDock { display: none; }
            #smartBar {
                bottom: calc(var(--bottom-row) + 5px); left: calc(var(--row-left) + 64px); right: var(--row-right);
                transform: none; max-width: none; padding: 0; gap: 8px;
                border: 0; border-radius: 0; background: none; box-shadow: none; -webkit-backdrop-filter: none; backdrop-filter: none;
            }
            body[data-smartbar="multi"] #smartBar { left: var(--row-left); }
            .sb-grip, .sb-kbd { display: none; }
            .sb-body { flex: 1; gap: 8px; }
            .sb-strip {
                flex: 1; height: 46px; box-sizing: border-box; padding: 0 4px; justify-content: space-around;
                border-radius: 23px; border: 2px solid var(--ui-border); background: rgba(var(--ui-surface-rgb), 0.9);
                -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
            }
            .sb-act {
                flex: 1 1 0; min-width: 44px; max-width: 76px; height: 42px; padding: 0;
                flex-direction: column; justify-content: center; gap: 2px; border-radius: 12px; font: 600 10px/1 var(--ui-font);
            }
            .sb-badge { top: -6px; left: calc(50% + 6px); }
            .sb-ico { height: 18px; }
            .sb-ico svg { width: 18px; height: 18px; }
            .sb-act.sb-step { flex: none; min-width: 36px; max-width: 44px; }
            /* The news offer is the tightest strip: the count and the steps first. */
            .sb-strip.sb-news { padding: 0 8px; gap: 2px; }
            .sb-strip.sb-drop { justify-content: center; }
            .sb-strip.sb-drop.over { border-color: rgba(255, 120, 100, 0.7); background: rgba(70, 20, 20, 0.92); }
            .sb-news > .sb-ico { display: none; }
            .sb-count { padding: 0 6px 0 10px; font-size: 14px; }
            .sb-at { padding: 0 2px; font-size: 12px; }
            .sb-strip.sb-note { justify-content: flex-start; padding: 0 16px; }
            .sb-note .sb-lbl { flex: 1; min-width: 0; }
            .sb-key { height: 44px; padding: 0 16px; border-radius: 22px; border-width: 2px; font-size: 13px; }
            .sb-drain { left: 18px; right: 18px; bottom: 4px; }
            .sb-faces i { width: 20px; height: 20px; }
            #smartBarHint { bottom: calc(var(--bottom-row) + 66px); }

            /* A running auto-tour: the dock folds (above), the minimap
               steps aside, and the tour pill takes the row. */
            body.smartbar-tour #minimapContainer { display: none !important; }
            body.smartbar-tour #hotspotNav {
                bottom: calc(var(--bottom-row) + 5px); left: calc(var(--row-left) + 64px); right: var(--row-right);
                width: auto; min-height: 46px; justify-content: space-between;
            }
            body.smartbar-tour #hotspotMid { flex: 1; }
            body.smartbar-tour #hotspotNav .hotspot-name { color: var(--ui-text-bright); }
            /* Typing: the keyboard owns the bottom of the screen. */
            body.smartbar-typing #actionDock, body.smartbar-typing #hotspotNav,
            body.smartbar-typing #minimapContainer, body.smartbar-typing #mobileSelectPill { display: none !important; }
        }

        /* ========== DESKTOP QUICK-CREATE BAR (hidden — use right-click to create) ========== */
        #quickCreateBar {
            display: none;
            align-items: center;
            gap: 2px;
            margin-left: 2px;
        }
        #quickCreateBar .qc-btn {
            background: rgba(20, 40, 55, 0.6);
            border: 2px solid var(--ui-border);
            border-radius: 6px;
            padding: 4px 7px;
            font-size: 15px;
            cursor: pointer;
            transition: all 0.15s ease;
            line-height: 1;
            -webkit-tap-highlight-color: transparent;
        }
        #quickCreateBar .qc-btn:hover {
            background: rgba(var(--ui-primary-rgb), 0.7);
            border-color: var(--ui-border-accent);
            box-shadow: 0 2px 10px rgba(var(--ui-accent-rgb), 0.1);
        }
        #quickCreateBar .qc-btn:active { transform: scale(0.93); }
        #quickCreateBar .qc-more-toggle {
            font-size: 16px;
            font-weight: bold;
            color: rgba(var(--ui-text-rgb), 0.7);
            min-width: 28px;
            text-align: center;
        }
        #quickCreateBar .qc-overflow {
            display: none;
            gap: 2px;
            align-items: center;
            animation: qcSlideIn 0.15s ease;
        }
        #quickCreateBar .qc-overflow.show {
            display: flex;
        }
        @keyframes qcSlideIn {
            from { opacity: 0; transform: translateX(-6px); }
            to { opacity: 1; transform: translateX(0); }
        }

        /* ========== CLOSE BUTTON HOVER (subtle × buttons) ========== */
        .close-btn-subtle {
            position: absolute; top: 2px; right: 4px;
            background: none; border: none;
            color: rgba(var(--ui-text-rgb),0.35);
            font-size: 14px; cursor: pointer;
            padding: 2px 4px; line-height: 1;
            transition: color 0.15s, transform 0.15s;
        }
        .close-btn-subtle:hover {
            color: rgba(var(--ui-text-rgb),0.8);
            transform: scale(1.2);
        }

        /* Activity lists — ONE card per space, and the news inside it.
           Boxes inside boxes is what made these lists hard to read: a pill for
           the place, a bordered strip for every line, and nothing saying which
           belonged to which. Now the card is the only border. Its plate names
           the space and is the door to it; the lines within are lines, told
           apart by hover and by the arrow that says where each one leads. */
        /* ✨ New, to spec: the card carries the unread signal in its own edge —
           a lift and an accent hairline along the top — so no row inside has to
           shout. A space whose news you have already seen keeps its place in
           the list, collapsed and muted, instead of vanishing. */
        .space-card.is-unread {
            background: rgba(var(--ui-accent-rgb), 0.05);
            border-color: rgba(var(--ui-accent-rgb), 0.22);
            box-shadow: inset 0 1px 0 0 rgba(var(--ui-accent-rgb), 0.45);
        }
        /* A card carries exactly TWO looks, and the difference between them is
           the only thing brightness is allowed to mean here: unread, or not.
           A seen card is the plain card — no third, dimmer treatment — because
           a third shade taught nothing and made People (which has no unread
           state to show) look like a lesser kind of list. What "is-read"
           changes is the LAYOUT: it is collapsed, so its plate is the whole
           card and has no body to be separated from. */
        .space-card.is-read .space-plate { background: none; border-bottom: none; }
        .space-card.is-read .space-plate-name { font-size: 13px; }
        /* One event, one row, with a hairline between them. Between rows of the
           same run the line is fainter still, so a thread reads as one block. */
        .event-row { border-top: 1px solid rgba(var(--ui-text-rgb), 0.08); }
        .event-row.is-run { border-top-color: rgba(var(--ui-text-rgb), 0.06); }
        .event-row:hover { background: rgba(var(--ui-text-rgb), 0.03); }

        /* A row that stands for SEVERAL says so with one triangle, big enough
           to be read as a control. At 9px it was a speck after the timestamp
           and nobody knew the row opened. (A stacked-paper effect was tried
           here first; in a list this dense it was more texture than message —
           the mark just had to be legible.) */
        .event-row .activity-run-caret {
            display: inline-block;
            font-size: 13px;
            line-height: 1;
            color: rgba(var(--ui-text-rgb), 0.5);
            transition: transform 0.18s ease, color 0.15s ease;
        }
        .activity-run:hover .activity-run-caret { color: rgba(var(--ui-accent-rgb), 0.9); }
        .event-row.is-open .activity-run-caret {
            transform: rotate(90deg);
            color: rgba(var(--ui-accent-rgb), 0.9);
        }

        /* What came OUT of a row has to look like it came out of that row.
           Opened, the members used to drop into the flat list as ordinary
           lines: four more "admin added a 3d model" appeared, indistinguishable
           from everything around them, and you lost the place you had opened.
           The opened row is lit, and its contents hang under it on a rail —
           inset, tinted, and joined to their parent with no line between. */
        .event-row.is-open {
            background: rgba(var(--ui-accent-rgb), 0.07);
        }
        .activity-run-members {
            margin: 0 0 2px 14px;
            border-left: 2px solid rgba(var(--ui-accent-rgb), 0.4);
            background: rgba(var(--ui-text-rgb), 0.025);
        }
        .activity-run-members .event-row {
            border-top-color: rgba(var(--ui-text-rgb), 0.05);
            padding-left: 10px;
        }
        /* Joined to the row that opened it — a line there would cut the block
           away from its own heading. */
        .activity-run-members .event-row:first-child { border-top: none; }
        .tag-pill {
            display: inline-block;
            /* As wide as the word, no wider. The row's content column is a
               COLUMN flexbox, and a flex child stretches across the cross axis
               unless told otherwise — which turned every pill into a full-width
               bar with a word at one end. */
            align-self: flex-start;
            padding: 3px 8px;
            border-radius: 6px;
            font-size: 11.5px;
            line-height: 1.2;
            font-weight: 500;
            max-width: 100%;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
            background: rgba(var(--ui-tag-rgb), 0.16);
            border: 1px solid rgba(var(--ui-tag-rgb), 0.3);
            color: rgb(var(--ui-tag-rgb));
        }
        .activity-foot {
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
            padding: 18px 16px 20px;
            font-size: 11.5px;
            color: rgba(var(--ui-text-rgb), 0.35);
        }
        .activity-foot a {
            color: rgba(var(--ui-accent-rgb), 0.85);
            font-weight: 500;
            cursor: pointer;
            text-decoration: none;
        }
        .activity-foot a:hover { color: var(--ui-text-bright); }

        .space-card {
            margin: 8px 8px 10px;
            border: 1px solid var(--ui-border-faint);
            border-radius: 12px;
            background: rgba(var(--ui-surface-rgb), 0.45);
            /* NOT overflow:hidden. The tooltips in this app are an ::after of
               their trigger, drawn OUTSIDE it — a clipping card ate every one
               of them, and a card holding a single row ate it whole. The plate
               rounds its own top corners instead, which is all the clipping
               was ever doing here. */
        }
        .space-plate {
            display: flex;
            align-items: center;
            gap: 9px;
            padding: 9px 11px;
            background: rgba(var(--ui-accent-rgb), 0.07);
            border-bottom: 1px solid var(--ui-border-faint);
            border-radius: 11px 11px 0 0;
        }
        .space-plate-go {
            display: inline-flex;
            align-items: center;
            gap: 9px;
            min-width: 0;
            cursor: pointer;
        }
        .space-card-body { padding: 3px 0 5px; }

        /* A line in any of these lists. The arrow sits there at rest because a
           touch screen never hovers; the highlight is the mouse's half of the
           same message. Lines with nowhere to go (a deleted node) get neither. */
        .list-row {
            display: flex;
            align-items: center;
            gap: 7px;
            margin: 1px 7px;
            padding: 5px 9px;
            border-radius: 7px;
            font-size: 12px;
            line-height: 1.4;
        }
        .row-go {
            color: var(--ui-text-dim);
            transition: color 0.15s ease, transform 0.15s ease;
        }
        .list-row:hover,
        .space-plate-go:hover .space-plate-name {
            color: var(--ui-text-bright);
        }
        .list-row:hover,
        .space-plate:hover {
            background: rgba(var(--ui-accent-rgb), 0.10);
        }
        .list-row[data-atype="delete"]:hover { background: none; }
        /* Unseen, in a list that also shows seen things. */
        .list-row.is-new { background: rgba(var(--ui-accent-rgb), 0.10); }
        .list-row.is-new:hover { background: rgba(var(--ui-accent-rgb), 0.18); }
        .list-row:hover .row-go,
        .space-plate:hover .row-go {
            color: rgba(var(--ui-accent-rgb), 0.95);
            transform: translateX(2px);
        }

        /* The owner row in a space profile lost its bordered tile when the card
           was merged into one frame — so the hover has to carry what the border
           used to: this line is a door to a person. */
        #spaceProfileOwnerRow {
            border-radius: 8px;
            margin: -4px -6px;
            padding: 4px 6px;
            transition: background 0.15s;
        }
        #spaceProfileOwnerRow:hover { background: rgba(var(--ui-accent-rgb), 0.08); }

        /* Storage list — one file per row. Hairlines between them, none above
           the first (the group's own border is already there), and a hover
           only where the row actually leads somewhere. */
        .storage-row:first-child { border-top: none !important; }
        .storage-row:hover { background: rgba(var(--ui-text-rgb), 0.03); }
        /* The row is a place to pick from; the arrow and the link are what act.
           A row that teleported on any stray click could not be ticked safely. */
        .storage-go:hover { color: rgba(var(--ui-accent-rgb), 0.95) !important; transform: translateX(2px); }
        .storage-view:hover { color: rgba(var(--ui-accent-rgb), 0.95) !important; }
        .storage-release:hover { background: rgba(255, 120, 120, 0.14); }

        /* Panel close buttons (messages, activity, etc.) */
        .panel-close-btn {
            background: none; border: none;
            color: var(--ui-text-muted); font-size: 16px;
            cursor: pointer; padding: 2px 6px;
            border-radius: 4px;
            transition: color 0.15s, background 0.15s;
        }
        .panel-close-btn:hover {
            color: #b2dcf0;
            background: rgba(var(--ui-accent-rgb), 0.12);
        }
        .panel-close-btn:active {
            transform: scale(0.9);
        }

        /* ========== BREADCRUMB NAVIGATION (replaces exit subspace button) ========== */
        .breadcrumb-nav {
            position: fixed; top: calc(var(--toolbar-height) + 8px); left: 18px;
            display: flex; align-items: center; gap: 0;
            background: rgba(12, 20, 30, 0.85);
            backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
            border: 2px solid var(--ui-border);
            border-radius: 8px;
            padding: 5px 10px;
            z-index: 100;
            box-shadow: 0 4px 16px rgba(0,0,0,0.3);
            font-family: var(--ui-font);
            font-size: 12px;
            max-width: calc(100vw - 36px);
            /* visible, not auto: the trail inside does the scrolling now, and
               a clipping context here swallowed the back button's tooltip. */
            overflow: visible;
        }
        /* The way out, said in a word. An icon alone was still chrome among
           chrome; BACK is the one thing in this bar that has to be found
           without looking for it. */
        .bc-back {
            display: flex; align-items: center; gap: 6px;
            flex-shrink: 0;
            padding: 6px 12px 6px 10px;
            margin-right: 8px;
            border: 1px solid rgba(var(--ui-teal-rgb), 0.45);
            border-radius: 7px;
            background: rgba(var(--ui-teal-rgb), 0.18);
            color: rgba(var(--ui-text-rgb), 0.98);
            font: inherit;
            font-size: 12.5px;
            font-weight: 600;
            letter-spacing: 0.02em;
            cursor: pointer;
            white-space: nowrap;
            transition: background 0.15s, border-color 0.15s, transform 0.25s var(--pop);
        }
        .bc-back:hover {
            background: rgba(var(--ui-teal-rgb), 0.34);
            border-color: rgba(var(--ui-teal-rgb), 0.7);
            transform: translateX(-2px);   /* leans the way it takes you */
        }
        .bc-back:active { transform: translateX(-1px) scale(0.96); }

        /* Only the PATH scrolls. The button must not slide away with it — and
           the overflow was also clipping the button's tooltip, which is why
           hovering it appeared to do nothing at all. */
        .bc-trail {
            display: flex; align-items: center;
            min-width: 0;
            overflow-x: auto;
            scrollbar-width: none;
        }
        .bc-trail::-webkit-scrollbar { display: none; }

        .bc-item {
            color: rgba(var(--ui-text-rgb), 0.62);
            padding: 3px 6px;
            border-radius: 4px;
            cursor: pointer;
            white-space: nowrap;
            transition: color 0.15s, background 0.15s;
            text-decoration: none;
            border: none; background: none;
            font: inherit;
        }
        .bc-item:hover {
            color: rgba(var(--ui-text-rgb), 0.9);
            background: rgba(var(--ui-accent-rgb), 0.1);
        }
        .bc-item.bc-current {
            color: rgba(var(--ui-text-rgb), 0.85);
            font-weight: 600;
            cursor: default;
        }
        .bc-item.bc-current:hover { background: none; }
        .bc-sep {
            color: rgba(var(--ui-accent-rgb), 0.2);
            font-size: 10px;
            padding: 0 2px;
            -webkit-user-select: none;
            user-select: none;
        }
        @media (max-width: 768px) {
            .breadcrumb-nav { top: calc(var(--toolbar-height) + 4px); left: 8px; max-width: calc(100vw - 16px); font-size: 11px; }
        }

        /* ========== DM BACK BUTTON ========== */
        /* ========== BOOKMARK ADD BUTTON ========== */

        /* ========== 3D CAPTURE BUTTON ========== */
        .capture-3d-btn {
            background: rgba(var(--ui-primary-rgb), 0.4);
            color: rgba(255, 255, 255, 0.75);
            border: 2px solid var(--ui-border);
            border-radius: 6px;
            padding: 5px 12px; font-size: 13px;
            cursor: pointer; white-space: nowrap;
            transition: all 0.15s ease;
        }
        .capture-3d-btn:hover {
            background: rgba(40, 80, 100, 0.6);
            border-color: var(--ui-border-accent);
            color: #fff;
        }
        .capture-3d-btn:active { transform: scale(0.97); }
        /* The amber Render button sets its background INLINE (resting amber / active
           Stop-red), which overrides the generic :hover background. A brightness filter
           brightens whatever colour it currently is, so the hover reads in BOTH states. */
        #pt-sidebar-render:hover { filter: brightness(1.4); }

        /* PT panel body lives in the shared Edit/Grade/PT skin (ui-theme.css);
           only its amber scrollbar stays here */
        #ptPropertiesPanel::-webkit-scrollbar { width: 4px; }
        #ptPropertiesPanel::-webkit-scrollbar-track { background: transparent; }
        #ptPropertiesPanel::-webkit-scrollbar-thumb { background: rgba(224,160,64,0.2); border-radius: 2px; }
        #ptPropertiesPanel::-webkit-scrollbar-thumb:hover { background: rgba(224,160,64,0.4); }
        #ptPropertiesPanel { scrollbar-width: thin; scrollbar-color: rgba(224,160,64,0.2) transparent; }
        #ptPropertiesPanel input[type="range"] { height: 4px; }

        /* 3D viewer bottom toolbar + advanced (rt) panel — geometry lives
           here; the rt panel's display stays inline because RenderLoop reads
           style.display to know whether the panel is open.
           Attribute selectors on purpose: the id starts with a DIGIT, and a
           CSS identifier may not — #3d-bottom-toolbar is silently invalid,
           which is how the whole toolbar spent a month rendering at the TOP
           of the modal underneath the canvas. The July CSS extraction moved
           these rules out of inline styles, where the digit was fine. */
        [id="3d-bottom-toolbar"] {
            position: absolute;
            bottom: 12px;
            left: 50%;
            transform: translateX(-50%);
            z-index: calc(var(--z-lightbox) + 1);
            overflow: visible;
        }
        [id="3d-bottom-toolbar"] > div:first-child {
            background: rgba(0,0,0,0.6);
            backdrop-filter: blur(10px);
            -webkit-backdrop-filter: blur(10px);
            padding: 8px 16px;
            border-radius: 8px;
            display: flex;
            align-items: center;
            gap: 10px;
        }
        #object-rotation-slider { width: 200px; cursor: pointer; }
        #rt-settings-panel {
            position: absolute;
            top: calc(100% + 4px);
            left: 0;
            right: 0;
            background: rgba(0,0,0,0.6);
            backdrop-filter: blur(10px);
            -webkit-backdrop-filter: blur(10px);
            padding: 10px 16px;
            border-radius: 8px;
            font-size: 11px;
        }
        #rt-settings-panel input[type="range"] { width: 70px; cursor: pointer; }
        #rt-settings-panel input[type="color"] {
            width: 22px;
            height: 18px;
            padding: 0;
            border: 1px solid rgba(255,255,255,0.2);
            border-radius: 3px;
            background: none;
            cursor: pointer;
        }
        #fullscreen-video {
            display: block;
            max-width: 90vw;
            max-height: 80vh;
            border-radius: 4px;
        }

        /* Tool-panel identity accents — these fullscreen overlays keep their
           own hue regardless of theme (grade panel purple, path-tracer amber,
           r128-viewer blue). Everything else inherits the theme accent from
           the global accent-color rule (ui-theme.css). */
        #imageEditPanel input { accent-color: #9a8ac0; }
        #ptPropertiesPanel input { accent-color: #e0a040; }
        #rt-settings-panel input { accent-color: #4ea8de; }

        /* ========== BURGER DROPDOWN ========== */
        #desktopBurgerDropdown {
            display: none;
            position: absolute;
            right: 0;
            top: calc(100% + 4px);
            min-width: 200px;
            background: rgba(var(--ui-surface-rgb), 0.97);
            border: 2px solid var(--ui-border);
            border-radius: 10px;
            padding: 6px 0;
            z-index: var(--z-nav);
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
            -webkit-backdrop-filter: blur(12px);
            backdrop-filter: blur(12px);
            transform-origin: top right;
        }
        #desktopBurgerDropdown.open {
            display: block;
            animation: burgerDropdownIn 0.15s ease-out forwards;
        }
        #desktopBurgerDropdown.closing {
            animation: burgerDropdownOut 0.12s ease-in forwards;
        }

        /* ========== KUUKAN UNIFIED MODAL SYSTEM ========== */
        
        /* Fullscreen overlay base */
        .kuukan-overlay {
            display: none;
            position: fixed;
            top: 0; left: 0;
            width: 100%; height: 100%;
            background: rgba(0,0,0,0.7);
            justify-content: center;
            align-items: center;
            /* Above the GameZone stack (diorama 2100 / game bar 2200 / rules
               panel 2300) — a modal prompt must never sink behind a game. */
            z-index: var(--z-modal);
        }
        /* Overlay backdrop blur variant (utility dialogs) */
        .kuukan-overlay--blur {
            background: rgba(0,0,0,0.5);
            backdrop-filter: blur(4px);
            -webkit-backdrop-filter: blur(4px);
        }
        /* Theme picking needs the real canvas behind it — a dimmed ground
           makes every theme read darker than it is. Toggled by the settings
           tab switchers; !important so it also beats inline backdrops. */
        .kuukan-overlay--clear {
            background: transparent !important;
            backdrop-filter: none !important;
            -webkit-backdrop-filter: none !important;
        }

        /* Centered dialog content box */
        /* ONE surface recipe for every modal/panel shell — dialogs,
           floating panels and the auth container previously each carried a
           private copy (and .kuukan-dialog's background was a hardcoded
           navy that ignored themes). Each class below adds geometry only. */
        /* Every dialog moves by its title (modules/DragPanel.js
           makeDialogsDraggable). Set before the first touch: touch-action is
           decided at pointerdown, and without it a phone scrolls instead. */
        .kuukan-dialog > h1, .kuukan-dialog > h2, .kuukan-dialog > h3, .kuukan-dialog > header,
        .authContainer > h2, [data-drag-handle] {
            cursor: grab;
            touch-action: none;
            -webkit-user-select: none;
            user-select: none;
        }
        /* A side panel is a sheet on a phone and does not move by its header. */
        @media (max-width: 768px) {
            [data-drag-panel] [data-drag-handle] { cursor: auto; touch-action: auto; }
        }

        .kuukan-dialog, .kuukan-panel, .authContainer {
            background: rgba(var(--ui-surface-rgb), 0.95);
            backdrop-filter: blur(16px);
            -webkit-backdrop-filter: blur(16px);
            border: 2px solid var(--ui-border);
            border-radius: 12px;
            box-shadow: 0 8px 32px rgba(0,0,0,0.5);
        }
        .kuukan-dialog {
            padding: 24px;
            width: 90%;
            max-width: 400px;
            animation: modalContentIn 0.25s ease;
        }
        /* Space settings' visibility cards (SpaceSettings _syncVisibilityCards):
           the theme's own surface, and its accent for the one chosen. */
        .vis-card {
            display: flex; flex-direction: column; align-items: center; gap: 4px;
            padding: 12px 6px 10px; border-radius: 10px; cursor: pointer;
            border: 1px solid var(--ui-border-faint); background: rgba(var(--ui-text-rgb), 0.05);
            transition: border-color 0.18s, background 0.18s, transform 0.18s, box-shadow 0.18s;
        }
        .vis-card:hover { border-color: var(--ui-border-accent); }
        .vis-card.on {
            border-color: var(--ui-accent); background: rgba(var(--ui-accent-rgb), 0.16);
            transform: translateY(-2px);
            box-shadow: 0 0 0 1px rgba(var(--ui-accent-rgb), 0.5), 0 6px 22px rgba(var(--ui-accent-rgb), 0.3);
        }
        /* A small control still takes a 44px tap under a finger (touch-first).
           The ring is invisible and moves nothing: it only widens where the
           control can be hit. ::before, because ::after is the tooltip. A
           control placed absolutely keeps its own position (inline wins). */
        @media (pointer: coarse) {
            .touch-target { position: relative; }
            .touch-target::before {
                content: ''; position: absolute; top: 50%; left: 50%;
                width: max(100%, 44px); height: max(100%, 44px);
                transform: translate(-50%, -50%);
            }
        }
        /* The confirm's two answers (KuukanModals showKuukanConfirm). The
           verb is the button's text; .danger is a verb that destroys. */
        .kuukan-confirm-cancel, .kuukan-confirm-ok {
            padding: 8px 16px; border-radius: 6px; font-size: 13px; cursor: pointer;
            min-height: 36px;
        }
        .kuukan-confirm-cancel {
            background: rgba(var(--ui-text-rgb), 0.06); border: 2px solid var(--ui-border);
            color: var(--ui-text-muted);
        }
        .kuukan-confirm-ok {
            padding-inline: 20px;
            background: linear-gradient(135deg, rgba(40,100,140,0.8), rgba(var(--ui-accent-rgb),0.8));
            border: 2px solid var(--ui-border-accent); color: #fff; font-weight: 600;
        }
        .kuukan-confirm-ok.danger {
            /* Darkened so white text reads on every theme's danger red. */
            background: color-mix(in srgb, var(--ui-danger) 70%, black);
            border-color: var(--ui-danger);
        }
        .kuukan-confirm-cancel:focus-visible, .kuukan-confirm-ok:focus-visible {
            outline: 2px solid var(--ui-border-accent); outline-offset: 2px;
        }
        @media (pointer: coarse) {
            .kuukan-confirm-cancel, .kuukan-confirm-ok { min-height: 44px; }
        }
        /* Dialog size variants */
        .kuukan-dialog--sm  { max-width: 380px; }
        .kuukan-dialog--md  { max-width: 450px; }
        .kuukan-dialog--lg  { max-width: 520px; }
        .kuukan-dialog--xl  { max-width: 600px; }
        /* Scrollable dialog */
        .kuukan-dialog--scroll {
            max-height: 80vh;
            overflow-y: auto;
        }
        /* The super-admin dialog. It was its own violet world, unreadable on a
           light theme; it wears the theme like every other panel now. */
        .kuukan-dialog--purple {
            background: var(--ui-surface);
            border-color: var(--ui-border-faint);
        }
        .admin-tab-btn {
            padding: 6px 14px; border-radius: 6px; cursor: pointer; font-size: 12px; font-family: var(--ui-font);
            background: var(--ui-surface-alt); border: 1px solid var(--ui-border-faint); color: var(--ui-text-muted);
        }
        .admin-tab-btn:hover { color: var(--ui-text); }
        .admin-tab-btn.active { background: rgba(var(--ui-accent-rgb), 0.16); border-color: var(--ui-border-accent); color: var(--ui-text-bright); }
        /* Theme: Green (confirm/prompt) */
        .kuukan-dialog--green {
            background: linear-gradient(135deg, #0a1210, #121e18);
            border-color: rgba(160,201,8,0.3);
            box-shadow: 0 8px 32px rgba(0,0,0,0.6), 0 0 40px rgba(160,201,8,0.1);
        }
        /* Theme: Cyan (AI) */
        .kuukan-dialog--cyan {
            background: linear-gradient(135deg, #0a1220, #142030);
            border-color: var(--ui-border-accent);
            box-shadow: 0 8px 32px rgba(0,0,0,0.6), 0 0 40px rgba(var(--ui-accent-rgb),0.1);
        }

        /* Fullscreen media viewer */
        .kuukan-viewer {
            display: none;
            position: fixed;
            top: 0; left: 0;
            width: 100%; height: 100%;
            background: rgba(0,0,0,0.95);
            justify-content: center;
            align-items: center;
        }
        .kuukan-viewer.active {
            display: flex;
            animation: modalFadeIn 0.2s ease;
        }
        .kuukan-viewer.show {
            display: flex;
            animation: modalFadeIn 0.2s ease;
        }

        /* Floating panel */
        .kuukan-panel {
            display: none;
            position: fixed;
            z-index: calc(var(--z-panel) + 1);
            font-family: var(--ui-font);
        }
        /* Panel theme: Gold (shout) */
        .kuukan-panel--gold {
            background: rgba(25,22,15,0.95);
            border-color: rgba(249,202,36,0.3);
            z-index: 999;
        }
        /* Panel theme: Purple (bookmarks) */
        /* ========== END KUUKAN MODAL SYSTEM ========== */

        #imageViewer {
            z-index: var(--z-nav);
        }
        #imageViewer.active {
            display: flex;
            animation: modalFadeIn 0.2s ease;
        }
        #imageViewer img {
            max-width: 90%;
            max-height: 90%;
            border-radius: 8px;
            user-select: none;
            -webkit-user-select: none;
            pointer-events: none;
        }
        #imageViewer .close {
            position: absolute;
            top: 20px;
            right: 20px;
            color: var(--ui-text);
            font-size: 32px;
            cursor: pointer;
            -webkit-user-select: none;
            user-select: none;
            pointer-events: auto;
        }
        #imageViewer .close:hover {
            color: #ffff00;
        }
        #object3dViewer {
            z-index: var(--z-org-modal);
        }
        #object3dViewer.active {
            display: flex;
            animation: modalFadeIn 0.2s ease;
        }
        #object3dViewer .close {
            position: absolute;
            top: 20px;
            right: 20px;
            color: var(--ui-text);
            font-size: 32px;
            cursor: pointer;
            -webkit-user-select: none;
            user-select: none;
            pointer-events: auto;
        }
        #object3dViewer .close:hover {
            color: #ffff00;
        }
        #threeContainer {
            width: 80vw;
            height: 80vh;
            background: #222;
            border-radius: 12px;
            box-shadow: 0 0 32px #000;
        }
        /* 3D Object Viewer Modal */
        #modal-3d {
            display: none;
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 690px;
            height: 690px;
            background: linear-gradient(180deg, #3b3b3b 0%, #0d0d0d 100%);
            z-index: var(--z-lightbox);
            overflow: visible;
            border-radius: 12px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
            border: 2px solid var(--ui-border);
        }
        #modal-3d.show {
            display: block;
            animation: modalContentIn 0.25s ease;
        }
        #modal-3d-content {
            width: 100%;
            height: 100%;
            background: transparent;
            overflow: hidden;
            display: block;
            position: relative;
            top: 0;
            left: 0;
        }
        
        /* Mobile responsive styles */
        /* Empty-state hint pair: desktop text by default, swapped on mobile
           by the media block below. */
        .mobile-hint { display: none; }

        /* TURNING THE PHONE MUST NOT CHANGE HOW IT IS NAVIGATED.
           844x390 is wider than 768, so a landscape phone was handed the
           desktop bar: no hamburger and none of the phone layout. The second condition is the
           device, not the window — a coarse pointer in a short viewport is a
           phone on its side. A touchscreen laptop or a tablet in landscape
           has the height (>500px) and keeps the desktop layout. */
        @media (max-width: 768px), (pointer: coarse) and (max-height: 500px) {
            /* Show hamburger, hide most toolbar items. Plain overrides win by
               source order over the base rules earlier in this file. */
            #hamburgerBtn { display: flex; align-items: center; }

            /* Toolbar: only show logo, essential buttons, hamburger.
               Toolbar stays above the open mobile menu (hamburger close). */
            #ui { padding: 6px 10px; z-index: calc(var(--z-nav) + 1); }
            .toolbar-container {
                flex-wrap: nowrap;
                gap: 4px;
            }
            .toolbar-logo { margin-right: 6px; }
            .toolbar-logo img { height: 26px; }
            .toolbar-divider { display: none; }

            /* Hide most toolbar buttons - they go in hamburger menu.
               !important is doing real work here: admin-mode JS reveals some
               of these with element.style.display = 'inline-block', and
               mobile must win over that app state. */
            #searchBtn, #shareToggleBtn, #snapGridBtn, #helpBtn,
            #itemsBtn, #toggle3DBtn, #adminModeBtn, #adminPanelBtn,
            /* Team Admin is a duplicate on a phone — the burger already
               carries Team Settings, and the bar's width belongs to the
               space name. */
            #orgAdminBtn,
            #downloadBtn, .toolbar-right, #quickCreateBar { display: none !important; }
            /* The Map pill slims to the compass alone — but the SPACE PILL
               STAYS: without it the phone bar was logo, a lone compass and
               a burger around a void ("aika orpo"), and the one fact a bar
               owes you — where am I — was hidden in the menu. Compact form:
               name only, owner line dropped, tap still shares. */
            .map-pill-label { display: none !important; }
            .space-pill { height: 36px; max-width: 46vw; padding: 0 10px; }
            #toolbarSpaceOwner { display: none !important; }

            /* Desktop burger hidden on mobile (mobile menu takes over) */
            #desktopBurgerWrap { display: none; }

            /* Keep: globalSpaceBtn visible — icon-only, still the 40px pill */
            #globalSpaceBtn {
                padding: 0 9px;
            }

            #placeHint { display: none; }

            /* Toggle mobile/desktop hints in empty state */
            .desktop-hint { display: none; }
            .mobile-hint { display: inline-block; }

            #nodeInput {
                width: 200px;
                font-size: 14px;
            }

            /* 3D Modal - full screen on mobile */
            #modal-3d {
                width: 95vw;
                height: 80vh;
                max-width: none;
                border-radius: 8px;
            }
            /* 3D toolbar - compact for tablet */
            [id="3d-bottom-toolbar"] {
                max-width: 95%;
            }
            [id="3d-bottom-toolbar"] > div:first-child {
                padding: 6px 10px;
                gap: 6px;
            }
            #object-rotation-slider {
                width: 120px;
            }
            #rt-settings-panel {
                padding: 8px 10px;
            }

            /* Video modal - better sizing */
            #fullscreen-video {
                max-width: 95vw;
                max-height: 70vh;
            }
            #video-close-btn {
                font-size: 12px;
                padding: 4px 8px;
            }
        }
        
        /* Extra small screens (phones) */
        @media (max-width: 480px) {
            #modal-3d {
                width: 100vw;
                height: 70vh;
                border-radius: 0;
                top: auto;
                bottom: 0;
                transform: translate(-50%, 0);
            }
            /* Toolbar: compact single row */
            [id="3d-bottom-toolbar"] {
                width: calc(100% - 16px);
                max-width: none;
                bottom: 6px;
                left: 8px;
                transform: none;
            }
            [id="3d-bottom-toolbar"] > div:first-child {
                padding: 5px 8px;
                gap: 4px;
                flex-wrap: wrap;
                justify-content: center;
            }
            #object-rotation-slider {
                width: 80px;
            }
            [id="3d-bottom-toolbar"] .capture-3d-btn {
                padding: 3px 6px;
                font-size: 10px;
            }
            [id="3d-bottom-toolbar"] span[style*="color:var(--ui-text-muted)"] {
                display: none;
            }
            [id="3d-bottom-toolbar"] span[style*="color:#444"] {
                display: none;
            }
            #rotation-value {
                display: none;
            }
            /* Flip advanced panel to open ABOVE toolbar on phones */
            #rt-settings-panel {
                top: auto;
                bottom: calc(100% + 4px);
                padding: 8px 10px;
                max-height: 40vh;
                overflow-y: auto;
            }
            #rt-settings-panel input[type="range"] {
                width: 65px;
            }
            #rt-settings-panel input[type="color"] {
                width: 20px;
                height: 16px;
            }
            .capture-3d-btn {
                padding: 4px 8px;
                font-size: 11px;
            }

            #fullscreen-video {
                max-height: 60vh;
            }
        }
        
        /* Messages panel — CSS is the single owner of the geometry; JS only
           flips display + the .open class (revealNodeCommentsPanel /
           hideNodeCommentsPanel). Desktop: left drawer that slides in,
           anchored below the toolbar with room at the bottom for the
           dock. Mobile: bottom sheet, half screen. */
        #nodeCommentsPanel {
            top: 130px;
            bottom: 140px;
            left: -460px;
            width: 440px;
            max-width: calc(100vw - 40px);
            border-radius: 12px;
            z-index: calc(var(--z-panel) - 1);
            transition: left 0.3s ease;
        }
        #nodeCommentsPanel.open { left: 20px; }
        @media (max-width: 768px) {
            #nodeCommentsPanel {
                top: auto;
                /* --kb-inset is what the on-screen keyboard covers (0 unless a
                   keyboard is up — see KeyboardInset.js). iOS leaves fixed
                   elements anchored to the layout viewport, so without this the
                   panel's input row spends the whole conversation underneath
                   the keyboard, with no way to scroll it into view. */
                bottom: var(--kb-inset, 0px);
                left: -100vw;
                width: 100%;
                max-width: 100%;
                height: 50vh;
                max-height: 50vh;
                border-radius: 12px 12px 0 0;
                padding-bottom: env(safe-area-inset-bottom, 0px); /* clear iOS home indicator */
                transition: left 0.3s ease, bottom 0.2s ease;
            }
            #nodeCommentsPanel.open { left: 0; }
            /* Full screen. A conversation on a phone is a full-screen thing —
               half a screen is the header, the input row and the keyboard, with
               a few lines of talk left between them.

               100dvh, not 100vh: on mobile browsers vh counts the space the URL
               bar occupies, so a 100vh sheet is taller than the window and the
               input row ends up below the fold — the one part you need. */
            #nodeCommentsPanel.chat-fullscreen {
                /* Defined by its EDGES, not by a height. Anchored to the bottom
                   and given a viewport height, the panel grew upwards past the
                   top of the window and put its own header — with the button
                   that turns this off — underneath the toolbar. You could get
                   in and not out.

                   Two edges also make the viewport-unit question go away: the
                   box is exactly the space between them, whatever the URL bar
                   is doing. */
                /* The same clearance the breadcrumb and the trash panel use:
                   the bar's buttons can wrap on a narrow phone, so sitting
                   exactly at its declared height is sitting too high. */
                top: calc(var(--toolbar-height) + 8px);
                bottom: var(--kb-inset, 0px); /* sit on the keyboard, not under it */
                height: auto;
                max-height: none;
                border-radius: 0;
            }
        }
        /* The panel is already beside a whole canvas on a desktop. */
        @media (min-width: 769px) {
            #chatFullscreenBtn { display: none; }
        }

        /* Floating overlays — geometry lives here; the display state stays
           inline because JS toggles (and for the notifications panel, reads)
           element.style.display. */
        #usersInSpace {
            position: absolute;
            top: 60px;
            right: 18px;
            z-index: 100; /* under every floating panel, above the canvas */
            background: rgba(var(--ui-surface-rgb), 0.88);
            backdrop-filter: blur(14px);
            -webkit-backdrop-filter: blur(14px);
            border: 2px solid var(--ui-border);
            border-radius: 10px;
            padding: 10px 12px;
            font-family: var(--ui-font);
            max-height: 320px;
            overflow-y: auto;
            min-width: 200px;
            max-width: 260px;
            box-shadow: 0 6px 28px rgba(0, 0, 0, 0.38);
        }
        #notificationsPanel {
            position: fixed;
            bottom: 96px;
            left: 20px;
            width: 320px;
            max-height: 420px;
            background: rgba(var(--ui-surface-rgb), 0.97);
            border: 2px solid var(--ui-border);
            border-radius: 12px;
            z-index: calc(var(--z-panel) - 2);
            overflow: hidden;
            flex-direction: column;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
        }

        /* Mobile only - floating overlay positions */
        @media (max-width: 768px) {
            #usersInSpace {
                top: 60px;
                right: 10px;
                max-height: 120px;
            }

            /* The dock's place is the PHONE BOTTOM ROW above. */
            /* Mobile: chat panel covers bottom half — hide the dock and the
               SmartBar so nothing overlaps the Send button (the panel has its
               own ×). */
            body.chat-panel-open #actionDock,
            body.chat-panel-open #smartBar {
                display: none;
            }

            #notificationsPanel {
                left: 10px;
                width: calc(100vw - 20px);
            }
        }
        
        /* Image and Video Viewer Modals */
        #modal-image.show,
        #modal-video.show {
            display: flex;
            animation: modalFadeIn 0.2s ease;
        }
        
        /* Hide Chrome's picture-in-picture and other overlay buttons on video */
        video::-webkit-media-controls-overlay-enclosure,
        video::-webkit-media-controls-enclosure {
            overflow: hidden;
        }
        video::-webkit-media-controls-panel {
            width: calc(100% + 30px); /* Hides overflow buttons */
        }
        /* Firefox */
        video::-moz-media-controls {
            overflow: hidden;
        }
        
        /* Avatar Picker Modal */
        #avatarPickerModal {
            z-index: calc(var(--z-lightbox) - 1);
            background: rgba(0, 0, 0, 0.8);
        }
        #avatarPickerModal.active {
            display: flex;
            animation: modalFadeIn 0.2s ease;
        }
        #avatarPickerModal.active > div {
            animation: modalContentIn 0.25s ease;
        }
        #avatarPickerModal .profile-settings-dialog {
            display: flex;
            flex-direction: column;
            max-height: min(80vh, calc(100dvh - 76px));
            overflow: hidden;
        }
        #avatarPickerModal .profile-settings-header { flex: none; }
        #profileSettingsBody {
            min-height: 0;
            overflow-y: auto;
            overscroll-behavior: contain;
            overflow-anchor: none;
            padding-right: 8px;
            margin-right: -8px;
        }
        #avatarPickerModal ::-webkit-scrollbar { width: 6px; }
        #avatarPickerModal ::-webkit-scrollbar-track { background: rgba(var(--ui-surface-rgb), 0.4); border-radius: 3px; }
        #avatarPickerModal ::-webkit-scrollbar-thumb { background: rgba(var(--ui-accent-rgb), 0.3); border-radius: 3px; }
        #avatarPickerModal ::-webkit-scrollbar-thumb:hover { background: rgba(var(--ui-accent-rgb), 0.5); }
        #avatarPickerModal * { scrollbar-width: thin; scrollbar-color: rgba(var(--ui-accent-rgb), 0.3) rgba(var(--ui-surface-rgb), 0.4); }

        /* Match the rest of Kuukan: buttons inside User Settings brighten on hover.
           Inline styles dominate, so we go via filter (which always wins for contrast). */
        #avatarPickerModal button { transition: filter 0.15s ease, transform 0.1s ease; }
        #avatarPickerModal button:not(:disabled):hover { filter: brightness(1.2); }
        #avatarPickerModal button:not(:disabled):active { transform: scale(0.97); }

        /* Plan cards on Plans page — make the whole card feel interactive when there's an Upgrade button.
           Inline styles always win over CSS rules, so we hover via `filter` which is independent. */
        .plan-card { transition: filter 0.15s ease, transform 0.15s ease; }
        .plan-card:has([data-plan-action]) { cursor: pointer; }
        .plan-card:has([data-plan-action]):hover { filter: brightness(1.3); transform: translateY(-1px); }

        /* Credit packs */
        .credit-pack { transition: filter 0.15s ease, transform 0.15s ease; }
        .credit-pack:hover { filter: brightness(1.3); transform: translateY(-1px); }

        /* Login/Register Modal */
        #authModal {
            z-index: var(--z-modal);
            background: rgba(0, 0, 0, 0.8);
        }
        #authModal.active,
        #forgotPasswordModal.active,
        #resetPasswordModal.active {
            display: flex;
            animation: modalFadeIn 0.2s ease;
        }
        /* ═══ Landing Page ═══ */
        #landingPage {
            position: fixed; inset: 0; z-index: var(--z-modal-2);
            background: linear-gradient(180deg, #0e2a2c 0%, #050505 100%);
            font-family: var(--ui-font);
            overflow-y: auto;
            overflow-x: hidden; /* tilted quips hang outside the hero wrapper */
            scroll-behavior: smooth;
            /* Offset anchor jumps so the target doesn't slide under the sticky nav */
            scroll-padding-top: 80px;
            /* Custom scrollbar — teal-tinted, thin, matches Kuukan theme */
            scrollbar-width: thin;
            scrollbar-color: rgba(var(--ui-teal-rgb),0.35) rgba(15,25,32,0.4);
            /* Mobile-friendly snap: swipes settle at section starts. Desktop
               wheel uses a JS handler that jumps section-by-section. */
            /* scroll-snap removed — JS section-snap handler manages wheel/swipe
               and CSS snap was tugging against programmatic scrollTo */
        }
        #landingPage::-webkit-scrollbar { width: 10px; }
        #landingPage::-webkit-scrollbar-track {
            background: rgba(15,25,32,0.4);
        }
        #landingPage::-webkit-scrollbar-thumb {
            background: rgba(var(--ui-teal-rgb),0.3);
            border-radius: 6px;
            border: 2px solid rgba(15,25,32,0.5);
        }
        #landingPage::-webkit-scrollbar-thumb:hover {
            background: rgba(var(--ui-teal-rgb),0.55);
        }
        /* Ambient portal canvas — same shader as Kuukan, a few orbs drifting
           in the background. Constrained to the hero viewport so they feel
           like accents rather than wallpaper. */
        .lp-portals-canvas {
            position: fixed; top: 0; left: 0; width: 100%; height: 100vh;
            pointer-events: none;
            opacity: 0.45;
            z-index: 0;
        }
        /* Grid overlay — rounded squares like the canvas grid */
        .lp-grid {
            position: fixed; top: 0; left: 0; width: 100%; height: 100vh;
            background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='60' height='60'%3E%3Crect x='0' y='0' width='60' height='60' rx='16' ry='16' fill='none' stroke='rgba(22,110,110,0.55)' stroke-width='1'/%3E%3C/svg%3E");
            background-size: 60px 60px;
            mask-image: radial-gradient(ellipse at 50% 40%, rgba(0,0,0,0.8) 0%, transparent 60%);
            -webkit-mask-image: radial-gradient(ellipse at 50% 40%, rgba(0,0,0,0.8) 0%, transparent 60%);
            pointer-events: none;
            z-index: 0;
        }
        /* Sticky top nav */
        .lp-nav {
            position: sticky; top: 0;
            display: flex; align-items: center; justify-content: space-between;
            padding: 12px 28px;
            background: rgba(10,22,28,0.7);
            backdrop-filter: blur(16px);
            -webkit-backdrop-filter: blur(16px);
            border-bottom: 1px solid var(--ui-border-faint);
            z-index: 10;
        }
        .lp-nav-brand {
            display: flex; align-items: center; gap: 10px;
            color: var(--ui-text); font-weight: 600; font-size: 16px;
            cursor: pointer; text-decoration: none;
        }
        .lp-nav-brand img { height: 28px; width: auto; }
        .lp-nav-anchors { display: flex; gap: 28px; }
        .lp-nav-anchors a {
            color: rgba(var(--ui-text-rgb),0.65);
            text-decoration: none;
            font-size: 13px;
            font-weight: 500;
            transition: color 0.15s;
        }
        .lp-nav-anchors a:hover { color: var(--ui-text-bright); }
        .lp-nav-cta { display: flex; gap: 10px; align-items: center; }
        .lp-nav-btn {
            padding: 8px 16px; border-radius: 8px; font-size: 13px; font-weight: 600;
            cursor: pointer; font-family: var(--ui-font);
            transition: filter 0.15s ease, background 0.15s ease, border-color 0.15s ease;
        }
        .lp-nav-btn-ghost {
            background: transparent;
            color: rgba(var(--ui-text-rgb),0.85);
            border: 2px solid var(--ui-border-accent);
        }
        .lp-nav-btn-ghost:hover { background: rgba(var(--ui-accent-rgb),0.06); color: var(--ui-text-bright); }
        .lp-nav-btn-primary {
            background: linear-gradient(135deg, rgba(var(--ui-primary-rgb),0.9), rgba(20,70,90,1));
            color: var(--ui-text-bright);
            border: 2px solid var(--ui-border-accent);
        }
        .lp-nav-btn-primary:hover { filter: brightness(1.2); }
        @media (max-width: 768px) {
            /* Nav fixed so it doesn't scroll away */
            .lp-nav {
                position: fixed; top: 0; left: 0; right: 0;
                padding: 10px 16px;
            }
            .lp-nav-anchors { display: none; }
            .lp-nav-btn { padding: 7px 12px; font-size: 12px; }
            .lp-nav-brand span { display: none; }

            /* Vertical snap — sections scroll downward one at a time */
            #landingPage {
                scroll-snap-type: y mandatory;
                scroll-behavior: auto;
                scroll-padding-top: 0;
                scrollbar-width: none;
            }
            #landingPage::-webkit-scrollbar { display: none; }

            /* border-box so padding doesn't add to width — fixes horizontal overflow */
            .lp-content { padding: 0 20px; box-sizing: border-box; }

            /* Each section fills the screen and snaps into place */
            .lp-section {
                scroll-snap-align: start;
                min-height: 100vh; min-height: 100dvh;
                padding: 58px 0 16px;
                box-sizing: border-box;
                display: flex; flex-direction: column; justify-content: center;
                scroll-margin-top: 0;
            }
            .lp-section:first-of-type { padding-top: 58px; }

            /* Section dots — right-side progress indicator */
            .lp-dots { display: flex; }

            /* Compact sizing for portrait */
            .lp-logo { max-width: 220px; }
            .lp-tagline { font-size: 18px; }
            .lp-subtagline { font-size: 14px; }
            .lp-hero { margin-bottom: 12px; }
            .lp-hero-card { padding: 28px 20px 24px; border-radius: 18px; }

            .lp-section h2 { font-size: 19px; margin-bottom: 8px; }
            .lp-section-label { font-size: 10px; margin-bottom: 8px; }
            .lp-section-body { font-size: 13px; margin-bottom: 12px; }

            .lp-actions { margin-bottom: 10px; gap: 8px; }
            .lp-btn { padding: 11px 20px; font-size: 13px; }

            .lp-features { gap: 8px; margin-bottom: 12px; }
            .lp-feature { padding: 12px 10px; }

            .lp-usecases-split { gap: 10px; }
            .lp-uc-card { padding: 18px 16px; }
            .lp-uc-icon { width: 50px; height: 50px; margin-bottom: 10px; }
            .lp-uc-label { font-size: 10.5px; margin-bottom: 12px; letter-spacing: 1.6px; }
            .lp-uc-badges { gap: 6px; margin-bottom: 14px; }
            .lp-uc-badge { padding: 5px 10px; font-size: 11.5px; }
            .lp-uc-tagline { font-size: 12.5px; margin-bottom: 12px; }

            .lp-teams-checklist { margin: 10px 0 14px; }
            .lp-teams-checklist li { font-size: 13px; padding: 4px 0; }
            .lp-teams-mockup { padding: 14px; }
        }

        /* Dots — hidden on desktop, shown on mobile via media query above */
        .lp-dots {
            display: none;
            position: fixed; right: 10px; top: 50%; transform: translateY(-50%);
            flex-direction: column; gap: 7px; z-index: 15;
            pointer-events: none;
        }
        .lp-dot {
            width: 6px; height: 6px; border-radius: 50%;
            background: rgba(var(--ui-text-rgb),0.25);
            transition: background 0.25s, transform 0.25s;
        }
        .lp-dot.active {
            background: rgba(var(--ui-teal-rgb),0.9);
            transform: scale(1.4);
        }
        .lp-content {
            position: relative; z-index: 1;
            max-width: 720px; width: 100%; margin: 0 auto;
            padding: 0 24px 32px;
            text-align: center;
        }
        /* Section rhythm — generous breathing room between sections gives
           the page the "scroll-y modern landing" feel instead of crammed.
           scroll-margin-top ensures anchor jumps don't land under the nav. */
        .lp-section {
            padding: 60px 0 40px;
            scroll-margin-top: 80px;
        }
        .lp-section:first-of-type { padding-top: 80px; }
        .lp-scroll-hint {
            position: relative; z-index: 1;
            margin-top: 32px;
            display: flex; justify-content: center;
            color: rgba(255,255,255,0.28);
            cursor: pointer;
            animation: lpScrollBounce 2.2s ease-in-out infinite;
        }
        .lp-scroll-hint:hover { color: rgba(255,255,255,0.55); }
        @keyframes lpScrollBounce {
            0%, 100% { transform: translateY(0); opacity: 0.28; }
            50%       { transform: translateY(9px); opacity: 0.55; }
        }
        .lp-section-label {
            color: rgba(var(--ui-teal-rgb),0.85);
            font-size: 11px; font-weight: 600;
            letter-spacing: 2px; text-transform: uppercase;
            margin-bottom: 12px;
        }
        .lp-section h2 {
            color: var(--ui-text); font-size: 26px; font-weight: 600;
            margin: 0 0 14px; line-height: 1.25;
        }
        .lp-section-body {
            color: rgba(var(--ui-text-rgb),0.7);
            font-size: 15px; line-height: 1.6;
            max-width: 540px; margin: 0 auto 24px;
        }
        .lp-hero { margin-bottom: 28px; }
        /* Shared wrapper — guarantees hero card and preview are identical width */
        .lp-hero-wrapper {
            width: 100%;
            max-width: 620px;
            margin: 0 auto;
        }
        /* Frosted hero card — wraps logo + taglines + action buttons. Same
           glass treatment as the other landing cards but with extra padding
           and a soft outer teal halo to feel "premium" rather than boxed in. */
        .lp-hero-card {
            position: relative;
            width: 100%;
            max-width: 620px;
            margin-left: auto;
            margin-right: auto;
            box-sizing: border-box;
            padding: 48px 40px 36px;
            border-radius: 24px;
            background: rgba(15,25,32,0.5);
            backdrop-filter: blur(14px);
            -webkit-backdrop-filter: blur(14px);
            border: 2px solid var(--ui-border);
            box-shadow:
                0 24px 80px rgba(0, 0, 0, 0.45),
                0 0 80px rgba(var(--ui-teal-rgb), 0.12);
        }
        .lp-logo {
            display: block;
            max-width: 360px;
            width: 100%;
            height: auto;
            margin: 0 auto 12px;
        }
        .lp-tagline {
            font-size: 19px; color: rgba(220,240,255,0.9);
            margin: 0; font-weight: 500; letter-spacing: 0.2px;
        }
        .lp-subtagline {
            /* This line IS the promise now — it carries the pitch, so it
               reads as a statement, not a caption. */
            font-size: 17px; color: rgba(205,235,250,0.88);
            margin: 10px 0 0; font-weight: 500; line-height: 1.45;
        }
        /* Rotating self-ironic one-liner — a tilted handwritten "quote"
           pinned to the viewport edges, so it rides along as you scroll and
           nobody mistakes it for the pitch. JS swaps text + slot class on a
           fade cycle. Mobile: no side margins to haunt → a small tilted
           stamp at the bottom of the screen instead. */
        .lp-quip {
            position: fixed;
            left: 50%;
            bottom: 14px;
            transform: translateX(-50%) rotate(-4deg);
            z-index: 5; /* over section content, under the sticky nav (10) */
            font-family: 'Klee One', 'Comic Sans MS', cursive;
            font-size: 19px;
            line-height: 1.35;
            color: rgba(150, 210, 235, 0.55);
            text-shadow: 0 2px 12px rgba(0, 0, 0, 0.45);
            pointer-events: none;
            -webkit-user-select: none;
            user-select: none;
            opacity: 0;
            transition: opacity 0.9s ease;
            text-align: center;
            max-width: 80vw;
        }
        /* Guest ambience on the map — the same handwriting as the landing
           quips, pinned to viewport edges (the map has no content column to
           hug). Shown by modules/GuestQuips.js, slowly, guests only. */
        .guest-quip {
            position: fixed;
            z-index: 900; /* under every floating panel and HUD element */
            font-family: 'Klee One', 'Comic Sans MS', cursive;
            font-size: 19px;
            line-height: 1.35;
            color: rgba(150, 210, 235, 0.5);
            text-shadow: 0 2px 12px rgba(0, 0, 0, 0.45);
            pointer-events: none;
            -webkit-user-select: none;
            user-select: none;
            opacity: 0;
            transition: opacity 1.2s ease;
            max-width: 220px;
        }
        .guest-quip.guest-quip-a { top: 18vh; left: 26px;  text-align: left;  transform: rotate(-7deg); }
        .guest-quip.guest-quip-b { top: 22vh; right: 26px; text-align: right; transform: rotate(6deg); }
        .guest-quip.guest-quip-c { top: 64vh; left: 30px;  text-align: left;  transform: rotate(5deg); }
        .guest-quip.guest-quip-d { top: 60vh; right: 30px; text-align: right; transform: rotate(-6deg); }

        /* The avatar delivering the line — upright and a notch bigger than
           the handwriting, like a sticker next to a scribble. Shared by the
           landing quips and the guest-map quips. */
        .lp-quip-speaker {
            font-style: normal;
            font-size: 26px;
            vertical-align: -5px;
            margin-right: 2px;
        }

        /* Screens without true side margins fit only the bottom stamp */
        @media (max-width: 1179px) {
            .lp-quip-extra { display: none; }
        }
        @media (min-width: 1180px) {
            .lp-quip {
                left: auto;
                bottom: auto;
                max-width: 200px;
            }
            /* Horizontal anchor is the CONTENT column, not the viewport
               edge: left slots pin their right edge outside the widest
               landing column (.lp-content 720px → half 360px + gap), right
               slots mirror. Vertical stays in vh so the quotes ride along
               with scrolling — and never reach over the content. */
            .lp-quip.lp-quip-a { top: 16vh; right: calc(50% + 390px); left: auto; text-align: right; transform: rotate(-8deg); }
            .lp-quip.lp-quip-b { top: 44vh; right: calc(50% + 415px); left: auto; text-align: right; transform: rotate(5deg); }
            .lp-quip.lp-quip-c { top: 18vh; left: calc(50% + 390px); right: auto; text-align: left; transform: rotate(7deg); }
            .lp-quip.lp-quip-d { top: 46vh; left: calc(50% + 415px); right: auto; text-align: left; transform: rotate(-6deg); }
            .lp-quip.lp-quip-e { top: 72vh; right: calc(50% + 395px); left: auto; text-align: right; transform: rotate(-5deg); }
            .lp-quip.lp-quip-f { top: 74vh; left: calc(50% + 395px); right: auto; text-align: left; transform: rotate(8deg); }
        }
        .lp-features {
            display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px;
            margin-bottom: 32px;
        }
        /* Preview card — rotating screenshots with subtle pan (Ken Burns).
           Five images crossfade every ~5.5s. Hover pauses both fade and pan
           so the user can pause to look. Pure decoration: no click action. */
        .lp-preview {
            position: relative;
            width: 100%;
            max-width: 620px;
            margin: 12px auto 0;
            box-sizing: border-box;
            aspect-ratio: 16 / 8;
            border-radius: 14px;
            overflow: hidden;
            border: 2px solid var(--ui-border);
            box-shadow: 0 24px 60px rgba(0,0,0,0.5);
            background: rgba(15,25,32,0.5);
            transform: scale(1);
            transition: border-color 0.25s ease, box-shadow 0.25s ease,
                        transform 0.5s var(--pop);
        }
        /* Hover used to only pause the pan, which reads as the picture giving
           up rather than responding. Now the panel lifts a little and the
           cursor takes over the pan (see auth.js): the view follows your
           mouse, so it stops being a video and becomes a window you look
           around in. The lift is small on purpose — 2% is a thing leaning
           toward you, 10% is a thing showing off. Springy, per the house
           style: everything gives slightly before it settles. */
        .lp-preview:hover {
            border-color: rgba(var(--ui-teal-rgb),0.55);
            box-shadow: 0 22px 60px rgba(var(--ui-teal-rgb),0.34);
            transform: scale(1.04);
        }
        .lp-preview-img {
            position: absolute;
            /* Bigger than the panel by MORE than the lean, so leaning the
               layer can never expose an edge. Coupled to LEAN in auth.js: if
               that grows past this, a bare strip appears along one side. The
               container clips the surplus. */
            inset: -26px;
            /* The cursor lean. A TRANSFORM, not background-position: moving a
               160%-scaled background repaints the whole layer every frame —
               ten stacked layers of it is what made this stutter — while a
               translate is composited and costs nothing. It also leaves the
               keyframe pan alone, so the ambient drift keeps rolling
               underneath instead of being switched off while you look. */
            transform: translate(var(--lp-tilt-x, 0px), var(--lp-tilt-y, 0px));
            background-size: 160%;
            background-repeat: no-repeat;
            background-position-x: 0%;
            background-position-y: 35%;
            opacity: 0;
            transition: opacity 2.8s ease-in-out,
                        transform 0.9s cubic-bezier(0.22, 1, 0.36, 1);
            animation: lpPan 20s linear infinite alternate;
        }
        .lp-preview-img.active { opacity: 1; }
        /* Only the visible layer is promoted, and only while you are there —
           ten permanently-composited layers is its own kind of waste. */
        .lp-preview:hover .lp-preview-img.active { will-change: transform; }

        /* The caption, centred and large, in Outfit — the app's own display
           face, the one the canvas cards are set in. It sits OUTSIDE the
           leaning layers on purpose: the picture answers the cursor, the words
           should not swim with it.
           A radial scrim rather than a bar: a big line in the middle of a busy
           screenshot needs the picture pushed back under it, but a hard band
           across the centre would cut the image in half. This darkens where
           the words are and fades to nothing at the edges. */
        .lp-preview-caption {
            position: absolute;
            inset: 0;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 0 8%;
            text-align: center;
            font-family: 'Outfit', 'Inter', sans-serif;
            font-size: clamp(20px, 3.4vw, 34px);
            font-weight: 700;
            line-height: 1.2;
            letter-spacing: -0.01em;
            /* 85% white — the house value. Full white on a photograph is the
               one thing that never belongs to it; a little transparency lets
               the picture through and the words stop looking pasted on. */
            color: rgba(255, 255, 255, 0.85);
            text-shadow: 0 2px 18px rgba(0, 0, 0, 0.85), 0 1px 4px rgba(0, 0, 0, 0.9);
            background: radial-gradient(ellipse 70% 45% at 50% 50%,
                        rgba(4, 12, 18, 0.72) 0%,
                        rgba(4, 12, 18, 0.45) 55%,
                        rgba(4, 12, 18, 0) 100%);
            /* Slower in than the image is out, so the words arrive on a
               picture that has already settled rather than mid-dissolve.
               A touch of travel with it — they rise into place. */
            opacity: 0;
            transform: translateY(8px);
            /* A long, soft fade. The image beneath crossfades over 2.8s, so a
               quick caption would snap into a picture that is still arriving —
               the words should drift in at something like the same pace and
               leave the same way. The scrim fades with the text, since a dark
               patch with nothing written on it is just a smudge. */
            transition: opacity 1.1s ease-in-out, transform 0.9s var(--pop);
            pointer-events: none;
        }
        .lp-preview-caption.visible { opacity: 1; transform: translateY(0); }

        /* Step arrows. Faint until you are over the panel, because the rotation
           is the default behaviour and the arrows are for when you want to go
           back to one — chrome that announces itself on a picture is chrome in
           the way. */
        .lp-preview-nav {
            position: absolute;
            top: 50%;
            transform: translateY(-50%);
            width: 34px; height: 34px;
            display: flex; align-items: center; justify-content: center;
            border: none;
            border-radius: 50%;
            background: rgba(6, 16, 24, 0.55);
            -webkit-backdrop-filter: blur(6px);
            backdrop-filter: blur(6px);
            color: rgba(255, 255, 255, 0.85);
            cursor: pointer;
            opacity: 0;
            transition: opacity 0.25s ease, background 0.2s ease, transform 0.25s var(--pop);
            z-index: 3;
        }
        .lp-preview-nav-prev { left: 10px; }
        .lp-preview-nav-next { right: 10px; }
        .lp-preview:hover .lp-preview-nav { opacity: 1; }
        .lp-preview-nav:hover { background: rgba(6, 16, 24, 0.8); transform: translateY(-50%) scale(1.12); }
        /* No hover on a touch screen, so the arrows have to be there already —
           dimmer, since the picture is still the point. */
        @media (hover: none) {
            .lp-preview-nav { opacity: 0.65; }
        }

        /* Full-size viewer for one shot. Its own overlay rather than the app's,
           because the landing runs before any of that is loaded. */
        /* dialog element resets — the UA centers dialogs with auto margins
           and fit-content sizing; this one owns the whole screen. Its own
           backdrop stays transparent because the overlay paints its own. */
        dialog.lp-shot-overlay { border: none; margin: 0; max-width: none; max-height: none; }
        dialog.lp-shot-overlay::backdrop { background: transparent; }
        .lp-shot-overlay {
            /* Explicit geometry, not inset shorthand: on one phone the
               overlay rendered as a confined column with the page showing
               around it — a fixed-positioning quirk we could not reproduce.
               100vw/100dvh cannot be confined by anyone's containing block
               arithmetic. Solid background instead of -webkit-backdrop-filter: the; backdrop-filter: the
               blur was costly on phones AND filter ancestors change how
               fixed children resolve, which is one quirk too many. */
            position: fixed; left: 0; top: 0;
            width: 100vw; height: 100vh; height: 100dvh;
            z-index: var(--z-top);
            display: flex; flex-direction: column;
            align-items: center; justify-content: center;
            gap: 14px;
            padding: 28px;
            box-sizing: border-box;
            background: rgba(2, 8, 12, 0.97);
            cursor: zoom-out;
            animation: lpShotIn 0.25s ease-out;
        }
        .lp-shot-overlay img {
            max-width: 100%;
            max-height: 82vh;
            border-radius: 10px;
            border: 1px solid rgba(255, 255, 255, 0.12);
            box-shadow: 0 30px 80px rgba(0, 0, 0, 0.6);
        }
        .lp-shot-nav {
            /* absolute against the (guaranteed fullscreen) overlay — it must
               not sail off with the sideways pan, and absolute-in-overlay is
               immune to the fixed-position quirks that bit us here. */
            position: absolute;
            top: 50%; transform: translateY(-50%);
            z-index: 2;
            width: 44px; height: 44px;
            display: flex; align-items: center; justify-content: center;
            border: none; border-radius: 50%;
            background: rgba(255, 255, 255, 0.1);
            color: rgba(255, 255, 255, 0.85);
            cursor: pointer;
            transition: background 0.15s;
        }
        .lp-shot-nav:hover { background: rgba(255, 255, 255, 0.22); }
        .lp-shot-nav-prev { left: 12px; }
        .lp-shot-nav-next { right: 12px; }

        .lp-shot-close {
            /* absolute against the overlay, same reasoning as .lp-shot-nav. */
            position: absolute;
            top: 14px; right: 14px;
            z-index: 2;
            width: 40px; height: 40px;
            display: flex; align-items: center; justify-content: center;
            border: none; border-radius: 50%;
            background: rgba(255, 255, 255, 0.1);
            color: rgba(255, 255, 255, 0.85);
            font-size: 22px; line-height: 1;
            cursor: pointer;
        }
        .lp-shot-close:hover { background: rgba(255, 255, 255, 0.2); }

        /* Portrait phones. A 16:9 screenshot shown "whole" on a tall narrow
           screen is a stamp in a field of black — technically the full picture,
           practically unreadable. Give it the height instead and let it run off
           the sides: swiping a wide thing sideways is the natural gesture, and
           the detail someone opened it for is legible at last. */
        @media (max-width: 768px) and (orientation: portrait) {
            .lp-shot-overlay {
                /* centered, not flex-start: the top padding plus start-
                   alignment stacked everything under a large dead band and
                   left a larger one below — the picture belongs in the
                   middle of the screen it was opened on. */
                justify-content: center;
                padding: 0;
                gap: 12px;
                overflow-x: auto;
                overscroll-behavior: contain;
            }
            .lp-shot-overlay img {
                height: 66vh;
                width: auto;
                max-width: none;
                max-height: none;
                flex-shrink: 0;
                border-radius: 0;
                border-left: none; border-right: none;
            }
            .lp-shot-overlay figcaption {
                font-size: 15px;
                padding: 0 18px;
                position: sticky; left: 0;   /* stays put while the picture scrolls */
            }
        }
        .lp-shot-overlay figcaption {
            font-family: 'Outfit', 'Inter', sans-serif;
            font-size: 17px;
            font-weight: 600;
            color: rgba(255, 255, 255, 0.85);
            text-align: center;
        }
        @keyframes lpShotIn {
            from { opacity: 0; transform: scale(0.985); }
            to   { opacity: 1; transform: scale(1); }
        }
        @media (max-width: 768px) {
            .lp-preview-caption { font-size: clamp(16px, 5vw, 24px); padding: 0 10%; }
        }
        /* Motion off: no lift, no pan — the crossfade alone still tells the
           story, and nothing here is load-bearing. */
        @media (prefers-reduced-motion: reduce) {
            .lp-preview, .lp-preview:hover { transform: none; transition: border-color 0.25s ease; }
            /* No drift and no lean — the JS still writes the tilt variables,
               and ignoring them here is simpler than teaching it the setting. */
            .lp-preview-img { animation: none; transform: none; transition: opacity 2.8s ease-in-out; }
        }
        /* The ambient drift, kept to the middle band: edge to edge is a lot of
           travel for something meant to be barely noticed. It runs the whole
           time, hover included — the cursor lean is a transform on top of it,
           not a replacement for it. */
        @keyframes lpPan {
            from { background-position-x: 32%; }
            to   { background-position-x: 68%; }
        }
        /* Two-card split: Professional / Casual. Each card lists its use cases
           as pill badges. Pro card uses brand teal accent, Casual uses warm
           tan/amber for visual contrast. Whole card is a single link to an
           example space. */
        .lp-usecases-split {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 16px;
            margin-bottom: 8px;
        }
        @media (max-width: 720px) {
            .lp-usecases-split { grid-template-columns: 1fr; }
        }
        .lp-uc-card {
            background: rgba(25,35,50,0.42);
            backdrop-filter: blur(14px);
            -webkit-backdrop-filter: blur(14px);
            border: 2px solid var(--ui-border-faint);
            border-radius: 16px;
            padding: 24px 22px;
            text-align: center;
            display: flex;
            flex-direction: column;
            align-items: center;
            text-decoration: none;
            color: inherit;
            font-family: inherit;
            position: relative;
            transition: transform 0.4s var(--pop), box-shadow 0.25s ease, border-color 0.2s ease, background 0.2s ease;
        }
        .lp-uc-card:hover { transform: translateY(-6px) scale(1.025); background: rgba(35,50,70,0.55); z-index: 2; }
        .lp-uc-pro:hover {
            border-color: rgba(var(--ui-teal-rgb),0.55);
            box-shadow: 0 14px 40px rgba(var(--ui-teal-rgb),0.32);
        }
        .lp-uc-casual:hover {
            border-color: rgba(245,170,100,0.55);
            box-shadow: 0 14px 40px rgba(245,170,100,0.32);
        }
        .lp-uc-card:focus-visible {
            outline: 2px solid rgba(var(--ui-teal-rgb),0.5);
            outline-offset: 2px;
        }
        .lp-uc-icon {
            width: 50px; height: 50px;
            display: flex; align-items: center; justify-content: center;
            margin-bottom: 14px;
        }
        .lp-uc-icon [data-ui-icon] {
            width: 100%; height: 100%;
            object-fit: contain;
            display: block;
        }
        .lp-uc-label {
            font-size: 11px;
            font-weight: 700;
            letter-spacing: 2px;
            text-transform: uppercase;
            margin-bottom: 16px;
        }
        .lp-uc-pro    .lp-uc-label { color: rgba(var(--ui-teal-rgb),0.9); }
        .lp-uc-casual .lp-uc-label { color: rgba(245,170,100,0.9); }
        .lp-uc-badges {
            display: flex;
            flex-wrap: wrap;
            justify-content: center;
            gap: 7px;
            margin-bottom: 18px;
        }
        .lp-uc-badge {
            display: inline-flex;
            align-items: center;
            gap: 5px;
            padding: 6px 11px;
            border-radius: 999px;
            font-size: 12.5px;
            font-weight: 500;
            background: rgba(15,25,32,0.55);
            border: 2px solid var(--ui-border);
            color: rgba(200,225,240,0.88);
            line-height: 1.2;
            white-space: nowrap;
        }
        .lp-uc-pro    .lp-uc-badge { border-color: rgba(var(--ui-teal-rgb),0.22); }
        .lp-uc-casual .lp-uc-badge { border-color: rgba(245,170,100,0.22); }
        .lp-uc-tagline {
            color: rgba(var(--ui-text-rgb),0.6);
            font-size: 13px;
            line-height: 1.5;
            margin: 0 0 14px;
        }
        .lp-uc-cta {
            font-size: 11px;
            font-weight: 700;
            letter-spacing: 1.2px;
            text-transform: uppercase;
            margin-top: auto;
        }
        .lp-uc-pro    .lp-uc-cta { color: rgba(var(--ui-teal-rgb),0.85); }
        .lp-uc-casual .lp-uc-cta { color: rgba(245,170,100,0.85); }
        .lp-uc-pro:hover    .lp-uc-cta { color: #7dd3c8; }
        .lp-uc-casual:hover .lp-uc-cta { color: #ffba70; }

        /* For-teams section — two-column layout with text on the left and
           a mocked-up org dashboard on the right. Stacks on mobile. */
        .lp-teams {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 40px;
            align-items: center;
            text-align: left;
        }
        @media (max-width: 768px) {
            .lp-teams { grid-template-columns: 1fr; gap: 28px; }
        }
        .lp-teams-text h2 { text-align: left; }
        .lp-teams-text .lp-section-body { margin-left: 0; max-width: none; }
        .lp-teams-checklist {
            list-style: none; padding: 0; margin: 18px 0 24px;
        }
        .lp-teams-checklist li {
            display: flex; align-items: flex-start; gap: 12px;
            color: rgba(var(--ui-text-rgb),0.85);
            font-size: 14px;
            line-height: 1.5;
            padding: 6px 0;
        }
        .lp-teams-checklist .lp-check {
            color: #D26B3F;
            flex-shrink: 0;
            font-weight: 700;
            font-size: 15px;
            margin-top: 1px;
        }
        .lp-teams-mockup {
            background: rgba(15,25,32,0.65);
            backdrop-filter: blur(14px);
            -webkit-backdrop-filter: blur(14px);
            border: 2px solid rgba(210,107,63,0.18);
            border-radius: 18px;
            padding: 22px;
            box-shadow: 0 12px 40px rgba(0,0,0,0.35), 0 0 40px rgba(210,107,63,0.06);
        }
        .lp-org-header {
            display: flex; align-items: center; justify-content: space-between;
            margin-bottom: 18px;
        }
        .lp-org-name {
            display: flex; align-items: center; gap: 12px;
        }
        .lp-org-logo {
            width: 38px; height: 38px;
            border-radius: 10px;
            background: rgba(210,107,63,0.12);
            border: 1px solid rgba(210,107,63,0.3);
            display: flex; align-items: center; justify-content: center;
        }
        .lp-org-name-title { color: var(--ui-text); font-weight: 600; font-size: 13px; line-height: 1.2; }
        .lp-org-name-meta  { color: rgba(var(--ui-text-rgb),0.55); font-size: 11px; margin-top: 2px; }
        .lp-org-online {
            display: inline-flex; align-items: center; gap: 5px;
            color: rgba(var(--ui-teal-rgb),0.9);
            font-size: 11px;
            background: rgba(var(--ui-teal-rgb),0.1);
            padding: 4px 9px;
            border-radius: 10px;
        }
        .lp-org-online::before {
            content: ''; width: 6px; height: 6px; border-radius: 50%;
            background: #4ecdc4; box-shadow: 0 0 6px #4ecdc4;
        }
        /* Roster rows. The avatar keeps the .lp-org-member class and its ring,
           so the emote animation that picks a random member still lands on
           something — it just now floats above a person with a name. */
        .lp-org-roster { display: flex; flex-direction: column; gap: 2px; }
        .lp-org-row {
            display: flex; align-items: center; gap: 10px;
            padding: 5px 6px;
            border-radius: 8px;
        }
        .lp-org-row-name {
            color: var(--ui-text-bright);
            font-size: 13px; font-weight: 600;
            flex-shrink: 0;
        }
        .lp-org-row-where {
            color: var(--ui-text-muted);
            font-size: 11.5px;
            overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
        }
        /* Away rows recede rather than disappear: a directory that showed only
           the people who happen to be online would be a presence list. */
        .lp-org-row-away { opacity: 0.55; }
        .lp-org-more {
            color: var(--ui-text-dim);
            font-size: 11.5px;
            padding: 4px 6px 0 44px;
        }
        .lp-org-activity {
            margin-top: 10px;
            padding: 8px 10px;
            border-radius: 8px;
            background: rgba(var(--ui-teal-rgb),0.07);
            border-left: 2px solid rgba(var(--ui-teal-rgb),0.4);
            color: var(--ui-text-muted);
            font-size: 11.5px;
        }
        .lp-org-activity strong { color: var(--ui-text); font-weight: 600; }
        .lp-org-member {
            width: 34px; height: 34px;
            flex-shrink: 0;
            border-radius: 50%;
            display: flex; align-items: center; justify-content: center;
            font-size: 19px;
            line-height: 1;
            position: relative;
        }
        /* The avatars people actually wear (the static 96 px portraits every
           small DOM surface uses), not emoji stand-ins (Mikko 27.9.). */
        .lp-org-member img {
            width: 30px; height: 30px;
            object-fit: contain;
            pointer-events: none;
        }
        .lp-org-member.online {
            box-shadow: 0 0 0 2px rgba(var(--ui-teal-rgb),0.55), 0 0 14px rgba(var(--ui-teal-rgb),0.25);
        }
        .lp-org-member-hat {
            position: absolute;
            top: -6px;
            left: 50%;
            transform: translateX(-50%);
            font-size: 16px;
            line-height: 1;
        }
        @keyframes lpEmoteFloat {
            0%   { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
            100% { opacity: 0; transform: translateX(-50%) translateY(-44px) scale(1.3); }
        }
        .lp-emote-float {
            position: absolute;
            top: -4px; left: 50%;
            transform: translateX(-50%);
            font-size: 18px;
            pointer-events: none;
            animation: lpEmoteFloat 1.1s ease-out forwards;
            z-index: 5;
        }
        @keyframes lpMemberBounce {
            0%, 100% { transform: translateY(0); }
            35%      { transform: translateY(-10px); }
            65%      { transform: translateY(-4px); }
        }
        .lp-emote-bounce { animation: lpMemberBounce 0.65s ease-out; }
        .lp-org-stats {
            margin-top: 14px;
            padding: 12px 14px;
            border-radius: 10px;
            background: rgba(25,40,55,0.5);
            border: 1px solid var(--ui-border-faint);
            display: flex; justify-content: space-between; align-items: center;
            font-size: 11px;
            color: rgba(var(--ui-text-rgb),0.6);
            gap: 8px;
        }
        .lp-org-stats strong {
            color: rgba(var(--ui-text-rgb),0.4);
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 0.8px;
            font-size: 10px;
            margin-right: 6px;
        }
        .lp-org-stats-bar {
            flex: 1; max-width: 80px;
            height: 4px;
            background: rgba(var(--ui-accent-rgb),0.1);
            border-radius: 2px;
            overflow: hidden;
        }
        .lp-org-stats-bar > div {
            height: 100%;
            background: linear-gradient(90deg, #D26B3F, #e8956a);
            width: 50%;
        }
        .lp-feature:last-child {
            grid-column: 1 / -1;
        }
        .lp-feature {
            background: rgba(25,35,50,0.42);
            backdrop-filter: blur(14px);
            -webkit-backdrop-filter: blur(14px);
            border: 2px solid var(--ui-border-faint);
            border-radius: 12px; padding: 18px 14px;
            position: relative;
            transition: transform 0.4s var(--pop), box-shadow 0.25s ease, border-color 0.2s ease, background 0.2s ease;
        }
        /* Rises AND grows — a lift alone is easy to miss on a small card, and
           the point is to say "this one" clearly. z-index so the one that grew
           sits over its neighbours instead of under them. */
        .lp-feature:hover {
            transform: translateY(-6px) scale(1.035);
            background: rgba(35,50,70,0.55);
            z-index: 2;
        }
        /* Per-card accent on hover — each icon implies a color family. */
        .lp-feature:nth-child(1):hover { border-color: rgba(var(--ui-teal-rgb),0.55); box-shadow: 0 14px 40px rgba(var(--ui-teal-rgb),0.32); }
        .lp-feature:nth-child(2):hover { border-color: var(--ui-border-accent); box-shadow: 0 14px 40px rgba(var(--ui-accent-rgb),0.32); }
        .lp-feature:nth-child(3):hover { border-color: rgba(180,130,255,0.55); box-shadow: 0 14px 40px rgba(180,130,255,0.32); }
        .lp-feature:nth-child(4):hover { border-color: rgba(130,220,150,0.55); box-shadow: 0 14px 40px rgba(130,220,150,0.32); }
        .lp-feature:nth-child(5):hover { border-color: rgba(255,170,80,0.55); box-shadow: 0 14px 40px rgba(255,170,80,0.32); }
        .lp-feature-icon {
            display: block;
            margin: 0 auto 12px;
            width: 50px; height: 50px;
            object-fit: contain;
        }
        .lp-feature h3 {
            color: var(--ui-text-bright); font-size: 15px; font-weight: 600;
            margin: 0 0 8px;
        }
        .lp-feature p {
            color: rgba(var(--ui-text-rgb),0.55); font-size: 13px; line-height: 1.5;
            margin: 0;
        }
.lp-actions { display: flex; gap: 12px; justify-content: center; margin-bottom: 16px; }
        .lp-btn {
            padding: 14px 32px; border-radius: 8px; font-size: 15px; font-weight: 600;
            cursor: pointer; transition: all 0.2s; border: none; font-family: var(--ui-font);
        }
        .lp-btn-primary {
            background: linear-gradient(135deg, rgba(var(--ui-primary-rgb),0.8), rgba(20,70,90,0.9));
            color: var(--ui-text-bright);
            border: 2px solid var(--ui-border-accent);
        }
        .lp-btn-primary:hover {
            background: linear-gradient(135deg, rgba(40,110,126,0.9), rgba(25,85,105,1));
            border-color: var(--ui-border-accent);
            box-shadow: 0 4px 20px rgba(30,90,120,0.3);
        }
        .lp-btn-ghost {
            background: transparent; color: rgba(var(--ui-text-rgb),0.7);
            border: 2px solid var(--ui-border);
        }
        .lp-btn-ghost:hover {
            background: rgba(var(--ui-accent-rgb),0.06); color: var(--ui-text-bright);
            border-color: var(--ui-border-accent);
        }
        .lp-footer {
            color: rgba(120,150,170,0.5); font-size: 12px; margin: 0; display: none;
        }
        /* ── Full feature list ── */
        .lp-all-features {
            margin-top: 24px;
            padding-top: 8px; text-align: center;
        }
        .lp-features-toggle {
            background: none; border: none; color: rgba(var(--ui-text-rgb),0.5);
            font-size: 13px; cursor: pointer; padding: 8px 0;
            text-align: center; font-family: var(--ui-font); transition: color 0.2s;
        }
        .lp-features-toggle:hover { color: var(--ui-text-bright); }
        .lp-features-toggle .lp-chevron {
            display: inline-block; transition: transform 0.25s; margin-left: 4px;
        }
        .lp-features-toggle.open .lp-chevron { transform: rotate(180deg); }
        /* Feature list overlay */
        .lp-fg-overlay {
            display: none; position: fixed; inset: 0; z-index: calc(var(--z-modal-2) + 100);
            background: rgba(5,5,5,0.85); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
            align-items: center; justify-content: center;
            animation: lpFgFadeIn 0.2s ease;
        }
        .lp-fg-overlay.active { display: flex; }
        @keyframes lpFgFadeIn { from { opacity: 0; } to { opacity: 1; } }
        .lp-fg-panel {
            background: linear-gradient(180deg, #0e2a2c, #0a1a1c);
            border: 2px solid var(--ui-border);
            border-radius: 16px; padding: 32px 28px;
            max-width: 560px; width: 90%; max-height: 80vh;
            overflow-y: auto; position: relative;
        }
        .lp-fg-close {
            position: absolute; top: 12px; right: 16px;
            background: none; border: none; color: rgba(var(--ui-text-rgb),0.5);
            font-size: 20px; cursor: pointer; padding: 4px 8px;
            transition: color 0.2s;
        }
        .lp-fg-close:hover { color: var(--ui-text-bright); }
        /* The legal texts (Privacy policy): lists that read as lists. */
        .lp-legal h3 { text-align: left; }
        .lp-legal ul { margin: 4px 0 12px; padding-left: 20px; }
        .lp-legal li { margin: 4px 0; }
        .lp-legal code { padding: 1px 5px; border-radius: 4px; background: rgba(var(--ui-accent-rgb), 0.12); color: var(--ui-text); font-size: 12px; }
        .lp-fg-panel h3 {
            color: var(--ui-text-bright); font-size: 16px; font-weight: 600;
            margin: 0 0 20px; text-align: center;
        }
        .lp-feature-grid {
            display: grid; grid-template-columns: 1fr 1fr; gap: 0;
        }
        .lp-fg-category {
            grid-column: 1 / -1;
            color: rgba(var(--ui-text-rgb),0.4); font-size: 11px; font-weight: 600;
            text-transform: uppercase; letter-spacing: 1px;
            padding: 16px 0 6px; border-bottom: 1px solid var(--ui-border-faint);
        }
        .lp-fg-category:first-child { padding-top: 0; }
        .lp-fg-item {
            padding: 6px 0; font-size: 13px; color: rgba(var(--ui-text-rgb),0.6);
            display: flex; align-items: flex-start; gap: 8px;
        }
        .lp-fg-item [data-ui-icon] { display: inline-flex; flex: 0 0 20px; }
        @media (max-width: 600px) {
            .lp-title { font-size: 36px; }
            .lp-tagline { font-size: 18px; }
            .lp-subtagline { font-size: 14px; }
            .lp-features { grid-template-columns: 1fr; gap: 8px; }
            .lp-features { margin-bottom: 12px; }
            .lp-actions { flex-direction: column; align-items: center; }
            .lp-btn { width: 100%; max-width: 280px; }
            .lp-feature-grid { grid-template-columns: 1fr; }
        }

        #authModal.active .authContainer {
            animation: modalContentIn 0.25s ease;
        }
        .authContainer {
            padding: 40px;
            width: 100%;
            max-width: 400px;
            max-height: calc(100vh - 32px);
            overflow-y: auto;
            overscroll-behavior: contain;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
            scrollbar-width: thin;
            scrollbar-color: rgba(var(--ui-accent-rgb),0.3) rgba(var(--ui-surface-rgb),0.4);
        }
        .authContainer::-webkit-scrollbar { width: 8px; }
        .authContainer::-webkit-scrollbar-track { background: rgba(var(--ui-surface-rgb),0.4); border-radius: 4px; }
        .authContainer::-webkit-scrollbar-thumb { background: rgba(var(--ui-accent-rgb),0.3); border-radius: 4px; }
        .authContainer::-webkit-scrollbar-thumb:hover { background: rgba(var(--ui-accent-rgb),0.5); }
        .authContainer h2 {
            color: var(--ui-text-bright);
            margin-bottom: 24px;
            text-align: center;
        }
        .authContainer label.auth-label {
            display: block;
            color: rgba(var(--ui-text-rgb),0.7);
            font-size: 12px;
            font-weight: 500;
            margin-bottom: 4px;
        }
        .authContainer input {
            width: 100%;
            padding: 12px;
            margin-bottom: 16px;
            background: rgba(var(--ui-surface-rgb),0.6);
            border: 2px solid var(--ui-border);
            border-radius: 6px;
            color: var(--ui-text);
            font-size: 14px;
            box-sizing: border-box;
        }
        .authContainer input:focus {
            outline: none;
            border-color: var(--ui-border-accent);
        }
        /* Classless form buttons exist only in the login/signup modal —
           everything else uses the .ui-btn system (ui-theme.css). */
        #authModal .authContainer button {
            width: 100%;
            padding: 12px;
            background: rgba(20,55,65,0.5);
            border: 2px solid var(--ui-border);
            border-radius: 6px;
            color: rgba(var(--ui-text-rgb),0.85);
            font-weight: 600;
            cursor: pointer;
            margin-bottom: 12px;
            transition: all 0.2s;
        }
        #authModal .authContainer button:hover {
            background: rgba(30,70,85,0.6);
            border-color: var(--ui-border-accent);
            color: var(--ui-text-bright);
        }
        .authToggle {
            text-align: center;
            color: var(--ui-text-muted);
            font-size: 12px;
            margin-top: 16px;
        }
        .authToggle a {
            color: var(--ui-text-bright);
            cursor: pointer;
            text-decoration: underline;
        }
        .authError {
            color: #ff6b6b;
            font-size: 12px;
            margin-bottom: 16px;
            text-align: center;
        }
        #userDisplay {
            position: absolute;
            top: 60px;
            right: 18px;
            background: rgba(var(--ui-surface-rgb), 0.85);
            backdrop-filter: blur(12px);
            -webkit-backdrop-filter: blur(12px);
            padding: 8px 12px;
            border-radius: 8px;
            border: 2px solid var(--ui-border);
            font-size: 12px;
            color: var(--ui-text);
            pointer-events: auto;
            display: flex;
            align-items: center;
            gap: 8px;
            flex-wrap: wrap;
            max-width: calc(100vw - 200px);
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
        }
        #logoutBtn {
            background: rgba(50, 50, 60, 0.6);
            border: 1px solid rgba(100, 100, 120, 0.3);
            color: #aaa;
            padding: 4px 10px;
            border-radius: 6px;
            cursor: pointer;
            font-size: 11px;
            white-space: nowrap;
            transition: all 0.2s;
        }
        #logoutBtn:hover {
            background: rgba(70, 70, 80, 0.7);
            color: var(--ui-text);
            border-color: rgba(150, 150, 170, 0.4);
        }
        #adminToggleBtn {
            background: #1a3a1a;
            border: 1px solid #4a8a4a;
            color: #7aff7a;
            padding: 4px 12px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 11px;
            font-weight: 600;
            transition: all 0.2s ease;
            white-space: nowrap;
        }
        #adminToggleBtn:hover {
            background: #2a4a2a;
            border-color: #7aff7a;
            box-shadow: 0 0 8px rgba(122, 255, 122, 0.3);
        }
        #adminToggleBtn.active {
            background: #2a7a2a;
            border-color: #00ff00;
            color: #00ff00;
            box-shadow: 0 0 12px rgba(0, 255, 0, 0.5);
        }
        #locationContainer {
            position: absolute;
            top: 60px;
            right: 18px;
            display: flex;
            align-items: center;
            gap: 6px;
            pointer-events: auto;
            z-index: var(--z-hud); /* own layer — an invisible HUD sibling above it ate the clicks */
            background: rgba(var(--ui-surface-rgb), 0.85);
            backdrop-filter: blur(12px);
            -webkit-backdrop-filter: blur(12px);
            padding: 6px 10px;
            border-radius: 8px;
            border: 2px solid var(--ui-border);
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
        }
        #locationDisplay {
            background: rgba(20, 40, 60, 0.6);
            border: 2px solid var(--ui-border-accent);
            color: var(--ui-text);
            padding: 5px 8px;
            border-radius: 6px;
            font-size: 11px;
            font-family: 'Consolas', 'Monaco', monospace;
            outline: none;
            width: 100px;
            cursor: text;
        }
        #locationDisplay:hover {
            border-color: var(--ui-border-accent);
            background: rgba(var(--ui-surface-rgb), 0.7);
        }
        #locationDisplay:focus {
            border-color: #7adc7a;
            box-shadow: 0 0 8px rgba(122, 220, 122, 0.3);
        }
        #locationDisplay::placeholder {
            color: #5a8a5a;
        }
        .location-btn {
            background: rgba(var(--ui-primary-rgb), 0.6);
            border: 2px solid var(--ui-border-accent);
            color: var(--ui-text);
            padding: 4px 8px;
            border-radius: 6px;
            font-size: 11px;
            cursor: pointer;
            transition: all 0.2s;
        }
        .location-btn:hover {
            background: rgba(40, 80, 110, 0.8);
            border-color: var(--ui-border-accent);
        }
        /* Compact location HUD on mobile — lives here, after the base rules,
           so plain source-order overrides work. */
        @media (max-width: 768px) {
            #locationContainer {
                top: 50px;
                right: 8px;
            }
            #locationDisplay {
                width: 80px;
                font-size: 10px;
                padding: 4px 6px;
            }
            .location-btn {
                padding: 4px 6px;
                font-size: 10px;
            }
        }
        .location-btn:active {
            background: rgba(50, 100, 130, 0.8);
        }
        #codeEditorModal {
            z-index: var(--z-nav);
            background: rgba(0, 0, 0, 0.9);
            flex-direction: column;
        }
        #codeEditorModal.active {
            display: flex;
            animation: modalFadeIn 0.2s ease;
        }
        #codeEditorModal.active #codeEditorContainer {
            animation: modalContentIn 0.25s ease;
        }
        #codeEditorContainer {
            width: 90%;
            height: 90%;
            background: #1a1a1a;
            border: 2px solid #666;
            border-radius: 8px;
            display: flex;
            flex-direction: column;
            padding: 0;
            overflow: hidden;
        }
        #codeEditorToolbar {
            background: var(--ui-surface-alt);
            border-bottom: 1px solid #666;
            padding: 12px 16px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            color: var(--ui-text);
            font-size: 12px;
        }
        #codeEditorToolbar button {
            background: var(--ui-btn-bg);
            border: 1px solid #666;
            color: var(--ui-text);
            padding: 6px 12px;
            border-radius: 4px;
            cursor: pointer;
            margin-left: 8px;
            font-size: 12px;
        }
        #codeEditorToolbar button:hover {
            background: #444;
            border-color: var(--ui-text-muted);
        }
        #codeEditorTextarea {
            flex: 1;
            background: #1a1a1a;
            border: none;
            color: var(--ui-text);
            padding: 16px;
            font-family: 'Courier New', monospace;
            font-size: 14px;
            resize: none;
            outline: none;
            overflow: auto;
            word-break: break-word;
            white-space: pre-wrap;
        }
        
        /* ── Unified info widget ────────────────────────────────────────
           Single top-left element showing contextual hints (from
           updateTutorial) OR transient toast notifications (from showToast).
           Click the body to open the history panel; × dismisses the current
           message back to the hint, or hides the widget if the hint itself
           was already dismissed. */
        .info-widget {
            position: fixed;
            top: 60px; /* JS overrides via positionUIElements */
            left: 18px;
            z-index: 100;
            display: flex;
            align-items: flex-start;
            gap: 10px;
            padding: 8px 12px;
            border-radius: 8px;
            background: rgba(var(--ui-surface-rgb), 0.85);
            backdrop-filter: blur(12px);
            -webkit-backdrop-filter: blur(12px);
            border: 2px solid var(--ui-border);
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
            color: rgba(var(--ui-text-rgb), 0.7);
            font-family: var(--ui-font);
            font-size: 11px;
            line-height: 1.5;
            max-width: 420px;
            cursor: pointer;
            transition: opacity 0.3s ease, background 0.18s, border-color 0.18s, transform 0.18s;
        }
        .info-widget .info-icon {
            flex-shrink: 0;
            font-size: 16px;
            line-height: 1.2;
            color: #64c8ff;
            display: none; /* shown only when a toast supplies an icon */
        }
        .info-widget.has-icon .info-icon { display: inline; }
        .info-widget .info-body {
            flex: 1;
            min-width: 0;
        }
        .info-widget .info-main {
            color: var(--ui-text);
            font-weight: 600;
            font-size: 12px;
            word-break: break-word;
        }
        .info-widget .info-sub {
            font-size: 10px;
            opacity: 0.7;
            margin-top: 4px;
            word-break: break-word;
        }
        .info-widget .info-sub:empty { display: none; }
        .info-widget .info-close {
            flex-shrink: 0;
            background: none;
            border: none;
            color: rgba(var(--ui-text-rgb), 0.4);
            font-size: 14px;
            line-height: 1;
            cursor: pointer;
            padding: 2px 4px;
            border-radius: 4px;
            transition: opacity 0.15s, color 0.15s, background 0.15s;
        }
        .info-widget .info-close:hover {
            color: var(--ui-text-bright);
            background: rgba(255, 255, 255, 0.08);
        }
        .info-widget .info-unread {
            flex-shrink: 0;
            color: #f87171;
            font-weight: 700;
            font-size: 11px;
            padding: 0 2px;
        }
        /* Toast states — widget temporarily takes on the toast's coloring */
        .info-widget.toast-active {
            border-color: var(--ui-border-accent);
            background: rgba(var(--ui-surface-rgb), 0.92);
        }
        .info-widget.toast-success { border-color: rgba(74, 222, 128, 0.4); }
        .info-widget.toast-success .info-icon { color: var(--ui-success-text, #4ade80); }
        .info-widget.toast-error { border-color: rgba(248, 113, 113, 0.45); }
        .info-widget.toast-error .info-icon { color: var(--ui-error-text, #f87171); }
        .info-widget.toast-warning { border-color: rgba(251, 191, 36, 0.4); }
        .info-widget.toast-warning .info-icon { color: var(--ui-warn-text, #fbbf24); }
        .info-widget.toast-info { border-color: var(--ui-border-accent); }
        .info-widget.toast-info .info-icon { color: var(--ui-info-text, #64c8ff); }
        .info-widget.toast-achievement {
            border-color: rgba(255, 215, 0, 0.4);
            background: linear-gradient(135deg, rgba(20, 30, 50, 0.95), rgba(30, 50, 80, 0.95));
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5), 0 0 20px rgba(255, 215, 0, 0.15);
        }
        .info-widget.toast-achievement .info-icon { color: #ffd700; font-size: 22px; }
        .info-widget.toast-achievement .toast-eyebrow {
            color: rgba(255, 215, 0, 0.9);
            font-size: 10px;
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 1px;
            margin-bottom: 2px;
        }
        .info-widget.toast-achievement .toast-title {
            font-size: 14px;
            font-weight: 600;
        }
        .info-widget.has-unread .info-icon { color: #f87171; }
        /* Collapsed: shrunk to just the clock icon. Always reachable so the
           user can open history at any time, but unobtrusive — full widget
           re-expands on hover, new toast, or contextual hint. */
        .info-widget.collapsed {
            padding: 4px 8px;
            opacity: 0.55;
        }
        .info-widget.collapsed .info-body,
        .info-widget.collapsed .info-close {
            display: none;
        }
        .info-widget.collapsed .info-icon {
            display: inline;
            color: var(--ui-text-muted);
            font-size: 13px;
        }
        .info-widget.collapsed.has-unread {
            opacity: 1;
        }
        .info-widget.collapsed.has-unread .info-icon { color: #f87171; }
        .info-widget.collapsed:hover { opacity: 1; }
        @media (max-width: 768px) {
            .info-widget {
                left: 12px;
                right: 12px;
                max-width: none;
            }
            /* Collapsed is the clock icon alone — stretched edge to edge it
               was an empty bar across the top of the phone. */
            .info-widget.collapsed { right: auto; }
        }

        #toastHistoryPanel {
            position: fixed;
            top: calc(var(--toolbar-height, 48px) + 8px);
            left: 24px;
            z-index: var(--z-top);
            width: 340px;
            max-width: calc(100vw - 48px);
            max-height: 60vh;
            /* A list scrolls down, never sideways: a tooltip bubble at the
               edge (hidden, but laid out) once gave it a scrollbar at rest. */
            overflow: hidden auto;
            background: rgba(var(--ui-surface-rgb), 0.95);
            backdrop-filter: blur(16px);
            -webkit-backdrop-filter: blur(16px);
            border: 1px solid rgba(255, 255, 255, 0.08);
            border-radius: 10px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
            display: none;
            font-family: var(--ui-font);
        }
        #toastHistoryPanel.open { display: block; }
        .toast-history-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 10px 14px;
            border-bottom: 1px solid rgba(255, 255, 255, 0.06);
            font-size: 12px;
            color: var(--ui-text-muted);
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }
        .toast-history-clear {
            background: none;
            border: none;
            color: var(--ui-text-muted);
            font-size: 11px;
            cursor: pointer;
            padding: 2px 6px;
            border-radius: 4px;
        }
        .toast-history-clear:hover { background: rgba(255,255,255,0.06); color: #fff; }
        .toast-history-title { display: inline-flex; align-items: center; gap: 6px; }
        .toast-history-actions { display: inline-flex; align-items: center; gap: 2px; }
        .toast-history-close {
            min-width: 32px;
            min-height: 32px;
            margin: -6px -8px -6px 0;
            background: none;
            border: none;
            border-radius: 6px;
            color: var(--ui-text-muted);
            font-size: 18px;
            line-height: 1;
            cursor: pointer;
        }
        .toast-history-close:hover { background: rgba(255,255,255,0.06); color: #fff; }
        @media (pointer: coarse) { .toast-history-close { min-width: 44px; min-height: 44px; } }
        .toast-history-count {
            margin-left: 4px;
            padding: 0 5px;
            border-radius: 8px;
            background: rgba(255,255,255,0.08);
            color: var(--ui-text-muted);
            font-size: 10.5px;
        }
        #toastHistoryList {
            padding: 4px 0;
        }
        .toast-history-empty {
            padding: 24px 14px;
            text-align: center;
            color: #6b7b86;
            font-size: 12px;
        }
        .toast-history-item {
            display: flex;
            gap: 10px;
            padding: 10px 14px;
            border-bottom: 1px solid rgba(255,255,255,0.04);
            color: #e0e6eb;
            font-size: 12.5px;
            line-height: 1.4;
        }
        .toast-history-item:last-child { border-bottom: none; }
        .toast-history-icon { flex-shrink: 0; font-size: 14px; margin-top: 1px; }
        .toast-history-body { flex: 1; min-width: 0; }
        .toast-history-msg { word-break: break-word; }
        .toast-history-time { margin-top: 3px; font-size: 10.5px; color: #6b7b86; }
        .toast-history-success .toast-history-icon { color: #4ade80; }
        .toast-history-error   .toast-history-icon { color: #f87171; }
        .toast-history-warning .toast-history-icon { color: #fbbf24; }
        .toast-history-info    .toast-history-icon { color: #64c8ff; }
        .toast-history-achievement .toast-history-icon { color: #ffd700; }
        @media (max-width: 768px) {
            #toastHistoryPanel { left: 12px; right: 12px; width: auto; }
        }
        
        /* ✨ Recent (ActivityPanel.js): new to you is marked, seen is dimmed. */
        .event-row.is-seen { opacity: 0.55; }
        .event-row.is-seen:hover { opacity: 0.85; }
        .activity-new-dot { flex: none; width: 7px; height: 7px; border-radius: 50%; align-self: center;
            background: rgba(var(--ui-accent-rgb), 0.95); box-shadow: 0 0 6px rgba(var(--ui-accent-rgb), 0.6); }

        /* A subspace's settings: its access and editing permissions are its
           outermost space's (SpaceSettings.js, utils/spaceView.js editRules). */
        .settings-subspace #spaceAccessWho,
        .settings-subspace #spaceEditPermissions,
        .settings-subspace #shareLinkSection,
        .settings-subspace #inviteLinkSection,
        .settings-subspace #allowedUsersSection { display: none !important; }
        .settings-subspace #spaceAccessInherited { display: block !important; }

        /* Drag & drop overlay */
        #dropOverlay {
            display: none;
            position: fixed;
            inset: 0;
            z-index: var(--z-max);
            background: rgba(var(--ui-surface-rgb), 0.35);
            pointer-events: none;
        }
        #dropOverlay.active {
            display: block;
        }
        .drop-zone {
            position: fixed;
            border: 2px dashed var(--ui-border-accent);
            border-radius: 14px;
            padding: 14px 22px;
            text-align: center;
            animation: dropPulse 1.5s ease-in-out infinite;
            background: rgba(var(--ui-surface-rgb), 0.85);
            backdrop-filter: blur(12px);
            -webkit-backdrop-filter: blur(12px);
            pointer-events: none;
            /* Above the cursor, clear of the OS drag image (Windows paints the
               dragged file's icon right under the pointer and hid a closer
               hint). JS sets the final offset and flips it below near the top
               of the screen; this is just the resting value. */
            transform: translate(-50%, calc(-100% - 120px));
            white-space: nowrap;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
        }
        .drop-zone-icon {
            font-size: 20px;
            display: inline;
            margin-right: 8px;
            vertical-align: middle;
        }
        .drop-zone-text {
            font-family: var(--ui-font);
            font-size: 14px;
            font-weight: 600;
            color: rgba(255, 255, 255, 0.9);
            display: inline;
            vertical-align: middle;
        }
        .drop-zone-hint {
            font-family: var(--ui-font);
            font-size: 11px;
            color: rgba(var(--ui-accent-rgb), 0.6);
            margin-top: 4px;
        }
        @keyframes dropPulse {
            0%, 100% { border-color: var(--ui-border-accent); }
            50% { border-color: var(--ui-border-accent); }
        }
        
        /* Upload progress toast */
        .toast-progress-bar {
            width: 100%;
            height: 3px;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 2px;
            margin-top: 6px;
            overflow: hidden;
        }
        .toast-progress-fill {
            height: 100%;
            background: linear-gradient(90deg, #64c8ff, #4ade80);
            border-radius: 2px;
            transition: width 0.2s ease;
            width: 0%;
        }
    

/* ===== section 2 (index.html extraction order preserved) ===== */
        @keyframes kuukanSpin { to { transform: rotate(360deg); } }
        @keyframes vfCornerTL {
            0%   { transform:translate(0,0);          opacity:.72; animation-timing-function:cubic-bezier(.1,0,0,1); }
            13%  { transform:translate(-12px,-12px);  opacity:1;   animation-timing-function:cubic-bezier(.5,0,.85,.9); }
            35%  { transform:translate(0,0);          opacity:.72; }
            100% { transform:translate(0,0);          opacity:.72; }
        }
        @keyframes vfCornerTR {
            0%   { transform:translate(0,0);          opacity:.72; animation-timing-function:cubic-bezier(.1,0,0,1); }
            13%  { transform:translate( 12px,-12px);  opacity:1;   animation-timing-function:cubic-bezier(.5,0,.85,.9); }
            35%  { transform:translate(0,0);          opacity:.72; }
            100% { transform:translate(0,0);          opacity:.72; }
        }
        @keyframes vfCornerBR {
            0%   { transform:translate(0,0);          opacity:.72; animation-timing-function:cubic-bezier(.1,0,0,1); }
            13%  { transform:translate( 12px, 12px);  opacity:1;   animation-timing-function:cubic-bezier(.5,0,.85,.9); }
            35%  { transform:translate(0,0);          opacity:.72; }
            100% { transform:translate(0,0);          opacity:.72; }
        }
        @keyframes vfCornerBL {
            0%   { transform:translate(0,0);          opacity:.72; animation-timing-function:cubic-bezier(.1,0,0,1); }
            13%  { transform:translate(-12px, 12px);  opacity:1;   animation-timing-function:cubic-bezier(.5,0,.85,.9); }
            35%  { transform:translate(0,0);          opacity:.72; }
            100% { transform:translate(0,0);          opacity:.72; }
        }
        @keyframes vfDotPulse {
            0%, 100% { transform: scale(1);    opacity: 0.72; }
            50%      { transform: scale(1.28); opacity: 1; }
        }
        .vf-c-tl { animation: vfCornerTL 2.2s linear infinite; animation-delay:    0s; }
        .vf-c-tr { animation: vfCornerTR 2.2s linear infinite; animation-delay: -1.9s; }
        .vf-c-br { animation: vfCornerBR 2.2s linear infinite; animation-delay: -1.6s; }
        .vf-c-bl { animation: vfCornerBL 2.2s linear infinite; animation-delay: -1.3s; }
        .vf-dot1 {
            transform-box: fill-box;
            transform-origin: center;
            animation: vfDotPulse 1.6s ease-in-out infinite;
        }
        .vf-dot2 {
            transform-box: fill-box;
            transform-origin: center;
            animation: vfDotPulse 1.6s ease-in-out 0.55s infinite;
        }
        @keyframes chatBubbleIn {
            from { opacity: 0; transform: translateY(8px); }
            to   { opacity: 1; transform: translateY(0); }
        }
        .chat-bubble-new { animation: chatBubbleIn 220ms cubic-bezier(.2,.7,.2,1) both; }
        @keyframes reactionPop {
            0%   { transform: scale(0.4); opacity: 0; }
            60%  { transform: scale(1.18); opacity: 1; }
            100% { transform: scale(1); }
        }
        /* Reaction pills — neutral surface at rest; YOUR reaction stands out
           with the primary fill + accent border (Discord anatomy). Look
           lives here; ReactionsRow.js only fills content. */
        .reaction-badge {
            display: inline-flex;
            align-items: center;
            gap: 3px;
            background: rgba(var(--ui-surface-rgb), 0.55);
            border: 1px solid var(--ui-border-faint);
            border-radius: 999px;
            padding: 2px 8px;
            cursor: pointer;
            -webkit-user-select: none;
            user-select: none;
            transition: border-color 0.12s, background 0.12s, transform 0.1s;
        }
        .reaction-badge:hover { border-color: var(--ui-border-accent); transform: scale(1.06); }
        .reaction-badge.reacted {
            background: rgba(var(--ui-primary-rgb), 0.35);
            border-color: var(--ui-border-accent);
        }
        .reaction-badge .reaction-count { color: var(--ui-text-muted); }
        /* Inventory tiles: a hold opens their menu (GifReactions
           _attachTileLongPress), so the browser's own hold callout stays out. */
        .gif-inv-tile { -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }
        @media (pointer: coarse) { .inv-tile-menu button { min-height: 44px; font-size: 14px !important; } }
        /* A row of pills under a finger: each one big enough to hit, the gap
           between them still there. */
        @media (pointer: coarse) { .reaction-badge { padding: 6px 11px; min-height: 32px; box-sizing: border-box; } }
        .reaction-badge.reacted .reaction-count { color: var(--ui-text-bright); }
        .reaction-badge-new { animation: reactionPop 280ms cubic-bezier(.3,1.5,.5,1) both; }
        /* Someone joined a reaction that already existed — a nudge, not an
           entrance. Softer than reactionPop so the two read differently. */
        .reaction-badge-bump { animation: reactionBump 260ms cubic-bezier(.3,1.4,.5,1) both; }
        @keyframes reactionBump {
            0%   { transform: scale(1); }
            45%  { transform: scale(1.15); }
            100% { transform: scale(1); }
        }
        @keyframes chatBubbleFlash {
            0%, 100% { box-shadow: 0 0 0 0 rgba(var(--ui-accent-rgb),0); }
            30%      { box-shadow: 0 0 0 3px rgba(var(--ui-accent-rgb),0.6); }
        }
        .chat-bubble-jump-flash { animation: chatBubbleFlash 1.1s ease-out both; border-radius: 8px; }
        @keyframes typingDotBounce {
            0%, 80%, 100% { transform: translateY(0); opacity: 0.4; }
            40%           { transform: translateY(-3px); opacity: 1; }
        }
        .typing-dot {
            display: inline-block;
            width: 4px; height: 4px;
            margin: 0 1px;
            background: currentColor;
            border-radius: 50%;
            animation: typingDotBounce 1.2s ease-in-out infinite;
        }
        .typing-dot:nth-child(2) { animation-delay: 0.15s; }
        .typing-dot:nth-child(3) { animation-delay: 0.30s; }
        @keyframes chatAvatarSpeaking {
            0%, 100% { filter: drop-shadow(0 0 1px rgba(var(--ui-teal-rgb),0)); transform: scale(1); }
            50%      { filter: drop-shadow(0 0 4px rgba(var(--ui-teal-rgb),0.8)); transform: scale(1.14); }
        }
        .chat-avatar-speaking { animation: chatAvatarSpeaking 0.9s ease-in-out infinite; }
        /* Chat input row utility buttons (emoji, / commands) — calm grey at
           rest, Kuukan orange on hover: the same convention as the message
           action icons. The send button stays primary and the shout button
           keeps its gold identity. */
        .chat-input-btn {
            padding: 6px 8px;
            background: transparent;
            border: 1px solid transparent;
            border-radius: 8px;
            color: #92a2b0;
            cursor: pointer;
            height: 32px;
            /* Never squash in the input row — the textarea beside these is the
               flexible one (flex:1 + min-width:0). Without this the buttons
               shrank instead and the row looked broken in a narrow panel. */
            flex-shrink: 0;
            transition: color 0.12s, background 0.12s;
        }
        .chat-input-btn:hover {
            color: #ff7a1a;
            background: rgba(255, 122, 26, 0.12);
        }

        /* Send buttons (space chat / comments / DM) — base recipe here,
           enabled/disabled is a class flip from ChatSystem/NodeComments. */
        .chat-send-btn {
            padding: 8px 12px;
            background: rgba(var(--ui-accent-rgb), 0.2);
            border: 2px solid var(--ui-border-accent);
            border-radius: 8px;
            color: var(--ui-accent);
            font-weight: 600;
            font-size: 12px;
            cursor: pointer;
            height: 32px;
            flex-shrink: 0;      /* same reason as .chat-input-btn */
            white-space: nowrap; /* "Send" must never wrap to two lines */
            transition: background 0.15s, color 0.15s, border-color 0.15s;
        }
        .chat-send-btn.send-disabled {
            background: rgba(var(--ui-accent-rgb),0.06);
            border-color: var(--ui-border-faint);
            color: rgba(var(--ui-accent-rgb),0.35);
            cursor: default;
        }
        /* 11a composer: ONE field that contains its tools; Send is the only
           filled element in the whole panel, so nothing competes with it. */
        .chat-composer-field {
            flex: 1;
            min-width: 0;
            display: flex;
            align-items: flex-end;
            gap: 2px;
            border: 1px solid var(--ui-border-accent);
            border-radius: 11px;
            background: rgba(0,0,0,0.15);
            padding: 3px 4px 3px 2px;
        }
        .chat-composer-field .ui-textarea {
            border: none;
            background: transparent;
            box-shadow: none;
            outline: none;
            /* No scrollbar until the 120px cap — an empty field showing
               scroll arrows read as broken. JS flips this to auto at cap. */
            overflow-y: hidden;
        }
        /* The hint ellipsises on one line — it never wraps and never clips
           mid-word (11a). */
        .chat-composer-field .ui-textarea::placeholder {
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        .chat-composer-field .chat-input-btn {
            width: 30px;
            height: 30px;
            padding: 0;
            border: none;
            background: transparent;
            border-radius: 8px;
            flex: none;
            color: rgba(var(--ui-text-rgb), 0.55);
            cursor: pointer;
        }
        .chat-composer-field .chat-input-btn:hover {
            background: rgba(var(--ui-text-rgb), 0.07);
            color: rgba(var(--ui-text-rgb), 1);
        }
        /* Single-line inputs inside a composer field lose their own chrome
           the same way the textarea does — the FIELD is the border. */
        .chat-composer-field .ui-input {
            border: none;
            background: transparent;
            box-shadow: none;
            outline: none;
        }
        /* Assistant mode pills — a checkbox wearing a pill. :has() paints
           the checked state; browsers without it keep a working (if plain)
           toggle. */
        .ai-toggle-pill {
            display: inline-flex;
            align-items: center;
            gap: 5px;
            height: 26px;
            padding: 0 11px;
            border: 1px solid var(--ui-border);
            border-radius: 999px;
            color: var(--ui-text-muted);
            font-size: 11px;
            font-weight: 600;
            cursor: pointer;
            -webkit-user-select: none;
            user-select: none;
            transition: background 0.15s, color 0.15s, border-color 0.15s;
        }
        .ai-toggle-pill input { display: none; }
        .ai-toggle-pill:has(input:checked) {
            border-color: rgba(var(--ui-accent-rgb), 0.4);
            background: rgba(var(--ui-accent-rgb), 0.12);
            color: rgba(var(--ui-accent-rgb), 1);
        }
        .ai-toggle-pill:has(input:checked) > span::before { content: '✓ '; }

        .chat-send-btn--fab {
            width: 40px;
            height: 40px;
            padding: 0;
            border: none;
            border-radius: 11px;
            background: var(--ui-accent);
            color: #04222a;
        }
        .chat-send-btn--fab.send-disabled {
            background: rgba(var(--ui-accent-rgb), 0.15);
            color: rgba(var(--ui-accent-rgb), 0.4);
        }
        @keyframes ctxMenuIn {
            from { opacity:0; transform:scale(0.92); }
            to { opacity:1; transform:scale(1); }
        }
        @keyframes ctxMenuOut {
            from { opacity:1; transform:scale(1); }
            to { opacity:0; transform:scale(0.92); }
        }
        @keyframes burgerDropdownIn {
            from { opacity:0; transform:translateY(-6px); }
            to { opacity:1; transform:translateY(0); }
        }
        @keyframes burgerDropdownOut {
            from { opacity:1; transform:translateY(0); }
            to { opacity:0; transform:translateY(-6px); }
        }
    

/* ===== section 3 (index.html extraction order preserved) ===== */
        @keyframes shake {
            0%, 100% { transform: translateX(0); }
            20% { transform: translateX(-8px); }
            40% { transform: translateX(8px); }
            60% { transform: translateX(-5px); }
            80% { transform: translateX(5px); }
        }
    

/* ===== section 4 (index.html extraction order preserved) ===== */
#avatarPickerModal .profile-tab { border-radius: 6px; }

/* ===== section 5 (index.html extraction order preserved) ===== */
        .help-row { display:flex; justify-content:space-between; align-items:center; padding:5px 0; border-bottom:1px solid var(--ui-border-faint); }
        .help-row:last-child { border-bottom:none; }
        .help-keys { color:rgba(var(--ui-text-rgb),0.7); font-size:12px; min-width:140px; }
        .help-keys kbd { background:rgba(var(--ui-accent-rgb),0.08); border:2px solid var(--ui-border); border-radius:4px; padding:2px 6px; font-size:11px; font-family: var(--ui-font); color:rgba(var(--ui-text-rgb),0.8); }
        .help-desc { color:rgba(var(--ui-text-rgb),0.45); font-size:12px; text-align:right; }
        #helpModal ::-webkit-scrollbar { width: 6px; }
        #helpModal ::-webkit-scrollbar-track { background: rgba(var(--ui-surface-rgb), 0.4); border-radius: 3px; }
        #helpModal ::-webkit-scrollbar-thumb { background: rgba(var(--ui-accent-rgb), 0.3); border-radius: 3px; }
        #helpModal ::-webkit-scrollbar-thumb:hover { background: rgba(var(--ui-accent-rgb), 0.5); }
    

/* ===== section 6 (index.html extraction order preserved) ===== */
        #onboardingWalkthrough {
            position: fixed;
            bottom: 32px;
            left: 50%;
            transform: translateX(-50%) translateY(10px);
            z-index: var(--z-system);
            opacity: 0;
            transition: opacity 0.35s ease, transform 0.35s ease;
            pointer-events: auto;
        }
        .ob-card {
            position: relative;
            background: rgba(12, 20, 30, 0.95);
            backdrop-filter: blur(20px);
            -webkit-backdrop-filter: blur(20px);
            border: 2px solid var(--ui-border-accent);
            border-radius: 16px;
            padding: 24px 28px 18px;
            min-width: 320px;
            max-width: 400px;
            box-shadow: 0 12px 48px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(var(--ui-accent-rgb), 0.08);
            text-align: center;
            font-family: var(--ui-font);
            overflow: hidden;
            transition: opacity 0.15s ease, transform 0.15s ease;
        }
        .ob-progress {
            position: absolute;
            top: 0; left: 0;
            height: 3px;
            background: linear-gradient(90deg, rgba(var(--ui-accent-rgb), 0.6), rgba(60, 230, 160, 0.6));
            border-radius: 0 3px 3px 0;
            transition: width 0.4s ease;
            width: 0%;
        }
        .ob-icon {
            font-size: 36px;
            margin-bottom: 8px;
            -webkit-user-select: none;
            user-select: none;
        }
        .ob-message {
            color: var(--ui-text-bright);
            font-size: 16px;
            font-weight: 700;
            margin-bottom: 4px;
            letter-spacing: 0.2px;
        }
        .ob-sub {
            color: rgba(var(--ui-text-rgb), 0.55);
            font-size: 13px;
            line-height: 1.5;
            margin-bottom: 16px;
        }
        .ob-actions {
            display: flex;
            justify-content: center;
            align-items: center;
            gap: 12px;
            margin-bottom: 12px;
        }
        .ob-skip {
            background: none;
            border: none;
            color: rgba(var(--ui-text-rgb), 0.35);
            font-size: 12px;
            cursor: pointer;
            padding: 4px 8px;
            font-family: var(--ui-font);
            transition: color 0.15s;
        }
        .ob-skip:hover { color: rgba(var(--ui-text-rgb), 0.65); }
        .ob-next {
            background: rgba(var(--ui-primary-rgb), 0.6);
            border: 2px solid var(--ui-border-accent);
            border-radius: 8px;
            color: var(--ui-text-bright);
            font-size: 13px;
            font-weight: 600;
            padding: 8px 20px;
            cursor: pointer;
            font-family: var(--ui-font);
            transition: all 0.15s;
        }
        .ob-next:hover {
            background: rgba(var(--ui-primary-rgb), 0.8);
            border-color: var(--ui-border-accent);
            box-shadow: 0 0 16px rgba(var(--ui-accent-rgb), 0.15);
        }
        .ob-dots {
            display: flex;
            justify-content: center;
            gap: 6px;
        }
        .ob-dot {
            width: 7px; height: 7px;
            border-radius: 50%;
            background: rgba(var(--ui-accent-rgb), 0.15);
            transition: all 0.3s ease;
        }
        .ob-dot.active {
            background: rgba(var(--ui-accent-rgb), 0.7);
            box-shadow: 0 0 6px rgba(var(--ui-accent-rgb), 0.4);
            transform: scale(1.2);
        }
        .ob-dot.done {
            background: rgba(60, 230, 160, 0.5);
        }
        .ob-canvas-highlight {
            position: fixed;
            inset: 0;
            pointer-events: none;
            z-index: calc(var(--z-system) - 1);
            border: 2px dashed var(--ui-border-accent);
            animation: obPulse 2s ease-in-out infinite;
        }
        @keyframes obPulse {
            0%, 100% { border-color: var(--ui-border); }
            50% { border-color: var(--ui-border-accent); }
        }
        @media (max-width: 500px) {
            #onboardingWalkthrough { bottom: 16px; }
            .ob-card { min-width: 280px; max-width: calc(100vw - 32px); padding: 20px 18px 14px; }
        }
    

/* ===== section 7 (index.html extraction order preserved) ===== */
        /* (Floating Tools panel CSS removed — tools live on the right-edge
           rail mounted by modules/ToolNodes.js.) */
    

        /* ── Search: the timeline ────────────────────────────────────────────
           The main control, not one filter among many: its own block at the top
           of the panel, above the chips. Dragging it keeps only the newest
           items in the space — the thing wanted almost every time. The chips
           below refine that result, and are the rarer case. */
        /* Full bleed. A timeline has to look like the whole span of the space —
           inset by a margin it reads as just another slider. Only the label is
           indented; the track itself runs edge to edge across the panel. */
        .search-timeline {
            /* #searchFilters is a wrapping FLEX row, so this is a flex ITEM and
               was sizing to its content — which is why the track stopped short
               however wide the input was told to be. Claim the whole line, plus
               the 12px the panel pads on each side. */
            flex: 0 0 calc(100% + 24px);
            box-sizing: border-box;
            margin: 2px -12px 8px;
            padding: 0 0 10px;
            border-bottom: 1px solid var(--ui-border-faint);
        }
        .search-timeline .search-time-head { padding: 0 12px 2px; }

        /* ── Phone: Browse is a bottom sheet ──────────────────────────────
           On a phone the panel is the whole window, so finding something and
           LOOKING at it can't happen at once — and the list wins every time.
           Two heights instead: a sheet you read, and a strip you steer with.

           The strip keeps only the grip and Prev / counter / Next, so the
           canvas gets the rest of the screen while you step through results.
           Tapping a result drops to the strip, because that tap is the moment
           the job changes from choosing to looking. */
        @media (max-width: 900px) {
            #searchPanel {
                top: auto !important;
                bottom: 0 !important;
                left: 0 !important;
                right: 0 !important;
                width: 100% !important;
                max-width: none !important;
                transform: none !important;
                border-radius: 16px 16px 0 0;
                /* Half the screen. The sheet is for choosing and the strip is
                   for looking, and a sheet that takes three quarters is trying
                   to be both — leaving too little canvas to judge anything by
                   and still not enough list to be comfortable. */
                max-height: 55vh;
                /* Column layout comes from the inline display the opener sets
                   — see openSearchPanel. It can't be forced from here: the
                   closed panel is display:none inline, and !important would
                   override that too and pin the sheet permanently open. */
                flex-direction: column;
                overflow: hidden;
                /* Clear of the home indicator on phones that have one. */
                padding-bottom: env(safe-area-inset-bottom, 0px);
            }
            #searchSheetGrip {
                display: flex !important;
                justify-content: center;
                align-items: center;
                /* A 44px band to grab, with a small bar drawn inside it. */
                min-height: 44px;
                padding: 0;
                cursor: grab;
                /* none, not manipulation: the browser must not claim the
                   vertical gesture for scrolling before the handler sees it. */
                touch-action: none;
                -webkit-user-select: none;
                user-select: none;
            }
            #searchSheetGrip:active { cursor: grabbing; }
            #searchSheetGrip span {
                display: block;
                width: 40px; height: 4px;
                border-radius: 2px;
                background: var(--ui-border-accent);
            }
            /* The panel header carries a drag handle that means nothing here. */
            #searchPanel #searchPanelHeader { cursor: default; }
            /* The list takes everything the controls don't. A min-height here
               was the wrong tool and made it worse: a flex child that refuses
               to shrink pushes the total past the sheet, and a sheet anchored
               to the bottom overflows DOWNWARD — off the screen, list and all.
               The controls are bounded instead, below. */
            #searchResults {
                max-height: none !important;
                flex: 1 1 auto;
                min-height: 0;
            }
            /* Bounded, and scrollable if a space has enough chips and arrivals
               to overrun it. The list can then never be squeezed out by them. */
            #searchQueryBlock {
                flex: 0 1 auto;
                max-height: 26vh;
                overflow-y: auto;
                scrollbar-width: none;
            }
            #searchQueryBlock::-webkit-scrollbar { display: none; }
            #searchViewFilterSection { flex: 0 0 auto; }
            /* Chips scroll sideways instead of wrapping. Twelve of them stacked
               four rows deep is most of a phone screen spent on buttons you
               are not pressing. */
            .search-filter-group {
                display: flex;
                flex-wrap: nowrap;
                overflow-x: auto;
                gap: 6px;
                scrollbar-width: none;
                padding-bottom: 2px;
            }
            .search-filter-group::-webkit-scrollbar { display: none; }
            .search-filter-group > * { flex: 0 0 auto; }

            /* The strip. Everything that helps you CHOOSE is gone; what's left
               is what helps you STEER. */
            body.browse-peek #searchPanel { max-height: none; }
            body.browse-peek #searchPanel #searchPanelHeader,
            body.browse-peek #searchPanel #searchQueryBlock,
            body.browse-peek #searchPanel #searchViewFilterSection,
            body.browse-peek #searchPanel #searchResults { display: none !important; }
            body.browse-peek #searchPanel #searchNavigation {
                display: flex !important;
                border-top: none;
                padding: 4px 12px 10px;
            }
            /* Fingers, not cursors. */
            body.browse-peek #searchPrevBtn,
            body.browse-peek #searchNextBtn { padding: 12px 20px; font-size: 14px; }
            body.browse-peek #searchCounter { font-size: 13px; }
        }

        /* Arrivals sit directly above the timeline and share its full-bleed
           geometry: they answer the same question ("what's new here?") by a
           different route — a set you pick rather than a range you drag. */
        .search-arrivals {
            flex: 0 0 calc(100% + 24px);
            box-sizing: border-box;
            margin: 2px -12px 6px;
            padding: 0 0 8px;
            border-bottom: 1px solid var(--ui-border-faint);
        }
        .search-arrivals .search-time-head { padding: 0 12px 4px; }
        /* Scrolls sideways rather than wrapping: a space with a long import
           history must not push the timeline off the panel. */
        .search-arrival-chips {
            display: flex; gap: 6px; overflow-x: auto;
            padding: 0 12px 2px;
            scrollbar-width: none;
        }
        .search-arrival-chips::-webkit-scrollbar { display: none; }
        .search-arrival {
            flex: 0 0 auto;
            min-height: 30px;               /* touch target — no hover-only affordances */
            padding: 5px 10px;
            border-radius: 14px;
            border: 1px solid rgba(var(--ui-accent-rgb), 0.25);
            background: rgba(var(--ui-surface-rgb), 0.5);
            color: var(--ui-text);
            font-size: 11px;
            white-space: nowrap;
            cursor: pointer;
            transition: background 0.15s, border-color 0.15s;
        }
        .search-arrival:hover { background: rgba(var(--ui-accent-rgb), 0.15); }
        .search-arrival.active {
            background: rgba(var(--ui-accent-rgb), 0.28);
            border-color: rgba(var(--ui-accent-rgb), 0.7);
        }
        /* The track holds both handles, the kept span, and any marks. */
        .search-timeline-track { position: relative; height: 18px; }
        /* The two ranges sit on top of each other; only their thumbs take
           pointer events, so each handle stays grabbable and neither blocks
           the other's half of the track. */
        .search-timeline-track .tl-range {
            position: absolute; left: 0; right: 0; top: 0;
            width: 100%; margin: 0; background: none; pointer-events: none;
            -webkit-appearance: none; appearance: none;
        }
        /* Upright bars, not knobs. A timeline handle marks a POSITION on an
           axis, and a bar is the shape that says so — a circle points at a
           spot only by its centre, which is why scrubbers everywhere use a
           blade. Tall enough to grab: 18px of height on a 4px track. */
        .search-timeline-track .tl-range::-webkit-slider-thumb {
            pointer-events: auto;
            -webkit-appearance: none;
            appearance: none;
            width: 5px;
            height: 18px;
            border-radius: 2px;
            border: none;
            background: var(--ui-accent);
            box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.35), 0 1px 4px rgba(0, 0, 0, 0.4);
            cursor: ew-resize;
        }
        .search-timeline-track .tl-range::-moz-range-thumb {
            pointer-events: auto;
            width: 5px;
            height: 18px;
            border-radius: 2px;
            border: none;
            background: var(--ui-accent);
            box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.35), 0 1px 4px rgba(0, 0, 0, 0.4);
            cursor: ew-resize;
        }
        .search-timeline-track .tl-range::-webkit-slider-runnable-track { background: transparent; }
        .search-timeline-track .tl-range::-moz-range-track { background: transparent; }
        /* The span that survives, painted under the handles. */
        .search-timeline-track::before {
            content: '';
            position: absolute; left: 0; right: 0; top: 7px; height: 4px;
            border-radius: 2px;
            background: linear-gradient(to right,
                var(--ui-border-faint) 0, var(--ui-border-faint) var(--tl-lo, 0%),
                var(--ui-accent) var(--tl-lo, 0%), var(--ui-accent) var(--tl-hi, 100%),
                var(--ui-border-faint) var(--tl-hi, 100%), var(--ui-border-faint) 100%);
        }
        /* Where you were the last time you opened this space. The same instant
           the green NEW glow uses, so the timeline agrees with the rest of the
           app about what counts as new. */
        .search-visit-mark {
            position: absolute;
            top: -2px;
            width: 2px;
            height: 18px;
            padding: 0;
            border: none;
            border-radius: 1px;
            background: rgba(60, 230, 110, 0.9);
            box-shadow: 0 0 6px rgba(60, 230, 110, 0.6);
            cursor: pointer;
            transform: translateX(-1px);
        }
        /* A pause in the space's history. Quieter than the visit mark, which
           answers one question; these are a scale you read along. Sits BELOW
           the track so it can't be mistaken for a handle. */
        .search-gap-mark {
            appearance: none;
            -webkit-appearance: none;
            box-sizing: border-box;
            position: absolute;
            top: 12px;
            width: 2px;
            min-width: 0;
            height: 7px;
            margin: 0;
            padding: 0;
            border: none;
            border-radius: 1px;
            font-size: 0;
            line-height: 0;
            background: rgba(var(--ui-accent-rgb), 0.5);
            cursor: pointer;
            transform: translateX(-1px);
        }
        .search-gap-mark::after { left: 50%; }
        /* Touch has no hover, so the target widens invisibly rather than the
           mark being drawn fat. */
        .search-gap-mark::before {
            content: '';
            position: absolute;
            /* Widened sideways and DOWNWARD only. Reaching up would put the
               target back under the handles, which is the overlap this is
               trying to stay out of. */
            left: -8px; right: -8px; top: 0; bottom: -12px;
        }
        .search-gap-mark:hover { background: var(--ui-accent); height: 11px; top: 10px; }

        .search-visit-mark::after { left: 50%; }   /* centre its tooltip on the line */
        .search-visit-mark:hover { background: rgb(60, 230, 110); height: 22px; top: -4px; }

        .search-timeline #searchTimeRange {
            display: block;
            width: 100%;
            margin: 0;
        }
        .search-time-head {
            display: flex; justify-content: space-between; align-items: baseline;
            font-size: 11px; color: var(--ui-text-muted); margin-bottom: 6px;
        }
        .search-time-value { color: var(--ui-accent); font-size: 12px; font-weight: 600; }
        /* The axis says which way it runs, so the heading doesn't have to. Two
           words under the ends beat any sentence above them. */
        .search-time-ends {
            display: flex;
            justify-content: space-between;
            padding: 2px 12px 0;
            font-size: 9px;
            letter-spacing: 0.04em;
            color: var(--ui-text-muted);
            pointer-events: none;
        }
        #searchTimeRange { width: 100%; cursor: pointer; }
        #searchTimeRange:disabled { opacity: 0.4; cursor: default; }


/* The Activity panel is as big as what it holds. It was a fixed column from
   under the toolbar to 140px off the bottom, 440px wide, so a list of three
   rows sat in a tall empty frame. Now: width and height from the content,
   the old frame as the ceiling, and the list scrolls once it reaches it. */
#spaceActivityPanel {
    top: calc(var(--toolbar-height) + 78px);
    left: 20px;
    min-width: min(340px, calc(100vw - 40px));
    max-width: min(460px, calc(100vw - 40px));
    max-height: calc(100vh - var(--toolbar-height) - 218px);
    max-height: calc(100dvh - var(--toolbar-height) - 218px);
}

/* A segmented choice: one track, equal cells, the avatar kind row's shape
   (avatar-gear.css) — several answers to one question, not loose chips.
   Messages' Space | Direct and Activity's Recent | People | This space |
   Mine. The lit cell carries .active. */
.ui-seg {
    display: inline-flex;
    gap: 2px;
    padding: 2px;
    border-radius: 9px;
    background: rgba(0, 0, 0, 0.22);
}
/* A cell is as wide as what it says: equal cells split the SUM of the
   labels evenly, which cut a long room name in half beside a short
   "Direct". A full-width row asks for equal cells itself (below). */
.ui-seg-cell {
    flex: 0 1 auto;
    min-width: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
    cursor: pointer;
    border: 1px solid transparent;
    background: none;
    color: var(--ui-text-muted);
    font-size: 11.5px;
    font-family: inherit;
    padding: 5px 10px;
    border-radius: 7px;
    white-space: nowrap;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.ui-seg-cell:hover { color: var(--ui-text-bright); }
/* A label that is a name (a room's) gives way before the cell does. */
.ui-seg-label { min-width: 0; max-width: 130px; overflow: hidden; text-overflow: ellipsis; }
.ui-seg-cell.active {
    background: var(--ui-surface-alt);
    color: var(--ui-text-bright);
    border-color: var(--ui-accent);
}
.ui-seg-cell:focus-visible { outline: 2px solid var(--ui-accent); outline-offset: 1px; }
@media (pointer: coarse) { .ui-seg-cell { min-height: 44px; } }
.ui-seg-badge {
    min-width: 15px; height: 15px; padding: 0 4px; border-radius: 8px;
    background: var(--ui-accent); color: #062026; font-size: 9px; font-weight: 700;
    align-items: center; justify-content: center; line-height: 1;
}
/* ── Direct (ConversationList.js): Instagram's shape ── */
.dm-topbar { display: flex; gap: 6px; align-items: center; margin-bottom: 8px; }
.dm-search {
    flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px;
    padding: 0 10px; height: 36px; border-radius: 10px;
    border: 1px solid var(--ui-border); background: rgba(0, 0, 0, 0.18);
    color: var(--ui-text-muted); cursor: text;
}
.dm-search input {
    flex: 1; min-width: 0; border: none; outline: none; background: none;
    color: var(--ui-text-bright); font: inherit; font-size: 12.5px;
}
.dm-search input::-webkit-search-cancel-button { filter: invert(0.6); }
.dm-search:focus-within { border-color: var(--ui-border-accent); }
.dm-icon-btn {
    flex: none; width: 36px; height: 36px; border-radius: 10px; cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid var(--ui-border); background: none; color: var(--ui-text-bright);
}
.dm-icon-btn:hover { background: rgba(var(--ui-accent-rgb), 0.08); }
/* New group says what it makes: a chat with one person is the search's. */
.dm-new-btn { width: auto; gap: 6px; padding: 0 12px; font: inherit; font-size: 12.5px; font-weight: 600; }
@media (pointer: coarse) { .dm-search { height: 44px; } .dm-icon-btn { width: 44px; height: 44px; } .dm-new-btn { width: auto; } }
/* Friends, as faces: tap to message. A ring says they are online. One row,
   as many as fit, and the last place is All — a hidden sideways scroll was
   invisible with a mouse (Mikko 28.9.). */
.dm-faces { display: flex; gap: 10px; overflow: hidden; padding: 4px 2px 8px; margin-bottom: 2px; }
.dm-face {
    flex: none; width: 58px; display: flex; flex-direction: column; align-items: center; gap: 5px;
    background: none; border: none; padding: 0; cursor: pointer; color: var(--ui-text-muted); font: inherit;
}
.dm-face-pic {
    position: relative; width: 48px; height: 48px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
    font-size: 30px; line-height: 1; background: rgba(var(--ui-text-rgb), 0.06);
}
/* Online: the teal dot in the corner, the app's one online mark (the People
   list's dot). The ring it replaced was the accent — selection's colour — and
   did not read as "here now" (Mikko 30.9.). Cut out of the panel by a ring of
   its ground, as every messenger does. */
.dm-face.is-online .dm-face-pic::after,
.dm-av.is-online::after {
    content: ''; position: absolute; border-radius: 50%; background: #4ecdc4;
    box-shadow: 0 0 0 2.5px rgba(var(--ui-surface-rgb), 1), 0 0 6px rgba(78, 205, 196, 0.55);
}
.dm-face.is-online .dm-face-pic::after { right: 1px; bottom: 1px; width: 12px; height: 12px; }
.dm-av { position: relative; flex: none; display: inline-flex; }
/* What Your AI is doing, at the foot of its thread (ChatSystem paintAiWorking): the rabbit bobbing, what it is at, how long — amber when slow, red when it may be stuck. */
.ai-working-row { display: flex; align-items: center; gap: 12px; margin: 10px 14px 6px; padding: 9px 12px; border-radius: 12px; background: rgba(var(--ui-accent-rgb), 0.08); box-shadow: inset 0 0 0 1px rgba(var(--ui-accent-rgb), 0.28); }
.ai-working-row .aw-face { flex: none; display: inline-flex; animation: awBob 1.5s ease-in-out infinite; }
.ai-working-row .aw-text { flex: 1; min-width: 0; display: grid; gap: 1px; }
.ai-working-row .aw-doing { font-size: 13px; font-weight: 600; color: var(--ui-text-bright, #f0f4f5); overflow-wrap: anywhere; }
.ai-working-row .aw-doing::after { content: ''; display: inline-block; width: 1.2em; text-align: left; animation: awDots 1.2s steps(4, end) infinite; }
.ai-working-row .aw-time { font-size: 11.5px; color: var(--ui-text-muted, #b9c9cf); }
.ai-working-row .aw-stop { flex: none; min-height: 32px; padding: 0 12px; border-radius: 9px; border: 1px solid rgba(255, 255, 255, 0.18); background: rgba(255, 255, 255, 0.06); color: var(--ui-text, #dadfe0); font: 600 12px var(--ui-font); cursor: pointer; }
.ai-working-row .aw-stop:hover { background: rgba(255, 120, 100, 0.16); border-color: rgba(255, 120, 100, 0.5); }
.ai-working-row .aw-stop:disabled { opacity: 0.6; cursor: default; }
.ai-working-row.slow { background: rgba(240, 180, 41, 0.09); box-shadow: inset 0 0 0 1px rgba(240, 180, 41, 0.38); }
.ai-working-row.slow .aw-time { color: #f0b429; }
.ai-working-row.stuck { background: rgba(255, 120, 100, 0.1); box-shadow: inset 0 0 0 1px rgba(255, 120, 100, 0.5); }
.ai-working-row.stuck .aw-time { color: #ff9a8f; }
.ai-working-row.stuck .aw-face { animation: none; opacity: 0.6; }
@keyframes awBob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-4px); } }
@keyframes awDots { 0% { content: ''; } 25% { content: '.'; } 50% { content: '..'; } 75% { content: '...'; } }
@media (prefers-reduced-motion: reduce) { .ai-working-row .aw-face, .ai-working-row .aw-doing::after { animation: none; } }
/* "Your AI", the first row of Direct: set apart from the people under it — a tinted plate with a rim, the rabbit at a larger size. */
.conv-row[data-kind="ai"] { background: rgba(var(--ui-accent-rgb), 0.10); box-shadow: inset 0 0 0 1px rgba(var(--ui-accent-rgb), 0.34); margin-bottom: 10px; padding: 8px 12px; }
.conv-row[data-kind="ai"]:hover { background: rgba(var(--ui-accent-rgb), 0.16); }
.dm-av.is-online::after { right: -2px; bottom: -1px; width: 8px; height: 8px; box-shadow: 0 0 0 2px rgba(var(--ui-surface-rgb), 1); }
.dm-face:hover .dm-face-pic { background: rgba(var(--ui-accent-rgb), 0.12); }
.dm-face-name { max-width: 58px; font-size: 10.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dm-face-all .dm-face-pic {
    font-size: 13px; font-weight: 700; color: var(--ui-text-bright);
    border: 1px solid var(--ui-border-accent); background: rgba(var(--ui-accent-rgb), 0.10);
}
.dm-face-all .dm-face-name { color: var(--ui-text-bright); font-weight: 600; }
.dm-heading {
    display: flex; align-items: center; justify-content: space-between;
    padding: 6px 10px 4px; font-size: 13px; font-weight: 700; color: var(--ui-text-bright);
}
.dm-link {
    background: none; border: none; padding: 4px 0; cursor: pointer; font: inherit;
    font-size: 12.5px; font-weight: 600; color: var(--ui-text-muted);
}
.dm-link.has-some { color: rgba(var(--ui-accent-rgb), 1); }
/* Talk in your spaces: one chip per room, with how much is unread there. */
.dm-chips { display: flex; flex-wrap: wrap; gap: 6px; padding: 2px 2px 8px; }
.dm-chip {
    display: inline-flex; align-items: center; gap: 7px; max-width: 100%;
    height: 32px; padding: 0 10px 0 5px; border-radius: 16px; cursor: pointer;
    border: 1px solid var(--ui-border); background: rgba(var(--ui-text-rgb), 0.04);
    color: var(--ui-text-bright); font: inherit; font-size: 12px; font-weight: 600;
}
.dm-chip:hover { background: rgba(var(--ui-accent-rgb), 0.10); border-color: var(--ui-border-accent); }
.dm-chip-pic {
    position: relative; overflow: hidden; flex: none; width: 22px; height: 22px; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    background: rgba(var(--ui-accent-rgb), 0.16); color: rgba(var(--ui-accent-rgb), 0.95);
}
.dm-chip-pic img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.dm-chip-name { min-width: 0; max-width: 130px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dm-chip-count {
    flex: none; min-width: 18px; height: 18px; padding: 0 5px; border-radius: 9px;
    background: var(--ui-accent); color: #062026; font-size: 10px; font-weight: 700;
    display: inline-flex; align-items: center; justify-content: center;
}
@media (pointer: coarse) { .dm-chip { height: 44px; border-radius: 22px; } }

/* Your team, a card under Friends: a door, with the team's logo if any. */
.dm-team-row {
    width: 100%; display: flex; align-items: center; gap: 10px; margin: 0 0 8px;
    padding: 8px 12px 8px 10px; border-radius: 12px; cursor: pointer;
    border: 1px solid var(--ui-border); background: rgba(var(--ui-text-rgb), 0.03);
    font: inherit; color: var(--ui-text-muted); text-align: left;
}
.dm-team-row:hover { background: rgba(var(--ui-accent-rgb), 0.08); border-color: var(--ui-border-accent); }
/* A logo is often a wordmark: its height is fixed and its width its own, as
   in the brand slot. */
.dm-team-logo { flex: none; height: 24px; width: auto; max-width: 80px; object-fit: contain; }
.dm-team-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; font-size: 11.5px; }
.dm-team-text b { color: var(--ui-text-bright); font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (pointer: coarse) { .dm-team-row { min-height: 48px; } }
/* The people view's modes (NodeComments.js switchToFriendsTab): each shows
   its own sections only. */
#friendsTabContent:not(.people-mode-requests) .people-requests-only,
#friendsTabContent:not(.people-mode-all) .people-all-only,
#friendsTabContent:not(.people-mode-team) .people-team-only { display: none !important; }

/* The Activity scope row is one of them, across the panel's width, in
   equal cells. */
#activityScopeRow { display: flex; margin: 6px 8px 4px; }
#activityScopeRow .ui-seg-cell { flex: 1 1 0; }

/* ── Voice call bar: who is here, and who is talking ────────────────
   The list used to be a line of text per person with a faint border when
   they spoke. It carried the information and none of the presence — you
   had to look for it to see it. An avatar makes the row a person, and the
   speaking state is a ring plus three bars: two signals, so it reads at a
   glance and still reads with the ring alone if motion is reduced. */
.voice-row {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 6px 9px;
    margin-bottom: 4px;
    border-radius: 10px;
    background: rgba(var(--ui-surface-rgb), 0.45);
    border: 1px solid transparent;
    /* Soft, never snappy — a call bar should breathe, not blink. */
    transition: border-color 0.25s ease, background 0.25s ease;
}
.voice-row:last-child { margin-bottom: 0; }

.voice-avatar {
    width: 26px; height: 26px;
    flex-shrink: 0;
    border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    font-size: 15px; line-height: 1;
    background: rgba(var(--ui-primary-rgb), 0.45);
    border: 1px solid var(--ui-border-faint);
    transition: border-color 0.25s ease, transform 0.35s cubic-bezier(0.2, 0.9, 0.3, 1.2);
}

.voice-name {
    flex: 1; min-width: 0;
    color: var(--ui-text-muted);
    font-size: 12px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    transition: color 0.25s ease;
}
.voice-you { color: var(--ui-text-dim); font-size: 10px; margin-left: 4px; }

/* Three bars that ride the voice. Present but flat when silent, so the row
   never changes width mid-sentence. */
.voice-level { display: flex; align-items: flex-end; gap: 2px; height: 13px; flex-shrink: 0; }
.voice-level i {
    width: 2px; height: 3px;
    border-radius: 1px;
    background: rgba(var(--ui-text-rgb), 0.2);
    transition: background 0.25s ease, height 0.2s ease;
}

.voice-row.is-speaking { border-color: rgba(var(--ui-teal-rgb), 0.55); background: rgba(var(--ui-teal-rgb), 0.10); }
.voice-row.is-speaking .voice-name { color: var(--ui-text-bright); }
.voice-row.is-speaking .voice-avatar {
    border-color: rgba(var(--ui-teal-rgb), 0.9);
    animation: voiceRing 1.5s ease-out infinite;
}
.voice-row.is-speaking .voice-level i { background: #4ecdc4; animation: voiceBar 0.6s ease-in-out infinite; }
.voice-row.is-speaking .voice-level i:nth-child(2) { animation-delay: 0.15s; }
.voice-row.is-speaking .voice-level i:nth-child(3) { animation-delay: 0.3s; }

/* Muted says it twice as well: the icon appears and the person dims. */
.voice-row.is-muted .voice-avatar { opacity: 0.5; }
.voice-row.is-muted .voice-name { color: var(--ui-text-dim); }
.voice-muted-icon { display: none; flex-shrink: 0; color: rgba(var(--ui-text-rgb), 0.45); }
.voice-row.is-muted .voice-muted-icon { display: block; }
.voice-row.is-muted .voice-level { display: none; }

@keyframes voiceRing {
    0%   { box-shadow: 0 0 0 0 rgba(78, 205, 196, 0.45); }
    70%  { box-shadow: 0 0 0 7px rgba(78, 205, 196, 0); }
    100% { box-shadow: 0 0 0 0 rgba(78, 205, 196, 0); }
}
@keyframes voiceBar {
    0%, 100% { height: 3px; }
    50%      { height: 11px; }
}

/* Motion off: the ring and the colour still carry the whole message. */
@media (prefers-reduced-motion: reduce) {
    .voice-row.is-speaking .voice-avatar { animation: none; box-shadow: 0 0 0 3px rgba(78, 205, 196, 0.28); }
    .voice-row.is-speaking .voice-level i { animation: none; height: 8px; }
}


/* Google Drive import: a focused modal, followed by Google's own file picker. */
.drive-import-dialog {
    width: min(480px, calc(100vw - 32px)); max-height: calc(100dvh - 32px);
    box-sizing: border-box; overflow-y: auto; padding: 24px;
    border: 1px solid var(--ui-border); border-radius: 16px;
    background: var(--ui-bg, #10262d); color: var(--ui-text, #dce5ed);
    font-family: var(--ui-font); box-shadow: 0 20px 70px #0008;
}
.drive-import-dialog::backdrop { background: #0009; }
.drive-import-dialog h2 { display: flex; align-items: center; gap: 10px; font-size: 19px; margin: 0 0 12px; }
.drive-import-destination { font-size: 13px; color: var(--ui-text-muted); overflow-wrap: anywhere; }
.drive-import-status { font-size: 14px; line-height: 1.5; overflow-wrap: anywhere; }
.drive-import-note { font-size: 12px; line-height: 1.5; color: var(--ui-text-muted); }
.drive-import-files { max-height: 200px; overflow-y: auto; padding-left: 20px; font-size: 13px; line-height: 1.6; overflow-wrap: anywhere; }
.drive-import-files:empty { display: none; }
.drive-import-dialog progress { width: 100%; accent-color: var(--ui-teal, #80c2b0); }
.drive-import-actions { display: flex; justify-content: flex-end; flex-wrap: wrap; gap: 8px; margin-top: 20px; }
.drive-import-actions button { min-height: 40px; padding: 8px 14px; }
.drive-import-dialog [hidden] { display: none !important; }
body.drive-picker-open .picker-dialog-bg { z-index: 10000 !important; }
body.drive-picker-open .picker-dialog { z-index: 10001 !important; }
.drive-import-location { display: grid; gap: 12px; margin: 16px 0; }
.drive-import-location[hidden] { display: none; }
.drive-import-location label { display: grid; gap: 6px; font-size: 13px; color: var(--ui-text); }
.drive-import-location .ui-input { width: 100%; min-width: 0; box-sizing: border-box; }
.drive-import-location small { color: var(--ui-text-muted); line-height: 1.5; }
.drive-import-location summary { cursor: pointer; color: var(--ui-text-muted); font-size: 13px; }
.drive-import-location details[open] label { margin: 12px 0 6px; }
.zip-import-dialog { width: min(560px, calc(100vw - 24px)); }
.zip-import-name { font-weight: 600; overflow-wrap: anywhere; margin-bottom: 4px; }
.zip-import-preview { border: 1px solid var(--ui-border, #31505b); background: #ffffff04; border-radius: 10px; padding: 14px; }
.zip-import-preview > p { margin: 0 0 12px; font-size: 13px; line-height: 1.5; color: var(--ui-text-muted, #9aafb7); }
.zip-import-folders { list-style: none; padding: 0; margin: 0; max-height: 240px; overflow: auto; scrollbar-width: thin; scrollbar-color: #355966 transparent; }
.zip-import-folders li { display: flex; gap: 8px; align-items: center; min-height: 34px; }
.zip-import-folders li > span { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.zip-import-folders small { color: var(--ui-text-muted, #9aafb7); white-space: nowrap; }
@media (max-width: 360px) {
    .zip-import-folders li { display: grid; grid-template-columns: 18px minmax(0, 1fr); gap: 2px 8px; padding-bottom: 8px; }
    .zip-import-folders small { grid-column: 2; }
}
@media (max-width: 480px) {
    .zip-import-dialog { padding: 16px; }
    .zip-import-dialog .drive-import-actions { display: grid; grid-template-columns: 1fr 1fr; }
    .zip-import-dialog [data-action="import"] { grid-column: 1 / -1; }
}

/* The assistant's staged build (modules/BuildPreview.js): the ghosts are drawn
   on the canvas; this area over them drags the whole build, and the bar under
   them places or cancels it. */
.build-preview-grip { position: fixed; z-index: calc(var(--z-panel) - 6); cursor: grab; touch-action: none; border-radius: 10px; }
.build-preview-grip.dragging { cursor: grabbing; background: rgba(var(--ui-accent-rgb), 0.06); }
.build-preview-bar { position: fixed; z-index: calc(var(--z-panel) - 5); display: flex; align-items: center; gap: 8px; padding: 6px 6px 6px 12px;
    background: rgba(var(--ui-surface-rgb), 0.96); border: 1px solid var(--ui-border); border-radius: 12px; box-shadow: 0 8px 24px var(--ui-shadow);
    font: 500 12px var(--ui-font); color: var(--ui-text-muted); white-space: nowrap; }
.build-preview-bar .ui-btn { min-height: 32px; }
