{"release_id":"01a1c502-4ad1-0000-ec62-adc54d3c69aa","chunk_id":"01a1c502-4cc0-7e73-874c-4ccd14053944","file":"165.B0Gsj8iU.mjs","mappings":"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","names":["draft","$.state","saving","creating","resetArmed","resetting","deleteArmed","deleting","shareState","shareTimer","$.user_effect","$$props","$.set","deleteSession","t","$.get","next","x","goto","saveDirection","value","res","r","newThread","resetNow","invalidateAll","share","shareOrCopy","fmtWhen","iso","d","div","$.first_child","fragment_1","section","$.sibling","header","$.child","button","div_1","div_2","textarea","$.remove_textarea_child","div_3","button_1","text","$.reset","node_1","button_2","root_2","$.template_effect","div_4","div_5","$.each","node_2","div_6","root_3","a","span","span_1","button_3","classes","$.set_text","text_1","$0","classes_1","$.set_class","$$args","button_4","p","root_4","$$render","consequent_1","div_7","button_5","div_8","button_6","a_1","root_5","button_7","node_4","text_4","fade","$$value","$.delegated","fly","expoOut","nextReset","now","untilLabel","ms","mins","h","m","agoLabel","wallNextRows","rows","PLACEMENT","onMount","trackOfferShown","root_1","ul","row","i","li","f","$$anchor","av","$.derived","getAvatarUrl","img","$.set_attribute","root_6","consequent","alternate","span_2","span_3","text_2","resolve","trackOfferClicked","$.append","root_7","span_4","span_5","span_6","text_3","a_2","root_8","span_7","span_8","fragment_2","consequent_3","alternate_1","consequent_2","consequent_4","consequent_5","backHref","page","requireAuth","pendingMessage","here","hitSignInGate","RESUME_ROLEPLAY_SEND","gateResumed","untrack","consumeResume","trackAuthGateResumed","send","activeMode","isPro","charFirst","photoCapHit","developing","withPhoto","reply","photoStarted","$.proxy","photoNow","PHOTO_STAGES","n","photoStage","id","secs","st","messages","develop","tries","chatId","scrollDown","upgradeHeadline","openPhotoUpgrade","upgradeOpen","segments","raw","body","out","re","last","plain","clean","direction","memories","sending","failed","blocked","capped","scroller","box","menuOpen","memOpen","suggesting","photoInput","uploadingPhoto","ttsId","ttsLoading","ttsAudio","MAX_BOX_PX","autoGrow","appendMessages","incoming","onScreen","fresh","tick","photoLoaded","e","hitWall","surface","trackRoleplayWallHit","trackRoleplayQuotaHit","askWallNext","wallNext","reset","resetTime","until","people","ctrl","timer","j","hitBlocked","trackRoleplayBlocked","ensureChat","trackRoleplayMessageSent","trackRoleplayGenerationFailed","retry","onKey","startingFresh","freshTimeline","suggest","onPhotoPicked","input","file","caption","form","openMenu","openMemories","toggleTts","audio","CTA_COPY","fmtClock","panelInitial","$.head","meta","main","fragment","aside","img_1","h2","p_1","p_2","text_5","img_2","root_9","node_5","root_10","root_11","text_8","consequent_6","consequent_7","root_12","consequent_8","p_3","p_4","root_13","text_10","consequent_9","node_9","fragment_3","p_5","root_16","root_17","b","fragment_5","text_11","consequent_11","alternate_2","root_20","span_9","$.set_style","consequent_12","root_21","span_10","b_1","consequent_13","alternate_3","span_11","root_22","consequent_14","div_9","fragment_6","div_10","node_15","img_3","root_25","$.event","span_12","root_26","consequent_16","alternate_4","span_13","root_27","text_13","consequent_17","node_18","seg","span_14","root_29","classes_2","text_14","text_15","consequent_19","alternate_5","root_31","span_15","span_16","classes_3","consequent_20","a_3","root_32","text_17","consequent_21","consequent_15","alternate_6","div_11","root_33","consequent_22","div_12","root_34","p_6","text_18","consequent_23","alternate_7","RpWallNext","a_4","root_38","d_1","consequent_24","consequent_25","div_13","root_39","div_14","text_20","root_40","consequent_26","consequent_27","consequent_28","div_15","input_1","button_8","button_9","button_10","RpChatMenu","node_25","UpgradeModal","node_26","RpMemoryPanel","node_27","text_6","text_9","classes_4"],"sources":["../../../../../../src/lib/components/roleplay/RpChatMenu.svelte","../../../../../../src/lib/rp-wall-next.ts","../../../../../../src/lib/components/roleplay/RpWallNext.svelte","../../../../../../src/routes/roleplay/c/[slug]/+page.svelte"],"sourcesContent":["<script lang=\"ts\">\n\t/**\n\t * The character chat's control sheet: scene direction, timelines, memory\n\t * entry point, share / edit / reset. One sheet so the header stays a bar\n\t * with a single ⋯ and the thread itself stays a thread.\n\t */\n\timport { goto, invalidateAll } from '$app/navigation';\n\timport { shareOrCopy } from '$lib/share';\n\timport { fade, fly } from 'svelte/transition';\n\timport { expoOut } from 'svelte/easing';\n\n\tinterface Thread {\n\t\tchatId: string;\n\t\tthreadNo: number;\n\t\tmessageCount: number;\n\t\tlastMessageAt: string;\n\t}\n\n\tinterface Props {\n\t\topen: boolean;\n\t\tchatId: string;\n\t\ttemplateId: string;\n\t\tslug: string;\n\t\ttitle: string;\n\t\tthreadNo: number;\n\t\tthreads: Thread[];\n\t\tdirection: string | null;\n\t\tisCreator: boolean;\n\t\tonClose: () => void;\n\t\tonOpenMemories: () => void;\n\t\tonDirection: (d: string | null) => void;\n\t}\n\n\tlet {\n\t\topen,\n\t\tchatId,\n\t\ttemplateId,\n\t\tslug,\n\t\ttitle,\n\t\tthreadNo,\n\t\tthreads,\n\t\tdirection,\n\t\tisCreator,\n\t\tonClose,\n\t\tonOpenMemories,\n\t\tonDirection\n\t}: Props = $props();\n\n\tlet draft = $state('');\n\tlet saving = $state(false);\n\tlet creating = $state(false);\n\tlet resetArmed = $state(false);\n\tlet resetting = $state(false);\n\t// Two-tap delete: the first tap arms the bin, the second commits. A session\n\t// with 200 messages in it is not worth a single mis-tap.\n\tlet deleteArmed = $state<string | null>(null);\n\tlet deleting = $state<string | null>(null);\n\tlet shareState = $state<'idle' | 'shared' | 'copied'>('idle');\n\tlet shareTimer: ReturnType<typeof setTimeout> | undefined;\n\n\t// Re-seed the textarea whenever the sheet opens on a (possibly new) session.\n\t$effect(() => {\n\t\tif (open) {\n\t\t\tdraft = direction ?? '';\n\t\t\tresetArmed = false;\n\t\t\tdeleteArmed = null;\n\t\t}\n\t});\n\n\tasync function deleteSession(t: Thread) {\n\t\tif (deleting) return;\n\t\tif (deleteArmed !== t.chatId) {\n\t\t\tdeleteArmed = t.chatId;\n\t\t\treturn;\n\t\t}\n\t\tdeleting = t.chatId;\n\t\ttry {\n\t\t\tconst res = await fetch(`/api/rp/chat/${t.chatId}`, { method: 'DELETE' });\n\t\t\tif (!res.ok) return;\n\t\t\tdeleteArmed = null;\n\t\t\tonClose();\n\t\t\t// Deleting the session you're reading has to move you somewhere real;\n\t\t\t// the bare character URL resumes the newest survivor, or opens a fresh\n\t\t\t// session when that was the last one.\n\t\t\tconst next = threads.find((x) => x.chatId !== t.chatId && x.threadNo !== t.threadNo);\n\t\t\tawait goto(\n\t\t\t\tt.chatId === chatId || !next ? `/roleplay/c/${slug}` : `/roleplay/c/${slug}?t=${next.threadNo}`,\n\t\t\t\t{ invalidateAll: true }\n\t\t\t);\n\t\t} finally {\n\t\t\tdeleting = null;\n\t\t}\n\t}\n\n\tasync function saveDirection(value: string | null) {\n\t\tsaving = true;\n\t\ttry {\n\t\t\tconst res = await fetch(`/api/rp/chat/${chatId}`, {\n\t\t\t\tmethod: 'PATCH',\n\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\tbody: JSON.stringify({ direction: value })\n\t\t\t});\n\t\t\tconst r = await res.json();\n\t\t\tif (res.ok) {\n\t\t\t\tonDirection(r.direction ?? null);\n\t\t\t\tonClose();\n\t\t\t}\n\t\t} finally {\n\t\t\tsaving = false;\n\t\t}\n\t}\n\n\tasync function newThread() {\n\t\tif (creating) return;\n\t\tcreating = true;\n\t\ttry {\n\t\t\tconst res = await fetch('/api/rp/threads', {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\tbody: JSON.stringify({ templateId })\n\t\t\t});\n\t\t\tconst r = await res.json();\n\t\t\tif (res.ok && r?.threadNo) {\n\t\t\t\tonClose();\n\t\t\t\tawait goto(`/roleplay/c/${slug}?t=${r.threadNo}`, { invalidateAll: true });\n\t\t\t}\n\t\t} finally {\n\t\t\tcreating = false;\n\t\t}\n\t}\n\n\tasync function resetNow() {\n\t\tif (!resetArmed) {\n\t\t\tresetArmed = true;\n\t\t\treturn;\n\t\t}\n\t\tresetting = true;\n\t\ttry {\n\t\t\tconst res = await fetch(`/api/rp/chat/${chatId}/reset`, { method: 'POST' });\n\t\t\tif (res.ok) {\n\t\t\t\tonClose();\n\t\t\t\tawait invalidateAll();\n\t\t\t}\n\t\t} finally {\n\t\t\tresetting = false;\n\t\t\tresetArmed = false;\n\t\t}\n\t}\n\n\tfunction share() {\n\t\t// SYNCHRONOUS inside the tap — iOS drops user activation across awaits.\n\t\tvoid shareOrCopy({\n\t\t\turl: `${window.location.origin}/roleplay/${slug}?utm_source=rp_chat_share`,\n\t\t\ttitle,\n\t\t\ttext: `talk to ${title} on ratemyd`\n\t\t}).then((r) => {\n\t\t\tif (r === 'shared' || r === 'copied') {\n\t\t\t\tshareState = r;\n\t\t\t\tclearTimeout(shareTimer);\n\t\t\t\tshareTimer = setTimeout(() => (shareState = 'idle'), 1800);\n\t\t\t}\n\t\t});\n\t}\n\n\tfunction fmtWhen(iso: string): string {\n\t\tconst d = Date.now() - new Date(iso).getTime();\n\t\tconst h = Math.floor(d / 3_600_000);\n\t\tif (h < 1) return 'now';\n\t\tif (h < 24) return `${h}h`;\n\t\treturn `${Math.floor(h / 24)}d`;\n\t}\n</script>\n\n{#if open}\n\t<div class=\"scrim\" role=\"presentation\" onclick={onClose} transition:fade={{ duration: 150 }}></div>\n\t<section\n\t\tclass=\"sheet\"\n\t\taria-label=\"chat controls\"\n\t\ttransition:fly={{ y: 48, duration: 260, easing: expoOut }}\n\t>\n\t\t<header class=\"head\">\n\t\t\t<h2>this scene</h2>\n\t\t\t<button type=\"button\" class=\"x\" onclick={onClose} aria-label=\"close\">✕</button>\n\t\t</header>\n\n\t\t<div class=\"body\">\n\t\t\t<div class=\"block\">\n\t\t\t\t<span class=\"label\">direction</span>\n\t\t\t\t<p class=\"hint\">\n\t\t\t\t\tsteer where this goes — the character follows it without ever mentioning it.\n\t\t\t\t</p>\n\t\t\t\t<textarea\n\t\t\t\t\tbind:value={draft}\n\t\t\t\t\trows=\"2\"\n\t\t\t\t\tmaxlength=\"500\"\n\t\t\t\t\tplaceholder=\"e.g. slow it down and make them jealous\"\n\t\t\t\t></textarea>\n\t\t\t\t<div class=\"row-btns\">\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"btn btn--fill\"\n\t\t\t\t\t\tdisabled={saving || draft.trim() === (direction ?? '')}\n\t\t\t\t\t\tonclick={() => saveDirection(draft.trim() || null)}\n\t\t\t\t\t>\n\t\t\t\t\t\t{saving ? 'saving…' : 'apply'}\n\t\t\t\t\t</button>\n\t\t\t\t\t{#if direction}\n\t\t\t\t\t\t<button type=\"button\" class=\"btn\" disabled={saving} onclick={() => saveDirection(null)}>\n\t\t\t\t\t\t\tclear\n\t\t\t\t\t\t</button>\n\t\t\t\t\t{/if}\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<div class=\"block\">\n\t\t\t\t<span class=\"label\">sessions</span>\n\t\t\t\t<div class=\"threads\">\n\t\t\t\t\t{#each threads as t (t.chatId)}\n\t\t\t\t\t\t<div class=\"thread-row\" class:thread-row--now={t.threadNo === threadNo}>\n\t\t\t\t\t\t\t<a class=\"thread\" href={`/roleplay/c/${slug}?t=${t.threadNo}`} onclick={onClose}>\n\t\t\t\t\t\t\t\t<span>session {t.threadNo}</span>\n\t\t\t\t\t\t\t\t<span class=\"thread-meta\"\n\t\t\t\t\t\t\t\t\t>{t.messageCount} {t.messageCount === 1 ? 'msg' : 'msgs'} · {fmtWhen(\n\t\t\t\t\t\t\t\t\t\tt.lastMessageAt\n\t\t\t\t\t\t\t\t\t)}</span\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tclass=\"trash\"\n\t\t\t\t\t\t\t\tclass:trash--armed={deleteArmed === t.chatId}\n\t\t\t\t\t\t\t\tdisabled={deleting === t.chatId}\n\t\t\t\t\t\t\t\taria-label={deleteArmed === t.chatId\n\t\t\t\t\t\t\t\t\t? `tap again to delete session ${t.threadNo}`\n\t\t\t\t\t\t\t\t\t: `delete session ${t.threadNo}`}\n\t\t\t\t\t\t\t\tonclick={() => deleteSession(t)}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\">\n\t\t\t\t\t\t\t\t\t<path d=\"M4 7h16\" stroke-linecap=\"round\" />\n\t\t\t\t\t\t\t\t\t<path d=\"M9 7V5.5A1.5 1.5 0 0 1 10.5 4h3A1.5 1.5 0 0 1 15 5.5V7\" />\n\t\t\t\t\t\t\t\t\t<path d=\"M6.5 7l.8 12.1A2 2 0 0 0 9.3 21h5.4a2 2 0 0 0 2-1.9L17.5 7\" />\n\t\t\t\t\t\t\t\t\t<path d=\"M10 11v6M14 11v6\" stroke-linecap=\"round\" />\n\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t{/each}\n\t\t\t\t\t<button type=\"button\" class=\"btn\" disabled={creating} onclick={newThread}>\n\t\t\t\t\t\t{creating ? 'opening…' : '+ new session'}\n\t\t\t\t\t</button>\n\t\t\t\t\t{#if deleteArmed}\n\t\t\t\t\t\t<p class=\"arm-hint\">tap the bin again to delete that session for good.</p>\n\t\t\t\t\t{/if}\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<div class=\"block\">\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"wide\"\n\t\t\t\t\taria-label=\"memories\"\n\t\t\t\t\tonclick={() => { onClose(); onOpenMemories(); }}\n\t\t\t\t>\n\t\t\t\t\t<span class=\"label\">memories</span>\n\t\t\t\t\t<span class=\"chev\" aria-hidden=\"true\">›</span>\n\t\t\t\t</button>\n\t\t\t</div>\n\n\t\t\t<div class=\"block acts\">\n\t\t\t\t<button type=\"button\" class=\"btn\" onclick={share}>\n\t\t\t\t\t{shareState === 'idle' ? 'share character' : shareState === 'shared' ? 'shared ✓' : 'link copied ✓'}\n\t\t\t\t</button>\n\t\t\t\t{#if isCreator}\n\t\t\t\t\t<a class=\"btn\" href={`/roleplay/${slug}/edit`}>edit character</a>\n\t\t\t\t{/if}\n\t\t\t\t<button type=\"button\" class=\"btn btn--danger\" disabled={resetting} onclick={resetNow}>\n\t\t\t\t\t{resetting ? 'resetting…' : resetArmed ? 'tap again to wipe this session' : 'wipe this session'}\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t</div>\n\t</section>\n{/if}\n\n<style>\n\t.scrim {\n\t\tposition: fixed;\n\t\tinset: 0;\n\t\tbackground: rgb(0 0 0 / 55%);\n\t\tz-index: 60;\n\t}\n\t.sheet {\n\t\tposition: fixed;\n\t\t/* Centered without transform — the fly transition animates the transform\n\t\t   property, so a CSS translateX(-50%) here would be overwritten mid-flight. */\n\t\tleft: 0;\n\t\tright: 0;\n\t\tmargin-inline: auto;\n\t\tbottom: 0;\n\t\twidth: 100%;\n\t\tmax-width: 640px;\n\t\tmax-height: 82dvh;\n\t\toverflow-y: auto;\n\t\tbackground: var(--color-bg);\n\t\tborder: 1px solid var(--color-border);\n\t\tborder-bottom: none;\n\t\tborder-radius: 18px 18px 0 0;\n\t\tz-index: 61;\n\t\tpadding-bottom: env(safe-area-inset-bottom, 0px);\n\t}\n\t.head {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: space-between;\n\t\tpadding: 14px 16px 8px;\n\t}\n\t.head h2 {\n\t\tfont-family: var(--font-display);\n\t\tfont-size: 15px;\n\t\tfont-weight: 700;\n\t\tcolor: var(--color-text);\n\t}\n\t.x {\n\t\tbackground: none;\n\t\tborder: none;\n\t\tcolor: var(--color-muted);\n\t\tfont-size: 15px;\n\t\tcursor: pointer;\n\t\tpadding: 4px 6px;\n\t}\n\t.body {\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tgap: 18px;\n\t\tpadding: 4px 16px 18px;\n\t}\n\t.block {\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tgap: 8px;\n\t}\n\t.label {\n\t\tfont-size: 12px;\n\t\tfont-weight: 700;\n\t\tletter-spacing: 0.05em;\n\t\ttext-transform: uppercase;\n\t\tcolor: var(--color-muted);\n\t}\n\t.hint {\n\t\tfont-size: 12px;\n\t\tline-height: 1.45;\n\t\tcolor: var(--color-muted);\n\t}\n\ttextarea {\n\t\twidth: 100%;\n\t\tresize: none;\n\t\tpadding: 10px 13px;\n\t\tborder-radius: 12px;\n\t\tborder: 1px solid var(--color-border);\n\t\tbackground: var(--color-surface);\n\t\tcolor: var(--color-text);\n\t\tfont-family: inherit;\n\t\tfont-size: 14px;\n\t\tline-height: 1.5;\n\t}\n\ttextarea:focus {\n\t\toutline: none;\n\t\tborder-color: var(--color-accent);\n\t}\n\t.row-btns {\n\t\tdisplay: flex;\n\t\tgap: 8px;\n\t}\n\t.btn {\n\t\tpadding: 9px 14px;\n\t\tborder-radius: 12px;\n\t\tborder: 1px solid var(--color-border);\n\t\tbackground: var(--color-surface);\n\t\tcolor: var(--color-text);\n\t\tfont-size: 13px;\n\t\tfont-weight: 600;\n\t\tcursor: pointer;\n\t\ttext-decoration: none;\n\t\ttext-align: center;\n\t}\n\t.btn--fill {\n\t\tbackground: var(--color-accent);\n\t\tborder-color: transparent;\n\t\tcolor: #fff;\n\t}\n\t.btn--danger {\n\t\tcolor: var(--color-coral);\n\t}\n\t.btn:disabled {\n\t\topacity: 0.5;\n\t\tcursor: default;\n\t}\n\t.threads {\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tgap: 6px;\n\t}\n\t.thread-row {\n\t\tdisplay: flex;\n\t\talign-items: stretch;\n\t\tgap: 6px;\n\t\tborder-radius: 12px;\n\t\tborder: 1px solid var(--color-border);\n\t\tbackground: var(--color-surface);\n\t\toverflow: hidden;\n\t}\n\t.thread-row--now {\n\t\tborder-color: var(--color-accent);\n\t}\n\t.thread {\n\t\tflex: 1;\n\t\tmin-width: 0;\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: space-between;\n\t\tgap: 8px;\n\t\tpadding: 10px 4px 10px 13px;\n\t\tcolor: var(--color-text);\n\t\tfont-size: 13px;\n\t\tfont-weight: 600;\n\t\ttext-decoration: none;\n\t}\n\t.trash {\n\t\twidth: 42px;\n\t\tflex-shrink: 0;\n\t\tdisplay: inline-flex;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tbackground: none;\n\t\tborder: none;\n\t\tborder-left: 1px solid var(--color-border);\n\t\tcolor: var(--color-muted);\n\t\tcursor: pointer;\n\t\tpadding: 0;\n\t}\n\t.trash svg { width: 17px; height: 17px; }\n\t.trash:hover:not(:disabled) { color: var(--color-coral); }\n\t.trash--armed {\n\t\tcolor: #fff;\n\t\tbackground: var(--color-coral);\n\t\tborder-left-color: transparent;\n\t}\n\t.trash:disabled { opacity: 0.5; cursor: default; }\n\t.arm-hint {\n\t\tfont-size: 11px;\n\t\tcolor: var(--color-coral);\n\t\tpadding: 2px 2px 0;\n\t}\n\t.thread-meta {\n\t\tfont-size: 11px;\n\t\tfont-weight: 400;\n\t\tcolor: var(--color-muted);\n\t}\n\t.wide {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: space-between;\n\t\twidth: 100%;\n\t\tpadding: 12px 13px;\n\t\tborder-radius: 12px;\n\t\tborder: 1px solid var(--color-border);\n\t\tbackground: var(--color-surface);\n\t\tcursor: pointer;\n\t}\n\t.chev {\n\t\tcolor: var(--color-muted);\n\t\tfont-size: 16px;\n\t}\n\t.acts {\n\t\tgap: 8px;\n\t}\n</style>\n","/** The roleplay daily wall's way out (RAT-538). Client-safe.\n *\n *  98% of the people who reach the wall don't buy there, and its only other\n *  link — \"browse other characters\" — leads to characters that are walled too.\n *  So the wall says when the free messages come back, and until then points at\n *  real people, who don't run out: the raters online now, the viewer's own\n *  room if it's awake, and their messages. The unlimited offer stays first and\n *  unchanged. */\n\n/** When the free allowance comes back: the next UTC midnight, which is when\n *  the roleplay quota's day turns over (quota.ts). */\nexport function nextReset(now = Date.now()): Date {\n\tconst d = new Date(now);\n\treturn new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() + 1));\n}\n\n/** \"3 h 12 min\", \"45 min\", \"under a minute\". */\nexport function untilLabel(ms: number): string {\n\tconst mins = Math.floor(ms / 60_000);\n\tif (mins < 1) return 'under a minute';\n\tconst h = Math.floor(mins / 60);\n\tconst m = mins % 60;\n\tif (h === 0) return `${m} min`;\n\treturn m === 0 ? `${h} h` : `${h} h ${m} min`;\n}\n\n/** \"4 min ago\", \"2 h ago\" — for the room row. */\nexport function agoLabel(iso: string, now = Date.now()): string {\n\tconst mins = Math.max(0, Math.floor((now - new Date(iso).getTime()) / 60_000));\n\tif (mins < 1) return 'just now';\n\tif (mins < 60) return `${mins} min ago`;\n\treturn `${Math.floor(mins / 60)} h ago`;\n}\n\nexport interface WallNextPeople {\n\t/** Raters taking pics right now, and up to three of their faces. */\n\traters: { count: number; faces: { nickname: string; avatarPath: string | null }[] };\n\t/** The viewer's most recent room with a message in the last day. */\n\troom: { name: string; code: string; lastMessageAt: string } | null;\n\t/** Unread direct messages, and whether they have any conversation at all. */\n\tunread: number;\n\thasChats: boolean;\n}\n\n/** The rows the wall offers, in order, leaving out what has nothing behind it:\n *  a room nobody spoke in today, an empty inbox. */\nexport function wallNextRows(p: WallNextPeople): Array<'raters' | 'room' | 'inbox'> {\n\tconst rows: Array<'raters' | 'room' | 'inbox'> = [];\n\tif (p.raters.count > 0) rows.push('raters');\n\tif (p.room) rows.push('room');\n\tif (p.unread > 0 || p.hasChats) rows.push('inbox');\n\treturn rows;\n}\n","<script lang=\"ts\">\n\t/**\n\t * RAT-538: under the roleplay wall's unlimited offer, the way out that isn't\n\t * another walled character — real people, until the free messages come back.\n\t * At most three rows, each only when there's someone behind it: the raters\n\t * online now, the viewer's own room if anyone spoke in it today, and their\n\t * messages. One grouped list, one line each, one verb each.\n\t */\n\timport { onMount } from 'svelte';\n\timport { resolve } from '$app/paths';\n\timport { getAvatarUrl } from '$lib/avatar';\n\timport { trackOfferClicked, trackOfferShown } from '$lib/analytics';\n\timport { agoLabel, wallNextRows, type WallNextPeople } from '$lib/rp-wall-next';\n\n\tinterface Props {\n\t\tpeople: WallNextPeople;\n\t\t/** The reset, already formatted for the viewer: \"02:00\". */\n\t\tresetTime: string;\n\t}\n\tlet { people, resetTime }: Props = $props();\n\n\tconst PLACEMENT = 'rp_wall_next';\n\tconst rows = $derived(wallNextRows(people));\n\n\tonMount(() => {\n\t\ttrackOfferShown(PLACEMENT, rows);\n\t});\n</script>\n\n{#if rows.length > 0}\n\t<div class=\"until\">\n\t\t<p class=\"until-label\">until {resetTime} — real people</p>\n\t\t<ul class=\"list\">\n\t\t\t{#each rows as row, i (row)}\n\t\t\t\t<li>\n\t\t\t\t\t{#if row === 'raters'}\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\tclass=\"item\"\n\t\t\t\t\t\t\thref={resolve('/r')}\n\t\t\t\t\t\t\tonclick={() => trackOfferClicked(PLACEMENT, row, i)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<span class=\"lead lead--faces\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t{#each people.raters.faces as f (f.nickname)}\n\t\t\t\t\t\t\t\t\t{@const av = getAvatarUrl(f.avatarPath)}\n\t\t\t\t\t\t\t\t\t{#if av}\n\t\t\t\t\t\t\t\t\t\t<img class=\"face\" src={av} alt=\"\" />\n\t\t\t\t\t\t\t\t\t{:else}\n\t\t\t\t\t\t\t\t\t\t<span class=\"face face--fallback\">{f.nickname[0]?.toUpperCase()}</span>\n\t\t\t\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t\t\t{/each}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span class=\"text\">\n\t\t\t\t\t\t\t\t<span class=\"title\"\n\t\t\t\t\t\t\t\t\t>{people.raters.count}\n\t\t\t\t\t\t\t\t\t{people.raters.count === 1 ? 'rater' : 'raters'} online now</span\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<span class=\"sub\">send a pic, get rated</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span class=\"chev\" aria-hidden=\"true\">›</span>\n\t\t\t\t\t\t</a>\n\t\t\t\t\t{:else if row === 'room' && people.room}\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\tclass=\"item\"\n\t\t\t\t\t\t\thref={resolve('/(chat)/g/[code]', { code: encodeURIComponent(people.room.code) })}\n\t\t\t\t\t\t\tonclick={() => trackOfferClicked(PLACEMENT, row, i)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<span class=\"lead lead--icon\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n\t\t\t\t\t\t\t\t\t<circle cx=\"9\" cy=\"8\" r=\"3.2\" />\n\t\t\t\t\t\t\t\t\t<path d=\"M3 19c.6-3.2 3-5 6-5s5.4 1.8 6 5\" />\n\t\t\t\t\t\t\t\t\t<circle cx=\"17\" cy=\"9\" r=\"2.6\" />\n\t\t\t\t\t\t\t\t\t<path d=\"M15.5 14.2c2.6-.3 4.9 1.3 5.5 4.3\" />\n\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span class=\"text\">\n\t\t\t\t\t\t\t\t<span class=\"title\">{people.room.name}</span>\n\t\t\t\t\t\t\t\t<span class=\"sub\">active {agoLabel(people.room.lastMessageAt)}</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span class=\"chev\" aria-hidden=\"true\">›</span>\n\t\t\t\t\t\t</a>\n\t\t\t\t\t{:else if row === 'inbox'}\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\tclass=\"item\"\n\t\t\t\t\t\t\thref={resolve('/(chat)/c')}\n\t\t\t\t\t\t\tonclick={() => trackOfferClicked(PLACEMENT, row, i)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<span class=\"lead lead--icon\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n\t\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\t\td=\"M20 12a8 8 0 0 1-11.6 7.1L4 20l1-4.2A8 8 0 1 1 20 12Z\"\n\t\t\t\t\t\t\t\t\t\tstroke-linejoin=\"round\"\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span class=\"text\">\n\t\t\t\t\t\t\t\t{#if people.unread > 0}\n\t\t\t\t\t\t\t\t\t<span class=\"title\">{people.unread} unread</span>\n\t\t\t\t\t\t\t\t\t<span class=\"sub\">in your messages</span>\n\t\t\t\t\t\t\t\t{:else}\n\t\t\t\t\t\t\t\t\t<span class=\"title\">your messages</span>\n\t\t\t\t\t\t\t\t\t<span class=\"sub\">pick a conversation back up</span>\n\t\t\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span class=\"chev\" aria-hidden=\"true\">›</span>\n\t\t\t\t\t\t</a>\n\t\t\t\t\t{/if}\n\t\t\t\t</li>\n\t\t\t{/each}\n\t\t</ul>\n\t</div>\n{/if}\n\n<style>\n\t.until {\n\t\tmargin-top: 6px;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tgap: 8px;\n\t}\n\t.until-label {\n\t\tfont-family: var(--font-display);\n\t\tfont-size: 11px;\n\t\tfont-weight: 700;\n\t\tletter-spacing: 0.1em;\n\t\ttext-transform: uppercase;\n\t\tcolor: var(--color-muted);\n\t}\n\t/* One grouped list with hairlines between rows — a single surface reads as\n\t   one choice set, where three bordered cards read as three more offers. */\n\t.list {\n\t\tlist-style: none;\n\t\tmargin: 0;\n\t\tpadding: 0;\n\t\tborder-radius: 12px;\n\t\tbackground: var(--color-surface);\n\t\toverflow: hidden;\n\t}\n\t.list li + li .item {\n\t\tborder-top: 1px solid var(--color-border);\n\t}\n\t.item {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tgap: 12px;\n\t\tmin-height: 56px;\n\t\tpadding: 10px 12px;\n\t\ttext-decoration: none;\n\t\tcolor: var(--color-text);\n\t\t-webkit-tap-highlight-color: transparent;\n\t\ttransition: background-color 0.12s ease-out;\n\t}\n\t.item:active {\n\t\tbackground: color-mix(in srgb, var(--color-text) 6%, transparent);\n\t}\n\t@media (hover: hover) {\n\t\t.item:hover {\n\t\t\tbackground: color-mix(in srgb, var(--color-text) 4%, transparent);\n\t\t}\n\t}\n\t/* One lead width for every row, so the titles line up whether the row\n\t   leads with three faces or one icon. */\n\t.lead {\n\t\tflex: 0 0 52px;\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t}\n\t.lead--icon {\n\t\tflex-basis: 36px;\n\t\tmargin: 0 8px;\n\t\twidth: 36px;\n\t\theight: 36px;\n\t\tborder-radius: 50%;\n\t\tbackground: color-mix(in srgb, var(--color-text) 8%, transparent);\n\t\tcolor: var(--color-text);\n\t}\n\t.lead--icon svg {\n\t\twidth: 19px;\n\t\theight: 19px;\n\t}\n\t.face {\n\t\twidth: 26px;\n\t\theight: 26px;\n\t\tborder-radius: 50%;\n\t\tobject-fit: cover;\n\t\tborder: 2px solid var(--color-surface);\n\t\tbackground: var(--color-bg);\n\t}\n\t.face + .face {\n\t\tmargin-left: -13px;\n\t}\n\t.face--fallback {\n\t\tdisplay: inline-flex;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tfont-size: 11px;\n\t\tfont-weight: 800;\n\t\tcolor: #fff;\n\t\tbackground: linear-gradient(135deg, #06b6d4, #0e7490);\n\t}\n\t.text {\n\t\tflex: 1;\n\t\tmin-width: 0;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tgap: 1px;\n\t}\n\t.title {\n\t\tfont-size: 14px;\n\t\tfont-weight: 600;\n\t\toverflow: hidden;\n\t\ttext-overflow: ellipsis;\n\t\twhite-space: nowrap;\n\t}\n\t.sub {\n\t\tfont-size: 12px;\n\t\tcolor: var(--color-muted);\n\t\toverflow: hidden;\n\t\ttext-overflow: ellipsis;\n\t\twhite-space: nowrap;\n\t}\n\t.chev {\n\t\tflex: 0 0 auto;\n\t\tfont-size: 20px;\n\t\tline-height: 1;\n\t\tcolor: var(--color-muted);\n\t}\n</style>\n","<script lang=\"ts\">\n\t/**\n\t * RAT-463: the character chat.\n\t *\n\t * Deliberately plain. The old roleplay screen carried a turn banner, a role\n\t * roster, stat bars, an action-type switcher and a score card — scaffolding\n\t * for a game 91% of sessions never played. What's left is a thread and a box.\n\t *\n\t * Character pack (2026-07): the box grew hands — a control sheet (direction,\n\t * timelines, memories, share/edit/reset), photo messages the character\n\t * actually sees, a suggest button that drafts into the composer, and voice\n\t * playback on character messages. All of it hangs off the one thread.\n\t */\n\timport { onMount, tick, untrack } from 'svelte';\n\timport { page } from '$app/state';\n\timport { goto } from '$app/navigation';\n\timport { activeMode } from '$lib/stores/activeMode';\n\timport {\n\t\ttrackAuthGateResumed,\n\t\ttrackRoleplayMessageSent,\n\t\ttrackRoleplayGenerationFailed,\n\t\ttrackRoleplayBlocked,\n\t\ttrackRoleplayQuotaHit,\n\t\ttrackRoleplayWallHit\n\t} from '$lib/analytics';\n\timport { hitSignInGate, RESUME_ROLEPLAY_SEND } from '$lib/contextual-sign-in';\n\timport { consumeResume } from '$lib/oauth-resume';\n\timport RpChatMenu from '$lib/components/roleplay/RpChatMenu.svelte';\n\timport RpMemoryPanel from '$lib/components/roleplay/RpMemoryPanel.svelte';\n\timport UpgradeModal from '$lib/components/UpgradeModal.svelte';\n\timport RpWallNext from '$lib/components/roleplay/RpWallNext.svelte';\n\timport { nextReset, untilLabel, wallNextRows, type WallNextPeople } from '$lib/rp-wall-next';\n\n\tlet { data } = $props();\n\n\t// Back goes where you came from. The inbox tags its links `?from=chat`,\n\t// because a character chat opened from messages that dumps you on the\n\t// marketplace has lost you the thread you were reading.\n\tconst backHref = $derived(page.url.searchParams.get('from') === 'chat' ? '/c' : '/roleplay');\n\n\t// RAT-521: logged-out visitors get the read-only preview (portrait, scene,\n\t// opener) — the gate sits on the first real action, not on page load. Every\n\t// send/suggest/photo/controls path funnels through here, sending them to\n\t// sign-in with a return path back to this exact scene rather than the old\n\t// load-time wall that dropped 61% of them out of roleplay entirely.\n\t//\n\t// RAT-519: in the contextual-sign-in arm the sheet opens here instead, and a\n\t// message they typed is kept and sent the moment they're in.\n\tfunction requireAuth(pendingMessage?: string): boolean {\n\t\tif (!data.anon) return false;\n\t\tconst here = page.url.pathname + page.url.search;\n\t\thitSignInGate({\n\t\t\tsurface: pendingMessage ? 'roleplay_send' : 'roleplay_action',\n\t\t\tnext: here,\n\t\t\tcontext: {\n\t\t\t\ttitle: `keep chatting with ${data.character.title}`,\n\t\t\t\tdetail: pendingMessage\n\t\t\t\t\t? \"your message is saved — it sends the moment you're in.\"\n\t\t\t\t\t: \"free to start — you'll be right back in this scene.\",\n\t\t\t\timageUrl: data.character.imageUrl\n\t\t\t},\n\t\t\tresume: pendingMessage\n\t\t\t\t? {\n\t\t\t\t\t\tkey: RESUME_ROLEPLAY_SEND,\n\t\t\t\t\t\tpayload: { slug: data.character.slug, draft: pendingMessage }\n\t\t\t\t\t}\n\t\t\t\t: undefined\n\t\t});\n\t\treturn true;\n\t}\n\n\t// RAT-519: back from signing in at the send gate — put their words back and\n\t// send them. Once per page; an entry for another character is dropped.\n\tlet gateResumed = false;\n\t$effect(() => {\n\t\tif (data.anon || gateResumed) return;\n\t\tgateResumed = true;\n\t\tuntrack(() => {\n\t\t\tconst r = consumeResume<{ slug?: string; draft?: string }>(RESUME_ROLEPLAY_SEND);\n\t\t\tif (!r?.draft || r.slug !== data.character.slug) return;\n\t\t\tdraft = r.draft;\n\t\t\ttrackAuthGateResumed({ surface: 'roleplay_send' });\n\t\t\tvoid send();\n\t\t});\n\t});\n\n\t// main carries `padding-bottom: 68px + safe-area` globally to clear the mobile\n\t// bottom nav (layout.css). The nav is hidden on this route, so that clearance\n\t// is dead space under the composer — body.fit-screen zeroes it, which is the\n\t// same escape hatch /rate, /b/start and the auth pages use.\n\tonMount(() => {\n\t\tdocument.body.classList.add('fit-screen');\n\t\t// Module chrome: without this the page (and every sheet it opens) styles\n\t\t// itself with the coral default accent instead of roleplay purple.\n\t\tactiveMode.set('roleplay');\n\t\treturn () => {\n\t\t\tdocument.body.classList.remove('fit-screen');\n\t\t\tactiveMode.set(null);\n\t\t};\n\t});\n\n\ttype Msg = {\n\t\tid: string;\n\t\trole: 'user' | 'character';\n\t\tbody: string;\n\t\tkind: string;\n\t\tmediaUrl?: string | null;\n\t\tcta?: string | null;\n\t\tcreatedAt: string;\n\t\tphoto?: Msg;\n\t};\n\n\t// ── photos the character sends ──\n\t// photo_locked / photo_developing → subscribers' client asks the server to\n\t// develop it; free users see a locked tile that opens the Pro sheet.\n\tconst isPro = $derived(!!page.data.isSubscribed);\n\tconst charFirst = $derived((data.character.title ?? '').split(/[\\s,:–—-]+/)[0] || 'they');\n\tlet photoCapHit = $state(false);\n\t// plain Set on purpose: it only remembers which photos were already asked\n\t// for; making it reactive would re-run the effect that fills it\n\tconst developing = new Set<string>();\n\tconst withPhoto = (reply: Msg): Msg[] => (reply.photo ? [reply, reply.photo] : [reply]);\n\t// a photo takes ~15–25s: the tile walks through what is happening so the\n\t// wait reads as progress, not a hang (rewrite → generate → check → upload)\n\tconst photoStarted: Record<string, number> = $state({});\n\tlet photoNow = $state(Date.now());\n\tconst PHOTO_STAGES = [\n\t\t{ at: 0, label: (n: string) => `${n} is getting ready…` },\n\t\t{ at: 4, label: (n: string) => `${n} is taking the photo…` },\n\t\t{ at: 14, label: () => 'sending it over…' },\n\t\t{ at: 24, label: () => 'almost there…' }\n\t];\n\tfunction photoStage(id: string) {\n\t\tconst secs = (photoNow - (photoStarted[id] ?? photoNow)) / 1000;\n\t\tconst stage = [...PHOTO_STAGES].reverse().find((st) => secs >= st.at) ?? PHOTO_STAGES[0];\n\t\t// eases toward 92% over ~30s; the image itself finishes it\n\t\treturn { label: stage.label(charFirst), pct: Math.round(92 * (1 - Math.exp(-secs / 11))) };\n\t}\n\t$effect(() => {\n\t\tif (!messages.some((m) => m.kind === 'photo_locked' || m.kind === 'photo_developing') || !isPro) return;\n\t\tconst t = setInterval(() => (photoNow = Date.now()), 500);\n\t\treturn () => clearInterval(t);\n\t});\n\tasync function develop(id: string, tries = 0): Promise<void> {\n\t\tif (!chatId) return;\n\t\tconst res = await fetch(`/api/rp/chat/${chatId}/photos/${id}`, { method: 'POST' }).catch(() => null);\n\t\tconst r = res?.ok ? await res.json().catch(() => null) : null;\n\t\tif (!r) return;\n\t\tif (r.state === 'image') {\n\t\t\tmessages = messages.map((m) => (m.id === id ? { ...m, kind: 'image', mediaUrl: r.mediaUrl } : m));\n\t\t\tvoid scrollDown();\n\t\t} else if (r.state === 'failed') {\n\t\t\tmessages = messages.map((m) => (m.id === id ? { ...m, kind: 'photo_failed' } : m));\n\t\t} else if (r.state === 'developing' && tries < 12) {\n\t\t\t// another tab or request has it: look again in a bit\n\t\t\tsetTimeout(() => void develop(id, tries + 1), 3000);\n\t\t} else if (r.state === 'locked' && r.reason === 'daily_cap') {\n\t\t\tphotoCapHit = true;\n\t\t}\n\t}\n\t$effect(() => {\n\t\tif (!isPro || !chatId) return;\n\t\tfor (const m of messages) {\n\t\t\tif ((m.kind === 'photo_locked' || m.kind === 'photo_developing') && !developing.has(m.id)) {\n\t\t\t\tdeveloping.add(m.id);\n\t\t\t\tphotoStarted[m.id] = Date.now();\n\t\t\t\tvoid develop(m.id);\n\t\t\t}\n\t\t}\n\t});\n\tlet upgradeHeadline = $state('unlimited messages with every character');\n\tfunction openPhotoUpgrade() {\n\t\tupgradeHeadline = `see what ${charFirst} sent — every photo, unlimited messages`;\n\t\tupgradeOpen = true;\n\t}\n\n\t/** Split a reply into spoken words and *narration*.\n\t *\n\t *  The character marks anything it does rather than says between asterisks, so\n\t *  the two read as different registers instead of one undifferentiated wall.\n\t *  Segments rather than {@html}: the body is model output rendered into a page\n\t *  the author also controls, and neither gets to inject markup here. */\n\tfunction segments(raw: string): { text: string; narration: boolean }[] {\n\t\t// a blank line between the action and the words read as a hole in the bubble\n\t\tconst body = raw.replace(/\\n{2,}/g, '\\n').trim();\n\t\tconst out: { text: string; narration: boolean }[] = [];\n\t\t// **double** is markdown emphasis, not narration — matched first so a single\n\t\t// pass can't tear it in half and swallow the real narration after it. Its\n\t\t// markers are dropped and the words stay plain.\n\t\tconst re = /\\*\\*([^*\\n]+)\\*\\*|\\*([^*\\n]+)\\*/g;\n\t\tlet last = 0;\n\t\tlet m: RegExpExecArray | null;\n\t\t// Any asterisk that reaches a PLAIN piece is a stray marker — real narration\n\t\t// is already captured above. Drop the markers so an unbalanced one, or the\n\t\t// model's asterisk-wall degeneration (a run of bare *****), collapses to\n\t\t// nothing instead of falling through and rendering as a literal wall of\n\t\t// asterisks. Server-side guarding now stops storing those, but old rows and\n\t\t// odd-count strays still reach here.\n\t\tconst plain = (text: string) => {\n\t\t\tconst clean = text.replace(/\\*+/g, '');\n\t\t\tif (clean.trim()) out.push({ text: clean, narration: false });\n\t\t};\n\t\twhile ((m = re.exec(body))) {\n\t\t\tif (m.index > last) plain(body.slice(last, m.index));\n\t\t\tout.push({ text: m[1] ?? m[2], narration: m[1] === undefined });\n\t\t\tlast = m.index + m[0].length;\n\t\t}\n\t\tif (last < body.length) plain(body.slice(last));\n\t\treturn out;\n\t}\n\n\t// Seeded from `data` inside an $effect (the RAT-117 pattern): timeline\n\t// switches and resets re-run the load, and the locals must follow. Replies\n\t// arrive over fetch and are appended locally — they don't touch `data`, so\n\t// the effect leaves them alone until the next real navigation.\n\tlet messages = $state<Msg[]>([]);\n\tlet direction = $state<string | null>(null);\n\tlet memories = $state<{ id: string; kind: string; body: string; locked: boolean }[]>([]);\n\t// Lazy sessions: null until a thread exists. Local rather than read straight\n\t// off `data` because the first send adopts the id the API returns without a\n\t// navigation — but it still has to be SEEDED from `data`, or a returning\n\t// user's live thread renders with the ⋯, memories and voice all hidden, and\n\t// their first send goes to the create-and-resume endpoint instead of this\n\t// timeline (RAT-117 all over again; shipped that way 2026-08-26 → 08-28).\n\t// Seeded at the declaration as well as in the effect so the server-rendered\n\t// HTML already carries the controls instead of popping them in on hydration.\n\tlet chatId = $state<string | null>(data.chatId ?? null);\n\t$effect(() => {\n\t\tmessages = [...(data.messages ?? [])];\n\t\tdirection = data.direction ?? null;\n\t\t// Defensive: a deploy-skew load (old server payload, new client bundle)\n\t\t// arrives without these and the spread threw, taking the whole page\n\t\t// down — caught live in error tracking.\n\t\tmemories = [...(data.memories ?? [])];\n\t\t// Same resync contract as `messages` above: only a real server load\n\t\t// replaces `data`, so the id the first send adopted in between survives,\n\t\t// and a timeline switch (?t=) or a session delete swaps in the right one.\n\t\tchatId = data.chatId ?? null;\n\t});\n\n\tlet draft = $state('');\n\tlet sending = $state(false);\n\tlet failed = $state<string | null>(null);\n\t// Provider refused this character on content policy. Permanent for the\n\t// template, so this replaces the retry affordance instead of joining it.\n\tlet blocked = $state(false);\n\t// Free daily allowance spent. Not an error — an offer.\n\tlet capped = $state(false);\n\tlet upgradeOpen = $state(false);\n\tlet scroller = $state<HTMLDivElement | null>(null);\n\tlet box = $state<HTMLTextAreaElement | null>(null);\n\tlet menuOpen = $state(false);\n\tlet memOpen = $state(false);\n\tlet suggesting = $state(false);\n\tlet photoInput = $state<HTMLInputElement | null>(null);\n\tlet uploadingPhoto = $state(false);\n\n\t// One voice at a time. `ttsId` is the message playing (or loading).\n\tlet ttsId = $state<string | null>(null);\n\tlet ttsLoading = $state(false);\n\tlet ttsAudio: HTMLAudioElement | null = null;\n\n\t/** Three lines of the composer's line-height — the cap autoGrow() grows to. */\n\tconst MAX_BOX_PX = 78;\n\n\t/** Grow the composer with the text, one line up to three, then scroll inside.\n\t *  Height has to be reset before measuring or scrollHeight only ever climbs. */\n\tfunction autoGrow() {\n\t\tif (!box) return;\n\t\tbox.style.height = 'auto';\n\t\tbox.style.height = `${Math.min(box.scrollHeight, MAX_BOX_PX)}px`;\n\t}\n\n\t/** Append to the thread without repeating an id already on it. The list is\n\t *  keyed by id, and a reply can arrive twice — a reload of the page's data\n\t *  while the reply was in flight brings the stored copy in first — which threw\n\t *  each_key_duplicate and took the whole chat down (the roleplay chat's most\n\t *  common crash). Same seen-set guard as the #1022 / #1052 fixes. */\n\tfunction appendMessages(...incoming: Msg[]) {\n\t\tconst onScreen = new Set(messages.map((m) => m.id));\n\t\tconst fresh = incoming.filter(\n\t\t\t(m, i) => !onScreen.has(m.id) && incoming.findIndex((x) => x.id === m.id) === i\n\t\t);\n\t\tif (fresh.length) messages = [...messages, ...fresh];\n\t}\n\n\tasync function scrollDown() {\n\t\tawait tick();\n\t\tscroller?.scrollTo({ top: scroller.scrollHeight, behavior: 'smooth' });\n\t}\n\t/** A photo grows the thread when it finishes loading, after the scroll that\n\t *  followed it already ran. If the reader was at the bottom before it grew,\n\t *  keep them there so the newest thing is what they see. */\n\tfunction photoLoaded(e: Event) {\n\t\tconst img = e.currentTarget as HTMLImageElement;\n\t\tif (!scroller) return;\n\t\tconst fromBottom = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight;\n\t\tif (fromBottom <= img.offsetHeight + 160) void scrollDown();\n\t}\n\n\t// Show the wall / blocked state and fire its telemetry exactly once on the\n\t// first-hit edge — the roleplay flow was otherwise fully uninstrumented, so\n\t// neither event ever reached analytics. `surface` records which action met it.\n\tfunction hitWall(surface: string) {\n\t\tif (!capped) {\n\t\t\ttrackRoleplayWallHit(surface);\n\t\t\ttrackRoleplayQuotaHit(chatId);\n\t\t\tvoid askWallNext();\n\t\t}\n\t\tcapped = true;\n\t}\n\n\t// RAT-538: who is around to talk to until the reset, asked the moment the\n\t// wall is hit. Until the answer (at most 2.5s) the secondary line stays\n\t// empty; no answer, or nobody around, falls back to \"browse other characters\".\n\tlet wallNext = $state<{\n\t\tasked: boolean;\n\t\tpeople: WallNextPeople | null;\n\t\tresetTime: string;\n\t\tuntil: string;\n\t}>({ asked: false, people: null, resetTime: '', until: '' });\n\tasync function askWallNext() {\n\t\tconst reset = nextReset();\n\t\tconst resetTime = new Intl.DateTimeFormat(undefined, {\n\t\t\thour: '2-digit',\n\t\t\tminute: '2-digit'\n\t\t}).format(reset);\n\t\tconst until = untilLabel(reset.getTime() - Date.now());\n\t\tlet people: WallNextPeople | null = null;\n\t\tconst ctrl = new AbortController();\n\t\tconst timer = setTimeout(() => ctrl.abort(), 2500);\n\t\ttry {\n\t\t\tconst res = await fetch('/api/roleplay/wall-next', { signal: ctrl.signal });\n\t\t\tconst j = res.ok ? ((await res.json()) as { people?: WallNextPeople | null }) : null;\n\t\t\tif (j?.people) people = j.people;\n\t\t} catch {\n\t\t\t// no answer: the old link\n\t\t} finally {\n\t\t\tclearTimeout(timer);\n\t\t}\n\t\twallNext = { asked: true, people, resetTime, until };\n\t\t// The rows land after the send's own scroll; bring them into view.\n\t\tvoid scrollDown();\n\t}\n\tfunction hitBlocked(surface: string) {\n\t\tif (!blocked) trackRoleplayBlocked(chatId, surface);\n\t\tblocked = true;\n\t}\n\n\t/** Lazy sessions: make sure the thread exists before a tool call that needs\n\t *  a chat id (photo, suggest). The send path creates inline instead — one\n\t *  round trip for the first message. */\n\tasync function ensureChat(): Promise<string | null> {\n\t\tif (chatId) return chatId;\n\t\ttry {\n\t\t\tconst res = await fetch('/api/rp/chat', {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\tbody: JSON.stringify({ templateId: data.character.templateId })\n\t\t\t});\n\t\t\tconst r = await res.json();\n\t\t\tif (res.ok && r?.chatId) {\n\t\t\t\tchatId = r.chatId;\n\t\t\t\treturn chatId;\n\t\t\t}\n\t\t} catch {\n\t\t\t/* caller shows its own failure state */\n\t\t}\n\t\treturn null;\n\t}\n\n\tasync function send() {\n\t\tconst body = draft.trim();\n\t\tif (!body || sending) return;\n\t\t// Anonymous: the first send is the gate. Send them to sign-in with a return\n\t\t// path that lands them right back on this scene, opener and all.\n\t\tif (requireAuth(body)) return;\n\t\tdraft = '';\n\t\tfailed = null;\n\t\tsending = true;\n\t\t// Back to one line — the draft is gone, so the box shouldn't stay tall.\n\t\tqueueMicrotask(autoGrow);\n\n\t\t// Optimistic: their own words appear instantly. The reply is the only thing\n\t\t// worth waiting on, and at ~2s it doesn't need a placeholder bubble.\n\t\tmessages = [\n\t\t\t...messages,\n\t\t\t{\n\t\t\t\tid: `tmp-${crypto.randomUUID()}`,\n\t\t\t\trole: 'user',\n\t\t\t\tbody,\n\t\t\t\tkind: 'text',\n\t\t\t\tcreatedAt: new Date().toISOString()\n\t\t\t}\n\t\t];\n\t\tvoid scrollDown();\n\t\ttrackRoleplayMessageSent(chatId);\n\n\t\ttry {\n\t\t\t// First send with no thread yet: the create-and-send route makes the\n\t\t\t// session and answers in one round trip; the client adopts the id.\n\t\t\tconst res = chatId\n\t\t\t\t? await fetch(`/api/rp/chat/${chatId}`, {\n\t\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\t\t\tbody: JSON.stringify({ body })\n\t\t\t\t\t})\n\t\t\t\t: await fetch('/api/rp/chat', {\n\t\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\t\t\tbody: JSON.stringify({ templateId: data.character.templateId, body })\n\t\t\t\t\t});\n\t\t\tconst r = await res.json();\n\t\t\tif (r?.chatId) chatId = r.chatId;\n\t\t\tif (res.ok && r?.reply) {\n\t\t\t\tappendMessages(...withPhoto(r.reply));\n\t\t\t} else if (r?.error === 'blocked') {\n\t\t\t\thitBlocked('send');\n\t\t\t} else if (r?.error === 'quota') {\n\t\t\t\t// Their message never reached the thread, so take it back into the\n\t\t\t\t// composer — retyping it after hitting a paywall is insulting.\n\t\t\t\tmessages = messages.filter((m) => !m.id.startsWith('tmp-'));\n\t\t\t\tdraft = body;\n\t\t\t\thitWall('send');\n\t\t\t} else {\n\t\t\t\t// Their message is already stored server-side, so retry means\n\t\t\t\t// re-asking for a reply, not re-typing.\n\t\t\t\tfailed = 'no reply came through — tap to try again';\n\t\t\t\ttrackRoleplayGenerationFailed({ chatId: chatId, stage: 'send', reason: 'no_reply' });\n\t\t\t}\n\t\t} catch {\n\t\t\tfailed = 'connection dropped — tap to try again';\n\t\t\ttrackRoleplayGenerationFailed({ chatId: chatId, stage: 'send', reason: 'network' });\n\t\t} finally {\n\t\t\tsending = false;\n\t\t\tvoid scrollDown();\n\t\t}\n\t}\n\n\tasync function retry() {\n\t\tif (requireAuth()) return;\n\t\tif (sending || !chatId) return;\n\t\t// Nothing to regenerate until the thread has a user turn — the retry route\n\t\t// re-asks for a reply to the last stored user message.\n\t\tif (!messages.some((m) => m.role === 'user')) return;\n\t\tfailed = null;\n\t\tsending = true;\n\t\t// Show the typing beat while it regenerates.\n\t\tvoid scrollDown();\n\t\ttry {\n\t\t\t// Re-ask for a reply to the turn already stored — NOT a re-send. The old\n\t\t\t// path re-POSTed the message to the send endpoint, which re-ran the quota\n\t\t\t// consume and inserted a duplicate user row, so every tap here silently\n\t\t\t// charged a second metered unit for a reply that never came. This route\n\t\t\t// stores nothing and spends nothing.\n\t\t\tconst res = await fetch(`/api/rp/chat/${chatId}/retry`, { method: 'POST' });\n\t\t\tconst r = await res.json();\n\t\t\tif (res.ok && r?.reply) {\n\t\t\t\tappendMessages(...withPhoto(r.reply));\n\t\t\t} else if (r?.error === 'blocked') {\n\t\t\t\thitBlocked('retry');\n\t\t\t} else if (r?.error === 'quota') {\n\t\t\t\thitWall('retry');\n\t\t\t} else {\n\t\t\t\tfailed = 'still nothing — try again in a moment';\n\t\t\t\ttrackRoleplayGenerationFailed({\n\t\t\t\t\tchatId: chatId,\n\t\t\t\t\tstage: 'retry',\n\t\t\t\t\treason: r?.error ?? 'no_reply'\n\t\t\t\t});\n\t\t\t}\n\t\t} catch {\n\t\t\tfailed = 'connection dropped — tap to try again';\n\t\t\ttrackRoleplayGenerationFailed({ chatId: chatId, stage: 'retry', reason: 'network' });\n\t\t} finally {\n\t\t\tsending = false;\n\t\t\tvoid scrollDown();\n\t\t}\n\t}\n\n\tfunction onKey(e: KeyboardEvent) {\n\t\t// Enter sends, shift+enter breaks the line — the convention everywhere else\n\t\t// in the app's composers.\n\t\tif (e.key === 'Enter' && !e.shiftKey) {\n\t\t\te.preventDefault();\n\t\t\tvoid send();\n\t\t}\n\t}\n\n\t/** Escape hatch from a provider-blocked thread: a new timeline with the same\n\t *  character carries none of this conversation, so it generates normally. */\n\tlet startingFresh = $state(false);\n\tasync function freshTimeline() {\n\t\tif (requireAuth()) return;\n\t\tif (startingFresh) return;\n\t\tstartingFresh = true;\n\t\ttry {\n\t\t\tconst res = await fetch('/api/rp/threads', {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\tbody: JSON.stringify({ templateId: data.character.templateId })\n\t\t\t});\n\t\t\tconst r = await res.json();\n\t\t\tif (res.ok && r?.threadNo) {\n\t\t\t\tawait goto(`/roleplay/c/${data.character.slug}?t=${r.threadNo}`, { invalidateAll: true });\n\t\t\t\tblocked = false;\n\t\t\t}\n\t\t} finally {\n\t\t\tstartingFresh = false;\n\t\t}\n\t}\n\n\t/** Pull one suggested line into the composer — editable, never auto-sent. */\n\tasync function suggest() {\n\t\tif (requireAuth()) return;\n\t\tif (suggesting || sending) return;\n\t\tsuggesting = true;\n\t\ttry {\n\t\t\t// Lazy sessions: the suggestion runs against a live thread.\n\t\t\tif (!(await ensureChat())) return;\n\t\t\tconst res = await fetch(`/api/rp/chat/${chatId}/suggest`, { method: 'POST' });\n\t\t\tconst r = await res.json();\n\t\t\tif (res.ok && r?.text) {\n\t\t\t\tdraft = r.text;\n\t\t\t\tawait tick();\n\t\t\t\tautoGrow();\n\t\t\t\tbox?.focus();\n\t\t\t}\n\t\t} catch {\n\t\t\t/* the button just settles back */\n\t\t} finally {\n\t\t\tsuggesting = false;\n\t\t}\n\t}\n\n\tasync function onPhotoPicked(e: Event) {\n\t\tconst input = e.currentTarget as HTMLInputElement;\n\t\tconst file = input.files?.[0];\n\t\tinput.value = '';\n\t\tif (requireAuth()) return;\n\t\tif (!file || uploadingPhoto || sending) return;\n\t\tif (file.size > 8 * 1024 * 1024) {\n\t\t\tfailed = 'photo is over 8MB';\n\t\t\treturn;\n\t\t}\n\t\tuploadingPhoto = true;\n\t\tfailed = null;\n\t\tconst caption = draft.trim();\n\t\tdraft = '';\n\t\tqueueMicrotask(autoGrow);\n\t\tvoid scrollDown();\n\n\t\ttry {\n\t\t\t// Lazy sessions: the upload route is thread-scoped, so make the thread\n\t\t\t// exist before the first photo.\n\t\t\tif (!(await ensureChat())) {\n\t\t\t\tfailed = 'connection dropped — try again';\n\t\t\t\tdraft = caption;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst form = new FormData();\n\t\t\tform.set('file', file);\n\t\t\tif (caption) form.set('caption', caption);\n\t\t\tconst res = await fetch(`/api/rp/chat/${chatId}/photo`, { method: 'POST', body: form });\n\t\t\tconst r = await res.json();\n\t\t\tif (res.ok && r?.userMessage && r?.reply) {\n\t\t\t\tappendMessages(r.userMessage, ...withPhoto(r.reply));\n\t\t\t} else if (r?.error === 'quota') {\n\t\t\t\thitWall('photo');\n\t\t\t\tdraft = caption;\n\t\t\t} else if (r?.error === 'blocked') {\n\t\t\t\t// Provider refused the scene on the photo turn — same permanent block\n\t\t\t\t// as the text path, so show the blocked state, not a retry.\n\t\t\t\thitBlocked('photo');\n\t\t\t\tdraft = caption;\n\t\t\t} else if (r?.error === 'rejected') {\n\t\t\t\tfailed = 'that photo didn’t pass moderation';\n\t\t\t\tdraft = caption;\n\t\t\t} else {\n\t\t\t\tfailed = 'photo didn’t go through — try again';\n\t\t\t\tdraft = caption;\n\t\t\t}\n\t\t} catch {\n\t\t\tfailed = 'connection dropped — try again';\n\t\t\tdraft = caption;\n\t\t} finally {\n\t\t\tuploadingPhoto = false;\n\t\t\tvoid scrollDown();\n\t\t}\n\t}\n\n\t// The controls sheet and memory panel both act on a live thread — there's no\n\t// chatId to hang them off in the anon preview, so they gate like the composer.\n\tfunction openMenu() {\n\t\tif (requireAuth()) return;\n\t\t// Lazy sessions: every control in the sheet (reset / delete / timelines /\n\t\t// direction) acts on a thread — nothing to manage before the first send.\n\t\tif (!chatId) return;\n\t\tmenuOpen = true;\n\t}\n\tfunction openMemories() {\n\t\tif (requireAuth()) return;\n\t\tif (!chatId) return;\n\t\tmemOpen = true;\n\t}\n\n\t/** Play/stop one character message through the voice endpoint. */\n\tfunction toggleTts(m: Msg) {\n\t\tif (requireAuth()) return;\n\t\tif (!chatId) return;\n\t\tif (ttsId === m.id) {\n\t\t\tttsAudio?.pause();\n\t\t\tttsAudio = null;\n\t\t\tttsId = null;\n\t\t\tttsLoading = false;\n\t\t\treturn;\n\t\t}\n\t\tttsAudio?.pause();\n\t\tttsId = m.id;\n\t\tttsLoading = true;\n\t\tconst audio = new Audio(`/api/rp/chat/${chatId}/tts/${m.id}`);\n\t\tttsAudio = audio;\n\t\taudio.onplaying = () => {\n\t\t\tif (ttsId === m.id) ttsLoading = false;\n\t\t};\n\t\taudio.onended = audio.onerror = () => {\n\t\t\tif (ttsId === m.id) {\n\t\t\t\tttsId = null;\n\t\t\t\tttsLoading = false;\n\t\t\t\tttsAudio = null;\n\t\t\t}\n\t\t};\n\t\tvoid audio.play().catch(() => {\n\t\t\tif (ttsId === m.id) {\n\t\t\t\tttsId = null;\n\t\t\t\tttsLoading = false;\n\t\t\t\tttsAudio = null;\n\t\t\t}\n\t\t});\n\t}\n\n\tconst CTA_COPY: Record<string, { label: string; href: string }> = {\n\t\trate: { label: 'dare taken? get it rated →', href: '/rate?utm_source=rp_cta' },\n\t\tarena: { label: 'dare taken? enter the arena →', href: '/a?utm_source=rp_cta' }\n\t};\n\n\t/** \"11:48PM\" — the DM thread's clock, space squeezed out to match it. */\n\tfunction fmtClock(iso: string): string {\n\t\treturn new Date(iso)\n\t\t\t.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true })\n\t\t\t.replace(' ', '');\n\t}\n\n\tonMount(() => () => ttsAudio?.pause());\n\n\t// Ghost initial for characters with no artwork — same move as the market card.\n\tconst panelInitial = $derived(\n\t\t(data.character.title.match(/\\p{L}|\\p{N}/u)?.[0] ?? '?').toUpperCase()\n\t);\n\n\t// Mount only — it reads no state, so it never re-runs. Every send/retry above\n\t// scrolls on its own; this is just landing a resumed thread at the newest\n\t// message the way any chat app does.\n\t$effect(() => {\n\t\tvoid scrollDown();\n\t});\n</script>\n\n<svelte:head>\n\t<title>{data.character.title} — ratemyd.</title>\n\t<meta name=\"robots\" content=\"noindex\" />\n</svelte:head>\n\n<main class=\"wrap\">\n\t<div class=\"layout\">\n\t\t<!-- Desktop-only character panel: who you're talking to and the scene,\n\t\t\t lifted out of the thread so the conversation is just conversation. -->\n\t\t<aside class=\"panel\">\n\t\t\t<div class=\"portrait\">\n\t\t\t\t{#if data.character.imageUrl}\n\t\t\t\t\t<img src={data.character.imageUrl} alt={data.character.title} />\n\t\t\t\t{:else}\n\t\t\t\t\t<span class=\"portrait-ghost\" aria-hidden=\"true\">{panelInitial}</span>\n\t\t\t\t{/if}\n\t\t\t\t{#if data.character.spiceLevel === 'explicit'}\n\t\t\t\t\t<span class=\"portrait-badge\">18+</span>\n\t\t\t\t{/if}\n\t\t\t\t<div class=\"portrait-fade\" aria-hidden=\"true\"></div>\n\t\t\t\t<div class=\"portrait-id\">\n\t\t\t\t\t<h2 class=\"panel-name\">{data.character.title}</h2>\n\t\t\t\t\t{#if data.character.tagline}<p class=\"panel-tagline\">{data.character.tagline}</p>{/if}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div class=\"panel-scene\">\n\t\t\t\t<span class=\"panel-eyebrow\">the scene</span>\n\t\t\t\t<p class=\"panel-setting\">{data.character.setting}</p>\n\t\t\t\t{#if data.character.you}<p class=\"panel-you\">{data.character.you}</p>{/if}\n\t\t\t</div>\n\t\t\t<div class=\"panel-acts\">\n\t\t\t\t{#if chatId}\n\t\t\t\t\t<button type=\"button\" onclick={openMemories}>memories</button>\n\t\t\t\t\t<button type=\"button\" onclick={openMenu}>chat controls</button>\n\t\t\t\t{/if}\n\t\t\t\t<a href={`/roleplay/${data.character.slug}`}>character page →</a>\n\t\t\t</div>\n\t\t</aside>\n\n\t\t<section class=\"chat\">\n\t\t\t<header class=\"bar\">\n\t\t\t\t<a\n\t\t\t\t\thref={backHref}\n\t\t\t\t\tclass=\"back\"\n\t\t\t\t\taria-label={backHref === '/c' ? 'back to messages' : 'back to characters'}>←</a\n\t\t\t\t>\n\t\t\t\t<!-- Avatar + name + line are one target to the character's own page —\n\t\t\t the same move every messenger makes, and the only route from a scene\n\t\t\t back to what the character actually is. -->\n\t\t\t\t<a class=\"who-link\" href={`/roleplay/${data.character.slug}`}>\n\t\t\t\t\t{#if data.character.imageUrl}\n\t\t\t\t\t\t<img src={data.character.imageUrl} alt=\"\" class=\"avatar\" />\n\t\t\t\t\t{:else}\n\t\t\t\t\t\t<span class=\"avatar avatar--blank\" aria-hidden=\"true\"></span>\n\t\t\t\t\t{/if}\n\t\t\t\t\t<span class=\"who\">\n\t\t\t\t\t\t<span class=\"name\">{data.character.title}</span>\n\t\t\t\t\t\t{#if data.threads.length > 1}\n\t\t\t\t\t\t\t<span class=\"tag\">session {data.threadNo}</span>\n\t\t\t\t\t\t{:else if data.character.tagline}<span class=\"tag\">{data.character.tagline}</span>{/if}\n\t\t\t\t\t</span>\n\t\t\t\t</a>\n\t\t\t\t{#if chatId}\n\t\t\t\t\t<button type=\"button\" class=\"menu-btn\" aria-label=\"chat controls\" onclick={openMenu}>\n\t\t\t\t\t\t<svg\n\t\t\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\t\t\tstroke-width=\"1.8\"\n\t\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<circle cx=\"12\" cy=\"12\" r=\"3.1\" />\n\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\td=\"M19.3 15a1.7 1.7 0 0 0 .34 1.87l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.7 1.7 0 0 0-1.87-.34 1.7 1.7 0 0 0-1.03 1.55V21a2 2 0 1 1-4 0v-.09A1.7 1.7 0 0 0 8.8 19.3a1.7 1.7 0 0 0-1.87.34l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.7 1.7 0 0 0 .34-1.87 1.7 1.7 0 0 0-1.55-1.03H3a2 2 0 1 1 0-4h.09A1.7 1.7 0 0 0 4.7 8.8a1.7 1.7 0 0 0-.34-1.87l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.7 1.7 0 0 0 1.87.34H9a1.7 1.7 0 0 0 1.03-1.55V3a2 2 0 1 1 4 0v.09A1.7 1.7 0 0 0 15.2 4.7a1.7 1.7 0 0 0 1.87-.34l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.7 1.7 0 0 0-.34 1.87V9a1.7 1.7 0 0 0 1.55 1.03H21a2 2 0 1 1 0 4h-.09A1.7 1.7 0 0 0 19.3 15z\"\n\t\t\t\t\t\t\t\tstroke-linecap=\"round\"\n\t\t\t\t\t\t\t\tstroke-linejoin=\"round\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</svg>\n\t\t\t\t\t</button>\n\t\t\t\t{/if}\n\t\t\t</header>\n\n\t\t\t<div class=\"thread\" bind:this={scroller}>\n\t\t\t\t<div class=\"intro\">\n\t\t\t\t\t<p class=\"intro-setting\">{data.character.setting}</p>\n\t\t\t\t\t{#if data.character.you}<p class=\"intro-you\">{data.character.you}</p>{/if}\n\t\t\t\t</div>\n\n\t\t\t\t{#each messages as m (m.id)}\n\t\t\t\t\t{#if m.role === 'character' && (m.kind === 'photo_locked' || m.kind === 'photo_developing' || m.kind === 'photo_failed')}\n\t\t\t\t\t\t<div class=\"row\">\n\t\t\t\t\t\t\t{#if m.kind === 'photo_failed'}\n\t\t\t\t\t\t\t\t<p class=\"photo-note\">the photo didn't come through</p>\n\t\t\t\t\t\t\t{:else if isPro}\n\t\t\t\t\t\t\t\t{@const st = photoStage(m.id)}\n\t\t\t\t\t\t\t\t<div class=\"ptile ptile--dev\" role=\"status\" aria-live=\"polite\">\n\t\t\t\t\t\t\t\t\t<span class=\"pblob b1\"></span><span class=\"pblob b2\"></span><span class=\"pblob b3\"></span>\n\t\t\t\t\t\t\t\t\t<span class=\"ptile-in\">\n\t\t\t\t\t\t\t\t\t\t{#if photoCapHit}\n\t\t\t\t\t\t\t\t\t\t\t<b>that's today's photos</b>\n\t\t\t\t\t\t\t\t\t\t\t<small>more tomorrow</small>\n\t\t\t\t\t\t\t\t\t\t{:else}\n\t\t\t\t\t\t\t\t\t\t\t<span class=\"pspin\" aria-hidden=\"true\"></span>\n\t\t\t\t\t\t\t\t\t\t\t<b>{st.label}</b>\n\t\t\t\t\t\t\t\t\t\t\t<small>a photo takes about 20 seconds</small>\n\t\t\t\t\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t{#if !photoCapHit}<span class=\"pbar\" aria-hidden=\"true\"><span style=\"transform: scaleX({st.pct / 100})\"></span></span>{/if}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t{:else}\n\t\t\t\t\t\t\t\t<button type=\"button\" class=\"ptile\" onclick={openPhotoUpgrade}>\n\t\t\t\t\t\t\t\t\t<span class=\"pblob b1\"></span><span class=\"pblob b2\"></span><span class=\"pblob b3\"></span>\n\t\t\t\t\t\t\t\t\t<span class=\"psheen\" aria-hidden=\"true\"></span>\n\t\t\t\t\t\t\t\t\t<span class=\"ptile-in\">\n\t\t\t\t\t\t\t\t\t\t<span class=\"plock\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t\t\t\t<svg width=\"22\" height=\"22\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"5\" y=\"11\" width=\"14\" height=\"10\" rx=\"2\" /><path d=\"M8 11V8a4 4 0 0 1 8 0v3\" /></svg>\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t<b>{charFirst} sent you a photo</b>\n\t\t\t\t\t\t\t\t\t\t<small>Pro members see every photo characters send.</small>\n\t\t\t\t\t\t\t\t\t\t<span class=\"pcta\">Unlock with Pro</span>\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t{#if m.kind !== 'photo_failed'}<span class=\"photo-label\">AI-generated · only in this chat</span>{/if}\n\t\t\t\t\t{:else}\n\t\t\t\t\t<div class=\"row\" class:row--mine={m.role === 'user'}>\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclass=\"bubble\"\n\t\t\t\t\t\t\tclass:bubble--mine={m.role === 'user'}\n\t\t\t\t\t\t\tclass:bubble--photo={m.kind === 'image'}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{#if m.kind === 'image'}\n\t\t\t\t\t\t\t\t{#if m.mediaUrl}\n\t\t\t\t\t\t\t\t\t<img class=\"photo\" src={m.mediaUrl} alt=\"sent in chat\" loading=\"lazy\" onload={photoLoaded} />\n\t\t\t\t\t\t\t\t{:else}\n\t\t\t\t\t\t\t\t\t<span class=\"photo photo--gone\">photo</span>\n\t\t\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t\t\t{#if m.body}<span class=\"caption\">{m.body}</span>{/if}\n\t\t\t\t\t\t\t{:else if m.role === 'character'}\n\t\t\t\t\t\t\t\t{#each segments(m.body) as seg, i (i)}<span class:narration={seg.narration}\n\t\t\t\t\t\t\t\t\t\t>{seg.text}</span\n\t\t\t\t\t\t\t\t\t>{/each}\n\t\t\t\t\t\t\t{:else}{m.body}{/if}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t{#if m.role === 'character' && m.kind !== 'image' && !data.anon && chatId}\n\t\t\t\t\t\t<!-- Voice row under the bubble — the DM voice-message look: purple\n\t\t\t\t\t disc with a waveform, the sent time beside it. Hidden in the anon\n\t\t\t\t\t AND lazy previews: playback needs a live thread and a real stored\n\t\t\t\t\t message row to synthesize against. -->\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tclass=\"tts-row\"\n\t\t\t\t\t\t\taria-label={ttsId === m.id ? 'stop voice' : 'play voice'}\n\t\t\t\t\t\t\tonclick={() => toggleTts(m)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tclass=\"tts-disc\"\n\t\t\t\t\t\t\t\tclass:tts-disc--loading={ttsId === m.id && ttsLoading}\n\t\t\t\t\t\t\t\tclass:tts-disc--playing={ttsId === m.id && !ttsLoading}\n\t\t\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<svg viewBox=\"0 0 20 20\" fill=\"currentColor\">\n\t\t\t\t\t\t\t\t\t<rect class=\"wb wb1\" x=\"3\" y=\"7\" width=\"2\" height=\"6\" rx=\"1\" />\n\t\t\t\t\t\t\t\t\t<rect class=\"wb wb2\" x=\"7\" y=\"4.5\" width=\"2\" height=\"11\" rx=\"1\" />\n\t\t\t\t\t\t\t\t\t<rect class=\"wb wb3\" x=\"11\" y=\"6\" width=\"2\" height=\"8\" rx=\"1\" />\n\t\t\t\t\t\t\t\t\t<rect class=\"wb wb4\" x=\"15\" y=\"7.5\" width=\"2\" height=\"5\" rx=\"1\" />\n\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span class=\"tts-when\">{fmtClock(m.createdAt)}</span>\n\t\t\t\t\t\t</button>\n\t\t\t\t\t{/if}\n\t\t\t\t\t{#if m.role === 'character' && m.cta && CTA_COPY[m.cta]}\n\t\t\t\t\t\t<a class=\"cta-chip\" href={CTA_COPY[m.cta].href}>{CTA_COPY[m.cta].label}</a>\n\t\t\t\t\t{/if}\n\t\t\t\t\t{/if}\n\t\t\t\t{/each}\n\n\t\t\t\t{#if sending || uploadingPhoto}\n\t\t\t\t\t<div class=\"row\">\n\t\t\t\t\t\t<div class=\"bubble bubble--typing\" aria-label=\"typing\">\n\t\t\t\t\t\t\t<span></span><span></span><span></span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t{/if}\n\n\t\t\t\t{#if capped}\n\t\t\t\t\t<!-- The wall, in the scene rather than over it: the character is the one\n\t\t\t\t who stops, and the offer sits under their line. A system modal here\n\t\t\t\t reads as an error; this reads as a cliffhanger. -->\n\t\t\t\t\t<div class=\"capped\">\n\t\t\t\t\t\t<!-- No name here: a character title is often a scene, not a person\n\t\t\t\t\t (\"Whos man is bigger?\" → \"Whos is still typing…\"). A neutral\n\t\t\t\t\t in-scene beat reads right for all 360 of them. -->\n\t\t\t\t\t\t<p class=\"capped-line\"><em>…and there it stops, for today.</em></p>\n\t\t\t\t\t\t<p class=\"capped-body\">\n\t\t\t\t\t\t\t{#if wallNext.people}\n\t\t\t\t\t\t\t\tyou've used today's free messages. they're back at {wallNext.resetTime}, in\n\t\t\t\t\t\t\t\t{wallNext.until} — or pick up right now, unlimited, with every character.\n\t\t\t\t\t\t\t{:else}\n\t\t\t\t\t\t\t\tyou've used today's free messages. they'll be here again tomorrow — or pick up right\n\t\t\t\t\t\t\t\tnow, unlimited, with every character.\n\t\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<button type=\"button\" class=\"capped-cta\" onclick={() => { upgradeHeadline = 'unlimited messages with every character'; upgradeOpen = true; }}>\n\t\t\t\t\t\t\tkeep talking — unlimited\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t{#if wallNext.people && wallNextRows(wallNext.people).length > 0}\n\t\t\t\t\t\t\t<RpWallNext people={wallNext.people} resetTime={wallNext.resetTime} />\n\t\t\t\t\t\t{:else if wallNext.asked}\n\t\t\t\t\t\t\t<a class=\"capped-alt\" href=\"/roleplay\">browse other characters</a>\n\t\t\t\t\t\t{/if}\n\t\t\t\t\t</div>\n\t\t\t\t{:else if blocked}\n\t\t\t\t\t<!-- Not a retryable error: the provider refused this scene on content\n\t\t\t\t policy, and tapping again only repeats it. Verified thread-scoped —\n\t\t\t\t a fresh timeline on the same character generates fine — so the first\n\t\t\t\t action offered is the one that actually works. -->\n\t\t\t\t\t<div class=\"blocked\">\n\t\t\t\t\t\t<p class=\"blocked-title\">this scene can’t continue</p>\n\t\t\t\t\t\t<p class=\"blocked-body\">\n\t\t\t\t\t\t\tthe AI provider won’t write any more of this conversation — it’s ruled the scene over\n\t\t\t\t\t\t\tits content line. retrying won’t change it, but a fresh session with the same\n\t\t\t\t\t\t\tcharacter starts clean.\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<div class=\"blocked-acts\">\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tclass=\"blocked-btn\"\n\t\t\t\t\t\t\t\tdisabled={startingFresh}\n\t\t\t\t\t\t\t\tonclick={freshTimeline}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{startingFresh ? 'starting…' : 'start a fresh session'}\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t<a class=\"blocked-btn\" href=\"/roleplay\">find another character</a>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t{:else if failed}\n\t\t\t\t\t<button type=\"button\" class=\"retry\" onclick={retry}>{failed}</button>\n\t\t\t\t{/if}\n\t\t\t</div>\n\n\t\t\t<!-- No direction chip above the composer: the note is a setting, not part of\n\t\t the conversation, and a permanent banner over the thread made it read\n\t\t like a message. It lives in the controls sheet, where it's changed. -->\n\t\t\t<div class=\"composer\">\n\t\t\t\t<input\n\t\t\t\t\tbind:this={photoInput}\n\t\t\t\t\ttype=\"file\"\n\t\t\t\t\taccept=\"image/jpeg,image/png,image/webp,image/heic,image/heif\"\n\t\t\t\t\tclass=\"hidden-input\"\n\t\t\t\t\tonchange={onPhotoPicked}\n\t\t\t\t/>\n\t\t\t\t<!-- Tools lead, composer follows, send closes: the two input helpers sit\n\t\t\t together on the left so neither crowds the send target. -->\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"tool\"\n\t\t\t\t\taria-label=\"send a photo\"\n\t\t\t\t\tdisabled={uploadingPhoto || sending}\n\t\t\t\t\tonclick={() => photoInput?.click()}\n\t\t\t\t>\n\t\t\t\t\t<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\">\n\t\t\t\t\t\t<rect x=\"3\" y=\"4\" width=\"18\" height=\"16\" rx=\"3\" />\n\t\t\t\t\t\t<circle cx=\"8.5\" cy=\"9.5\" r=\"1.6\" fill=\"currentColor\" stroke=\"none\" />\n\t\t\t\t\t\t<path d=\"M4 17l4.5-4.5a2 2 0 0 1 2.8 0L20 20\" stroke-linecap=\"round\" />\n\t\t\t\t\t</svg>\n\t\t\t\t</button>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"tool\"\n\t\t\t\t\tclass:tool--busy={suggesting}\n\t\t\t\t\taria-label=\"suggest a reply\"\n\t\t\t\t\tdisabled={suggesting || sending || uploadingPhoto}\n\t\t\t\t\tonclick={suggest}\n\t\t\t\t>\n\t\t\t\t\t<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\">\n\t\t\t\t\t\t<path d=\"M9 18h6\" stroke-linecap=\"round\" />\n\t\t\t\t\t\t<path d=\"M10 21h4\" stroke-linecap=\"round\" />\n\t\t\t\t\t\t<path\n\t\t\t\t\t\t\td=\"M12 3a6 6 0 0 0-3.5 10.9c.5.4.8 1 .8 1.6v.5h5.4v-.5c0-.6.3-1.2.8-1.6A6 6 0 0 0 12 3z\"\n\t\t\t\t\t\t\tstroke-linejoin=\"round\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</svg>\n\t\t\t\t</button>\n\t\t\t\t<textarea\n\t\t\t\t\tbind:this={box}\n\t\t\t\t\tbind:value={draft}\n\t\t\t\t\tonkeydown={onKey}\n\t\t\t\t\toninput={autoGrow}\n\t\t\t\t\trows=\"1\"\n\t\t\t\t\tmaxlength=\"2000\"\n\t\t\t\t\tplaceholder={uploadingPhoto ? 'sending photo…' : 'say something…'}\n\t\t\t\t\tdisabled={sending || uploadingPhoto}\n\t\t\t\t></textarea>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"send\"\n\t\t\t\t\taria-label=\"send\"\n\t\t\t\t\tonclick={send}\n\t\t\t\t\tdisabled={!draft.trim() || sending || uploadingPhoto}\n\t\t\t\t>\n\t\t\t\t\t<svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n\t\t\t\t\t\t<path d=\"M3.6 20.4l17.2-8.4L3.6 3.6l-.1 6.5 12 1.9-12 1.9z\" />\n\t\t\t\t\t</svg>\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t</section>\n\t</div>\n</main>\n\n<RpChatMenu\n\topen={menuOpen}\n\tchatId={chatId ?? ''}\n\ttemplateId={data.character.templateId}\n\tslug={data.character.slug}\n\ttitle={data.character.title}\n\tthreadNo={data.threadNo}\n\tthreads={data.threads}\n\t{direction}\n\tisCreator={data.isCreator}\n\tonClose={() => (menuOpen = false)}\n\tonOpenMemories={() => (memOpen = true)}\n\tonDirection={(d) => (direction = d)}\n/>\n\n<UpgradeModal\n\topen={upgradeOpen}\n\tonclose={() => (upgradeOpen = false)}\n\tfeature=\"roleplay\"\n\theadline={upgradeHeadline}\n\tsuccessPath={`/roleplay/c/${data.character.slug}`}\n/>\n\n<RpMemoryPanel\n\topen={memOpen}\n\tapiBase={`/api/rp/chat/${chatId}`}\n\t{memories}\n\tonClose={() => (memOpen = false)}\n\tonChange={(m) => (memories = m)}\n/>\n\n<style>\n\t/* Owns the viewport, same contract as the DM thread: Navbar + BottomNav hide\n\t   on this route, so the header and the composer are the top and bottom edges.\n\t   `fixed` rather than `height: 100dvh` because the document still carries the\n\t   layout's own offsets — in flow the composer floated mid-page with dead space\n\t   under it, and on iOS the URL bar collapsing would move it again. */\n\t.wrap {\n\t\tposition: fixed;\n\t\tinset: 0;\n\t\t/* Belt and braces: inset alone leaves the height to the containing block,\n\t\t   and 100dvh pins it to the visual viewport when iOS collapses its URL bar\n\t\t   mid-scroll. overflow:hidden stops the thread from pushing the composer\n\t\t   past the bottom edge. */\n\t\theight: 100dvh;\n\t\toverflow: hidden;\n\t\tbackground: var(--color-bg);\n\t\tz-index: 40;\n\t}\n\t@media (min-width: 640px) {\n\t\t.wrap {\n\t\t\t/* Desktop keeps the global navbar (it hides mobile-only), and the\n\t\t\t   navbar is fixed at z-50 over this z-40 pane — without this offset it\n\t\t\t   buries the chat's own header and the ⋯ controls behind it. */\n\t\t\ttop: 65px;\n\t\t\theight: calc(100dvh - 65px);\n\t\t}\n\t}\n\n\t.layout {\n\t\tposition: relative;\n\t\theight: 100%;\n\t\tdisplay: flex;\n\t\tjustify-content: center;\n\t\talign-items: stretch;\n\t}\n\t@media (min-width: 640px) {\n\t\t.layout {\n\t\t\t/* Same container as the navbar (max-w-5xl, px-5 md:px-0) so the panes\n\t\t\t   line up with the logo's left edge and the nav's right edge. */\n\t\t\twidth: 100%;\n\t\t\tmax-width: 64rem;\n\t\t\tmargin: 0 auto;\n\t\t\tgap: 20px;\n\t\t\tpadding: 18px 20px;\n\t\t}\n\t}\n\t@media (min-width: 768px) {\n\t\t.layout {\n\t\t\tpadding-inline: 0;\n\t\t}\n\t}\n\t@media (min-width: 1024px) {\n\t\t.layout {\n\t\t\tjustify-content: flex-start;\n\t\t}\n\t}\n\n\t.chat {\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tflex: 1;\n\t\tmin-width: 0;\n\t\tmax-width: 640px;\n\t\tbackground: var(--color-bg);\n\t}\n\t@media (min-width: 640px) {\n\t\t.chat {\n\t\t\tborder: 1px solid var(--color-border);\n\t\t\tborder-radius: 18px;\n\t\t\toverflow: hidden;\n\t\t}\n\t}\n\t@media (min-width: 1024px) {\n\t\t/* Beside the panel the chat runs to the container's right edge — the\n\t\t   pair spans exactly the navbar's width. */\n\t\t.chat {\n\t\t\tmax-width: none;\n\t\t}\n\t}\n\n\t/* Character panel — desktop only. Portrait on top, the scene, then the\n\t   controls the ⋯ menu also reaches, one hop closer. */\n\t.panel {\n\t\tdisplay: none;\n\t}\n\t@media (min-width: 1024px) {\n\t\t.panel {\n\t\t\tdisplay: flex;\n\t\t\tflex-direction: column;\n\t\t\twidth: 300px;\n\t\t\tflex-shrink: 0;\n\t\t\t/* Content-sized card, not a full-height column — it ends where the\n\t\t\t   character info ends. Scrolls only if the scene text overflows. */\n\t\t\talign-self: flex-start;\n\t\t\tmax-height: 100%;\n\t\t\toverflow-y: auto;\n\t\t\tscrollbar-width: none;\n\t\t\tborder: 1px solid var(--color-border);\n\t\t\tborder-radius: 18px;\n\t\t\tbackground: var(--color-surface);\n\t\t}\n\t\t.panel::-webkit-scrollbar {\n\t\t\tdisplay: none;\n\t\t}\n\t}\n\t.portrait {\n\t\tposition: relative;\n\t\taspect-ratio: 3 / 4;\n\t\tflex-shrink: 0;\n\t\toverflow: hidden;\n\t\tbackground: linear-gradient(135deg, var(--color-surface-2), var(--color-bg));\n\t}\n\t.portrait img {\n\t\tposition: absolute;\n\t\tinset: 0;\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tobject-fit: cover;\n\t}\n\t.portrait-ghost {\n\t\tposition: absolute;\n\t\ttop: -28px;\n\t\tright: -10px;\n\t\tfont-family: var(--font-display);\n\t\tfont-size: 9rem;\n\t\tline-height: 1;\n\t\tfont-weight: 700;\n\t\tcolor: rgba(255, 255, 255, 0.05);\n\t\tuser-select: none;\n\t}\n\t.portrait-badge {\n\t\tposition: absolute;\n\t\ttop: 10px;\n\t\tleft: 10px;\n\t\tpadding: 2px 6px;\n\t\tborder-radius: 6px;\n\t\tbackground: rgba(0, 0, 0, 0.55);\n\t\tfont-size: 10px;\n\t\tfont-weight: 600;\n\t\tcolor: rgba(255, 255, 255, 0.8);\n\t}\n\t.portrait-fade {\n\t\tposition: absolute;\n\t\tinset: 0;\n\t\tbackground: linear-gradient(180deg, transparent 45%, rgba(12, 12, 12, 0.92));\n\t}\n\t.portrait-id {\n\t\tposition: absolute;\n\t\tinset-inline: 0;\n\t\tbottom: 0;\n\t\tpadding: 14px;\n\t}\n\t.panel-name {\n\t\tfont-family: var(--font-display);\n\t\tfont-size: 19px;\n\t\tfont-weight: 700;\n\t\tline-height: 1.15;\n\t\tcolor: #fff;\n\t}\n\t.panel-tagline {\n\t\tmargin-top: 4px;\n\t\tfont-size: 12px;\n\t\tline-height: 1.45;\n\t\tcolor: rgba(255, 255, 255, 0.6);\n\t}\n\t.panel-scene {\n\t\tpadding: 14px;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tgap: 6px;\n\t}\n\t.panel-eyebrow {\n\t\tfont-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n\t\tfont-size: 9px;\n\t\tletter-spacing: 0.14em;\n\t\ttext-transform: uppercase;\n\t\tcolor: var(--color-muted);\n\t}\n\t.panel-setting {\n\t\tfont-size: 12.5px;\n\t\tline-height: 1.55;\n\t\tcolor: var(--color-muted);\n\t}\n\t.panel-you {\n\t\tfont-size: 12px;\n\t\tline-height: 1.5;\n\t\tcolor: color-mix(in srgb, var(--color-muted) 70%, transparent);\n\t}\n\t.panel-acts {\n\t\tpadding: 14px;\n\t\tborder-top: 1px solid var(--color-border);\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tgap: 8px;\n\t}\n\t.panel-acts button,\n\t.panel-acts a {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tpadding: 9px 12px;\n\t\tborder-radius: 12px;\n\t\tborder: 1px solid var(--color-border);\n\t\tbackground: rgba(255, 255, 255, 0.03);\n\t\tcolor: var(--color-text);\n\t\tfont-size: 12.5px;\n\t\tfont-weight: 600;\n\t\ttext-decoration: none;\n\t\tcursor: pointer;\n\t\ttransition: border-color 0.15s;\n\t}\n\t.panel-acts button:hover,\n\t.panel-acts a:hover {\n\t\tborder-color: rgba(255, 255, 255, 0.2);\n\t}\n\t.bar {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tgap: 10px;\n\t\tpadding: 10px 14px;\n\t\tborder-bottom: 1px solid var(--color-border);\n\t\tbackground: var(--color-bg);\n\t\tflex-shrink: 0;\n\t\tpadding-top: calc(10px + env(safe-area-inset-top, 0px));\n\t}\n\t@media (min-width: 640px) {\n\t\t/* Inside the translucent card the solid bg would punch opaque strips\n\t\t   through the ambient glow. */\n\t\t.bar,\n\t\t.composer {\n\t\t\tbackground: transparent;\n\t\t}\n\t}\n\t.back {\n\t\tfont-size: 20px;\n\t\tline-height: 1;\n\t\tcolor: var(--color-muted);\n\t\ttext-decoration: none;\n\t\tpadding: 4px 6px;\n\t}\n\t.back:hover {\n\t\tcolor: var(--color-text);\n\t}\n\t.avatar {\n\t\twidth: 36px;\n\t\theight: 36px;\n\t\tborder-radius: 50%;\n\t\tobject-fit: cover;\n\t\tflex-shrink: 0;\n\t}\n\t.avatar--blank {\n\t\tbackground: var(--color-surface);\n\t}\n\t.who-link {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tgap: 10px;\n\t\tflex: 1;\n\t\tmin-width: 0;\n\t\ttext-decoration: none;\n\t\tcolor: inherit;\n\t}\n\t.who-link:hover .name {\n\t\ttext-decoration: underline;\n\t}\n\t.who {\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tmin-width: 0;\n\t\tflex: 1;\n\t}\n\t.name {\n\t\tfont-family: var(--font-display);\n\t\tfont-size: 15px;\n\t\tfont-weight: 700;\n\t\tcolor: var(--color-text);\n\t\twhite-space: nowrap;\n\t\toverflow: hidden;\n\t\ttext-overflow: ellipsis;\n\t}\n\t.tag {\n\t\tfont-size: 11px;\n\t\tcolor: var(--color-muted);\n\t\twhite-space: nowrap;\n\t\toverflow: hidden;\n\t\ttext-overflow: ellipsis;\n\t}\n\t.menu-btn {\n\t\t/* The ⋯ glyph gave a 16x26 target — under a long character name there was\n\t\t   barely anything left to hit. Fixed 44x44 box (the tap-target floor) that\n\t\t   never shrinks, so the name's ellipsis always stops clear of it; the\n\t\t   negative margin keeps the icon optically on the same edge as before\n\t\t   while the hit area spills into the bar's own padding. */\n\t\tflex-shrink: 0;\n\t\twidth: 44px;\n\t\theight: 44px;\n\t\tmargin-right: -8px;\n\t\tdisplay: grid;\n\t\tplace-items: center;\n\t\tpadding: 0;\n\t\tbackground: none;\n\t\tborder: none;\n\t\tborder-radius: 50%;\n\t\tcolor: var(--color-muted);\n\t\tcursor: pointer;\n\t}\n\t.menu-btn svg {\n\t\twidth: 20px;\n\t\theight: 20px;\n\t}\n\t.menu-btn:hover {\n\t\tcolor: var(--color-text);\n\t}\n\n\t.thread {\n\t\tflex: 1;\n\t\tmin-height: 0;\n\t\toverflow-y: auto;\n\t\tscrollbar-width: thin;\n\t\tscrollbar-color: #2a2a2a transparent;\n\t\tpadding: 12px 14px 6px;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tgap: 10px;\n\t}\n\t.thread::-webkit-scrollbar { width: 8px; }\n\t.thread::-webkit-scrollbar-track { background: transparent; }\n\t.thread::-webkit-scrollbar-thumb { background: #2a2a2a; border-radius: 4px; }\n\t.intro {\n\t\tborder: 1px solid var(--color-border);\n\t\tbackground: var(--color-surface);\n\t\tborder-radius: 14px;\n\t\tpadding: 12px 14px;\n\t\tmargin-bottom: 6px;\n\t}\n\t@media (min-width: 1024px) {\n\t\t/* The panel carries the scene — printing it at the top of the thread too\n\t\t   would show it twice on one screen. */\n\t\t.intro {\n\t\t\tdisplay: none;\n\t\t}\n\t}\n\t.intro-setting {\n\t\tfont-size: 13px;\n\t\tline-height: 1.5;\n\t\tcolor: var(--color-muted);\n\t}\n\t.intro-you {\n\t\tmargin-top: 6px;\n\t\tfont-size: 12px;\n\t\tline-height: 1.45;\n\t\tcolor: color-mix(in srgb, var(--color-muted) 70%, transparent);\n\t}\n\n\t.row {\n\t\tdisplay: flex;\n\t\talign-items: flex-end;\n\t\tgap: 6px;\n\t}\n\t.row--mine {\n\t\tjustify-content: flex-end;\n\t}\n\t.bubble {\n\t\tmax-width: 82%;\n\t\tpadding: 10px 13px;\n\t\tborder-radius: 16px;\n\t\tfont-size: 14px;\n\t\tline-height: 1.5;\n\t\twhite-space: pre-wrap;\n\t\toverflow-wrap: anywhere;\n\t\t/* same bubble as the group room: a filled block, no outline */\n\t\tbackground: #161616;\n\t\tcolor: var(--color-text);\n\t\tborder: 1px solid transparent;\n\t}\n\t.row:not(.row--mine) .bubble:not(.bubble--photo) {\n\t\tborder-radius: 6px 18px 18px 18px;\n\t}\n\t.narration {\n\t\tfont-style: italic;\n\t\tcolor: color-mix(in srgb, var(--color-muted) 85%, var(--color-text));\n\t}\n\t.bubble--mine {\n\t\tbackground: var(--color-accent);\n\t\tcolor: #fff;\n\t\tborder-color: transparent;\n\t}\n\t.bubble--photo {\n\t\tpadding: 6px;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tgap: 6px;\n\t}\n\t.photo {\n\t\tdisplay: block;\n\t\tmax-width: 100%;\n\t\tmax-height: 320px;\n\t\tborder-radius: 12px;\n\t\tobject-fit: cover;\n\t}\n\t.photo--gone {\n\t\tpadding: 22px 30px;\n\t\tfont-size: 12px;\n\t\tcolor: var(--color-muted);\n\t}\n\t.caption {\n\t\tpadding: 0 7px 4px;\n\t\tfont-size: 13px;\n\t}\n\t.tts-row {\n\t\tdisplay: inline-flex;\n\t\talign-items: center;\n\t\tgap: 10px;\n\t\talign-self: flex-start;\n\t\tmargin: -2px 0 0 2px;\n\t\tpadding: 2px 4px 2px 0;\n\t\tbackground: none;\n\t\tborder: none;\n\t\tcursor: pointer;\n\t}\n\t.tts-disc {\n\t\twidth: 34px;\n\t\theight: 34px;\n\t\tborder-radius: 50%;\n\t\tbackground: var(--color-purple);\n\t\tcolor: #fff;\n\t\tdisplay: inline-flex;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tflex-shrink: 0;\n\t}\n\t.tts-disc svg {\n\t\twidth: 18px;\n\t\theight: 18px;\n\t}\n\t.tts-when {\n\t\tfont-size: 13px;\n\t\tcolor: var(--color-muted);\n\t}\n\t.tts-row:hover .tts-when {\n\t\tcolor: var(--color-text);\n\t}\n\t/* The bars breathe while synthesizing, dance while playing. transform-box\n\t   keeps each rect scaling around its own middle instead of the SVG origin. */\n\t.wb {\n\t\ttransform-box: fill-box;\n\t\ttransform-origin: center;\n\t}\n\t.tts-disc--loading .wb {\n\t\tanimation: wb-breathe 1s ease-in-out infinite;\n\t}\n\t.tts-disc--playing .wb {\n\t\tanimation: wb-dance 0.9s ease-in-out infinite;\n\t}\n\t.wb2 {\n\t\tanimation-delay: 0.15s;\n\t}\n\t.wb3 {\n\t\tanimation-delay: 0.3s;\n\t}\n\t.wb4 {\n\t\tanimation-delay: 0.45s;\n\t}\n\t@keyframes wb-breathe {\n\t\t0%,\n\t\t100% {\n\t\t\topacity: 0.45;\n\t\t}\n\t\t50% {\n\t\t\topacity: 1;\n\t\t}\n\t}\n\t@keyframes wb-dance {\n\t\t0%,\n\t\t100% {\n\t\t\ttransform: scaleY(0.55);\n\t\t}\n\t\t50% {\n\t\t\ttransform: scaleY(1.25);\n\t\t}\n\t}\n\t@media (prefers-reduced-motion: reduce) {\n\t\t.tts-disc--loading .wb,\n\t\t.tts-disc--playing .wb {\n\t\t\tanimation: none;\n\t\t\topacity: 0.8;\n\t\t}\n\t}\n\t.cta-chip {\n\t\talign-self: flex-start;\n\t\tmargin: -4px 0 0 4px;\n\t\tpadding: 6px 12px;\n\t\tborder-radius: 12px;\n\t\tbackground: var(--color-accent);\n\t\tcolor: #fff;\n\t\tfont-size: 12px;\n\t\tfont-weight: 700;\n\t\ttext-decoration: none;\n\t}\n\t.bubble--typing {\n\t\tdisplay: inline-flex;\n\t\tgap: 4px;\n\t\tpadding: 14px 15px;\n\t}\n\t.bubble--typing span {\n\t\twidth: 6px;\n\t\theight: 6px;\n\t\tborder-radius: 50%;\n\t\tbackground: var(--color-muted);\n\t\tanimation: blink 1.2s infinite;\n\t}\n\t.bubble--typing span:nth-child(2) {\n\t\tanimation-delay: 0.2s;\n\t}\n\t.bubble--typing span:nth-child(3) {\n\t\tanimation-delay: 0.4s;\n\t}\n\t@keyframes blink {\n\t\t0%,\n\t\t60%,\n\t\t100% {\n\t\t\topacity: 0.25;\n\t\t}\n\t\t30% {\n\t\t\topacity: 1;\n\t\t}\n\t}\n\t@media (prefers-reduced-motion: reduce) {\n\t\t.bubble--typing span {\n\t\t\tanimation: none;\n\t\t\topacity: 0.5;\n\t\t}\n\t}\n\n\t.retry {\n\t\talign-self: center;\n\t\tfont-size: 12px;\n\t\tcolor: var(--color-coral);\n\t\tbackground: none;\n\t\tborder: none;\n\t\tcursor: pointer;\n\t\tpadding: 6px;\n\t}\n\n\t.capped {\n\t\tborder: 1px solid var(--color-purple);\n\t\tbackground: color-mix(in srgb, var(--color-purple) 10%, var(--color-surface));\n\t\tborder-radius: 14px;\n\t\tpadding: 14px;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tgap: 8px;\n\t}\n\t.capped-line {\n\t\tfont-size: 13px;\n\t\tcolor: var(--color-muted);\n\t}\n\t.capped-body {\n\t\tfont-size: 13px;\n\t\tline-height: 1.5;\n\t\tcolor: var(--color-text);\n\t}\n\t.capped-cta {\n\t\tmargin-top: 2px;\n\t\tpadding: 11px 16px;\n\t\tborder-radius: 12px;\n\t\tborder: none;\n\t\tbackground: var(--color-purple);\n\t\tcolor: #fff;\n\t\tfont-family: var(--font-display);\n\t\tfont-size: 14px;\n\t\tfont-weight: 700;\n\t\tcursor: pointer;\n\t}\n\t.capped-alt {\n\t\talign-self: center;\n\t\tfont-size: 12px;\n\t\tcolor: var(--color-muted);\n\t}\n\n\t.blocked {\n\t\tborder: 1px solid var(--color-border);\n\t\tbackground: var(--color-surface);\n\t\tborder-radius: 14px;\n\t\tpadding: 14px;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tgap: 8px;\n\t}\n\t.blocked-title {\n\t\tfont-family: var(--font-display);\n\t\tfont-size: 14px;\n\t\tfont-weight: 700;\n\t\tcolor: var(--color-text);\n\t}\n\t.blocked-body {\n\t\tfont-size: 13px;\n\t\tline-height: 1.5;\n\t\tcolor: var(--color-muted);\n\t}\n\t.blocked-acts {\n\t\tdisplay: flex;\n\t\tgap: 8px;\n\t\tflex-wrap: wrap;\n\t}\n\t.blocked-btn {\n\t\tpadding: 8px 13px;\n\t\tborder-radius: 12px;\n\t\tborder: 1px solid var(--color-border);\n\t\tbackground: var(--color-bg);\n\t\tcolor: var(--color-text);\n\t\tfont-size: 12px;\n\t\tfont-weight: 600;\n\t\ttext-decoration: none;\n\t\tcursor: pointer;\n\t}\n\t.blocked-btn:disabled {\n\t\topacity: 0.5;\n\t\tcursor: default;\n\t}\n\n\t.composer {\n\t\tdisplay: flex;\n\t\talign-items: flex-end;\n\t\tgap: 8px;\n\t\t/* Flush to the bottom edge of the fixed pane. Only the iOS home-indicator\n\t\t   inset is added — anything else here reads as dead space. */\n\t\tpadding: 8px 12px calc(8px + env(safe-area-inset-bottom, 0px));\n\t\tmargin-bottom: 0;\n\t\tborder-top: 1px solid var(--color-border);\n\t\tbackground: var(--color-bg);\n\t\tflex-shrink: 0;\n\t}\n\t.hidden-input {\n\t\tdisplay: none;\n\t}\n\t.tool {\n\t\twidth: 34px;\n\t\theight: 34px;\n\t\tborder-radius: 50%;\n\t\tborder: none;\n\t\tbackground: none;\n\t\tcolor: var(--color-muted);\n\t\tcursor: pointer;\n\t\tflex-shrink: 0;\n\t\tdisplay: inline-flex;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tpadding: 0;\n\t}\n\t.tool svg {\n\t\twidth: 21px;\n\t\theight: 21px;\n\t}\n\t.tool:hover:not(:disabled) {\n\t\tcolor: var(--color-text);\n\t}\n\t.tool:disabled {\n\t\topacity: 0.4;\n\t\tcursor: default;\n\t}\n\t.tool--busy {\n\t\tanimation: tool-pulse 0.9s ease-in-out infinite;\n\t}\n\t@keyframes tool-pulse {\n\t\t0%,\n\t\t100% {\n\t\t\topacity: 0.4;\n\t\t}\n\t\t50% {\n\t\t\topacity: 1;\n\t\t}\n\t}\n\t@media (prefers-reduced-motion: reduce) {\n\t\t.tool--busy {\n\t\t\tanimation: none;\n\t\t}\n\t}\n\t.composer textarea {\n\t\tflex: 1;\n\t\tresize: none;\n\t\t/* One line at rest, grown by autoGrow() to at most three. Past that the\n\t\t   thread above is worth more than the draft below, so it scrolls. */\n\t\tmax-height: 78px;\n\t\toverflow-y: auto;\n\t\t/* Hide the scrollbar track: on always-visible-scrollbar setups it drew a\n\t\t   grey bar down the composer's right edge, outside the rounded corner, and\n\t\t   read as a stray divider between the box and the send button. Still\n\t\t   scrolls, just without the rail. */\n\t\tscrollbar-width: none;\n\t\t-ms-overflow-style: none;\n\t\tpadding: 9px 13px;\n\t\tborder-radius: 18px;\n\t\tborder: 1px solid #262626;\n\t\tbackground: #161616;\n\t\tcolor: var(--color-text);\n\t\tfont-family: inherit;\n\t\tfont-size: 15px;\n\t\tline-height: 1.4;\n\t}\n\t.composer textarea::-webkit-scrollbar {\n\t\tdisplay: none;\n\t}\n\t/* the forms plugin paints a blue ring on focus; ours is a quiet purple edge */\n\t.composer textarea:focus {\n\t\toutline: none;\n\t\tborder-color: color-mix(in srgb, var(--color-purple) 70%, var(--color-border));\n\t\tbox-shadow: none;\n\t}\n\t.send {\n\t\twidth: 38px;\n\t\theight: 38px;\n\t\tborder-radius: 50%;\n\t\tborder: none;\n\t\tbackground: var(--color-purple);\n\t\tcolor: #fff;\n\t\tcursor: pointer;\n\t\tflex-shrink: 0;\n\t\tdisplay: inline-flex;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tpadding: 0;\n\t}\n\t.send svg {\n\t\twidth: 19px;\n\t\theight: 19px;\n\t}\n\t.send:disabled {\n\t\topacity: 0.35;\n\t\tcursor: default;\n\t}\n\t/* photos the character sends: a locked tile (free), a developing tile (Pro) */\n\t.ptile { position: relative; width: 250px; max-width: 78%; aspect-ratio: 3 / 4; border-radius: 20px; overflow: hidden; background: #17121f; border: 0; padding: 0; cursor: pointer; box-shadow: 0 14px 34px rgba(0, 0, 0, 0.55); color: #fff; font: inherit; text-align: center; }\n\t.ptile--dev { cursor: default; }\n\t.pblob { position: absolute; border-radius: 50%; filter: blur(46px); animation: pbreathe 6s ease-in-out infinite; }\n\t.b1 { width: 76%; height: 61%; left: -12%; top: 10%; background: #6d3a7a; opacity: 0.85; }\n\t.b2 { width: 64%; height: 51%; right: -16%; top: 35%; background: #9a5a4a; opacity: 0.7; animation-delay: -2s; }\n\t.b3 { width: 56%; height: 45%; left: 20%; bottom: -13%; background: #3e2f6e; opacity: 0.9; animation-delay: -4s; }\n\t@keyframes pbreathe { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.06); } }\n\t.psheen { position: absolute; top: -40%; left: 0; width: 70px; height: 180%; background: rgba(255, 255, 255, 0.07); filter: blur(10px); animation: psheen 4.5s ease-in-out infinite; }\n\t@keyframes psheen { 0% { transform: translateX(-120%) rotate(18deg); } 60%, 100% { transform: translateX(380%) rotate(18deg); } }\n\t.ptile-in { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; padding: 20px; }\n\t.ptile-in b { font-family: var(--font-display); font-size: 18px; font-weight: 700; letter-spacing: -0.01em; line-height: 1.15; }\n\t.ptile-in small { font-size: 13px; color: rgba(255, 255, 255, 0.72); line-height: 1.4; }\n\t.plock { width: 52px; height: 52px; border-radius: 50%; background: rgba(255, 255, 255, 0.12); display: grid; place-items: center; }\n\t.pcta { margin-top: 4px; height: 42px; padding: 0 20px; border-radius: 999px; background: #fff; color: #17121f; display: inline-flex; align-items: center; font: 700 14px var(--font-body); }\n\t.pbar { position: absolute; left: 20px; right: 20px; bottom: 20px; height: 3px; border-radius: 2px; background: rgba(255, 255, 255, 0.15); overflow: hidden; }\n\t.pbar span { display: block; height: 100%; background: rgba(255, 255, 255, 0.85); transform-origin: left; transition: transform 0.5s linear; }\n\t.pspin { width: 28px; height: 28px; border-radius: 50%; border: 3px solid rgba(255, 255, 255, 0.25); border-top-color: #fff; animation: pspin 0.9s linear infinite; }\n\t@keyframes pspin { to { transform: rotate(360deg); } }\n\t.photo-label { align-self: flex-start; margin: -4px 0 0 2px; font-size: 11px; color: #6b6b6b; }\n\t.photo-note { margin: 0; font-size: 12px; color: #8a8a8a; font-style: italic; }\n\t@media (prefers-reduced-motion: reduce) {\n\t\t.pblob, .psheen, .pspin { animation: none; }\n\t}\n</style>\n"],"version":3}