{"release_id":"01a1c502-4ad1-0000-ec62-adc54d3c69aa","chunk_id":"01a1c502-4d22-7bb0-8030-fe0ff022e499","file":"98.DVaBDkzI.mjs","ignoreList":[],"mappings":"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","names":["onward","$$anchor","div","root_2","div_1","$.sibling","$.first_child","fragment","$$props","f","a","root_4","getAvatarUrl","$.get","DuelAvatar","node_1","span","span_1","span_2","$.set_text","text_1","$$render","consequent","RATE_SCALE","status","$.state","$.proxy","stage","queue","comments","myVote","$.user_effect","$.set","viewerCount","stageImgFailed","composer","hostScore","hostLine","busy","toast","now","copied","origin","feedEl","chatOpen","stickToBottom","pinFeed","el","t","onScroll","ro","shareUrl","ended","$.derived","onStage","iAmOnStage","advanceAsked","secsLeft","invalidateAll","clockPct","clockText","verdictIn","tick","flash","m","fmt","x","sendHeartbeat","onVisible","onMount","clockTimer","heartbeatTimer","unsub","realtime","event","p","c","ch","createSupabaseBrowserClient","s","post","path","body","opts","res","j","broke","sendComment","vote","score","entryId","deliver","skip","cut","extend","raiseTipWall","amount","trackTokenWallHit","short","buyTokensReason","buyTokensSource","showBuyTokens","tip","endAsk","ending","endRoom","r","copyLink","shareNative","shareOrCopy","fileInput","uploading","pickerOpen","picked","openQueuePicker","trackFloorGetInLineTapped","chooseRateOption","o","submitToQueue","clean","stripExifClient","fd","trackFloorQueueJoined","$.head","div_2","fragment_1","div_3","div_4","div_5","a_1","$.child","node_2","span_3","root_5","$.template_effect","text_2","consequent_1","button","root_6","consequent_2","div_6","div_7","span_4","span_5","root_7","classes_1","span_6","root_8","consequent_3","consequent_4","button_1","root_9","$.delegated","consequent_5","img","root_11","$.event","div_8","root_12","button_2","consequent_6","alternate","div_9","node_7","span_7","div_10","root_13","div_11","root_14","span_8","span_9","root_15","text_6","button_3","root_16","span_10","root_17","consequent_7","consequent_8","consequent_9","text_5","consequent_10","div_12","node_9","div_13","a_2","root_18","span_11","root_19","consequent_11","alternate_1","span_12","node_11","div_14","span_13","span_14","text_10","consequent_12","div_15","root_20","span_15","span_16","button_4","text_11","text_12","consequent_13","classes_2","$.set_style","div_16","root_21","button_5","root_23","consequent_15","consequent_16","node_16","alternate_2","consequent_17","consequent_14","alternate_3","div_17","div_18","root_26","span_17","div_19","$.each","n","button_6","root_27","text_14","$.reset","classes_4","$.append","classes_3","text_13","consequent_18","div_20","fragment_6","div_21","p_1","p_2","div_22","span_18","button_7","button_8","text_15","text_16","consequent_19","div_23","root_30","span_19","span_20","span_21","root_31","text_21","button_9","root_32","span_22","root_33","consequent_20","consequent_21","consequent_22","text_19","text_20","consequent_23","button_10","root_34","span_23","TokenCoin","text_23","consequent_24","button_11","root_35","span_24","text_24","consequent_25","input","node_24","$$value","consequent_26","div_24","node_19","button_12","span_25","div_25","div_26","root_37","span_26","p_3","text_26","text_27","div_27","root_38","b","node_27","b_1","text_28","text_29","div_28","root_39","div_29","node_28","div_30","div_31","span_27","UserBadge","node_29","span_28","root_40","consequent_29","div_32","classes_6","$.set_class","text_30","text_31","consequent_28","alternate_4","div_33","root_41","div_34","root_43","div_35","span_29","span_30","root_44","text_33","consequent_30","div_36","button_13","root_45","classes_7","div_37","input_1","button_14","text_32","div_38","root_46","div_39","root_47","button_15","root_48","span_31","span_32","text_35","text_36","consequent_32","form","node_35","input_2","$.remove_input_defaults","button_16","$.set_attribute","$0","e","consequent_31","alternate_5","consequent_33","div_40","root_49","button_17","div_41","div_42","div_43","div_44","opt","tooPoor","button_18","root_50","span_33","span_34","span_35","root_51","text_39","consequent_34","span_36","span_37","root_52","span_38","fragment_10","span_39","root_54","consequent_36","classes_8","text_40","consequent_35","alternate_6","text_38","consequent_37","div_45","root_55","button_19","div_46","div_47","button_20","button_21","consequent_38","div_48","root_56","consequent_39","BuyTokensModal","node_44","classes","classes_5","openAuthSheet"],"sources":["../../../../../../src/routes/floor/[id]/+page.svelte"],"sourcesContent":["<script lang=\"ts\">\n\timport { onMount, tick } from 'svelte';\n\timport { invalidateAll } from '$app/navigation';\n\timport { openAuthSheet } from '$lib/stores/authSheet';\n\timport { realtime } from '$lib/realtime/client';\n\timport { shareOrCopy } from '$lib/share';\n\timport TokenCoin from '$lib/components/TokenCoin.svelte';\n\timport DuelAvatar from '$lib/components/duel/DuelAvatar.svelte';\n\timport UserBadge from '$lib/components/UserBadge.svelte';\n\timport BuyTokensModal from '$lib/components/BuyTokensModal.svelte';\n\timport { getAvatarUrl } from '$lib/avatar';\n\timport { trackFloorGetInLineTapped, trackFloorQueueJoined, trackTokenWallHit } from '$lib/analytics';\n\timport { stripExifClient } from '$lib/utils/strip-exif-client';\n\n\tlet { data } = $props();\n\n\tconst RATE_SCALE = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];\n\n\t// RAT-117: everything below is seeded from `data` inside an $effect, so an\n\t// invalidateAll() (which is how the stage advances after a verdict) actually\n\t// re-syncs instead of leaving frozen locals.\n\tlet status = $state(data.session.status);\n\tlet stage = $state(data.stage);\n\tlet queue = $state(data.queue);\n\tlet comments = $state(data.comments);\n\t// seeded from the load: a viewer who already voted on the entry on stage must\n\t// not be shown the rate row again. The floor_stage handler nulls this before\n\t// invalidateAll(), so a fresh entry re-opens voting.\n\tlet myVote = $state<number | null>(data.myVote);\n\t$effect(() => {\n\t\tstatus = data.session.status;\n\t\tstage = data.stage;\n\t\tqueue = data.queue;\n\t\tcomments = data.comments;\n\t\tmyVote = data.myVote;\n\t});\n\n\tlet viewerCount = $state(1);\n\t// the stage image is a signed URL minted server-side; it can expire or fail to\n\t// load (network / storage blip), which used to leave a broken/black stage with\n\t// no way out. Track the failed URL (not a bool) so a new entry — a different\n\t// imageUrl — clears the fallback automatically without a reset effect.\n\tlet stageImgFailed = $state<string | null>(null);\n\tlet composer = $state('');\n\tlet hostScore = $state<number | null>(null);\n\tlet hostLine = $state('');\n\tlet busy = $state(false);\n\tlet toast = $state('');\n\tlet now = $state(Date.now());\n\tlet copied = $state(false);\n\tlet origin = $state('');\n\tlet feedEl = $state<HTMLDivElement | null>(null);\n\n\t// New UI state (redesign): on mobile the chat feed collapses behind a\n\t// tap-to-open header. Svelte-driven, not a CSS-only checkbox. Desktop ignores\n\t// it — the feed is always shown there.\n\tlet chatOpen = $state(false);\n\n\t// The feed follows the newest message unless the reader has scrolled up. Only\n\t// their own scrolling flips this — nothing else may decide for them.\n\tlet stickToBottom = $state(true);\n\n\tfunction pinFeed() {\n\t\tif (feedEl) feedEl.scrollTop = feedEl.scrollHeight;\n\t}\n\n\t// Everything above the feed moves: the stage swaps, the verdict block appears,\n\t// the queue strip changes, and for the host `.judging` collapses the feed to\n\t// 118px. Each one resizes the feed, and a resize leaves scrollTop where it was\n\t// — which after a shrink means stranded at the OLDEST message. That was\n\t// invisible while the feed couldn't scroll; now it's the bug. So re-pin on\n\t// resize rather than chase each cause, and let the reader's own scroll be the\n\t// only thing that opts out.\n\t$effect(() => {\n\t\tconst el = feedEl;\n\t\tif (!el) return;\n\t\t// The read is debounced on purpose. A shrink fires its own scroll event\n\t\t// BEFORE the ResizeObserver runs, and at that instant the feed measures as\n\t\t// \"scrolled away from the bottom\" — reading it there flips stickToBottom\n\t\t// off and the observer then refuses to re-pin, which is exactly how the\n\t\t// feed ended up stranded at the oldest message. Waiting lets the observer\n\t\t// pin first, so what we finally measure is where the reader actually is.\n\t\tlet t: ReturnType<typeof setTimeout>;\n\t\tconst onScroll = () => {\n\t\t\tclearTimeout(t);\n\t\t\tt = setTimeout(() => {\n\t\t\t\tstickToBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 60;\n\t\t\t}, 120);\n\t\t};\n\t\tel.addEventListener('scroll', onScroll, { passive: true });\n\t\tconst ro = new ResizeObserver(() => {\n\t\t\tif (stickToBottom) pinFeed();\n\t\t});\n\t\tro.observe(el);\n\t\treturn () => {\n\t\t\tclearTimeout(t);\n\t\t\tro.disconnect();\n\t\t\tel.removeEventListener('scroll', onScroll);\n\t\t};\n\t});\n\n\n\tconst shareUrl = $derived(origin ? `${origin}/floor/${data.session.id}` : '');\n\tconst ended = $derived(status === 'ended');\n\tconst onStage = $derived(stage !== null);\n\tconst iAmOnStage = $derived(!!stage && stage.userId === data.viewerId);\n\t// when the local clock hits zero, ask the server for the new stage — the\n\t// cron sweep is the backstop, but a viewer sitting on 0:00 shouldn't have\n\t// to wait for it (or, in local dev, for a cron that never runs).\n\tlet advanceAsked = false;\n\t$effect(() => {\n\t\tif (stage?.deadlineAt && secsLeft === 0 && !advanceAsked && status === 'live') {\n\t\t\tadvanceAsked = true;\n\t\t\tsetTimeout(() => {\n\t\t\t\tinvalidateAll().finally(() => (advanceAsked = false));\n\t\t\t}, 1200);\n\t\t}\n\t});\n\t// the clock is the product: it caps the queue's wall-clock, sets the host's\n\t// pace, and is the scarcity that makes a paid extend mean anything.\n\tconst secsLeft = $derived(\n\t\tstage?.deadlineAt ? Math.max(0, Math.round((new Date(stage.deadlineAt).getTime() - now) / 1000)) : 0\n\t);\n\tconst clockPct = $derived(\n\t\tstage?.deadlineAt ? Math.min(100, (secsLeft / data.session.secondsPerEntry) * 100) : 0\n\t);\n\tconst clockText = $derived(`${Math.floor(secsLeft / 60)}:${String(secsLeft % 60).padStart(2, '0')}`);\n\tconst verdictIn = $derived(stage?.hostScore !== null && stage?.hostScore !== undefined);\n\n\t// Re-pin on every state that changes the feed's height. These are the actual\n\t// causes, so handle them directly rather than leaning on the observer above:\n\t// messages arriving, the list being swapped wholesale by invalidateAll() on a\n\t// stage change (the load returns its own window of 40), and each block above\n\t// the feed coming and going — the stage, the verdict, the rate row, and the\n\t// host's judging layout, which collapses the feed to 118px.\n\t$effect(() => {\n\t\tcomments.length;\n\t\tstage;\n\t\tverdictIn;\n\t\tmyVote;\n\t\tif (!stickToBottom) return;\n\t\ttick().then(pinFeed);\n\t});\n\n\tfunction flash(m: string) {\n\t\ttoast = m;\n\t\tsetTimeout(() => (toast = ''), 2200);\n\t}\n\tconst fmt = (x: number) => (x / 100).toFixed(1);\n\n\t// Host heartbeat. sweepStaleLiveSessions ends ANY open row ('live' or, since\n\t// the presence fix, 'pending') whose last_seen_at goes 120s stale — it doesn't\n\t// care about kind — so without this the every-minute cron would kill every\n\t// floor about two minutes after it opened. It's also what keeps the room in\n\t// the /r grid: a floor sitting in 'pending' waiting for its first submission\n\t// is listed only while these pings keep coming. Reuses the livecam endpoint\n\t// verbatim: markLiveHeartbeat matches on id + raterId + status only, so it's\n\t// already kind-agnostic.\n\t// Like livecam, we deliberately DON'T end on visibilitychange/pagehide: those\n\t// fire on every mobile app-switch, which would wrongly kill a running show.\n\tfunction sendHeartbeat() {\n\t\tfetch(`/api/livecam/${data.session.id}/heartbeat`, { method: 'POST', keepalive: true }).catch(() => {});\n\t}\n\tfunction onVisible() {\n\t\tif (document.visibilityState === 'visible') sendHeartbeat();\n\t}\n\n\tonMount(() => {\n\t\torigin = window.location.origin;\n\t\t// not `tick`: that name is Svelte's tick() now, imported for the feed pin.\n\t\tconst clockTimer = setInterval(() => (now = Date.now()), 500);\n\n\t\tlet heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n\t\tif (data.isOwner) {\n\t\t\tsendHeartbeat();\n\t\t\theartbeatTimer = setInterval(sendHeartbeat, 20000);\n\t\t\tdocument.addEventListener('visibilitychange', onVisible);\n\t\t}\n\n\t\tconst unsub = realtime.subscribe(`live:${data.session.id}`, (event: { type: string; payload: any }) => {\n\t\t\tconst p = event.payload ?? {};\n\t\t\tif (event.type === 'live_comment_new') {\n\t\t\t\t// dedupe by id, like the livecam room does: socket and replay can\n\t\t\t\t// overlap, and a duplicate id in the keyed {#each} throws and tears\n\t\t\t\t// the page down mid-render.\n\t\t\t\tif (comments.some((c) => c.id === p.id)) return;\n\t\t\t\tcomments = [...comments, p];\n\t\t\t} else if (event.type === 'floor_stage') {\n\t\t\t\t// a new entry hit the stage — its image only exists from this moment,\n\t\t\t\t// so re-load rather than patch (the signed URL is minted server-side).\n\t\t\t\t// Deliberately NOT `myVote = null` here. That re-opened the rate row\n\t\t\t\t// instantly while `stage` still held the PREVIOUS entry — for the whole\n\t\t\t\t// round-trip of the reload. A vote landing in that window went to the\n\t\t\t\t// old entryId, which the viewer had already rated, so the unique index\n\t\t\t\t// rejected it: the row stayed up and the server's raw reason (\"duplicate\")\n\t\t\t\t// came out as a toast. The $effect below re-seeds myVote from the load,\n\t\t\t\t// which is the only source that knows which entry it belongs to.\n\t\t\t\tinvalidateAll();\n\t\t\t} else if (event.type === 'floor_queue') {\n\t\t\t\tqueue = { ...queue, depth: p.depth, nextNickname: p.nextNickname };\n\t\t\t} else if (event.type === 'floor_score_update') {\n\t\t\t\tif (stage && stage.entryId === p.entryId) {\n\t\t\t\t\tstage = { ...stage, avgScore: p.avgScore, ratingCount: p.ratingCount };\n\t\t\t\t}\n\t\t\t} else if (event.type === 'floor_verdict') {\n\t\t\t\tif (stage && stage.entryId === p.entryId) {\n\t\t\t\t\tstage = { ...stage, hostScore: p.score, verdict: p.verdict };\n\t\t\t\t}\n\t\t\t} else if (event.type === 'live_started') {\n\t\t\t\tstatus = 'live';\n\t\t\t\tinvalidateAll();\n\t\t\t} else if (event.type === 'live_ended') {\n\t\t\t\tstatus = 'ended';\n\t\t\t}\n\t\t});\n\n\t\t// presence: viewer count, ephemeral, no DB (mirrors the livecam room).\n\t\tlet ch: any = null;\n\t\t(async () => {\n\t\t\tconst { createSupabaseBrowserClient } = await import('$lib/supabase');\n\t\t\tconst supabase = createSupabaseBrowserClient();\n\t\t\tch = supabase.channel(`floor-presence:${data.session.id}`, {\n\t\t\t\tconfig: { presence: { key: data.viewerId ?? crypto.randomUUID() } }\n\t\t\t});\n\t\t\tch.on('presence', { event: 'sync' }, () => {\n\t\t\t\tviewerCount = Object.keys(ch.presenceState() ?? {}).length || 1;\n\t\t\t});\n\t\t\tch.subscribe((s: string) => {\n\t\t\t\tif (s === 'SUBSCRIBED') ch.track({ at: Date.now() });\n\t\t\t});\n\t\t})();\n\n\t\treturn () => {\n\t\t\tclearInterval(clockTimer);\n\t\t\tif (heartbeatTimer) clearInterval(heartbeatTimer);\n\t\t\tdocument.removeEventListener('visibilitychange', onVisible);\n\t\t\tunsub?.();\n\t\t\tch?.unsubscribe?.();\n\t\t};\n\t});\n\n\tasync function post(path: string, body?: unknown, opts?: { onInsufficient?: () => void }) {\n\t\tif (busy) return null;\n\t\tbusy = true;\n\t\ttry {\n\t\t\tconst res = await fetch(path, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: body ? { 'content-type': 'application/json' } : {},\n\t\t\t\tbody: body ? JSON.stringify(body) : undefined\n\t\t\t});\n\t\t\tconst j = await res.json().catch(() => ({}));\n\t\t\tif (!res.ok) {\n\t\t\t\tconst broke = j.error === 'insufficient' || j.error === 'insufficient_tokens';\n\t\t\t\t// Let the caller own the short-on-tokens case (it can raise the buy\n\t\t\t\t// sheet); everything else still falls back to the toast.\n\t\t\t\tif (broke && opts?.onInsufficient) opts.onInsufficient();\n\t\t\t\telse flash(broke ? 'not enough tokens' : (j.error ?? 'failed'));\n\t\t\t}\n\t\t\treturn res.ok ? j : null;\n\t\t} finally {\n\t\t\tbusy = false;\n\t\t}\n\t}\n\n\tasync function sendComment() {\n\t\tconst body = composer.trim();\n\t\tif (!body) return;\n\t\tcomposer = '';\n\t\tawait fetch(`/api/livecam/${data.session.id}/comment`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: { 'content-type': 'application/json' },\n\t\t\tbody: JSON.stringify({ body })\n\t\t});\n\t}\n\n\tasync function vote(score: number) {\n\t\tif (!stage) return;\n\t\tconst entryId = stage.entryId;\n\t\tconst res = await fetch(`/api/floor/${data.session.id}/rate`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: { 'content-type': 'application/json' },\n\t\t\tbody: JSON.stringify({ entryId, score })\n\t\t});\n\t\tif (res.ok) {\n\t\t\tmyVote = score;\n\t\t\treturn;\n\t\t}\n\t\t// 409 = we voted on this entry already, so what's on screen is stale.\n\t\t// Resync rather than flash the server's enum at someone who did nothing\n\t\t// wrong; the reload closes the row on its own.\n\t\tif (res.status === 409) {\n\t\t\tawait invalidateAll();\n\t\t\treturn;\n\t\t}\n\t\tconst j = await res.json().catch(() => ({}));\n\t\tflash(j.error === 'out_of_range' ? 'pick 1 to 10' : 'could not send your score');\n\t}\n\n\tasync function deliver() {\n\t\tif (!stage || hostScore === null) return;\n\t\tconst r = await post(`/api/floor/${data.session.id}/verdict`, {\n\t\t\tentryId: stage.entryId,\n\t\t\tscore: hostScore,\n\t\t\tverdict: hostLine.trim() || null\n\t\t});\n\t\tif (r) {\n\t\t\thostScore = null;\n\t\t\thostLine = '';\n\t\t}\n\t}\n\n\tconst skip = async () => stage && (await post(`/api/floor/${data.session.id}/skip`, { entryId: stage.entryId })) && invalidateAll();\n\tconst cut = async () => (await post(`/api/floor/${data.session.id}/cut`)) && invalidateAll();\n\tconst extend = async () => (await post(`/api/floor/${data.session.id}/extend`)) && invalidateAll();\n\t// `amount`, not `tokens` — the server reads body.amount, so the old key meant\n\t// Number(undefined) → NaN → a silent 400 on every tip.\n\t// Raise the in-context buy-tokens sheet for a reaction chip. Called from two\n\t// places: the optimistic pre-check below, and the server's 402 — the pre-check\n\t// alone is not enough, because `data.viewerTokens` is the balance from page\n\t// load and a few successful tips leave it stale (a 5-token chip then sails\n\t// past the client check and dead-ends on a toast, while a 15-token one still\n\t// opens the sheet — exactly the split this was reported for).\n\tfunction raiseTipWall(amount: number) {\n\t\ttrackTokenWallHit({\n\t\t\tsurface: 'floor_tip',\n\t\t\tsessionId: data.session.id,\n\t\t\ttokensRequired: amount,\n\t\t\ttokensHeld: data.viewerTokens\n\t\t});\n\t\tconst short = Math.max(1, amount - data.viewerTokens);\n\t\tbuyTokensReason = `you're ${short} token${short === 1 ? '' : 's'} short for that one — top up to react.`;\n\t\tbuyTokensSource = 'floor_tip';\n\t\tshowBuyTokens = true;\n\t}\n\n\tconst tip = async (amount: number) => {\n\t\t// Fast path: skip a request we know will 402.\n\t\tif (data.viewerTokens < amount) {\n\t\t\traiseTipWall(amount);\n\t\t\treturn;\n\t\t}\n\t\tconst r = await post(`/api/floor/${data.session.id}/tip`, { amount }, {\n\t\t\tonInsufficient: () => raiseTipWall(amount)\n\t\t});\n\t\tif (r) {\n\t\t\tflash(`tipped ${amount} 🪙`);\n\t\t\t// Re-read the balance so the next chip's pre-check isn't spending\n\t\t\t// tokens this tip already consumed.\n\t\t\tawait invalidateAll();\n\t\t}\n\t};\n\n\t// The host's kill switch, asked in a sheet rather than with confirm(): on\n\t// iOS Safari the native dialog left \"end\" looking dead (98% of its rage\n\t// clicks came from Mobile Safari). The room turns \"ended\" only once the\n\t// server says so; it used to flip locally when the request failed, or when\n\t// post() skipped it because another request was still in flight.\n\tlet endAsk = $state(false);\n\tlet ending = $state(false);\n\tasync function endRoom() {\n\t\tif (ending) return;\n\t\tending = true;\n\t\tconst r = await post(`/api/floor/${data.session.id}/end`);\n\t\tending = false;\n\t\tif (!r) return; // post() has already flashed why\n\t\tendAsk = false;\n\t\tstatus = 'ended';\n\t}\n\n\tfunction copyLink() {\n\t\tif (!shareUrl) return;\n\t\tnavigator.clipboard?.writeText(shareUrl).then(() => {\n\t\t\tcopied = true;\n\t\t\tsetTimeout(() => (copied = false), 1800);\n\t\t});\n\t}\n\tconst shareNative = () =>\n\t\tshareOrCopy({ url: shareUrl, title: 'get rated live · ratemyd', text: 'get in line — rated live' });\n\n\t// ── join the line\n\tlet fileInput = $state<HTMLInputElement | null>(null);\n\tlet uploading = $state(false);\n\n\t// RAT-442: the options used to sit open under the queue strip, eating the\n\t// viewer's screen — the one thing a floor can't spare — to answer a question\n\t// nobody had asked yet. Now \"get in line\" opens the picker, same as the DM\n\t// rate sheet. No default: the sheet is the only way in, so nothing is\n\t// pre-selected and nobody gets charged for a tier they never chose.\n\tlet pickerOpen = $state(false);\n\tlet picked = $state<string | null>(null);\n\t// Short-on-tokens now opens the same in-context buy-tokens sheet livecam uses,\n\t// instead of a dead-end toast. `buyTokensReason` carries the tier-specific gap.\n\tlet showBuyTokens = $state(false);\n\tlet buyTokensReason = $state<string | null>(null);\n\t// Which wall raised the sheet — keeps the buy-tokens funnel splittable\n\t// (queue tier vs reaction chip) instead of collapsing both into one source.\n\tlet buyTokensSource = $state('floor_queue');\n\n\t// \"get in line\" tap → open the package picker. Its own handler so the funnel\n\t// entry point is instrumented (the whole flow was previously uninstrumented).\n\tfunction openQueuePicker() {\n\t\ttrackFloorGetInLineTapped(data.session.id);\n\t\tpickerOpen = true;\n\t}\n\n\tfunction chooseRateOption(o: { id: string; kind: 'free' | 'paid'; tokens: number }) {\n\t\tpickerOpen = false;\n\t\t// Out of reach: catch it here rather than after they've picked a file and\n\t\t// eaten a 402. Instead of a dead-end toast, offer the in-context buy-tokens\n\t\t// sheet (same as livecam) so they can top up and get back in line — and log\n\t\t// the wall so the abandonment is finally visible in the funnel.\n\t\tif (o.kind === 'paid' && data.viewerTokens < o.tokens) {\n\t\t\ttrackTokenWallHit({\n\t\t\t\tsurface: 'floor_queue',\n\t\t\t\tsessionId: data.session.id,\n\t\t\t\ttokensRequired: o.tokens,\n\t\t\t\ttokensHeld: data.viewerTokens\n\t\t\t});\n\t\t\tconst short = o.tokens - data.viewerTokens;\n\t\t\tbuyTokensReason = `you're ${short} token${short === 1 ? '' : 's'} short for that one — top up to get in line.`;\n\t\t\tbuyTokensSource = 'floor_queue';\n\t\t\tshowBuyTokens = true;\n\t\t\treturn;\n\t\t}\n\t\tpicked = o.id;\n\t\tfileInput?.click();\n\t}\n\n\tasync function submitToQueue(e: Event) {\n\t\tconst f = (e.target as HTMLInputElement).files?.[0];\n\t\tif (!f || uploading || !picked) return;\n\t\tuploading = true;\n\t\t// iPhones default to HEIC, which the server's sharp build can't decode\n\t\t// (no libheif) — the join used to fail with a bare \"could not join\" after\n\t\t// the escrow was refunded. Convert (+ strip EXIF, downscale) client-side\n\t\t// first, same as every other upload surface. Falls back to the original\n\t\t// file on any failure, so a browser without the conversion path isn't\n\t\t// hard-blocked (gets the pre-fix behaviour instead).\n\t\tconst clean = await stripExifClient(f);\n\t\tconst fd = new FormData();\n\t\tfd.set('file', clean);\n\t\tfd.set('packageId', picked);\n\t\ttry {\n\t\t\tconst res = await fetch(`/api/floor/${data.session.id}/queue`, { method: 'POST', body: fd });\n\t\t\tconst j = await res.json().catch(() => ({}));\n\t\t\tif (!res.ok) {\n\t\t\t\ttrackFloorQueueJoined(data.session.id, 'failure');\n\t\t\t\tflash(j.error === 'insufficient_tokens' ? 'not enough tokens' : (j.error ?? 'could not join'));\n\t\t\t} else {\n\t\t\t\ttrackFloorQueueJoined(data.session.id, 'success');\n\t\t\t\tawait invalidateAll();\n\t\t\t\t// Confirm the join landed — the flow only ever surfaced a toast on\n\t\t\t\t// FAILURE before, so a successful join looked like nothing happened\n\t\t\t\t// and viewers re-tapped \"get in line\".\n\t\t\t\tflash(\"you're in line ✓\");\n\t\t\t}\n\t\t} finally {\n\t\t\tuploading = false;\n\t\t\tif (fileInput) fileInput.value = '';\n\t\t}\n\t}\n</script>\n\n<svelte:head><title>{data.session.hostNick ? `@${data.session.hostNick}` : 'public stage'} · ratemyd</title></svelte:head>\n\n<!-- RAT-459: onward CTA for the dead-end overlays (empty floor / closed floor).\n\t `data.otherFloors` is the same \"live + occupied right now\" list the discovery\n\t rail is built from, so every row is a room that can actually put on a show. If\n\t none are live, we still hand over the two standing exits (feed + raters) rather\n\t than leaving the viewer stranded. -->\n{#snippet onward()}\n\t<div class=\"ov-onward\">\n\t\t{#if data.otherFloors.length > 0}\n\t\t\t<span class=\"ov-onward-k\">another stage is live right now</span>\n\t\t\t<div class=\"ov-floors\">\n\t\t\t\t{#each data.otherFloors as f (f.id)}\n\t\t\t\t\t<a class=\"ov-floor\" href=\"/floor/{f.id}\">\n\t\t\t\t\t\t<DuelAvatar nickname={f.nickname} avatarUrl={getAvatarUrl(f.avatarPath)} size={34} link={false} />\n\t\t\t\t\t\t<span class=\"ov-floor-main\">\n\t\t\t\t\t\t\t<span class=\"ov-floor-nick\">@{f.nickname ?? 'host'}</span>\n\t\t\t\t\t\t\t<span class=\"ov-floor-sub\">{f.queueDepth ? `${f.queueDepth} in line` : 'rating live'}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span class=\"ov-floor-join\">join</span>\n\t\t\t\t\t</a>\n\t\t\t\t{/each}\n\t\t\t</div>\n\t\t{/if}\n\t\t<div class=\"ov-exits\">\n\t\t\t<a class=\"ov-exit\" href=\"/f\">← back to the feed</a>\n\t\t\t<a class=\"ov-exit\" href=\"/r\">browse live raters →</a>\n\t\t</div>\n\t</div>\n{/snippet}\n\n<div class=\"room\" class:judging={data.isOwner && onStage && !verdictIn} class:vacant={!onStage} class:owner={data.isOwner}>\n\t<!-- ───────── STAGE (left column / mobile top) ─────────\n\t\t the ONLY place an entry is ever visible. -->\n\t<div class=\"stage\">\n\t\t<!-- RAT-443: the room's chrome, floated over the stage. It used to be a\n\t\t\t 53px strip of its own — 7% of a phone, about two chat messages, to\n\t\t\t carry a back arrow and two things the clock already implies.\n\t\t\t OUTSIDE the imageUrl branch on purpose: everything below only renders\n\t\t\t with an entry on stage, and `←` is the only way out of this route\n\t\t\t (Navbar, BottomNav, ChatFab and the announcement bar all hide here),\n\t\t\t so an empty room would have no exit.\n\t\t\t Each piece carries its own pill: landing on the image is fine, being\n\t\t\t unreadable on a bright one is not. -->\n\t\t<div class=\"fl fl-l\">\n\t\t\t<div class=\"fl-row\">\n\t\t\t<!-- back goes to the chat, not the site root: you arrive here from a\n\t\t\t\t link in a thread, and the room is a detour off it. For a signed-out\n\t\t\t\t viewer /c is gated, so raise the in-place sign-in sheet (with /c as\n\t\t\t\t `next`) rather than bouncing them to a full-page /auth redirect. -->\n\t\t\t<a\n\t\t\t\tclass=\"fchip fbtn\"\n\t\t\t\thref=\"/c\"\n\t\t\t\taria-label=\"back to chat\"\n\t\t\t\tonclick={(e) => {\n\t\t\t\t\tif (data.isAuthed) return;\n\t\t\t\t\te.preventDefault();\n\t\t\t\t\topenAuthSheet('/c');\n\t\t\t\t}}>←</a\n\t\t\t>\n\t\t\t<!-- The only name that belongs up here is whoever is on the stage —\n\t\t\t\t that's what the room is watching. The host's name was taking a whole\n\t\t\t\t chip to say something the score row already says, and it pushed this\n\t\t\t\t tag onto a second line. -->\n\t\t\t{#if stage?.imageUrl}\n\t\t\t\t<span class=\"fchip fstag\">{iAmOnStage ? 'you' : `@${stage.nickname}`} · on stage</span>\n\t\t\t{/if}\n\t\t\t</div>\n\t\t\t<!-- Own row, host only. It was bottom-right — I'd checked that hosts never\n\t\t\t\t render .sc-ov and called that corner dead, but missed .q-ov, which is\n\t\t\t\t host-ONLY, spans the full width of the stage bottom and carries `skip`.\n\t\t\t\t They landed on each other. Here it's the furthest point from both skip\n\t\t\t\t (bottom-right) and share (top-right), and it survives an empty stage,\n\t\t\t\t which .q-ov does not — this is the only kill switch a live room has. -->\n\t\t\t{#if data.isOwner && !ended}\n\t\t\t\t<button class=\"fchip fend\" onclick={() => (endAsk = true)}>end</button>\n\t\t\t{/if}\n\t\t</div>\n\t\t<div class=\"fl fl-r\">\n\t\t\t<div class=\"fl-row\">\n\t\t\t\t<span class=\"fchip fviewers\">\n\t\t\t\t\t<svg class=\"eye\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\">\n\t\t\t\t\t\t<path d=\"M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z\" /><circle cx=\"12\" cy=\"12\" r=\"3\" />\n\t\t\t\t\t</svg>\n\t\t\t\t\t{viewerCount}\n\t\t\t\t</span>\n\t\t\t\t{#if stage?.imageUrl}\n\t\t\t\t\t<span class=\"fchip cd-chip\" class:warn={secsLeft <= 10}>{clockText}</span>\n\t\t\t\t{:else if ended}\n\t\t\t\t\t<span class=\"fchip fstate\">ended</span>\n\t\t\t\t{/if}\n\t\t\t</div>\n\t\t\t{#if !ended}\n\t\t\t\t<button class=\"fchip fbtn\" onclick={shareNative} aria-label=\"share this room\" title=\"share this room\">\n\t\t\t\t\t<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n\t\t\t\t\t\t<circle cx=\"18\" cy=\"5\" r=\"3\" /><circle cx=\"6\" cy=\"12\" r=\"3\" /><circle cx=\"18\" cy=\"19\" r=\"3\" />\n\t\t\t\t\t\t<line x1=\"8.6\" y1=\"10.5\" x2=\"15.4\" y2=\"6.5\" /><line x1=\"8.6\" y1=\"13.5\" x2=\"15.4\" y2=\"17.5\" />\n\t\t\t\t\t</svg>\n\t\t\t\t</button>\n\t\t\t{/if}\n\t\t</div>\n\t\t{#if stage?.imageUrl}\n\t\t\t{#if stageImgFailed !== stage.imageUrl}\n\t\t\t\t<img src={stage.imageUrl} alt=\"\" onerror={() => (stageImgFailed = stage?.imageUrl ?? null)} />\n\t\t\t{:else}\n\t\t\t\t<!-- The signed image URL failed to load (expired / storage blip). Swap\n\t\t\t\t\t ONLY the photo — everything else in this branch has to survive it:\n\t\t\t\t\t the countdown, the viewer's \"get in line\" chip, and above all the\n\t\t\t\t\t host's `skip`, which is the only kill switch a live room has. A\n\t\t\t\t\t broken photo is exactly when the host needs to skip past it. -->\n\t\t\t\t<div class=\"ov\">\n\t\t\t\t\t<p class=\"ov-title\">couldn't load the photo</p>\n\t\t\t\t\t<small class=\"ov-sub\">the image didn't come through</small>\n\t\t\t\t\t<button class=\"ov-share\" onclick={() => invalidateAll()}>reload →</button>\n\t\t\t\t</div>\n\t\t\t{/if}\n\t\t\t<div class=\"cd\" class:warn={secsLeft <= 10}><span style=\"width:{clockPct}%\"></span></div>\n\t\t\t{#if !data.isOwner}\n\t\t\t\t<div class=\"sc-ov\">\n\t\t\t\t\t<!-- RAT: the queue lives here on mobile — a small \"N in line\" chip\n\t\t\t\t\t\t pinned left (margin-right:auto) so the scores stay pushed right and\n\t\t\t\t\t\t the standalone queue row is reclaimed for chat height. The full\n\t\t\t\t\t\t queue panel is hidden on mobile while someone is on stage (see the\n\t\t\t\t\t\t .room:not(.vacant) rule); it returns for the empty-stage case. -->\n\t\t\t\t\t{#if !iAmOnStage && !ended}\n\t\t\t\t\t\t<div class=\"sc-queue\">\n\t\t\t\t\t\t\t<span class=\"scq-n\">{queue.depth}</span>\n\t\t\t\t\t\t\t<span class=\"scq-t\">in line</span>\n\t\t\t\t\t\t\t{#if queue.myPosition}\n\t\t\t\t\t\t\t\t<span class=\"scq-mine\">you're {queue.myPosition}{queue.myPosition === 1 ? 'st' : queue.myPosition === 2 ? 'nd' : queue.myPosition === 3 ? 'rd' : 'th'}</span>\n\t\t\t\t\t\t\t{:else if data.isAuthed && data.rateOptions.length > 0}\n\t\t\t\t\t\t\t\t<button class=\"scq-cta\" onclick={openQueuePicker} disabled={uploading}>\n\t\t\t\t\t\t\t\t\t{uploading ? 'sending…' : 'get in line →'}\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t{:else if data.isAuthed}\n\t\t\t\t\t\t\t\t<span class=\"scq-shut\">closed</span>\n\t\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t{/if}\n\t\t\t\t\t<!-- Scores wrapped so mobile can drop them onto their own line under the\n\t\t\t\t\t\t queue chip (the two used to collide on a narrow screen). -->\n\t\t\t\t\t<div class=\"sc-scores\">\n\t\t\t\t\t\t<div class=\"s host\">\n\t\t\t\t\t\t\t<!-- just \"host\" (not the nickname) so the bar stays one compact line\n\t\t\t\t\t\t\t\t on mobile; still links to the host's profile. -->\n\t\t\t\t\t\t\t{#if data.session.hostNick}\n\t\t\t\t\t\t\t\t<a class=\"k klink\" href=\"/u/{data.session.hostNick}\">host</a>\n\t\t\t\t\t\t\t{:else}\n\t\t\t\t\t\t\t\t<span class=\"k\">host</span>\n\t\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t\t<span class=\"v\">{stage.hostScore ?? '—'}</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<span class=\"sep\"></span>\n\t\t\t\t\t\t<!-- \"crowd\", NOT \"room\": this is inside the component whose ROOT is\n\t\t\t\t\t\t\t `.room` (fixed, inset-0, z-20, opaque bg), and Svelte scoping puts\n\t\t\t\t\t\t\t the same hash on both — so a class named `room` here inherits the\n\t\t\t\t\t\t\t root's rule and becomes a full-viewport black cover. That was the\n\t\t\t\t\t\t\t viewer black-screen bug: hosts never render this overlay, viewers\n\t\t\t\t\t\t\t always do. -->\n\t\t\t\t\t\t<div class=\"s crowd\">\n\t\t\t\t\t\t\t<span class=\"k\">the room</span>\n\t\t\t\t\t\t\t<span class=\"v\">{fmt(stage.avgScore)}</span>\n\t\t\t\t\t\t\t<span class=\"n\">{stage.ratingCount}</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t{/if}\n\t\t\t{#if data.isOwner}\n\t\t\t\t<div class=\"q-ov\">\n\t\t\t\t\t<span class=\"qs-n\">{queue.depth}</span>\n\t\t\t\t\t<span class=\"qs-t\">in line{queue.nextNickname ? ` · @${queue.nextNickname} next` : ''}</span>\n\t\t\t\t\t<button class=\"qs-cta ghost\" onclick={skip}>skip →</button>\n\t\t\t\t</div>\n\t\t\t{/if}\n\t\t{:else}\n\t\t\t<div class=\"ov\">\n\t\t\t\t{#if !ended}\n\t\t\t\t\t<span class=\"ov-eyebrow\"><span class=\"ov-live-dot\"></span>the stage is live</span>\n\t\t\t\t\t<p class=\"ov-title\">waiting for the first one</p>\n\t\t\t\t\t<small class=\"ov-sub\">the stage opens the moment someone lands</small>\n\t\t\t\t\t{#if data.isAuthed}\n\t\t\t\t\t\t<!-- an empty stage is the one moment a viewer can be first, and\n\t\t\t\t\t\t\t the room needs someone either way — so give them the link too.\n\t\t\t\t\t\t\t The owner gets it here on MOBILE (their getem card's big green\n\t\t\t\t\t\t\t share button is hidden there); on desktop the getem card carries\n\t\t\t\t\t\t\t it, so the owner's copy is hidden (see .room.owner .ov-share). -->\n\t\t\t\t\t\t<button class=\"ov-share\" onclick={shareNative}>share the room →</button>\n\t\t\t\t\t{/if}\n\t\t\t\t\t<!-- RAT-459: a VIEWER who landed on an empty floor has nothing to do\n\t\t\t\t\t\t but leave — so give them a live room to jump to instead of a dead\n\t\t\t\t\t\t end. The owner is kept out of this: their job is to fill THIS room\n\t\t\t\t\t\t (the getem card / share button), not to be nudged off it. -->\n\t\t\t\t\t{#if !data.isOwner}\n\t\t\t\t\t\t{@render onward()}\n\t\t\t\t\t{/if}\n\t\t\t\t{:else}\n\t\t\t\t\t<p class=\"ov-title\">the stage is closed</p>\n\t\t\t\t\t<small class=\"ov-sub\">thanks for coming</small>\n\t\t\t\t\t<!-- RAT-459: the closed floor was a terminal screen with no way out.\n\t\t\t\t\t\t Everyone here (host included) came for a live rate room — hand them\n\t\t\t\t\t\t another one, then the feed. -->\n\t\t\t\t\t{@render onward()}\n\t\t\t\t{/if}\n\t\t\t</div>\n\t\t{/if}\n\t</div>\n\n\t<!-- ───────── SIDEBAR (right column / mobile stack) ───────── -->\n\t<div class=\"side\">\n\t\t<!-- viewer: one-shot vote, retires into the scoreboard once cast. On mobile\n\t\t\t this is the sticky action bar under the stage. -->\n\t\t{#if stage && !data.isOwner && data.isAuthed && !iAmOnStage && !ended}\n\t\t\t<!-- After voting the row stays put: every button greys out and the one you\n\t\t\t\t picked stays lit — so \"your vote\" reads off the scale itself, and the\n\t\t\t\t scoreboard doesn't need a separate YOU chip. -->\n\t\t\t<div class=\"panel rate\" class:voted={myVote !== null}>\n\t\t\t\t<span class=\"rate-hint\">{myVote !== null ? 'your vote' : verdictIn ? 'now you — 1–10' : 'rate him 1–10'}</span>\n\t\t\t\t<div class=\"rate-row\">\n\t\t\t\t\t{#each RATE_SCALE as n (n)}\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclass=\"rb\"\n\t\t\t\t\t\t\tclass:picked={myVote === n}\n\t\t\t\t\t\t\tdisabled={busy || myVote !== null}\n\t\t\t\t\t\t\tonclick={() => vote(n)}>{n}</button\n\t\t\t\t\t\t>\n\t\t\t\t\t{/each}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t{/if}\n\n\t\t<!-- An empty line has exactly one job: get someone into it. So the link is\n\t\t\t here, in the room, not on a step the host already walked past.\n\t\t\t This used to be gated on status === 'pending', which meant it vanished\n\t\t\t the moment the floor went live — exactly when a line that empties out\n\t\t\t leaves the host staring at \"waiting for the first one\" with no way to\n\t\t\t do anything about it. Empty is empty, whatever the status says. -->\n\t\t{#if data.isOwner && !onStage && queue.depth === 0 && !ended}\n\t\t\t<div class=\"panel getem\">\n\t\t\t\t<div class=\"getem-top\">\n\t\t\t\t\t<span class=\"getem-eyebrow\"><span class=\"getem-dot\"></span>your stage is live</span>\n\t\t\t\t\t<p class=\"getem-title\">\n\t\t\t\t\t\t{status === 'pending' ? 'you need a first one' : 'the line is empty'}\n\t\t\t\t\t</p>\n\t\t\t\t\t<p class=\"getem-sub\">\n\t\t\t\t\t\t{status === 'pending'\n\t\t\t\t\t\t\t? \"the stage can't open until someone lands — drop the link where your people are\"\n\t\t\t\t\t\t\t: 'the room is live and waiting on you — drop the link to fill the line'}\n\t\t\t\t\t</p>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"linkfield\">\n\t\t\t\t\t<span class=\"url\">{shareUrl}</span>\n\t\t\t\t\t<button class=\"linkcopy\" onclick={copyLink}>{copied ? 'copied ✓' : 'copy'}</button>\n\t\t\t\t</div>\n\t\t\t\t<button class=\"shchip\" onclick={shareNative}>share the link →</button>\n\t\t\t\t<p class=\"nudge\">post it anywhere you already have people — one link is the whole line.</p>\n\t\t\t</div>\n\t\t\t<p class=\"blind\">the line is blind — nobody sees a thing until the stage, not even you.</p>\n\t\t{/if}\n\n\t\t<!-- The queue strip does two jobs, and both are for people who AREN'T on\n\t\t\t stage: depth is social proof for someone deciding to join, and the CTA\n\t\t\t is how they join. To the person on stage it's neither — the green\n\t\t\t \"you · on stage\" tag already says where they are, so a \"you're up\"\n\t\t\t here just repeats it, and \"0 in line\" answers a question they didn't\n\t\t\t ask. Hidden for them; their control is \"keep me up there\" below. -->\n\t\t{#if !data.isOwner && !ended}\n\t\t\t{#if !iAmOnStage}\n\t\t\t\t<div class=\"panel queue\">\n\t\t\t\t\t<span class=\"qs-n\">{queue.depth}</span>\n\t\t\t\t\t<span class=\"qs-t\">in line{queue.nextNickname ? ` · @${queue.nextNickname} next` : ''}</span>\n\t\t\t\t\t{#if queue.myPosition}\n\t\t\t\t\t\t<span class=\"qs-mine\">you're {queue.myPosition}{queue.myPosition === 1 ? 'st' : queue.myPosition === 2 ? 'nd' : queue.myPosition === 3 ? 'rd' : 'th'}</span>\n\t\t\t\t\t{:else if data.isAuthed && data.rateOptions.length > 0}\n\t\t\t\t\t\t<button class=\"qs-cta\" onclick={openQueuePicker} disabled={uploading}>\n\t\t\t\t\t\t\t{uploading ? 'sending…' : 'get in line →'}\n\t\t\t\t\t\t</button>\n\t\t\t\t\t{:else if data.isAuthed}\n\t\t\t\t\t\t<!-- buildRateOptions returns [] when the host has free off AND no\n\t\t\t\t\t\t\t packages — there is genuinely no way in. The old code hid this\n\t\t\t\t\t\t\t behind a `?? 'free'` default; the sheet would just open empty. -->\n\t\t\t\t\t\t<span class=\"qs-shut\">not taking rates</span>\n\t\t\t\t\t{/if}\n\t\t\t\t</div>\n\t\t\t{/if}\n\t\t\t{#if queue.myPosition && queue.myPosition > 1}\n\t\t\t\t<button class=\"cut\" onclick={cut} disabled={busy}>\n\t\t\t\t\t<span>cut the line — go next</span>\n\t\t\t\t\t<span class=\"cost\">{data.session.cutPrice} <TokenCoin size={13} /></span>\n\t\t\t\t</button>\n\t\t\t{/if}\n\t\t\t{#if iAmOnStage}\n\t\t\t\t<button class=\"cut hot\" onclick={extend} disabled={busy}>\n\t\t\t\t\t<span>keep me up there · +30s</span>\n\t\t\t\t\t<span class=\"cost\">{data.session.extendPrice} <TokenCoin size={13} /></span>\n\t\t\t\t</button>\n\t\t\t{/if}\n\t\t\t<input\n\t\t\t\tbind:this={fileInput}\n\t\t\t\ttype=\"file\"\n\t\t\t\taccept=\"image/*\"\n\t\t\t\thidden\n\t\t\t\tonchange={submitToQueue}\n\t\t\t/>\n\t\t{/if}\n\n\t\t<!-- chat panel: scrollable feed + paid reaction chips + composer. On mobile\n\t\t\t the feed collapses behind the tap-to-open header; reactions + composer\n\t\t\t are the sticky bottom bar. -->\n\t\t<div class=\"panel chat\" class:vdock={data.isOwner && onStage && !verdictIn} class:open={chatOpen}>\n\t\t\t<button type=\"button\" class=\"chat-head\" onclick={() => (chatOpen = !chatOpen)} aria-expanded={chatOpen}>\n\t\t\t\t<span>chat · everyone sees it</span>\n\t\t\t\t<span class=\"chat-toggle\" aria-hidden=\"true\">{chatOpen ? '▼' : '▲'}</span>\n\t\t\t</button>\n\n\t\t\t<div class=\"feed\" bind:this={feedEl}>\n\t\t\t\t{#each comments as c (c.id)}\n\t\t\t\t\t{#if c.kind === 'verdict'}\n\t\t\t\t\t\t<!-- the same block it used to be, just in the stream now: it lands in\n\t\t\t\t\t\t\t order with the reactions and stays scrollable afterwards. -->\n\t\t\t\t\t\t<div class=\"verdict\">\n\t\t\t\t\t\t\t<span class=\"verdict-k\">@{c.nickname}'s verdict</span>\n\t\t\t\t\t\t\t<p class=\"verdict-t\">{c.body}</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t{:else if c.kind === 'tip'}\n\t\t\t\t\t\t<div class=\"tip-line\">\n\t\t\t\t\t\t\t<TokenCoin size={15} />\n\t\t\t\t\t\t\t<b>@{c.nickname}</b> tipped <b>{c.body} tokens</b>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t{:else}\n\t\t\t\t\t\t<div class=\"msg\">\n\t\t\t\t\t\t\t<div class=\"ava\">\n\t\t\t\t\t\t\t\t<DuelAvatar nickname={c.nickname} avatarUrl={getAvatarUrl(c.avatarPath)} size={30} />\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div class=\"msg-main\">\n\t\t\t\t\t\t\t\t<div class=\"msg-head\">\n\t\t\t\t\t\t\t\t\t<span class=\"author\" class:host={c.senderId === data.session.raterId}>@{c.nickname}</span>\n\t\t\t\t\t\t\t\t\t<UserBadge\n\t\t\t\t\t\t\t\t\t\trater={c.isRater}\n\t\t\t\t\t\t\t\t\t\tverified={c.isVerified}\n\t\t\t\t\t\t\t\t\t\tsubscriber={c.isSubscriber}\n\t\t\t\t\t\t\t\t\t\tsize={13}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t{#if c.senderId === data.session.raterId}<span class=\"host-tag\">host</span>{/if}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<div class=\"bub\">{c.body}</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t{/if}\n\t\t\t\t{:else}\n\t\t\t\t\t<div class=\"empty\">\n\t\t\t\t\t\t<p class=\"empty-title\">no comments yet</p>\n\t\t\t\t\t\t<p class=\"empty-sub\">say something — everyone watching sees it.</p>\n\t\t\t\t\t</div>\n\t\t\t\t{/each}\n\t\t\t</div>\n\n\t\t\t{#if !ended}\n\t\t\t\t{#if data.isOwner && onStage && !verdictIn}\n\t\t\t\t\t<!-- verdict mode: the composer IS the form. One box at a time, so\n\t\t\t\t\t\t the host can never wonder which one is official. -->\n\t\t\t\t\t<div class=\"dock vdock-inner\">\n\t\t\t\t\t\t<div class=\"vk\">\n\t\t\t\t\t\t\t<span class=\"vk-l\">your verdict · @{stage?.nickname}</span>\n\t\t\t\t\t\t\t{#if stage && stage.priceTokens > 0}\n\t\t\t\t\t\t\t\t<span class=\"vk-e\">{stage.priceTokens} <TokenCoin size={12} /> on deliver</span>\n\t\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"rate-row\">\n\t\t\t\t\t\t\t{#each RATE_SCALE as n (n)}\n\t\t\t\t\t\t\t\t<button class=\"rb\" class:picked={hostScore === n} onclick={() => (hostScore = n)}>{n}</button>\n\t\t\t\t\t\t\t{/each}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"composer\">\n\t\t\t\t\t\t\t<input class=\"c-in\" bind:value={hostLine} placeholder=\"add a line — optional\" maxlength=\"500\" />\n\t\t\t\t\t\t\t<button class=\"c-send wide\" onclick={deliver} disabled={hostScore === null || busy}>deliver</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t{:else}\n\t\t\t\t\t<div class=\"dock\">\n\t\t\t\t\t\t<!-- Reactions: paid \"hype\" phrases broadcast to the room. Neutral\n\t\t\t\t\t\t\t surface-2 pills, horizontal-scroll row, phrase + token cost. Wired\n\t\t\t\t\t\t\t to the existing tip menu / tip() handler. -->\n\t\t\t\t\t\t{#if !data.isOwner && data.session.tipMenu.length > 0 && data.isAuthed}\n\t\t\t\t\t\t\t<div class=\"reacts\">\n\t\t\t\t\t\t\t\t{#each data.session.tipMenu as t (t.label)}\n\t\t\t\t\t\t\t\t\t<button class=\"react\" onclick={() => tip(t.tokens)} disabled={busy}>\n\t\t\t\t\t\t\t\t\t\t<span class=\"rl\">{t.label}</span>\n\t\t\t\t\t\t\t\t\t\t<span class=\"rc\">{t.tokens} <TokenCoin size={11} /></span>\n\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t{/each}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t<form class=\"composer\" onsubmit={(e) => { e.preventDefault(); sendComment(); }}>\n\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\tclass=\"c-in\"\n\t\t\t\t\t\t\t\tbind:value={composer}\n\t\t\t\t\t\t\t\tplaceholder={data.isAuthed ? 'say something…' : 'sign in to comment'}\n\t\t\t\t\t\t\t\tdisabled={!data.isAuthed}\n\t\t\t\t\t\t\t\tmaxlength=\"500\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<button class=\"c-send\" type=\"submit\" disabled={!data.isAuthed || !composer.trim()} aria-label=\"send\">→</button>\n\t\t\t\t\t\t</form>\n\t\t\t\t\t</div>\n\t\t\t\t{/if}\n\t\t\t{/if}\n\t\t</div>\n\t</div>\n\n\t<!-- RAT-442: rate package picker — the DM sheet (c/[nickname]), copied rather\n\t\t than extracted: sharing it would mean touching the working 1:1 rate flow,\n\t\t and we chose not to risk that here. If either side changes, change both. -->\n\t{#if pickerOpen}\n\t\t<div class=\"rp-overlay\" role=\"dialog\" aria-modal=\"true\">\n\t\t\t<button class=\"rp-scrim\" aria-label=\"close\" onclick={() => (pickerOpen = false)}></button>\n\t\t\t<div class=\"rp-sheet\">\n\t\t\t\t<div class=\"rp-grip\"></div>\n\t\t\t\t<div class=\"rp-head\">\n\t\t\t\t\t<div class=\"rp-title\">get in line for @{data.session.hostNick ?? 'the host'}</div>\n\t\t\t\t\t<div class=\"rp-sub\">pick how you want your rate</div>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"rp-opts\">\n\t\t\t\t\t{#each data.rateOptions as opt (opt.id)}\n\t\t\t\t\t\t{@const tooPoor = opt.kind === 'paid' && data.viewerTokens < opt.tokens}\n\t\t\t\t\t\t<button type=\"button\" class=\"rp-opt\" onclick={() => chooseRateOption(opt)}>\n\t\t\t\t\t\t\t<span class=\"rp-opt-main\">\n\t\t\t\t\t\t\t\t<span class=\"rp-opt-title\">{opt.title}</span>\n\t\t\t\t\t\t\t\t{#if opt.desc}<span class=\"rp-opt-desc\">{opt.desc}</span>{/if}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span class=\"rp-opt-price\">\n\t\t\t\t\t\t\t\t{#if opt.kind === 'free'}\n\t\t\t\t\t\t\t\t\t<span class=\"rp-free\">free</span>\n\t\t\t\t\t\t\t\t{:else}\n\t\t\t\t\t\t\t\t\t<span class=\"rp-cost\" class:rp-cost--poor={tooPoor}><TokenCoin size={14} /> {opt.tokens}</span>\n\t\t\t\t\t\t\t\t\t{#if tooPoor}<span class=\"rp-need\">need more</span>{/if}\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</button>\n\t\t\t\t\t{/each}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t{/if}\n\n\t<!-- End the floor: the picker sheet's shell, so it reads as the same room.\n\t\t The buttons are the only way out besides the scrim; \"end the floor\" is\n\t\t held while another request is in flight, because post() would drop it. -->\n\t{#if endAsk && !ended}\n\t\t<div class=\"rp-overlay\" role=\"dialog\" aria-modal=\"true\" aria-label=\"end the floor\">\n\t\t\t<button class=\"rp-scrim\" aria-label=\"keep the floor going\" onclick={() => (endAsk = false)}></button>\n\t\t\t<div class=\"rp-sheet\">\n\t\t\t\t<div class=\"rp-grip\"></div>\n\t\t\t\t<div class=\"rp-head\">\n\t\t\t\t\t<div class=\"rp-title\">end the floor?</div>\n\t\t\t\t\t<div class=\"rp-sub\">everyone still in the line gets refunded.</div>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"end-actions\">\n\t\t\t\t\t<button type=\"button\" class=\"end-keep\" onclick={() => (endAsk = false)} disabled={ending}>keep going</button>\n\t\t\t\t\t<button type=\"button\" class=\"end-go\" onclick={endRoom} disabled={ending || busy}>\n\t\t\t\t\t\t{ending ? 'ending…' : 'end the floor'}\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t{/if}\n\n\t{#if toast}<div class=\"toast\">{toast}</div>{/if}\n</div>\n\n<!-- RAT-442: short-on-tokens now raises the in-context buy-tokens sheet (as on\n\t livecam) instead of a dead-end toast — top up and get straight back in line. -->\n<BuyTokensModal\n\topen={showBuyTokens}\n\tonclose={() => (showBuyTokens = false)}\n\treason={buyTokensReason}\n\tsuccessPath=\"/floor/{data.session.id}\"\n\tsource={buyTokensSource}\n/>\n\n<style>\n\t.room {\n\t\tposition: fixed;\n\t\tinset: 0;\n\t\tz-index: 20;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tbackground: var(--color-bg);\n\t\tcolor: var(--color-text);\n\t\t--lc: var(--color-green);\n\t\t--lc-ink: #05230f;\n\t\t/* the room itself never scrolls — the chat feed inside is the only scroll\n\t\t   region (mobile + desktop alike), so the composer stays pinned in view\n\t\t   and messages are never hidden behind a collapse toggle. */\n\t\toverflow: hidden;\n\t}\n\t/* RAT-443 · the room chrome, floated over the stage. No backdrop-filter\n\t   anywhere here: RAT-38 — every blurred instance is its own compositor\n\t   texture, and this room already stacks several overlays. A flat rgba pill\n\t   reads on any image and costs nothing. */\n\t.fl { position: absolute; top: 10px; z-index: 6; display: flex; }\n\t.fl-l { left: 10px; flex-direction: column; align-items: flex-start; gap: 6px; max-width: calc(100% - 132px); }\n\t.fl-r { right: 10px; flex-direction: column; align-items: flex-end; gap: 6px; }\n\t.fl-row { display: flex; align-items: center; gap: 6px; }\n\t.fchip {\n\t\tbackground: rgba(0, 0, 0, 0.62);\n\t\tborder: 1px solid rgba(255, 255, 255, 0.16);\n\t\tcolor: #f2f2f4;\n\t\tborder-radius: 999px;\n\t\tfont-family: var(--font-display);\n\t\tfont-size: 0.72rem;\n\t\tfont-weight: 700;\n\t\tpadding: 4px 10px;\n\t\tline-height: 1.35;\n\t\ttext-decoration: none;\n\t\tcursor: pointer;\n\t}\n\t.fbtn {\n\t\tdisplay: inline-flex;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\twidth: 30px;\n\t\theight: 30px;\n\t\tpadding: 0;\n\t\tflex: 0 0 auto;\n\t\tfont-size: 0.9rem;\n\t}\n\t.fbtn svg { width: 14px; height: 14px; }\n\t.fbtn:hover, .fchip:hover { border-color: rgba(255, 255, 255, 0.34); }\n\t/* the nick is the only unbounded chip, so it is the one that gives */\n\t/* one line, always: a long nick gets cut rather than wrap the tag onto a\n\t   second row, which is what was eating the top of the image. */\n\t.fstag {\n\t\tmin-width: 0;\n\t\toverflow: hidden;\n\t\ttext-overflow: ellipsis;\n\t\twhite-space: nowrap;\n\t\tbackground: var(--lc);\n\t\tborder-color: transparent;\n\t\tcolor: var(--lc-ink);\n\t}\n\t.fviewers { display: inline-flex; align-items: center; gap: 5px; flex: 0 0 auto; white-space: nowrap; font-family: inherit; font-weight: 600; }\n\t.eye { width: 13px; height: 13px; display: inline-block; }\n\t.fstate { flex: 0 0 auto; color: #9a9a9a; }\n\t.fend {\n\t\tbackground: color-mix(in srgb, #ef4444 22%, rgba(0, 0, 0, 0.62));\n\t\tborder-color: color-mix(in srgb, #ef4444 48%, transparent);\n\t\tcolor: #ffbdbd;\n\t}\n\t.fend:hover { border-color: color-mix(in srgb, #ef4444 70%, transparent); }\n\n\t/* ── the sidebar: rate / queue / chat. Mobile: a stacked column under the\n\t   stage. Desktop: a full-height column beside the stage. ── */\n\t.side {\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\t/* mobile: sections butt together edge-to-edge, panel borders are the only\n\t\t   separators — no gaps eating chat height (desktop restores the gap). */\n\t\tgap: 0;\n\t\tpadding-top: 0;\n\t\tflex: 1;\n\t\tmin-height: 0;\n\t}\n\t/* loose (non-panel) items in the sidebar keep a gutter on mobile since the\n\t   panels themselves go edge-to-edge */\n\t.cut, .blind { margin-left: 0.9rem; margin-right: 0.9rem; }\n\t/* shared surface for the rate / queue / chat panels */\n\t.panel {\n\t\tborder: 1px solid var(--color-border);\n\t\tbackground: var(--color-surface);\n\t\tborder-radius: 14px;\n\t}\n\n\t.stage {\n\t\tposition: relative;\n\t\t/* RAT-443: height, not aspect-ratio. The box was 16/10 landscape while the\n\t\t   entries are portrait phone photos, so `contain` bound them by HEIGHT and\n\t\t   a 3:4 shot filled 47% of the box — the other 53% was black bars. Giving\n\t\t   it height is what makes the image bigger: at 40dvh a 3:4 entry renders\n\t\t   1.8x the area it does at 30. */\n\t\theight: 40dvh;\n\t\tmin-height: 190px;\n\t\tbackground: #0d0d0f;\n\t\tborder-radius: 0;\n\t\toverflow: hidden;\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tflex: 0 0 auto;\n\t}\n\t/* An empty stage was still taking its full 40dvh — 304px of black to hold two\n\t   lines of text. The height is for the IMAGE; with no image there is nothing\n\t   to give it to. Floor is the chrome: back+end stack to ~82px, and the message\n\t   needs room under that. */\n\t/* RAT-459: the vacant stage now carries the onward CTA (other live floors +\n\t   exits) on the empty/closed states, which is taller than the old two-line\n\t   message. Let it grow to fit and scroll inside itself past a cap rather than\n\t   clip under `overflow: hidden`. margin-block: auto on the overlay keeps it\n\t   centred when it fits and, unlike justify-content: center, leaves the top\n\t   reachable when it overflows into a scroll. */\n\t.room.vacant .stage {\n\t\theight: auto;\n\t\tmin-height: 288px;\n\t\tmax-height: 58dvh;\n\t\toverflow-y: auto;\n\t\tjustify-content: flex-start;\n\t}\n\t.room.vacant .ov { padding-top: 2rem; margin-block: auto; }\n\t.stage img { width: 100%; height: 100%; object-fit: contain; }\n\t.ov {\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\ttext-align: center;\n\t\tcolor: #8a8a90;\n\t\tpadding: 1rem;\n\t}\n\t/* hero treatment (matches the site's landing voice: eyebrow + giant tight\n\t   headline + uppercase microcopy) so an empty stage reads as a designed\n\t   moment, not a stranded line of grey text. */\n\t.ov-eyebrow {\n\t\tdisplay: inline-flex;\n\t\talign-items: center;\n\t\tgap: 0.45rem;\n\t\tmargin-bottom: 0.85rem;\n\t\tfont-family: var(--font-display);\n\t\tfont-size: 0.62rem;\n\t\tfont-weight: 700;\n\t\tletter-spacing: 0.2em;\n\t\ttext-transform: uppercase;\n\t\tcolor: var(--lc);\n\t}\n\t/* the live beacon: a solid dot with one slow expanding ring — a 1px stroke\n\t   that fades as it grows, no blur/aura (house rule: no neon glow). */\n\t.ov-live-dot { position: relative; width: 7px; height: 7px; border-radius: 50%; background: var(--lc); }\n\t.ov-live-dot::after {\n\t\tcontent: '';\n\t\tposition: absolute;\n\t\tinset: 0;\n\t\tborder-radius: 50%;\n\t\tborder: 1px solid var(--lc);\n\t\tanimation: ov-pulse 2.2s var(--ease-out-expo) infinite;\n\t}\n\t@keyframes ov-pulse {\n\t\t0% { transform: scale(1); opacity: 0.55; }\n\t\t100% { transform: scale(4.5); opacity: 0; }\n\t}\n\t.ov-title {\n\t\tmargin: 0;\n\t\tmax-width: 12em;\n\t\tcolor: var(--color-text);\n\t\tfont-family: var(--font-display);\n\t\tfont-size: clamp(1.6rem, 7vw, 2.1rem);\n\t\tfont-weight: 700;\n\t\tline-height: 1.02;\n\t\tletter-spacing: -0.03em;\n\t}\n\t.ov-sub {\n\t\tmargin-top: 0.8rem;\n\t\tfont-size: 0.66rem;\n\t\tfont-weight: 600;\n\t\tletter-spacing: 0.14em;\n\t\ttext-transform: uppercase;\n\t\tcolor: #7a7a80;\n\t}\n\t.cd { position: absolute; top: 0; left: 0; right: 0; height: 3px; background: rgba(255, 255, 255, 0.14); z-index: 4; }\n\t.cd > span { display: block; height: 100%; background: var(--lc); transition: width 0.5s linear; }\n\t.cd.warn > span { background: #ef4444; }\n\t.cd-chip {\n\t\t/* lives inside .fl-row now — .fchip carries the pill */\n\t\tflex: 0 0 auto;\n\t\tfont-size: 0.78rem;\n\t\tfont-size: 0.78rem;\n\t\tfont-weight: 700;\n\t\tfont-variant-numeric: tabular-nums;\n\t\tpadding: 3px 9px;\n\t\tborder-radius: 20px;\n\t\tz-index: 4;\n\t}\n\t.cd-chip.warn { color: #ffd5d5; border-color: color-mix(in srgb, #ef4444 48%, transparent); background: color-mix(in srgb, #ef4444 24%, rgba(0, 0, 0, 0.5)); }\n\t/* scores ride the stage: zero extra vertical space, and they belong to what's on it */\n\t.sc-ov {\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tz-index: 4;\n\t\tdisplay: flex;\n\t\talign-items: flex-end;\n\t\tgap: 0.55rem;\n\t\tpadding: 1.4rem 0.7rem 0.55rem;\n\t\tbackground: linear-gradient(to top, rgba(0, 0, 0, 0.85), rgba(0, 0, 0, 0.5) 62%, transparent);\n\t}\n\t/* scores grouped so the queue chip pushes them right as one unit */\n\t.sc-scores { display: flex; align-items: flex-end; gap: 0.7rem; min-width: 0; }\n\t.s { display: flex; align-items: baseline; gap: 0.35rem; min-width: 0; }\n\t.k { font-size: 0.56rem; letter-spacing: 0.1em; text-transform: uppercase; font-weight: 700; color: rgba(255, 255, 255, 0.62); white-space: nowrap; }\n\t.klink { text-decoration: none; cursor: pointer; }\n\t.klink:hover { color: #fff; text-decoration: underline; }\n\t.v { font-family: var(--font-display); font-size: 1.45rem; font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums; }\n\t.s.host .v { color: #fff; }\n\t.s.crowd .v { color: var(--lc); }\n\t.n { font-size: 0.6rem; color: rgba(255, 255, 255, 0.5); }\n\t.sep { width: 1px; align-self: stretch; margin: 3px 0; background: rgba(255, 255, 255, 0.2); }\n\t/* the queue chip that lives on the LEFT of the stage scoreboard bar — pushes\n\t   the score groups to the right. Small + over-image, so white text + a filled\n\t   green CTA on the scrim. */\n\t.sc-queue { display: flex; align-items: center; gap: 0.4rem; margin-right: auto; min-width: 0; }\n\t.scq-n { font-family: var(--font-display); font-weight: 700; font-size: 0.95rem; line-height: 1; color: #fff; font-variant-numeric: tabular-nums; }\n\t.scq-t { font-size: 0.62rem; color: rgba(255, 255, 255, 0.6); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }\n\t.scq-mine { flex: 0 0 auto; font-size: 0.62rem; font-weight: 700; color: var(--lc); white-space: nowrap; }\n\t.scq-shut { flex: 0 0 auto; font-size: 0.62rem; font-weight: 700; color: rgba(255, 255, 255, 0.5); white-space: nowrap; }\n\t.scq-cta {\n\t\tposition: relative;\n\t\toverflow: hidden;\n\t\tflex: 0 0 auto;\n\t\tbackground: var(--lc);\n\t\tcolor: var(--lc-ink);\n\t\tborder: none;\n\t\tborder-radius: 999px;\n\t\tfont-family: var(--font-display);\n\t\tfont-size: 0.62rem;\n\t\tfont-weight: 700;\n\t\tpadding: 0.28rem 0.6rem;\n\t\tcursor: pointer;\n\t\twhite-space: nowrap;\n\t}\n\t.scq-cta:disabled { opacity: 0.6; }\n\t/* a periodic light sweep to pull the eye to the one action on the stage.\n\t   A shine across a FILLED button — not a glow/aura, so it stays on-brand. */\n\t.scq-cta::after {\n\t\tcontent: '';\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\tleft: -60%;\n\t\twidth: 45%;\n\t\theight: 100%;\n\t\tbackground: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.55), transparent);\n\t\ttransform: skewX(-20deg);\n\t\tanimation: scq-shimmer 2.8s var(--ease-out-expo) infinite;\n\t}\n\t@keyframes scq-shimmer {\n\t\t0% { left: -60%; }\n\t\t55%, 100% { left: 150%; }\n\t}\n\t.q-ov {\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tz-index: 4;\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tgap: 0.5rem;\n\t\tpadding: 1.5rem 0.55rem 0.5rem;\n\t\tbackground: linear-gradient(to top, rgba(0, 0, 0, 0.88), rgba(0, 0, 0, 0.5) 58%, transparent);\n\t}\n\n\t.verdict {\n\t\tbackground: var(--color-surface);\n\t\tborder: 1px solid var(--color-border);\n\t\tborder-left: 2px solid var(--lc);\n\t\tborder-radius: 3px 12px 12px 3px;\n\t\tpadding: 0.5rem 0.7rem;\n\t\tflex: 0 0 auto;\n\t\talign-self: stretch;\n\t}\n\t.verdict-k { font-family: var(--font-display); font-size: 0.56rem; letter-spacing: 0.12em; text-transform: uppercase; font-weight: 700; color: var(--lc); }\n\t.verdict-t { margin: 3px 0 0; font-size: 0.85rem; line-height: 1.4; }\n\n\t.rate { flex: 0 0 auto; padding: 0.75rem 0.85rem; }\n\t.rate-hint { display: block; font-family: var(--font-display); font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.12em; font-weight: 700; color: var(--color-muted); margin-bottom: 8px; }\n\t.rate-row { display: grid; grid-template-columns: repeat(10, 1fr); gap: 5px; }\n\t.rb {\n\t\tbackground: var(--color-surface);\n\t\tborder: 1px solid var(--color-border);\n\t\tborder-radius: 10px;\n\t\theight: 36px;\n\t\tcolor: var(--color-text);\n\t\tfont: inherit;\n\t\tfont-size: 0.85rem;\n\t\tfont-weight: 600;\n\t\tcursor: pointer;\n\t}\n\t.rb.picked { background: var(--lc); border-color: transparent; color: var(--lc-ink); }\n\t.rb:disabled { opacity: 0.5; cursor: default; }\n\t/* after voting: the whole scale greys out and only the chosen number stays lit,\n\t   so the row itself reads as \"your vote\" (higher specificity than :disabled). */\n\t.rate.voted .rb { opacity: 0.32; }\n\t.rate.voted .rb.picked { opacity: 1; }\n\n\t.queue, .getem, .cut { flex: 0 0 auto; }\n\t.queue {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tgap: 0.6rem;\n\t\tpadding: 0.6rem 0.7rem 0.6rem 0.85rem;\n\t}\n\t.queue .qs-n { font-size: 1.35rem; }\n\t.queue .qs-t { flex: 1; }\n\t.qs-n { font-family: var(--font-display); font-weight: 700; font-size: 0.92rem; font-variant-numeric: tabular-nums; }\n\t.qs-t { flex: 1; min-width: 0; font-size: 0.71rem; color: #8a8a90; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n\t.q-ov .qs-n { color: #fff; }\n\t.q-ov .qs-t { color: rgba(255, 255, 255, 0.66); }\n\t.qs-mine { font-size: 0.72rem; font-weight: 700; color: var(--lc); }\n\t.qs-shut { font-size: 0.72rem; font-weight: 700; color: var(--color-muted); }\n\t.qs-cta {\n\t\tflex: 0 0 auto;\n\t\tbackground: var(--lc);\n\t\tcolor: var(--lc-ink);\n\t\tborder: none;\n\t\tborder-radius: 7px;\n\t\tfont-family: var(--font-display);\n\t\tfont-size: 0.7rem;\n\t\tfont-weight: 700;\n\t\tpadding: 0.32rem 0.6rem;\n\t\tcursor: pointer;\n\t}\n\t.qs-cta.ghost { background: rgba(255, 255, 255, 0.15); color: #fff; }\n\t.qs-cta:disabled { opacity: 0.6; }\n\t/* the queue-panel CTA is the primary \"join\" — bigger than the overlay skip */\n\t.queue .qs-cta { font-size: 0.78rem; padding: 0.55rem 0.9rem; border-radius: 10px; }\n\n\t.getem {\n\t\tborder: 1px solid var(--color-border);\n\t\tbackground: var(--color-surface);\n\t\tborder-radius: 14px;\n\t\tpadding: 1rem;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tgap: 0.85rem;\n\t}\n\t/* hero-consistent header: eyebrow (live dot) + tight headline + sub, so the\n\t   owner's empty-floor prompt reads as a designed \"fill your floor\" card. */\n\t.getem-top { display: flex; flex-direction: column; }\n\t.getem-eyebrow {\n\t\tdisplay: inline-flex;\n\t\talign-items: center;\n\t\tgap: 0.4rem;\n\t\tmargin-bottom: 0.6rem;\n\t\tfont-family: var(--font-display);\n\t\tfont-size: 0.58rem;\n\t\tfont-weight: 700;\n\t\tletter-spacing: 0.18em;\n\t\ttext-transform: uppercase;\n\t\tcolor: var(--lc);\n\t}\n\t.getem-dot { position: relative; width: 6px; height: 6px; border-radius: 50%; background: var(--lc); }\n\t.getem-dot::after {\n\t\tcontent: '';\n\t\tposition: absolute;\n\t\tinset: 0;\n\t\tborder-radius: 50%;\n\t\tborder: 1px solid var(--lc);\n\t\tanimation: ov-pulse 2.2s var(--ease-out-expo) infinite;\n\t}\n\t.getem-title {\n\t\tmargin: 0 0 0.3rem;\n\t\tfont-family: var(--font-display);\n\t\tfont-size: 1.15rem;\n\t\tfont-weight: 700;\n\t\tletter-spacing: -0.02em;\n\t\tcolor: var(--color-text);\n\t}\n\t.getem-sub { margin: 0; font-size: 0.78rem; color: #8a8a90; line-height: 1.5; }\n\t.linkfield {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tgap: 0.5rem;\n\t\tbackground: #0f0f10;\n\t\tborder: 1px solid var(--color-border);\n\t\tborder-radius: 11px;\n\t\tpadding: 0.4rem 0.4rem 0.4rem 0.7rem;\n\t}\n\t.url { flex: 1; min-width: 0; font-size: 0.8rem; color: #cfcfd4; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }\n\t.linkcopy {\n\t\tflex: 0 0 auto;\n\t\tbackground: var(--color-surface-2);\n\t\tborder: 1px solid var(--color-border);\n\t\tborder-radius: 8px;\n\t\tcolor: var(--color-text);\n\t\tfont: inherit;\n\t\tfont-size: 0.78rem;\n\t\tfont-weight: 600;\n\t\tpadding: 0.4rem 0.85rem;\n\t\tcursor: pointer;\n\t}\n\t.linkcopy:hover { border-color: var(--color-muted); }\n\t/* the share button is the ONE action on an empty floor — make it primary:\n\t   filled green, full width, not a quiet outlined chip. */\n\t.shchip {\n\t\tbackground: var(--lc);\n\t\tborder: none;\n\t\tborder-radius: 11px;\n\t\tcolor: var(--lc-ink);\n\t\tfont-family: var(--font-display);\n\t\tfont-size: 0.82rem;\n\t\tfont-weight: 700;\n\t\tpadding: 0.62rem;\n\t\tcursor: pointer;\n\t\ttext-align: center;\n\t}\n\t.nudge { font-size: 0.68rem; color: #8a8a90; margin: 0; line-height: 1.4; }\n\t/* Viewers get the link from the empty stage itself — it's the one screen where\n\t   there is nothing else to look at. Ghost, not filled: \"get in line\" below is\n\t   still the thing we want them to do. */\n\t.ov-share {\n\t\tmargin-top: 0.7rem;\n\t\tborder: 1px solid var(--color-border);\n\t\tbackground: var(--color-surface);\n\t\tcolor: var(--color-text);\n\t\tborder-radius: 20px;\n\t\tpadding: 0.4rem 0.85rem;\n\t\tfont: 700 0.72rem var(--font-display);\n\t\tcursor: pointer;\n\t}\n\t.ov-share:hover { border-color: var(--color-muted); }\n\t.blind { font-size: 0.68rem; color: #8a8a90; margin: 0; line-height: 1.4; flex: 0 0 auto; }\n\n\t/* RAT-459: onward block on the dead-end overlays — a real next step instead of\n\t   a terminal line of grey text. Solid surface cards + a filled green join chip\n\t   (house rule: fill to draw the eye, never a colored glow). */\n\t.ov-onward {\n\t\tmargin-top: 1.6rem;\n\t\twidth: 100%;\n\t\tmax-width: 22rem;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tgap: 0.7rem;\n\t}\n\t.ov-onward-k {\n\t\tfont-family: var(--font-display);\n\t\tfont-size: 0.58rem;\n\t\tfont-weight: 700;\n\t\tletter-spacing: 0.18em;\n\t\ttext-transform: uppercase;\n\t\tcolor: var(--lc);\n\t\ttext-align: left;\n\t}\n\t.ov-floors { display: flex; flex-direction: column; gap: 0.5rem; }\n\t.ov-floor {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tgap: 0.65rem;\n\t\tpadding: 0.55rem 0.6rem;\n\t\tborder: 1px solid var(--color-border);\n\t\tbackground: var(--color-surface);\n\t\tborder-radius: 12px;\n\t\ttext-decoration: none;\n\t\tcolor: var(--color-text);\n\t\ttransition: border-color 0.12s;\n\t}\n\t.ov-floor:hover { border-color: var(--color-muted); }\n\t.ov-floor-main { display: flex; flex-direction: column; min-width: 0; flex: 1; text-align: left; gap: 1px; }\n\t.ov-floor-nick { font-size: 0.85rem; font-weight: 600; color: var(--color-text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n\t.ov-floor-sub { font-size: 0.68rem; font-weight: 600; color: var(--lc); }\n\t.ov-floor-join {\n\t\tflex: 0 0 auto;\n\t\tbackground: var(--lc);\n\t\tcolor: var(--lc-ink);\n\t\tborder-radius: 999px;\n\t\tfont-family: var(--font-display);\n\t\tfont-size: 0.68rem;\n\t\tfont-weight: 700;\n\t\tpadding: 0.28rem 0.7rem;\n\t}\n\t.ov-exits {\n\t\tdisplay: flex;\n\t\tflex-wrap: wrap;\n\t\tjustify-content: center;\n\t\tgap: 0.4rem 1.1rem;\n\t\tmargin-top: 0.3rem;\n\t}\n\t.ov-exit {\n\t\tfont-family: var(--font-display);\n\t\tfont-size: 0.72rem;\n\t\tfont-weight: 700;\n\t\tcolor: var(--color-muted);\n\t\ttext-decoration: none;\n\t}\n\t.ov-exit:hover { color: var(--color-text); text-decoration: underline; }\n\n\t.cut {\n\t\twidth: 100%;\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: space-between;\n\t\tgap: 0.6rem;\n\t\tbackground: var(--color-surface-2);\n\t\tborder: 1px solid var(--color-border);\n\t\tborder-radius: 11px;\n\t\tpadding: 0.65rem 0.8rem;\n\t\tcolor: var(--color-text);\n\t\tfont: inherit;\n\t\tfont-size: 0.86rem;\n\t\tfont-weight: 600;\n\t\tcursor: pointer;\n\t}\n\t.cut .cost { display: inline-flex; align-items: center; gap: 4px; color: var(--lc); font-size: 0.8rem; font-weight: 700; }\n\t.cut.hot { background: var(--lc); border-color: transparent; color: var(--lc-ink); }\n\t.cut.hot .cost { color: var(--lc-ink); }\n\n\t/* RAT-442: rate picker sheet — copied verbatim from c/[nickname] so the two\n\t   surfaces look identical. z-index 120 clears .room (20) and the chrome. */\n\t.rp-overlay { position: fixed; inset: 0; z-index: 120; display: flex; align-items: flex-end; justify-content: center; }\n\t.rp-scrim { position: absolute; inset: 0; background: rgba(0,0,0,.7); backdrop-filter: blur(4px); border: none; cursor: pointer; }\n\t.rp-sheet { position: relative; width: 100%; max-width: 440px; background: var(--color-bg); border: 1px solid var(--color-border); border-radius: 24px 24px 0 0; padding: 12px 18px 24px; display: flex; flex-direction: column; gap: 14px; }\n\t.rp-grip { width: 40px; height: 4px; border-radius: 999px; background: var(--color-muted); opacity: .3; align-self: center; }\n\t.rp-head { text-align: center; }\n\t.rp-title { font-family: var(--font-display); font-weight: 700; font-size: 17px; color: var(--color-text); }\n\t.rp-sub { margin-top: 2px; font-size: 12.5px; color: var(--color-muted); }\n\t.rp-opts { display: flex; flex-direction: column; gap: 10px; }\n\t.rp-opt { display: flex; align-items: center; justify-content: space-between; gap: 12px; width: 100%; text-align: left; padding: 14px 16px; background: var(--color-surface); border: 1px solid var(--color-border); border-radius: 16px; cursor: pointer; transition: border-color .15s; }\n\t.rp-opt:hover { border-color: var(--color-muted); }\n\t.rp-opt-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }\n\t.rp-opt-title { font-family: var(--font-display); font-weight: 700; font-size: 14px; color: var(--color-text); }\n\t.rp-opt-desc { font-size: 12px; color: var(--color-muted); }\n\t.rp-opt-price { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; flex-shrink: 0; }\n\t.rp-free { font-family: var(--font-display); font-weight: 700; font-size: 13px; color: var(--color-accent); }\n\t.rp-cost { display: inline-flex; align-items: center; gap: 4px; font-family: var(--font-display); font-weight: 700; font-size: 15px; color: var(--color-yellow, #f5b50a); }\n\t.rp-cost--poor { opacity: .55; }\n\t.rp-need { font-size: 10.5px; font-weight: 600; color: var(--color-muted); }\n\t/* end-the-floor sheet: a neutral way back, a solid fill for the kill switch */\n\t.end-actions { display: flex; gap: 10px; }\n\t.end-keep, .end-go { flex: 1; padding: 13px 16px; border-radius: 14px; font-family: var(--font-display); font-weight: 700; font-size: 14px; cursor: pointer; }\n\t.end-keep { background: var(--color-surface); border: 1px solid var(--color-border); color: var(--color-text); }\n\t.end-go { background: #ef4444; border: 0; color: #fff; }\n\t.end-keep:disabled, .end-go:disabled { opacity: .6; cursor: wait; }\n\n\t/* chat panel — grows to fill the sidebar, holds the scrollable feed, the\n\t   reaction row and the composer. */\n\t.chat {\n\t\tflex: 1;\n\t\tmin-height: 0;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\toverflow: hidden;\n\t\tborder-left: 0;\n\t\tborder-right: 0;\n\t\tborder-bottom: 0;\n\t\tborder-radius: 0;\n\t}\n\t.chat-head {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: space-between;\n\t\twidth: 100%;\n\t\tpadding: 0.7rem 0.9rem;\n\t\tborder: 0;\n\t\tborder-bottom: 1px solid var(--color-border);\n\t\tbackground: transparent;\n\t\tcolor: var(--color-muted);\n\t\tfont-family: var(--font-display);\n\t\tfont-size: 0.7rem;\n\t\tfont-weight: 700;\n\t\tletter-spacing: 0.12em;\n\t\ttext-transform: uppercase;\n\t\tcursor: pointer;\n\t}\n\t.chat-toggle { font-size: 9px; }\n\t/* desktop: the feed is always open, so the tap-to-open toggle is chrome the\n\t   viewer doesn't need — hide the caret. */\n\n\t.feed {\n\t\tflex: 1;\n\t\tmin-height: 0;\n\t\toverflow-y: auto;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tgap: 0.7rem;\n\t\tpadding: 0.6rem 0.9rem;\n\t\t/* dark, muted scrollbar — the default light one glared against the room */\n\t\tscrollbar-width: thin;\n\t\tscrollbar-color: #3a3a3a transparent;\n\t}\n\t.feed::-webkit-scrollbar { width: 8px; }\n\t.feed::-webkit-scrollbar-track { background: transparent; }\n\t.feed::-webkit-scrollbar-thumb {\n\t\tbackground: #333;\n\t\tborder-radius: 4px;\n\t\tborder: 2px solid transparent;\n\t\tbackground-clip: padding-box;\n\t}\n\t.feed::-webkit-scrollbar-thumb:hover { background: #444; background-clip: padding-box; }\n\t/* Bottom-anchor the feed WITHOUT breaking scroll. `justify-content: flex-end`\n\t   looks identical while the feed is short, but once it overflows the browser\n\t   reports scrollHeight === clientHeight and pushes the excess above the scroll\n\t   origin — measured at -445px, unreachable, no scrollbar. Auto margin on the\n\t   first child collapses to 0 once there's overflow, so the same \"sit at the\n\t   bottom\" effect survives into a scrollable feed. */\n\t.feed > :first-child { margin-top: auto; }\n\t.msg { display: flex; gap: 0.55rem; align-items: flex-start; flex: 0 0 auto; }\n\t.ava {\n\t\tflex: 0 0 auto;\n\t\twidth: 30px;\n\t\theight: 30px;\n\t\t/* wraps the real DuelAvatar now (photo or nickname-hash gradient) — no\n\t\t   letter-circle chrome of its own. */\n\t}\n\t.msg-main { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 3px; }\n\t.msg-head { display: flex; align-items: center; gap: 6px; }\n\t.author { font-size: 0.78rem; font-weight: 600; color: #b59ad6; }\n\t.author.host { color: var(--lc); }\n\t.host-tag { font-size: 0.58rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; padding: 1px 6px; border-radius: 10px; background: var(--lc); color: var(--lc-ink); }\n\t.bub {\n\t\tdisplay: inline-block;\n\t\twidth: fit-content;\n\t\tmax-width: 100%;\n\t\tbackground: var(--color-surface-2);\n\t\tborder: 1px solid var(--color-border);\n\t\tborder-radius: 4px 14px 14px 14px;\n\t\tpadding: 0.45rem 0.65rem;\n\t\tfont-size: 0.88rem;\n\t\tline-height: 1.45;\n\t\toverflow-wrap: anywhere;\n\t}\n\t.tip-line {\n\t\talign-self: center;\n\t\tdisplay: inline-flex;\n\t\talign-items: center;\n\t\tgap: 5px;\n\t\tpadding: 0.48rem 0.95rem;\n\t\tborder-radius: 999px;\n\t\tfont-size: 0.82rem;\n\t\tcolor: #fff7e6;\n\t\tbackground: linear-gradient(135deg, #8a5f0c 0%, #6a480a 52%, #533908 100%);\n\t\tflex: 0 0 auto;\n\t}\n\t.empty { margin: auto; text-align: center; color: #8a8a90; padding: 1.4rem 1rem; }\n\t.empty-title { margin: 0 0 4px; font-weight: 600; color: var(--color-text); }\n\t.empty-sub { margin: 0; font-size: 0.82rem; }\n\n\t.dock { flex: 0 0 auto; display: flex; flex-direction: column; gap: 0.55rem; border-top: 1px solid var(--color-border); padding: 0.7rem 0.9rem; }\n\t/* verdict mode: the whole chat panel takes the host's accent, and the dock is\n\t   the form. */\n\t.dock.vdock-inner {\n\t\tbackground: color-mix(in srgb, var(--lc) 7%, var(--color-surface));\n\t}\n\t.vk { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }\n\t.vk-l { font-family: var(--font-display); font-size: 0.66rem; letter-spacing: 0.14em; text-transform: uppercase; font-weight: 700; color: var(--lc); }\n\t.vk-e {\n\t\tdisplay: inline-flex;\n\t\talign-items: center;\n\t\tgap: 4px;\n\t\tfont-size: 0.66rem;\n\t\tfont-weight: 700;\n\t\tcolor: var(--lc);\n\t\tbackground: color-mix(in srgb, var(--lc) 15%, transparent);\n\t\tpadding: 3px 9px;\n\t\tborder-radius: 999px;\n\t}\n\t/* Reactions — paid \"hype\" phrases broadcast to the room. Neutral surface-2\n\t   pills (the gold coin is the only accent), horizontal-scroll row, phrase +\n\t   cost inline. Wired to the tip menu / tip() handler. */\n\t.reacts { display: flex; gap: 8px; overflow-x: auto; scrollbar-width: none; }\n\t.reacts::-webkit-scrollbar { display: none; }\n\t.react {\n\t\tflex-shrink: 0;\n\t\tdisplay: inline-flex;\n\t\talign-items: center;\n\t\tgap: 6px;\n\t\tpadding: 0.35rem 0.7rem;\n\t\tborder-radius: 999px;\n\t\tborder: 1px solid var(--color-border);\n\t\tbackground: var(--color-surface-2);\n\t\tcursor: pointer;\n\t\tfont: inherit;\n\t\ttransition: border-color 0.12s, background 0.12s;\n\t}\n\t.react:hover { border-color: var(--color-muted); background: #202024; }\n\t.react:disabled { opacity: 0.5; cursor: default; }\n\t.rl { font-family: var(--font-display); font-size: 0.78rem; font-weight: 700; color: var(--color-text); white-space: nowrap; }\n\t.rc { display: inline-flex; align-items: center; gap: 3px; font-size: 0.7rem; font-weight: 600; color: var(--color-muted); }\n\t.composer { display: flex; gap: 8px; align-items: center; }\n\t.c-in {\n\t\tflex: 1;\n\t\tmin-width: 0;\n\t\tbackground: var(--color-surface);\n\t\tborder: 1px solid var(--color-border);\n\t\tborder-radius: 14px;\n\t\tpadding: 0.6rem 0.8rem;\n\t\tcolor: var(--color-text);\n\t\tfont: inherit;\n\t\tfont-size: 0.9rem;\n\t}\n\t.c-in:focus { outline: none; border-color: var(--lc); }\n\t.c-send {\n\t\tflex: 0 0 auto;\n\t\tdisplay: inline-flex;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\twidth: 40px;\n\t\theight: 40px;\n\t\tbackground: var(--lc);\n\t\tcolor: var(--lc-ink);\n\t\tborder: none;\n\t\tborder-radius: 12px;\n\t\tcursor: pointer;\n\t\tfont: inherit;\n\t\tfont-weight: 700;\n\t}\n\t.c-send.wide { width: auto; padding: 0 0.85rem; font-family: var(--font-display); font-size: 0.78rem; }\n\t.c-send:disabled { opacity: 0.4; cursor: default; }\n\t.toast {\n\t\tposition: absolute;\n\t\tbottom: 5rem;\n\t\tleft: 50%;\n\t\ttransform: translateX(-50%);\n\t\tbackground: rgba(0, 0, 0, 0.85);\n\t\tborder: 1px solid var(--color-border);\n\t\tborder-radius: 999px;\n\t\tpadding: 0.5rem 1rem;\n\t\tfont-size: 0.8rem;\n\t\tz-index: 30;\n\t}\n\n\t/* ─────────────────────────── MOBILE (default) ───────────────────────────\n\t   Single column: stage on top, a compact rate bar, the queue, then the chat\n\t   feed filling the rest with the reactions + composer docked at its bottom. */\n\n\t/* rate is the primary action — sticky just under the stage. On mobile it is\n\t   just the number row: the \"rate him 1–10\" label is redundant next to the\n\t   photo and only ate chat height, so it is hidden and the panel tightened. */\n\t.rate.panel {\n\t\tposition: sticky;\n\t\ttop: 0;\n\t\tz-index: 10;\n\t\tborder-radius: 0;\n\t\tborder-left: 0;\n\t\tborder-right: 0;\n\t\tbackground: var(--color-bg);\n\t\tpadding: 0.55rem 0.7rem;\n\t}\n\t.rate-hint { display: none; }\n\t.queue.panel { border-radius: 0; border-left: 0; border-right: 0; }\n\t/* while someone is on stage the in-line chip lives in the scoreboard bar, so\n\t   the full-width queue row is hidden — it only returns for the empty stage\n\t   (vacant), where there is no scoreboard to carry it. */\n\t.room:not(.vacant) .queue.panel { display: none; }\n\t.getem.panel { border-radius: 0; border-left: 0; border-right: 0; }\n\n\t/* the chat feed is always visible and IS the scroll region; the dock\n\t   (reactions + composer) sits in-flow at the bottom of the chat panel —\n\t   same model as desktop — so messages are never hidden behind a toggle.\n\t   The header + its collapse caret are gone entirely (mobile + desktop) —\n\t   the panel's top border is separator enough, and the space goes to chat. */\n\t.chat-head { display: none; }\n\t/* verdict mode is host-only and rarely on a phone; keep it inline (not the\n\t   fixed bottom bar) so the score row + deliver button stay together. */\n\t.chat.vdock .dock { position: static; padding: 0.7rem 0.9rem; }\n\t.chat.vdock .composer { position: static; padding: 0; border-top: 0; background: transparent; }\n\n\t/* Desktop overrides live at the END on purpose. Source order decides between\n\t   same-specificity rules, so the desktop layout must win over the mobile\n\t   defaults above. livecam puts its media block last for exactly this reason. */\n\t@media (min-width: 900px) {\n\t\t/* desktop: a centered, bounded app pane in normal flow (matches the\n\t\t   approved mockup) — NOT the mobile fullscreen fixed takeover, which\n\t\t   stretched to bottom:0 and blew the stage into a full-viewport void. */\n\t\t.room {\n\t\t\t/* stay fixed (base) so top:4rem deterministically clears the fixed\n\t\t\t   navbar — but release the inherited bottom:0 and bound the height so\n\t\t\t   the stage stops stretching into a full-viewport void. */\n\t\t\ttop: 4rem;\n\t\t\tbottom: auto;\n\t\t\tinset-inline: 0;\n\t\t\tmax-width: 64rem;\n\t\t\tmargin: 0 auto;\n\t\t\tdisplay: grid;\n\t\t\tgrid-template-columns: 1fr 22rem;\n\t\t\tgap: 1rem;\n\t\t\tpadding: 1rem;\n\t\t\theight: min(44rem, calc(100dvh - 5.5rem));\n\t\t\toverflow: hidden;\n\t\t}\n\t\t/* stage fills the left column, full height, rounded, and a real bordered\n\t\t   surface so an EMPTY stage still reads as a panel, not a black void */\n\t\t.stage {\n\t\t\theight: auto;\n\t\t\tborder-radius: 16px;\n\t\t\tborder: 1px solid var(--color-border);\n\t\t\tbackground: radial-gradient(120% 80% at 50% 0%, #1b1b1f, #0b0b0d);\n\t\t}\n\t\t/* desktop stage fills the left column; drop the mobile scroll cap but keep\n\t\t   the internal scroll so a tall onward block never clips (RAT-459). */\n\t\t.room.vacant .stage { height: auto; max-height: none; overflow-y: auto; justify-content: center; }\n\t\t.room.vacant .ov { margin-block: 0; }\n\t\t/* the owner's getem card (sidebar) carries the share button on desktop, so\n\t\t   the stage-hero share button is a duplicate here — hide it for the owner.\n\t\t   Viewers keep it (they have no getem card). */\n\t\t.room.owner .ov-share { display: none; }\n\n\t\t/* sidebar is the right column, full height, its own scroll region.\n\t\t   Restores the inter-card gap the mobile column drops. */\n\t\t.side { padding-top: 0; min-height: 0; gap: 0.7rem; }\n\t\t.cut, .blind { margin-left: 0; margin-right: 0; }\n\n\t\t/* the scoreboard queue chip is a mobile space-saver — desktop has room for\n\t\t   the full queue row in the sidebar, so hide the chip and keep the row. */\n\t\t.sc-queue { display: none; }\n\t\t.room:not(.vacant) .queue.panel { display: flex; }\n\n\t\t/* panels regain their card look on desktop */\n\t\t.rate.panel, .queue.panel, .getem.panel {\n\t\t\tposition: static;\n\t\t\tborder: 1px solid var(--color-border);\n\t\t\tborder-radius: 14px;\n\t\t}\n\n\t\t/* the chat panel fills the rest of the sidebar; feed always open */\n\t\t.chat { margin: 0; border: 1px solid var(--color-border); border-radius: 14px; }\n\t\t.chat-head { display: none; }\n\t\t.chat .feed { display: flex; position: static; max-height: none; bottom: auto; background: transparent; border-top: 0; }\n\t\t.chat .reacts, .chat .composer {\n\t\t\tposition: static;\n\t\t\tbackground: transparent;\n\t\t\tborder-top: 0;\n\t\t\tleft: auto;\n\t\t\tright: auto;\n\t\t\tbottom: auto;\n\t\t}\n\t\t.chat .reacts { padding: 0; }\n\t\t.chat .composer { padding: 0; }\n\t\t.chat .dock { border-top: 1px solid var(--color-border); padding: 0.7rem 0.9rem; }\n\t\t.chat.vdock .dock { padding: 0.7rem 0.9rem; }\n\t}\n\n\t/* mobile: the stage hero already says the floor is waiting, so the getem card's\n\t   OWN hero (eyebrow + \"the line is empty\" + sub) is redundant — drop it and keep\n\t   just the share widget (link + button + nudge). getem only renders in the owner\n\t   live-empty state, so no further qualifier is needed. Desktop (min-width: 900px)\n\t   keeps the full card in the sidebar. */\n\t@media (max-width: 899px) {\n\t\t/* the whole getem card (hero + link field + copy + share button + nudge) is\n\t\t   redundant on mobile — the share action lives in the stage hero's\n\t\t   \"share the room →\" button. Hide the entire card + the \"line is blind\" note;\n\t\t   desktop keeps them in the sidebar. */\n\t\t.getem.panel { display: none; }\n\t\t.blind { display: none; }\n\t}\n</style>\n"],"version":3}