@hadoku/watchparty-ui 0.45.26 → 0.45.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { E as T, w as c, X as d, Y as A, d as S } from "./index-DOW8Etk-.js";
1
+ import { E as T, w as c, X as d, Y as A, d as S } from "./index-CXl-xAUD.js";
2
2
  function j(e) {
3
3
  return e.trim().toLowerCase().startsWith("magnet:") ? "magnet" : "url";
4
4
  }
@@ -314,4 +314,4 @@ export {
314
314
  N as s,
315
315
  te as u
316
316
  };
317
- //# sourceMappingURL=submissionsApi-D2UlQjUa.js.map
317
+ //# sourceMappingURL=submissionsApi-DFGFSika.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"submissionsApi-D2UlQjUa.js","sources":["../../src/lib/submissionsApi.ts"],"sourcesContent":["/**\n * Client for user submissions — \"a friend asked for this media\".\n *\n * HTTP, not socket events, for the same reason the tierlist is: a submission\n * is not room state. It belongs to a PERSON, it outlives every socket that\n * person ever opens, and `GET /api/submissions/mine` must be readable from any\n * room (or none).\n *\n * ── The rules live here, not in the component ──────────────────────────────\n * Everything that can be decided without a network is a pure exported\n * function, because the failure modes are exactly the ones a component test\n * cannot reach: a quota refusal that arrives with a number, a transition\n * refusal that arrives with two states, and a host-allowlist refusal that is\n * indistinguishable from a typo unless the reason is carried through. The\n * component renders what these return.\n *\n * ── Status codes the server can answer with ────────────────────────────────\n * The route (`http/routes/submissions.ts`) is the citation for each:\n * 400 `submissions/invalid_source` — `checkSource` refused, with a `reason`\n * 400 `submissions/invalid_body` — the Zod parse failed\n * 403 `submissions/no_identity` — friend tier, but no registry userId\n * 403 (adminAuth) — below friend tier\n * 404 `submissions/not_found` — no such id\n * 409 `submissions/invalid_transition` — with `from` and `to`\n * 429 — over the per-user pending cap\n * 503 `submissions/unavailable` — no store (STATS_DB_DIR empty)\n * A 429 body is read LIBERALLY (`remainingBytes` / `remaining_bytes` /\n * `remaining`) rather than pinned to one spelling: the number is the whole\n * point of that response, and a spelling mismatch would silently degrade it to\n * a bare \"over quota\" with no way to tell that from a server that sent none.\n */\nimport { getRuntimeServerOrigin } from './config';\nimport { getUserType } from './auth';\nimport { debug, warn, errorFields } from './debug';\nimport type { TSubmission, TSubmissionKind } from '@watchparty/shared';\n\n/** Why a submissions call did not succeed. */\nexport type SubmissionFailure =\n | { code: 'invalid_source'; reason?: string }\n | { code: 'invalid_body' }\n | { code: 'quota'; remainingBytes?: number }\n | { code: 'no_identity' }\n | { code: 'forbidden' }\n | { code: 'conflict'; from?: string; to?: string }\n | { code: 'not_found' }\n | { code: 'unavailable' }\n | { code: 'network' }\n | { code: 'unknown'; status: number };\n\ntype SubmissionResult<T> =\n | { ok: true; value: T }\n | { ok: false; failure: SubmissionFailure };\n\n/**\n * Which kind of source this text is, decided by its scheme alone.\n *\n * One field, two kinds: asking a user to pick \"url\" or \"magnet\" from a select\n * is asking them to restate what they already typed. `magnet:` is the only\n * scheme with a second meaning here, so everything else is offered as a URL\n * and the SERVER decides whether it is acceptable — `checkSource` is the\n * boundary, and a client-side copy of the allowlist would be a second\n * allowlist that drifts.\n */\nexport function guessSubmissionKind(raw: string): TSubmissionKind {\n return raw.trim().toLowerCase().startsWith('magnet:') ? 'magnet' : 'url';\n}\n\n/** First numeric field among several accepted spellings. */\nfunction readNumber(body: Record<string, unknown>, keys: string[]): number | undefined {\n for (const key of keys) {\n const v = body[key];\n if (typeof v === 'number' && Number.isFinite(v) && v >= 0) return v;\n }\n return undefined;\n}\n\nfunction readString(body: Record<string, unknown>, key: string): string | undefined {\n const v = body[key];\n return typeof v === 'string' && v ? v : undefined;\n}\n\n/**\n * Turn a response status plus its parsed body into a failure.\n *\n * Pure and total: the body arrives as `unknown` because it is JSON off the\n * wire, and a server that answers a 429 with an HTML error page must still\n * produce `{ code: 'quota' }` rather than throwing inside a click handler.\n */\nexport function classifySubmissionFailure(status: number, body: unknown): SubmissionFailure {\n const obj: Record<string, unknown> =\n body && typeof body === 'object' && !Array.isArray(body)\n ? (body as Record<string, unknown>)\n : {};\n const error = readString(obj, 'error');\n\n if (status === 429) {\n // The route sends what it MEASURED (`pendingBytes`, `capBytes`); the user\n // wants the difference. Derive it rather than asking the server to send a\n // third number that could disagree with the two it already sent — and\n // clamp, because a cap lowered under an existing backlog makes it\n // negative and \"−2 GB free\" is worse than no figure at all.\n const pending = readNumber(obj, ['pendingBytes', 'pending_bytes']);\n const cap = readNumber(obj, ['capBytes', 'cap_bytes']);\n const remaining = readNumber(obj, ['remainingBytes', 'remaining_bytes', 'remaining'])\n ?? (pending !== undefined && cap !== undefined ? Math.max(0, cap - pending) : undefined);\n return { code: 'quota', remainingBytes: remaining };\n }\n if (status === 409) {\n return { code: 'conflict', from: readString(obj, 'from'), to: readString(obj, 'to') };\n }\n if (status === 404) return { code: 'not_found' };\n if (status === 503) return { code: 'unavailable' };\n if (status === 400) {\n if (error === 'submissions/invalid_source') {\n return { code: 'invalid_source', reason: readString(obj, 'reason') };\n }\n return { code: 'invalid_body' };\n }\n if (status === 401 || status === 403) {\n // `no_identity` is a friend who IS admitted and has nowhere to store a\n // row; a plain 403 is a caller below friend tier. They need different\n // words, and collapsing them makes a registry gap look like a permission.\n return error === 'submissions/no_identity' ? { code: 'no_identity' } : { code: 'forbidden' };\n }\n return { code: 'unknown', status };\n}\n\nconst UNITS = ['B', 'KB', 'MB', 'GB', 'TB'] as const;\n\n/** Bytes as a short human figure. Used only for the quota number. */\nexport function formatSubmissionBytes(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes < 0) return '—';\n let n = bytes;\n let u = 0;\n while (n >= 1024 && u < UNITS.length - 1) { n /= 1024; u++; }\n const rounded = u === 0 ? String(Math.round(n)) : n.toFixed(n < 10 ? 1 : 0);\n return `${rounded} ${UNITS[u]}`;\n}\n\n/**\n * The failure as a short STATE line.\n *\n * Deliberately not a sentence and never an instruction: the UI rule for this\n * repo is to ship state rather than narration, so this is the same register as\n * the state badge beside it. The quota case is the one that carries a number,\n * because \"over quota\" with no figure gives the user nothing to act on.\n */\nexport function submissionFailureLabel(failure: SubmissionFailure): string {\n switch (failure.code) {\n case 'invalid_source':\n switch (failure.reason) {\n case 'host_not_allowed': return 'host not allowed';\n case 'not_https': return 'https only';\n case 'bad_magnet': return 'not a magnet';\n case 'bad_upload_ref': return 'bad upload ref';\n case 'unparseable': return 'not a link';\n default: return 'rejected';\n }\n case 'invalid_body': return 'rejected';\n case 'quota':\n return failure.remainingBytes === undefined\n ? 'over quota'\n : `over quota — ${formatSubmissionBytes(failure.remainingBytes)} free`;\n case 'no_identity': return 'no identity';\n case 'forbidden': return 'not allowed';\n case 'conflict': return failure.from ? `already ${failure.from}` : 'conflict';\n case 'not_found': return 'not found';\n case 'unavailable': return 'unavailable';\n case 'network': return 'network error';\n case 'unknown': return `error ${failure.status}`;\n }\n}\n\n/**\n * May this browser offer the submit surface at all?\n *\n * The API is friend tier and above. This is a membership test rather than a\n * rank comparison against a second copy of `TIER_RANK` — `auth.ts` owns that\n * ladder and does not export it, and a hand-copied rank table is exactly the\n * drift `isPrivileged`'s own comment records (the UI's copy was missed when\n * the server's exact-match allowlist was fixed). An unlisted tier is refused,\n * so the gate fails CLOSED; the server enforces the real boundary either way.\n */\nconst SUBMIT_TIERS = new Set(['friend', 'service', 'wife', 'admin']);\nexport function canSubmitMedia(): boolean {\n return SUBMIT_TIERS.has(getUserType());\n}\n\nfunction url(path: string): string {\n return `${getRuntimeServerOrigin()}${path}`;\n}\n\n/** Parse a body without letting a non-JSON error page throw. */\nasync function safeJson(res: Response): Promise<unknown> {\n try { return await res.json(); } catch { return null; }\n}\n\n/**\n * One request to `POST /api/submissions`, with whatever fields the caller\n * has. `series` is sent even though `SubmissionCreateIn` (shared) may not yet\n * declare it — a plain `z.object()` strips unknown keys rather than erroring,\n * so this is forward-compatible with the schema change landing independently:\n * nothing here needs to change again once it does.\n */\ninterface PostSubmissionInput {\n kind: TSubmissionKind;\n source: string;\n title?: string;\n /** Magnet only. Dropped silently by the server until it recognises the field. */\n series?: string;\n}\n\nasync function postSubmission(input: PostSubmissionInput): Promise<SubmissionResult<TSubmission>> {\n const body: Record<string, string> = { kind: input.kind, source: input.source };\n if (input.title) body.title = input.title;\n if (input.series) body.series = input.series;\n try {\n const res = await fetch(url('/api/submissions'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n const failure = classifySubmissionFailure(res.status, await safeJson(res));\n debug('submissions', 'Submit refused', { status: res.status, code: failure.code });\n return { ok: false, failure };\n }\n const parsed = (await res.json()) as { submission: TSubmission };\n debug('submissions', 'Submitted', { id: parsed.submission.id, kind: input.kind });\n return { ok: true, value: parsed.submission };\n } catch (err: unknown) {\n warn('submissions', 'Submit threw', errorFields(err));\n return { ok: false, failure: { code: 'network' } };\n }\n}\n\n/** Record a request. Always lands in `pending` — approval is the operator's. */\nexport async function submitSubmission(\n rawSource: string, title?: string,\n): Promise<SubmissionResult<TSubmission>> {\n const source = rawSource.trim();\n const kind = guessSubmissionKind(source);\n return postSubmission({ kind, source, title });\n}\n\n/**\n * A magnet carries a `series` — the anime folder the scraper will file it\n * under. `checkSource('magnet', …)` is the AUTHORITY on requiring it and on\n * the title's `SxxExx` shape; this call sends what the caller typed and lets\n * the server's 400 (`invalid_source`, reason `bad_magnet` or similar) speak\n * for a missing or malformed one rather than a client-side copy of that rule.\n */\nexport async function submitMagnetSubmission(\n rawSource: string, opts: { title?: string; series: string },\n): Promise<SubmissionResult<TSubmission>> {\n return postSubmission({\n kind: 'magnet', source: rawSource.trim(), title: opts.title, series: opts.series.trim(),\n });\n}\n\n/** Record an upload once its bytes are already sitting in the filetransfer\n * inbox. `fileId` is the filetransfer file id (`FileInfo.Id`) a chunk-upload\n * completed with — see {@link uploadFileForSubmission}. */\nexport async function submitUploadSubmission(\n fileId: string, title?: string,\n): Promise<SubmissionResult<TSubmission>> {\n return postSubmission({ kind: 'upload', source: fileId, title });\n}\n\n/**\n * Whether a title carries an episode code the scraper's destination rule\n * needs (`SxxExx`, any case). Pure and client-side as a courtesy ONLY — the\n * server's `checkSource('magnet', …)` is the one place this is enforced; see\n * mutation check (a) in the wave brief: dropping the requirement here must\n * still leave a bad title refused by the server.\n */\nconst SXXEXX_RE = /S\\d{2}E\\d{2}/i;\nexport function hasEpisodeCode(title: string): boolean {\n return SXXEXX_RE.test(title);\n}\n\n/**\n * A magnet's `dn=` (display name), if it has one — the title default per the\n * tracker's design call. `new URLSearchParams` is the same parse\n * `checkSource`'s `checkMagnet` uses server-side, so the two cannot disagree\n * about what a magnet's query string means.\n */\nexport function extractMagnetDisplayName(rawSource: string): string | undefined {\n const trimmed = rawSource.trim();\n if (!trimmed.toLowerCase().startsWith('magnet:?')) return undefined;\n try {\n const params = new URLSearchParams(trimmed.slice('magnet:?'.length));\n const dn = params.get('dn')?.trim();\n return dn || undefined;\n } catch {\n return undefined;\n }\n}\n\n/** The caller's own submissions, newest first. Never anybody else's. */\nexport async function fetchMySubmissions(): Promise<SubmissionResult<TSubmission[]>> {\n try {\n const res = await fetch(url('/api/submissions/mine'), { cache: 'no-store' });\n if (!res.ok) {\n return { ok: false, failure: classifySubmissionFailure(res.status, await safeJson(res)) };\n }\n const body = (await res.json()) as { submissions?: TSubmission[] };\n return { ok: true, value: Array.isArray(body.submissions) ? body.submissions : [] };\n } catch (err: unknown) {\n warn('submissions', 'List threw', errorFields(err));\n return { ok: false, failure: { code: 'network' } };\n }\n}\n\n/**\n * A failed moderation call as a short STATE line for the operator.\n *\n * A 409 here is not an error in the operator's own action, it is the normal\n * way to meet a stale queue page: another operator (or another tab) moved the\n * row first. Saying \"already approved\" would be wrong half the time — it may\n * have been rejected — and naming neither state hides that the page is behind,\n * so the line says who got there first and the row is dropped from the list.\n * The 404 is the same situation one step further on (the row is gone).\n */\nexport function decisionFailureLabel(failure: SubmissionFailure): string {\n switch (failure.code) {\n case 'conflict': return 'someone else already decided this';\n case 'not_found': return 'no longer exists';\n default: return submissionFailureLabel(failure);\n }\n}\n\n/** Whether a failed decision means the row is no longer `pending` server-side. */\nexport function decisionMeansRowMoved(failure: SubmissionFailure): boolean {\n return failure.code === 'conflict' || failure.code === 'not_found';\n}\n\n/** Age as one coarse unit: `45s`, `12m`, `3h`, `2d`. Never negative. */\nexport function formatSubmissionAge(createdAt: number, now: number): string {\n const s = Math.max(0, Math.floor((now - createdAt) / 1000));\n if (s < 60) return `${s}s`;\n if (s < 3600) return `${Math.floor(s / 60)}m`;\n if (s < 86400) return `${Math.floor(s / 3600)}h`;\n return `${Math.floor(s / 86400)}d`;\n}\n\n/** The operator queue: every `pending` row, oldest first (service tier). */\nexport async function fetchPendingSubmissions(): Promise<SubmissionResult<TSubmission[]>> {\n try {\n const res = await fetch(url('/api/submissions?state=pending'), { cache: 'no-store' });\n if (!res.ok) {\n return { ok: false, failure: classifySubmissionFailure(res.status, await safeJson(res)) };\n }\n const body = (await res.json()) as { submissions?: TSubmission[] };\n const rows = Array.isArray(body.submissions) ? body.submissions : [];\n return { ok: true, value: [...rows].sort((a, b) => a.createdAt - b.createdAt) };\n } catch (err: unknown) {\n warn('submissions', 'Queue list threw', errorFields(err));\n return { ok: false, failure: { code: 'network' } };\n }\n}\n\nasync function decide(\n id: string, verb: 'approve' | 'reject', body?: { reason: string },\n): Promise<SubmissionResult<TSubmission>> {\n try {\n const res = await fetch(url(`/api/submissions/${encodeURIComponent(id)}/${verb}`), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body ?? {}),\n });\n if (!res.ok) {\n const failure = classifySubmissionFailure(res.status, await safeJson(res));\n debug('submissions', 'Decision refused', { verb, status: res.status, code: failure.code });\n return { ok: false, failure };\n }\n const parsed = (await res.json()) as { submission: TSubmission };\n return { ok: true, value: parsed.submission };\n } catch (err: unknown) {\n warn('submissions', 'Decision threw', errorFields(err));\n return { ok: false, failure: { code: 'network' } };\n }\n}\n\nexport function approveSubmission(id: string): Promise<SubmissionResult<TSubmission>> {\n return decide(id, 'approve');\n}\n\n/** A blank reason is omitted: the server's schema refuses an empty string. */\nexport function rejectSubmission(id: string, reason: string): Promise<SubmissionResult<TSubmission>> {\n const trimmed = reason.trim();\n return decide(id, 'reject', trimmed ? { reason: trimmed } : undefined);\n}\n\n// ── Uploads: filetransfer chunk upload ──────────────────────────────────────\n//\n// The server never hands the browser its own SERVICE_KEY — `upload-target`\n// resolves a recipient userId and a cap server-side (see\n// `http/routes/submissions.ts`). What the browser does with them is Gokapi's\n// OWN chunked-upload protocol, verified against filetransfer's own browser\n// client (`hadoku-filetransfer/packages/ui/src/lib/upload.ts`) rather than\n// guessed: `POST /uploadChunk` per slice (session-authed via the\n// `hadoku_session` cookie — same-origin with this app's own apex, so\n// `credentials: 'include'` is what carries it, same as that client), then\n// `GET /auth/token` for a short-lived apikey, then `POST /api/chunk/complete`\n// with the assembly parameters as HEADERS (not form fields — Gokapi's API\n// routes read `r.Header.Get`) plus `X-Hadoku-Recipient`, which is what makes\n// this a DIRECT transfer rather than an ordinary link-shared file.\n\n/** What the server resolved for this upload. */\nexport interface UploadTarget { recipient: string; maxBytes: number; uploadUrl: string }\n\n/** `GET /api/submissions/upload-target` — friend tier. */\nexport async function fetchUploadTarget(): Promise<SubmissionResult<UploadTarget>> {\n try {\n const res = await fetch(url('/api/submissions/upload-target'), { cache: 'no-store' });\n if (!res.ok) {\n return { ok: false, failure: classifySubmissionFailure(res.status, await safeJson(res)) };\n }\n const body = (await res.json()) as Partial<UploadTarget>;\n if (\n typeof body.recipient !== 'string' || !body.recipient ||\n typeof body.maxBytes !== 'number' || typeof body.uploadUrl !== 'string' || !body.uploadUrl\n ) {\n return { ok: false, failure: { code: 'unknown', status: res.status } };\n }\n return { ok: true, value: { recipient: body.recipient, maxBytes: body.maxBytes, uploadUrl: body.uploadUrl } };\n } catch (err: unknown) {\n warn('submissions', 'Upload target fetch threw', errorFields(err));\n return { ok: false, failure: { code: 'network' } };\n }\n}\n\n/** Why a chunk upload did not produce a filetransfer file id. */\nexport type UploadFailure =\n | { code: 'too_large'; maxBytes: number }\n | { code: 'target_unavailable' }\n | { code: 'upload_failed'; message: string };\n\ntype UploadResult<T> = { ok: true; value: T } | { ok: false; failure: UploadFailure };\n\nexport function uploadFailureLabel(failure: UploadFailure): string {\n switch (failure.code) {\n case 'too_large': return `over ${formatSubmissionBytes(failure.maxBytes)} cap`;\n case 'target_unavailable': return 'upload unavailable';\n case 'upload_failed': return 'upload failed';\n }\n}\n\nexport type UploadPhase = 'uploading' | 'assembling';\n\n/** Per-request slice size: above Gokapi's 5 MB floor for a non-final chunk,\n * below Cloudflare's 100 MB single-request body ceiling. Matches\n * filetransfer's own client, which measured both bounds against the live\n * backend. */\nconst UPLOAD_CHUNK_BYTES = 40 * 1024 * 1024;\nconst UPLOAD_REQUEST_TIMEOUT_MS = 10 * 60 * 1000;\nconst UPLOAD_ATTEMPTS = 5;\n/** This is a machine-to-machine staging area, not a shared download link —\n * generous enough that neither bound is what makes the transfer fail before\n * the runner (wave 4, lane A) collects it. */\nconst UPLOAD_ALLOWED_DOWNLOADS = 50;\nconst UPLOAD_EXPIRY_DAYS = 14;\n\n/** A response retrying cannot fix — a 4xx means the request itself is wrong. */\nclass FatalUploadError extends Error {}\n\nasync function uploadFetchWithRetry(input: string, init: RequestInit, what: string): Promise<Response> {\n let last: unknown;\n for (let attempt = 0; attempt < UPLOAD_ATTEMPTS; attempt++) {\n if (attempt > 0) {\n await new Promise(r => setTimeout(r, Math.min(1000 * 2 ** (attempt - 1), 8000)));\n }\n try {\n const res = await fetch(input, { ...init, signal: AbortSignal.timeout(UPLOAD_REQUEST_TIMEOUT_MS) });\n if (res.ok) return res;\n if (res.status < 500 && res.status !== 429) {\n throw new FatalUploadError(`${what} failed (${res.status})`);\n }\n last = new Error(`${what} failed (${res.status})`);\n } catch (err: unknown) {\n if (err instanceof FatalUploadError) throw err;\n last = err;\n }\n }\n const detail = last instanceof Error ? last.message : String(last);\n throw new Error(`${what} failed after ${UPLOAD_ATTEMPTS} attempts — ${detail}`);\n}\n\n/** UTF-8-aware base64, matching what Gokapi's `base64:` header prefix decodes\n * (`btoa` alone throws on any character outside Latin-1, which a unicode\n * filename reliably has). */\nfunction b64(s: string): string {\n return btoa(String.fromCharCode(...new TextEncoder().encode(s)));\n}\n\nasync function mintUploadToken(uploadUrl: string): Promise<string> {\n const res = await fetch(`${uploadUrl}/auth/token`, {\n credentials: 'include',\n headers: { permission: 'PERM_UPLOAD' },\n });\n if (!res.ok) throw new FatalUploadError(`could not obtain an upload token (${res.status})`);\n const body = (await res.json()) as { key?: string };\n if (!body.key) throw new Error('token response had no key');\n return body.key;\n}\n\n/**\n * Chunk-upload `file` to filetransfer, addressed to `target.recipient`, and\n * return the filetransfer file id a submission's `source` should carry.\n *\n * The 4 GB cap is checked here BEFORE any network call — a courtesy only; the\n * server enforces it authoritatively on bytes actually received (wave 4,\n * lane A), because the size reported here is advisory.\n */\nexport async function uploadFileForSubmission(\n file: File,\n target: UploadTarget,\n onProgress?: (sent: number, total: number, phase: UploadPhase) => void,\n): Promise<UploadResult<{ fileId: string; bytes: number }>> {\n if (file.size > target.maxBytes) {\n return { ok: false, failure: { code: 'too_large', maxBytes: target.maxBytes } };\n }\n try {\n const id = crypto.randomUUID();\n const total = file.size;\n onProgress?.(0, total, 'uploading');\n\n for (let offset = 0; offset < total || (total === 0 && offset === 0); offset += UPLOAD_CHUNK_BYTES) {\n const slice = file.slice(offset, Math.min(offset + UPLOAD_CHUNK_BYTES, total));\n const form = new FormData();\n // Dropzone's own field names — Gokapi's non-API chunk endpoint parses\n // these with `isApiCall=false`, not the header form step 3 uses.\n form.append('dzuuid', id);\n form.append('dzchunkbyteoffset', String(offset));\n form.append('dztotalfilesize', String(total));\n form.append('file', slice, file.name);\n await uploadFetchWithRetry(\n `${target.uploadUrl}/uploadChunk`,\n { method: 'POST', credentials: 'include', body: form },\n `sending the part at ${Math.round(offset / 1024 / 1024)} MB`,\n );\n onProgress?.(Math.min(offset + UPLOAD_CHUNK_BYTES, total), total, 'uploading');\n if (total === 0) break;\n }\n\n onProgress?.(total, total, 'assembling');\n\n // Fresh key per attempt, same as filetransfer's own client: a key that\n // aged out mid-retry should not be reused by the next attempt.\n let res: Response | null = null;\n let lastErr: unknown;\n for (let attempt = 0; attempt < 2 && !res; attempt++) {\n const key = await mintUploadToken(target.uploadUrl);\n const headers: Record<string, string> = {\n uuid: id,\n filename: `base64:${b64(file.name)}`,\n filesize: String(total),\n contenttype: file.type || 'application/octet-stream',\n allowedDownloads: String(UPLOAD_ALLOWED_DOWNLOADS),\n expiryDays: String(UPLOAD_EXPIRY_DAYS),\n apikey: key,\n // The direct-transfer binding. The shim intercepts exactly this call\n // when this header is present and records (owner, recipient) against\n // the file id in its own response body.\n 'X-Hadoku-Recipient': target.recipient,\n };\n try {\n res = await uploadFetchWithRetry(\n `${target.uploadUrl}/api/chunk/complete`,\n { method: 'POST', credentials: 'include', headers },\n 'assembling the upload',\n );\n } catch (err: unknown) {\n lastErr = err;\n if (!(err instanceof FatalUploadError && /\\((401|403)\\)/.test(err.message))) throw err;\n }\n }\n if (!res) throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));\n\n const body = (await res.json()) as { FileInfo?: { Id?: string; SizeBytes?: number } };\n if (!body.FileInfo?.Id) {\n return { ok: false, failure: { code: 'upload_failed', message: 'no file info returned' } };\n }\n const bytes = typeof body.FileInfo.SizeBytes === 'number' ? body.FileInfo.SizeBytes : total;\n return { ok: true, value: { fileId: body.FileInfo.Id, bytes } };\n } catch (err: unknown) {\n warn('submissions', 'Upload threw', errorFields(err));\n const message = err instanceof Error ? err.message : String(err);\n return { ok: false, failure: { code: 'upload_failed', message } };\n }\n}\n"],"names":["guessSubmissionKind","raw","readNumber","body","keys","key","v","readString","classifySubmissionFailure","status","obj","error","pending","cap","UNITS","formatSubmissionBytes","bytes","n","u","submissionFailureLabel","failure","SUBMIT_TIERS","canSubmitMedia","getUserType","url","path","getRuntimeServerOrigin","safeJson","res","postSubmission","input","debug","parsed","err","warn","errorFields","submitSubmission","rawSource","title","source","kind","submitMagnetSubmission","opts","submitUploadSubmission","fileId","SXXEXX_RE","hasEpisodeCode","extractMagnetDisplayName","trimmed","fetchMySubmissions","decisionFailureLabel","decisionMeansRowMoved","formatSubmissionAge","createdAt","now","s","fetchPendingSubmissions","a","b","decide","id","verb","approveSubmission","rejectSubmission","reason","fetchUploadTarget","uploadFailureLabel","UPLOAD_CHUNK_BYTES","UPLOAD_REQUEST_TIMEOUT_MS","UPLOAD_ATTEMPTS","UPLOAD_ALLOWED_DOWNLOADS","UPLOAD_EXPIRY_DAYS","FatalUploadError","uploadFetchWithRetry","init","what","last","attempt","r","detail","b64","mintUploadToken","uploadUrl","uploadFileForSubmission","file","target","onProgress","total","offset","slice","form","lastErr","headers"],"mappings":"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"}
1
+ {"version":3,"file":"submissionsApi-DFGFSika.js","sources":["../../src/lib/submissionsApi.ts"],"sourcesContent":["/**\n * Client for user submissions — \"a friend asked for this media\".\n *\n * HTTP, not socket events, for the same reason the tierlist is: a submission\n * is not room state. It belongs to a PERSON, it outlives every socket that\n * person ever opens, and `GET /api/submissions/mine` must be readable from any\n * room (or none).\n *\n * ── The rules live here, not in the component ──────────────────────────────\n * Everything that can be decided without a network is a pure exported\n * function, because the failure modes are exactly the ones a component test\n * cannot reach: a quota refusal that arrives with a number, a transition\n * refusal that arrives with two states, and a host-allowlist refusal that is\n * indistinguishable from a typo unless the reason is carried through. The\n * component renders what these return.\n *\n * ── Status codes the server can answer with ────────────────────────────────\n * The route (`http/routes/submissions.ts`) is the citation for each:\n * 400 `submissions/invalid_source` — `checkSource` refused, with a `reason`\n * 400 `submissions/invalid_body` — the Zod parse failed\n * 403 `submissions/no_identity` — friend tier, but no registry userId\n * 403 (adminAuth) — below friend tier\n * 404 `submissions/not_found` — no such id\n * 409 `submissions/invalid_transition` — with `from` and `to`\n * 429 — over the per-user pending cap\n * 503 `submissions/unavailable` — no store (STATS_DB_DIR empty)\n * A 429 body is read LIBERALLY (`remainingBytes` / `remaining_bytes` /\n * `remaining`) rather than pinned to one spelling: the number is the whole\n * point of that response, and a spelling mismatch would silently degrade it to\n * a bare \"over quota\" with no way to tell that from a server that sent none.\n */\nimport { getRuntimeServerOrigin } from './config';\nimport { getUserType } from './auth';\nimport { debug, warn, errorFields } from './debug';\nimport type { TSubmission, TSubmissionKind } from '@watchparty/shared';\n\n/** Why a submissions call did not succeed. */\nexport type SubmissionFailure =\n | { code: 'invalid_source'; reason?: string }\n | { code: 'invalid_body' }\n | { code: 'quota'; remainingBytes?: number }\n | { code: 'no_identity' }\n | { code: 'forbidden' }\n | { code: 'conflict'; from?: string; to?: string }\n | { code: 'not_found' }\n | { code: 'unavailable' }\n | { code: 'network' }\n | { code: 'unknown'; status: number };\n\ntype SubmissionResult<T> =\n | { ok: true; value: T }\n | { ok: false; failure: SubmissionFailure };\n\n/**\n * Which kind of source this text is, decided by its scheme alone.\n *\n * One field, two kinds: asking a user to pick \"url\" or \"magnet\" from a select\n * is asking them to restate what they already typed. `magnet:` is the only\n * scheme with a second meaning here, so everything else is offered as a URL\n * and the SERVER decides whether it is acceptable — `checkSource` is the\n * boundary, and a client-side copy of the allowlist would be a second\n * allowlist that drifts.\n */\nexport function guessSubmissionKind(raw: string): TSubmissionKind {\n return raw.trim().toLowerCase().startsWith('magnet:') ? 'magnet' : 'url';\n}\n\n/** First numeric field among several accepted spellings. */\nfunction readNumber(body: Record<string, unknown>, keys: string[]): number | undefined {\n for (const key of keys) {\n const v = body[key];\n if (typeof v === 'number' && Number.isFinite(v) && v >= 0) return v;\n }\n return undefined;\n}\n\nfunction readString(body: Record<string, unknown>, key: string): string | undefined {\n const v = body[key];\n return typeof v === 'string' && v ? v : undefined;\n}\n\n/**\n * Turn a response status plus its parsed body into a failure.\n *\n * Pure and total: the body arrives as `unknown` because it is JSON off the\n * wire, and a server that answers a 429 with an HTML error page must still\n * produce `{ code: 'quota' }` rather than throwing inside a click handler.\n */\nexport function classifySubmissionFailure(status: number, body: unknown): SubmissionFailure {\n const obj: Record<string, unknown> =\n body && typeof body === 'object' && !Array.isArray(body)\n ? (body as Record<string, unknown>)\n : {};\n const error = readString(obj, 'error');\n\n if (status === 429) {\n // The route sends what it MEASURED (`pendingBytes`, `capBytes`); the user\n // wants the difference. Derive it rather than asking the server to send a\n // third number that could disagree with the two it already sent — and\n // clamp, because a cap lowered under an existing backlog makes it\n // negative and \"−2 GB free\" is worse than no figure at all.\n const pending = readNumber(obj, ['pendingBytes', 'pending_bytes']);\n const cap = readNumber(obj, ['capBytes', 'cap_bytes']);\n const remaining = readNumber(obj, ['remainingBytes', 'remaining_bytes', 'remaining'])\n ?? (pending !== undefined && cap !== undefined ? Math.max(0, cap - pending) : undefined);\n return { code: 'quota', remainingBytes: remaining };\n }\n if (status === 409) {\n return { code: 'conflict', from: readString(obj, 'from'), to: readString(obj, 'to') };\n }\n if (status === 404) return { code: 'not_found' };\n if (status === 503) return { code: 'unavailable' };\n if (status === 400) {\n if (error === 'submissions/invalid_source') {\n return { code: 'invalid_source', reason: readString(obj, 'reason') };\n }\n return { code: 'invalid_body' };\n }\n if (status === 401 || status === 403) {\n // `no_identity` is a friend who IS admitted and has nowhere to store a\n // row; a plain 403 is a caller below friend tier. They need different\n // words, and collapsing them makes a registry gap look like a permission.\n return error === 'submissions/no_identity' ? { code: 'no_identity' } : { code: 'forbidden' };\n }\n return { code: 'unknown', status };\n}\n\nconst UNITS = ['B', 'KB', 'MB', 'GB', 'TB'] as const;\n\n/** Bytes as a short human figure. Used only for the quota number. */\nexport function formatSubmissionBytes(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes < 0) return '—';\n let n = bytes;\n let u = 0;\n while (n >= 1024 && u < UNITS.length - 1) { n /= 1024; u++; }\n const rounded = u === 0 ? String(Math.round(n)) : n.toFixed(n < 10 ? 1 : 0);\n return `${rounded} ${UNITS[u]}`;\n}\n\n/**\n * The failure as a short STATE line.\n *\n * Deliberately not a sentence and never an instruction: the UI rule for this\n * repo is to ship state rather than narration, so this is the same register as\n * the state badge beside it. The quota case is the one that carries a number,\n * because \"over quota\" with no figure gives the user nothing to act on.\n */\nexport function submissionFailureLabel(failure: SubmissionFailure): string {\n switch (failure.code) {\n case 'invalid_source':\n switch (failure.reason) {\n case 'host_not_allowed': return 'host not allowed';\n case 'not_https': return 'https only';\n case 'bad_magnet': return 'not a magnet';\n case 'bad_upload_ref': return 'bad upload ref';\n case 'unparseable': return 'not a link';\n default: return 'rejected';\n }\n case 'invalid_body': return 'rejected';\n case 'quota':\n return failure.remainingBytes === undefined\n ? 'over quota'\n : `over quota — ${formatSubmissionBytes(failure.remainingBytes)} free`;\n case 'no_identity': return 'no identity';\n case 'forbidden': return 'not allowed';\n case 'conflict': return failure.from ? `already ${failure.from}` : 'conflict';\n case 'not_found': return 'not found';\n case 'unavailable': return 'unavailable';\n case 'network': return 'network error';\n case 'unknown': return `error ${failure.status}`;\n }\n}\n\n/**\n * May this browser offer the submit surface at all?\n *\n * The API is friend tier and above. This is a membership test rather than a\n * rank comparison against a second copy of `TIER_RANK` — `auth.ts` owns that\n * ladder and does not export it, and a hand-copied rank table is exactly the\n * drift `isPrivileged`'s own comment records (the UI's copy was missed when\n * the server's exact-match allowlist was fixed). An unlisted tier is refused,\n * so the gate fails CLOSED; the server enforces the real boundary either way.\n */\nconst SUBMIT_TIERS = new Set(['friend', 'service', 'wife', 'admin']);\nexport function canSubmitMedia(): boolean {\n return SUBMIT_TIERS.has(getUserType());\n}\n\nfunction url(path: string): string {\n return `${getRuntimeServerOrigin()}${path}`;\n}\n\n/** Parse a body without letting a non-JSON error page throw. */\nasync function safeJson(res: Response): Promise<unknown> {\n try { return await res.json(); } catch { return null; }\n}\n\n/**\n * One request to `POST /api/submissions`, with whatever fields the caller\n * has. `series` is sent even though `SubmissionCreateIn` (shared) may not yet\n * declare it — a plain `z.object()` strips unknown keys rather than erroring,\n * so this is forward-compatible with the schema change landing independently:\n * nothing here needs to change again once it does.\n */\ninterface PostSubmissionInput {\n kind: TSubmissionKind;\n source: string;\n title?: string;\n /** Magnet only. Dropped silently by the server until it recognises the field. */\n series?: string;\n}\n\nasync function postSubmission(input: PostSubmissionInput): Promise<SubmissionResult<TSubmission>> {\n const body: Record<string, string> = { kind: input.kind, source: input.source };\n if (input.title) body.title = input.title;\n if (input.series) body.series = input.series;\n try {\n const res = await fetch(url('/api/submissions'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n const failure = classifySubmissionFailure(res.status, await safeJson(res));\n debug('submissions', 'Submit refused', { status: res.status, code: failure.code });\n return { ok: false, failure };\n }\n const parsed = (await res.json()) as { submission: TSubmission };\n debug('submissions', 'Submitted', { id: parsed.submission.id, kind: input.kind });\n return { ok: true, value: parsed.submission };\n } catch (err: unknown) {\n warn('submissions', 'Submit threw', errorFields(err));\n return { ok: false, failure: { code: 'network' } };\n }\n}\n\n/** Record a request. Always lands in `pending` — approval is the operator's. */\nexport async function submitSubmission(\n rawSource: string, title?: string,\n): Promise<SubmissionResult<TSubmission>> {\n const source = rawSource.trim();\n const kind = guessSubmissionKind(source);\n return postSubmission({ kind, source, title });\n}\n\n/**\n * A magnet carries a `series` — the anime folder the scraper will file it\n * under. `checkSource('magnet', …)` is the AUTHORITY on requiring it and on\n * the title's `SxxExx` shape; this call sends what the caller typed and lets\n * the server's 400 (`invalid_source`, reason `bad_magnet` or similar) speak\n * for a missing or malformed one rather than a client-side copy of that rule.\n */\nexport async function submitMagnetSubmission(\n rawSource: string, opts: { title?: string; series: string },\n): Promise<SubmissionResult<TSubmission>> {\n return postSubmission({\n kind: 'magnet', source: rawSource.trim(), title: opts.title, series: opts.series.trim(),\n });\n}\n\n/** Record an upload once its bytes are already sitting in the filetransfer\n * inbox. `fileId` is the filetransfer file id (`FileInfo.Id`) a chunk-upload\n * completed with — see {@link uploadFileForSubmission}. */\nexport async function submitUploadSubmission(\n fileId: string, title?: string,\n): Promise<SubmissionResult<TSubmission>> {\n return postSubmission({ kind: 'upload', source: fileId, title });\n}\n\n/**\n * Whether a title carries an episode code the scraper's destination rule\n * needs (`SxxExx`, any case). Pure and client-side as a courtesy ONLY — the\n * server's `checkSource('magnet', …)` is the one place this is enforced; see\n * mutation check (a) in the wave brief: dropping the requirement here must\n * still leave a bad title refused by the server.\n */\nconst SXXEXX_RE = /S\\d{2}E\\d{2}/i;\nexport function hasEpisodeCode(title: string): boolean {\n return SXXEXX_RE.test(title);\n}\n\n/**\n * A magnet's `dn=` (display name), if it has one — the title default per the\n * tracker's design call. `new URLSearchParams` is the same parse\n * `checkSource`'s `checkMagnet` uses server-side, so the two cannot disagree\n * about what a magnet's query string means.\n */\nexport function extractMagnetDisplayName(rawSource: string): string | undefined {\n const trimmed = rawSource.trim();\n if (!trimmed.toLowerCase().startsWith('magnet:?')) return undefined;\n try {\n const params = new URLSearchParams(trimmed.slice('magnet:?'.length));\n const dn = params.get('dn')?.trim();\n return dn || undefined;\n } catch {\n return undefined;\n }\n}\n\n/** The caller's own submissions, newest first. Never anybody else's. */\nexport async function fetchMySubmissions(): Promise<SubmissionResult<TSubmission[]>> {\n try {\n const res = await fetch(url('/api/submissions/mine'), { cache: 'no-store' });\n if (!res.ok) {\n return { ok: false, failure: classifySubmissionFailure(res.status, await safeJson(res)) };\n }\n const body = (await res.json()) as { submissions?: TSubmission[] };\n return { ok: true, value: Array.isArray(body.submissions) ? body.submissions : [] };\n } catch (err: unknown) {\n warn('submissions', 'List threw', errorFields(err));\n return { ok: false, failure: { code: 'network' } };\n }\n}\n\n/**\n * A failed moderation call as a short STATE line for the operator.\n *\n * A 409 here is not an error in the operator's own action, it is the normal\n * way to meet a stale queue page: another operator (or another tab) moved the\n * row first. Saying \"already approved\" would be wrong half the time — it may\n * have been rejected — and naming neither state hides that the page is behind,\n * so the line says who got there first and the row is dropped from the list.\n * The 404 is the same situation one step further on (the row is gone).\n */\nexport function decisionFailureLabel(failure: SubmissionFailure): string {\n switch (failure.code) {\n case 'conflict': return 'someone else already decided this';\n case 'not_found': return 'no longer exists';\n default: return submissionFailureLabel(failure);\n }\n}\n\n/** Whether a failed decision means the row is no longer `pending` server-side. */\nexport function decisionMeansRowMoved(failure: SubmissionFailure): boolean {\n return failure.code === 'conflict' || failure.code === 'not_found';\n}\n\n/** Age as one coarse unit: `45s`, `12m`, `3h`, `2d`. Never negative. */\nexport function formatSubmissionAge(createdAt: number, now: number): string {\n const s = Math.max(0, Math.floor((now - createdAt) / 1000));\n if (s < 60) return `${s}s`;\n if (s < 3600) return `${Math.floor(s / 60)}m`;\n if (s < 86400) return `${Math.floor(s / 3600)}h`;\n return `${Math.floor(s / 86400)}d`;\n}\n\n/** The operator queue: every `pending` row, oldest first (service tier). */\nexport async function fetchPendingSubmissions(): Promise<SubmissionResult<TSubmission[]>> {\n try {\n const res = await fetch(url('/api/submissions?state=pending'), { cache: 'no-store' });\n if (!res.ok) {\n return { ok: false, failure: classifySubmissionFailure(res.status, await safeJson(res)) };\n }\n const body = (await res.json()) as { submissions?: TSubmission[] };\n const rows = Array.isArray(body.submissions) ? body.submissions : [];\n return { ok: true, value: [...rows].sort((a, b) => a.createdAt - b.createdAt) };\n } catch (err: unknown) {\n warn('submissions', 'Queue list threw', errorFields(err));\n return { ok: false, failure: { code: 'network' } };\n }\n}\n\nasync function decide(\n id: string, verb: 'approve' | 'reject', body?: { reason: string },\n): Promise<SubmissionResult<TSubmission>> {\n try {\n const res = await fetch(url(`/api/submissions/${encodeURIComponent(id)}/${verb}`), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body ?? {}),\n });\n if (!res.ok) {\n const failure = classifySubmissionFailure(res.status, await safeJson(res));\n debug('submissions', 'Decision refused', { verb, status: res.status, code: failure.code });\n return { ok: false, failure };\n }\n const parsed = (await res.json()) as { submission: TSubmission };\n return { ok: true, value: parsed.submission };\n } catch (err: unknown) {\n warn('submissions', 'Decision threw', errorFields(err));\n return { ok: false, failure: { code: 'network' } };\n }\n}\n\nexport function approveSubmission(id: string): Promise<SubmissionResult<TSubmission>> {\n return decide(id, 'approve');\n}\n\n/** A blank reason is omitted: the server's schema refuses an empty string. */\nexport function rejectSubmission(id: string, reason: string): Promise<SubmissionResult<TSubmission>> {\n const trimmed = reason.trim();\n return decide(id, 'reject', trimmed ? { reason: trimmed } : undefined);\n}\n\n// ── Uploads: filetransfer chunk upload ──────────────────────────────────────\n//\n// The server never hands the browser its own SERVICE_KEY — `upload-target`\n// resolves a recipient userId and a cap server-side (see\n// `http/routes/submissions.ts`). What the browser does with them is Gokapi's\n// OWN chunked-upload protocol, verified against filetransfer's own browser\n// client (`hadoku-filetransfer/packages/ui/src/lib/upload.ts`) rather than\n// guessed: `POST /uploadChunk` per slice (session-authed via the\n// `hadoku_session` cookie — same-origin with this app's own apex, so\n// `credentials: 'include'` is what carries it, same as that client), then\n// `GET /auth/token` for a short-lived apikey, then `POST /api/chunk/complete`\n// with the assembly parameters as HEADERS (not form fields — Gokapi's API\n// routes read `r.Header.Get`) plus `X-Hadoku-Recipient`, which is what makes\n// this a DIRECT transfer rather than an ordinary link-shared file.\n\n/** What the server resolved for this upload. */\nexport interface UploadTarget { recipient: string; maxBytes: number; uploadUrl: string }\n\n/** `GET /api/submissions/upload-target` — friend tier. */\nexport async function fetchUploadTarget(): Promise<SubmissionResult<UploadTarget>> {\n try {\n const res = await fetch(url('/api/submissions/upload-target'), { cache: 'no-store' });\n if (!res.ok) {\n return { ok: false, failure: classifySubmissionFailure(res.status, await safeJson(res)) };\n }\n const body = (await res.json()) as Partial<UploadTarget>;\n if (\n typeof body.recipient !== 'string' || !body.recipient ||\n typeof body.maxBytes !== 'number' || typeof body.uploadUrl !== 'string' || !body.uploadUrl\n ) {\n return { ok: false, failure: { code: 'unknown', status: res.status } };\n }\n return { ok: true, value: { recipient: body.recipient, maxBytes: body.maxBytes, uploadUrl: body.uploadUrl } };\n } catch (err: unknown) {\n warn('submissions', 'Upload target fetch threw', errorFields(err));\n return { ok: false, failure: { code: 'network' } };\n }\n}\n\n/** Why a chunk upload did not produce a filetransfer file id. */\nexport type UploadFailure =\n | { code: 'too_large'; maxBytes: number }\n | { code: 'target_unavailable' }\n | { code: 'upload_failed'; message: string };\n\ntype UploadResult<T> = { ok: true; value: T } | { ok: false; failure: UploadFailure };\n\nexport function uploadFailureLabel(failure: UploadFailure): string {\n switch (failure.code) {\n case 'too_large': return `over ${formatSubmissionBytes(failure.maxBytes)} cap`;\n case 'target_unavailable': return 'upload unavailable';\n case 'upload_failed': return 'upload failed';\n }\n}\n\nexport type UploadPhase = 'uploading' | 'assembling';\n\n/** Per-request slice size: above Gokapi's 5 MB floor for a non-final chunk,\n * below Cloudflare's 100 MB single-request body ceiling. Matches\n * filetransfer's own client, which measured both bounds against the live\n * backend. */\nconst UPLOAD_CHUNK_BYTES = 40 * 1024 * 1024;\nconst UPLOAD_REQUEST_TIMEOUT_MS = 10 * 60 * 1000;\nconst UPLOAD_ATTEMPTS = 5;\n/** This is a machine-to-machine staging area, not a shared download link —\n * generous enough that neither bound is what makes the transfer fail before\n * the runner (wave 4, lane A) collects it. */\nconst UPLOAD_ALLOWED_DOWNLOADS = 50;\nconst UPLOAD_EXPIRY_DAYS = 14;\n\n/** A response retrying cannot fix — a 4xx means the request itself is wrong. */\nclass FatalUploadError extends Error {}\n\nasync function uploadFetchWithRetry(input: string, init: RequestInit, what: string): Promise<Response> {\n let last: unknown;\n for (let attempt = 0; attempt < UPLOAD_ATTEMPTS; attempt++) {\n if (attempt > 0) {\n await new Promise(r => setTimeout(r, Math.min(1000 * 2 ** (attempt - 1), 8000)));\n }\n try {\n const res = await fetch(input, { ...init, signal: AbortSignal.timeout(UPLOAD_REQUEST_TIMEOUT_MS) });\n if (res.ok) return res;\n if (res.status < 500 && res.status !== 429) {\n throw new FatalUploadError(`${what} failed (${res.status})`);\n }\n last = new Error(`${what} failed (${res.status})`);\n } catch (err: unknown) {\n if (err instanceof FatalUploadError) throw err;\n last = err;\n }\n }\n const detail = last instanceof Error ? last.message : String(last);\n throw new Error(`${what} failed after ${UPLOAD_ATTEMPTS} attempts — ${detail}`);\n}\n\n/** UTF-8-aware base64, matching what Gokapi's `base64:` header prefix decodes\n * (`btoa` alone throws on any character outside Latin-1, which a unicode\n * filename reliably has). */\nfunction b64(s: string): string {\n return btoa(String.fromCharCode(...new TextEncoder().encode(s)));\n}\n\nasync function mintUploadToken(uploadUrl: string): Promise<string> {\n const res = await fetch(`${uploadUrl}/auth/token`, {\n credentials: 'include',\n headers: { permission: 'PERM_UPLOAD' },\n });\n if (!res.ok) throw new FatalUploadError(`could not obtain an upload token (${res.status})`);\n const body = (await res.json()) as { key?: string };\n if (!body.key) throw new Error('token response had no key');\n return body.key;\n}\n\n/**\n * Chunk-upload `file` to filetransfer, addressed to `target.recipient`, and\n * return the filetransfer file id a submission's `source` should carry.\n *\n * The 4 GB cap is checked here BEFORE any network call — a courtesy only; the\n * server enforces it authoritatively on bytes actually received (wave 4,\n * lane A), because the size reported here is advisory.\n */\nexport async function uploadFileForSubmission(\n file: File,\n target: UploadTarget,\n onProgress?: (sent: number, total: number, phase: UploadPhase) => void,\n): Promise<UploadResult<{ fileId: string; bytes: number }>> {\n if (file.size > target.maxBytes) {\n return { ok: false, failure: { code: 'too_large', maxBytes: target.maxBytes } };\n }\n try {\n const id = crypto.randomUUID();\n const total = file.size;\n onProgress?.(0, total, 'uploading');\n\n for (let offset = 0; offset < total || (total === 0 && offset === 0); offset += UPLOAD_CHUNK_BYTES) {\n const slice = file.slice(offset, Math.min(offset + UPLOAD_CHUNK_BYTES, total));\n const form = new FormData();\n // Dropzone's own field names — Gokapi's non-API chunk endpoint parses\n // these with `isApiCall=false`, not the header form step 3 uses.\n form.append('dzuuid', id);\n form.append('dzchunkbyteoffset', String(offset));\n form.append('dztotalfilesize', String(total));\n form.append('file', slice, file.name);\n await uploadFetchWithRetry(\n `${target.uploadUrl}/uploadChunk`,\n { method: 'POST', credentials: 'include', body: form },\n `sending the part at ${Math.round(offset / 1024 / 1024)} MB`,\n );\n onProgress?.(Math.min(offset + UPLOAD_CHUNK_BYTES, total), total, 'uploading');\n if (total === 0) break;\n }\n\n onProgress?.(total, total, 'assembling');\n\n // Fresh key per attempt, same as filetransfer's own client: a key that\n // aged out mid-retry should not be reused by the next attempt.\n let res: Response | null = null;\n let lastErr: unknown;\n for (let attempt = 0; attempt < 2 && !res; attempt++) {\n const key = await mintUploadToken(target.uploadUrl);\n const headers: Record<string, string> = {\n uuid: id,\n filename: `base64:${b64(file.name)}`,\n filesize: String(total),\n contenttype: file.type || 'application/octet-stream',\n allowedDownloads: String(UPLOAD_ALLOWED_DOWNLOADS),\n expiryDays: String(UPLOAD_EXPIRY_DAYS),\n apikey: key,\n // The direct-transfer binding. The shim intercepts exactly this call\n // when this header is present and records (owner, recipient) against\n // the file id in its own response body.\n 'X-Hadoku-Recipient': target.recipient,\n };\n try {\n res = await uploadFetchWithRetry(\n `${target.uploadUrl}/api/chunk/complete`,\n { method: 'POST', credentials: 'include', headers },\n 'assembling the upload',\n );\n } catch (err: unknown) {\n lastErr = err;\n if (!(err instanceof FatalUploadError && /\\((401|403)\\)/.test(err.message))) throw err;\n }\n }\n if (!res) throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));\n\n const body = (await res.json()) as { FileInfo?: { Id?: string; SizeBytes?: number } };\n if (!body.FileInfo?.Id) {\n return { ok: false, failure: { code: 'upload_failed', message: 'no file info returned' } };\n }\n const bytes = typeof body.FileInfo.SizeBytes === 'number' ? body.FileInfo.SizeBytes : total;\n return { ok: true, value: { fileId: body.FileInfo.Id, bytes } };\n } catch (err: unknown) {\n warn('submissions', 'Upload threw', errorFields(err));\n const message = err instanceof Error ? err.message : String(err);\n return { ok: false, failure: { code: 'upload_failed', message } };\n }\n}\n"],"names":["guessSubmissionKind","raw","readNumber","body","keys","key","v","readString","classifySubmissionFailure","status","obj","error","pending","cap","UNITS","formatSubmissionBytes","bytes","n","u","submissionFailureLabel","failure","SUBMIT_TIERS","canSubmitMedia","getUserType","url","path","getRuntimeServerOrigin","safeJson","res","postSubmission","input","debug","parsed","err","warn","errorFields","submitSubmission","rawSource","title","source","kind","submitMagnetSubmission","opts","submitUploadSubmission","fileId","SXXEXX_RE","hasEpisodeCode","extractMagnetDisplayName","trimmed","fetchMySubmissions","decisionFailureLabel","decisionMeansRowMoved","formatSubmissionAge","createdAt","now","s","fetchPendingSubmissions","a","b","decide","id","verb","approveSubmission","rejectSubmission","reason","fetchUploadTarget","uploadFailureLabel","UPLOAD_CHUNK_BYTES","UPLOAD_REQUEST_TIMEOUT_MS","UPLOAD_ATTEMPTS","UPLOAD_ALLOWED_DOWNLOADS","UPLOAD_EXPIRY_DAYS","FatalUploadError","uploadFetchWithRetry","init","what","last","attempt","r","detail","b64","mintUploadToken","uploadUrl","uploadFileForSubmission","file","target","onProgress","total","offset","slice","form","lastErr","headers"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { aO as o, aM as r, aN as e } from "./chunks/index-DOW8Etk-.js";
1
+ import { aO as o, aM as r, aN as e } from "./chunks/index-CXl-xAUD.js";
2
2
  export {
3
3
  o as createWatchpartyRouter,
4
4
  r as mount,
@@ -1 +1,126 @@
1
1
  @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){@scope (.wp-room-chrome){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--advanced-gradient:initial}}}}@layer theme{@scope (.wp-room-chrome){:scope,:scope{--color-black:#000;--color-white:#fff;--spacing:.25rem;--text-xs:var(--hdk-text-xs);--text-xs--line-height:calc(1/.75);--text-sm:var(--hdk-text-sm);--text-sm--line-height:calc(1.25/.875);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--font-weight-normal:var(--font-weight-normal);--font-weight-medium:500;--font-weight-semibold:var(--font-weight-semibold);--font-weight-bold:var(--font-weight-bold);--tracking-normal:0em;--tracking-wide:.025em;--leading-tight:1.25;--leading-snug:1.375;--leading-relaxed:1.625;--radius-lg:var(--hdk-radius-lg);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--radius:var(--hdk-radius);--color-on-primary:var(--color-on-primary);--color-on-primary-bg:var(--color-on-primary-bg);--color-primary:var(--color-primary);--color-primary-bg:var(--color-primary-bg);--color-primary-dark:var(--color-primary-dark);--color-primary-hover:var(--color-primary-hover);--color-on-success:var(--color-on-success);--color-on-success-bg:var(--color-on-success-bg);--color-success:var(--color-success);--color-success-bg:var(--color-success-bg);--color-success-dark:var(--color-success-dark);--color-success-hover:var(--color-success-hover);--color-on-warning:var(--color-on-warning);--color-on-warning-bg:var(--color-on-warning-bg);--color-warning:var(--color-warning);--color-warning-bg:var(--color-warning-bg);--color-warning-dark:var(--color-warning-dark);--color-warning-hover:var(--color-warning-hover);--color-danger:var(--color-danger);--color-danger-bg:var(--color-danger-bg);--color-danger-dark:var(--color-danger-dark);--color-danger-hover:var(--color-danger-hover);--color-on-danger:var(--color-on-danger);--color-on-danger-bg:var(--color-on-danger-bg);--color-neutral:var(--color-neutral);--color-neutral-bg:var(--color-neutral-bg);--color-neutral-dark:var(--color-neutral-dark);--color-neutral-hover:var(--color-neutral-hover);--color-on-neutral:var(--color-on-neutral);--color-on-neutral-bg:var(--color-on-neutral-bg);--color-text:var(--color-text);--color-text-muted:var(--color-text-muted);--color-text-secondary:var(--color-text-secondary);--color-text-tertiary:var(--color-text-tertiary);--color-border:var(--color-border);--color-border-light:var(--color-border-light);--color-bg:var(--color-bg);--color-bg-alt:var(--color-bg-alt);--color-bg-card:var(--color-bg-card);--color-bg-hover:var(--color-bg-hover);--color-bg-overlay:var(--color-bg-overlay);--line-height-normal:var(--line-height-normal);--line-height-relaxed:var(--line-height-relaxed)}}}@layer utilities{@scope (.wp-room-chrome){.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.inset-0{inset:calc(var(--spacing)*0)}.-top-1{top:calc(var(--spacing)*-1)}.top-1\/2{top:50%}.top-4{top:calc(var(--spacing)*4)}.top-full{top:100%}.-right-1{right:calc(var(--spacing)*-1)}.right-0{right:calc(var(--spacing)*0)}.right-2{right:calc(var(--spacing)*2)}.right-4{right:calc(var(--spacing)*4)}.right-full{right:100%}.bottom-2{bottom:calc(var(--spacing)*2)}.bottom-4{bottom:calc(var(--spacing)*4)}.bottom-14{bottom:calc(var(--spacing)*14)}.bottom-full{bottom:100%}.left-0{left:calc(var(--spacing)*0)}.left-1\/2{left:50%}.left-2{left:calc(var(--spacing)*2)}.z-10{z-index:10}.z-30{z-index:30}.z-50{z-index:50}.z-\[10000\]{z-index:10000}.container{width:100%}}@media(min-width:40rem){@scope (.wp-room-chrome){.container{max-width:40rem}}}@media(min-width:48rem){@scope (.wp-room-chrome){.container{max-width:48rem}}}@media(min-width:64rem){@scope (.wp-room-chrome){.container{max-width:64rem}}}@media(min-width:80rem){@scope (.wp-room-chrome){.container{max-width:80rem}}}@media(min-width:96rem){@scope (.wp-room-chrome){.container{max-width:96rem}}}@scope (.wp-room-chrome){.m-0{margin:calc(var(--spacing)*0)}.m-2{margin:calc(var(--spacing)*2)}.my-1{margin-block:calc(var(--spacing)*1)}.-mt-0\.5{margin-top:calc(var(--spacing)*-.5)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mr-2{margin-right:calc(var(--spacing)*2)}.mb-0\.5{margin-bottom:calc(var(--spacing)*.5)}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.ml-1{margin-left:calc(var(--spacing)*1)}.ml-1\.5{margin-left:calc(var(--spacing)*1.5)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.table{display:table}.aspect-square{aspect-ratio:1}.h-4{height:calc(var(--spacing)*4)}.h-6{height:calc(var(--spacing)*6)}.h-14{height:calc(var(--spacing)*14)}.h-full{height:100%}.h-px{height:1px}.max-h-56{max-height:calc(var(--spacing)*56)}.max-h-64{max-height:calc(var(--spacing)*64)}.max-h-80{max-height:calc(var(--spacing)*80)}.max-h-full{max-height:100%}.w-4{width:calc(var(--spacing)*4)}.w-6{width:calc(var(--spacing)*6)}.w-14{width:calc(var(--spacing)*14)}.w-72{width:calc(var(--spacing)*72)}.w-80{width:calc(var(--spacing)*80)}.w-full{width:100%}.w-max{width:max-content}.max-w-\[220px\]{max-width:220px}.max-w-full{max-width:100%}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-\[180px\]{min-width:180px}.flex-1{flex:1}.flex-shrink-0,.shrink-0{flex-shrink:0}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x)var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.cursor-pointer{cursor:pointer}.cursor-zoom-in{cursor:zoom-in}.list-none{list-style-type:none}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-center{justify-content:center}.gap-1{gap:calc(var(--spacing)*1)}.gap-1\.5{gap:calc(var(--spacing)*1.5)}.gap-2{gap:calc(var(--spacing)*2)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:var(--radius)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-border{border-color:var(--color-border)}.border-border-light{border-color:var(--color-border-light)}.border-danger{border-color:var(--color-danger)}.border-primary{border-color:var(--color-primary)}.bg-bg{background-color:var(--color-bg)}.bg-bg-alt,.bg-bg-alt\/30{background-color:var(--color-bg-alt)}}@supports (color:color-mix(in lab,red,red)){@scope (.wp-room-chrome){.bg-bg-alt\/30{background-color:color-mix(in oklab,var(--color-bg-alt)30%,transparent)}}}@scope (.wp-room-chrome){.bg-bg-card,.bg-bg-card\/30{background-color:var(--color-bg-card)}}@supports (color:color-mix(in lab,red,red)){@scope (.wp-room-chrome){.bg-bg-card\/30{background-color:color-mix(in oklab,var(--color-bg-card)30%,transparent)}}}@scope (.wp-room-chrome){.bg-black\/85{background-color:#000000d9}}@supports (color:color-mix(in lab,red,red)){@scope (.wp-room-chrome){.bg-black\/85{background-color:color-mix(in oklab,var(--color-black)85%,transparent)}}}@scope (.wp-room-chrome){.bg-border{background-color:var(--color-border)}.bg-danger-bg{background-color:var(--color-danger-bg)}.bg-primary-bg{background-color:var(--color-primary-bg)}.bg-success\/20{background-color:var(--color-success)}}@supports (color:color-mix(in lab,red,red)){@scope (.wp-room-chrome){.bg-success\/20{background-color:color-mix(in oklab,var(--color-success)20%,transparent)}}}@scope (.wp-room-chrome){.object-contain{object-fit:contain}.object-cover{object-fit:cover}.p-0{padding:calc(var(--spacing)*0)}.p-1\.5{padding:calc(var(--spacing)*1.5)}.p-2{padding:calc(var(--spacing)*2)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-1\.5{padding-inline:calc(var(--spacing)*1.5)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.pr-12{padding-right:calc(var(--spacing)*12)}.pb-1\.5{padding-bottom:calc(var(--spacing)*1.5)}.pl-2\.5{padding-left:calc(var(--spacing)*2.5)}.text-center{text-align:center}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[9px\]{font-size:9px}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-normal{--tw-tracking:var(--tracking-normal);letter-spacing:var(--tracking-normal)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.break-words{overflow-wrap:break-word}.text-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-on-danger-bg{color:var(--color-on-danger-bg)}.text-on-primary-bg{color:var(--color-on-primary-bg)}.text-success{color:var(--color-success)}.text-text{color:var(--color-text)}.text-text-muted{color:var(--color-text-muted)}.text-text-secondary{color:var(--color-text-secondary)}.text-white\/70{color:#ffffffb3}}@supports (color:color-mix(in lab,red,red)){@scope (.wp-room-chrome){.text-white\/70{color:color-mix(in oklab,var(--color-white)70%,transparent)}}}@scope (.wp-room-chrome){.text-white\/80{color:#fffc}}@supports (color:color-mix(in lab,red,red)){@scope (.wp-room-chrome){.text-white\/80{color:color-mix(in oklab,var(--color-white)80%,transparent)}}}@scope (.wp-room-chrome){.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.italic{font-style:italic}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.shadow{--tw-shadow:var(--hdk-shadow-md);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:var(--hdk-shadow-lg);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.placeholder\:text-text-secondary::placeholder{color:var(--color-text-secondary)}}@media(hover:hover){@scope (.wp-room-chrome){.hover\:border-primary:hover{border-color:var(--color-primary)}.hover\:bg-bg-alt:hover{background-color:var(--color-bg-alt)}.hover\:bg-bg-card\/20:hover{background-color:var(--color-bg-card)}}@supports (color:color-mix(in lab,red,red)){@scope (.wp-room-chrome){.hover\:bg-bg-card\/20:hover{background-color:color-mix(in oklab,var(--color-bg-card)20%,transparent)}}}@scope (.wp-room-chrome){.hover\:bg-danger-bg:hover{background-color:var(--color-danger-bg)}.hover\:text-on-danger-bg:hover{color:var(--color-on-danger-bg)}.hover\:text-text:hover{color:var(--color-text)}.hover\:text-white:hover{color:var(--color-white)}}}@scope (.wp-room-chrome){.focus\:border-primary:focus{border-color:var(--color-primary)}.focus\:border-text-muted:focus{border-color:var(--color-text-muted)}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-white\/50:focus{--tw-ring-color:#ffffff80}}@supports (color:color-mix(in lab,red,red)){@scope (.wp-room-chrome){.focus\:ring-white\/50:focus{--tw-ring-color:color-mix(in oklab,var(--color-white)50%,transparent)}}}@scope (.wp-room-chrome){.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:opacity-50:disabled{opacity:.5}}@media(min-width:40rem){@scope (.wp-room-chrome){.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}}}@property --advanced-gradient{syntax:"*";inherits:false}@scope (.wp-room-chrome){:scope,:scope[data-theme=light]{color-scheme:light;--color-primary:#2563eb;--color-primary-dark:#1d4ed8;--color-primary-bg:#eff6ff;--color-primary-hover:#2563eb0f;--color-on-primary:white;--color-on-primary-bg:#2563eb;--color-success:#0ea5e9;--color-success-dark:#0284c7;--color-success-bg:#0ea5e91a;--color-success-hover:#0ea5e90f;--color-on-success:black;--color-on-success-bg:#0067a7;--color-warning:#f59e0b;--color-warning-dark:#ce7a00;--color-warning-bg:#f59e0b1a;--color-warning-hover:#f59e0b0f;--color-on-warning:black;--color-on-warning-bg:#9e4d00;--color-danger:#f97316;--color-danger-dark:#ea580c;--color-danger-bg:#ffedd5;--color-danger-hover:#f973160f;--color-on-danger:black;--color-on-danger-bg:#c13e00;--color-neutral:#637389;--color-neutral-dark:#435267;--color-neutral-bg:#e2e8f0;--color-neutral-hover:#64748b0f;--color-on-neutral:white;--color-on-neutral-bg:#58677d;--color-text:#0f172a;--color-text-secondary:#475569;--color-text-tertiary:#64748b;--color-text-muted:#94a3b8;--color-border:#cdd4db;--color-border-light:#dfe5ed;--color-bg:#e7eef5;--color-bg-card:#fff;--color-bg-alt:#e7eef5;--color-bg-hover:#e2e9f0;--color-bg-overlay:#0f172a80;--hdk-space-xs:2px;--hdk-space-sm:4px;--hdk-space-md:8px;--hdk-space-lg:12px;--hdk-space-xl:16px;--hdk-space-2xl:20px;--hdk-space-3xl:24px;--hdk-space-4xl:40px;--hdk-space-5xl:60px;--font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;--hdk-text-xs:12px;--hdk-text-sm:14px;--hdk-text-md:15px;--hdk-text-base:16px;--hdk-text-lg:24px;--font-weight-normal:400;--font-weight-semibold:500;--font-weight-bold:600;--line-height-normal:1.4;--line-height-relaxed:1.5;--hdk-radius-sm:4px;--hdk-radius:6px;--hdk-radius-lg:8px;--hdk-shadow-sm:0 1px 2px #0f172a0a;--hdk-shadow-md:0 2px 4px #0f172a14;--hdk-shadow-lg:0 4px 12px #0f172a26;--hdk-shadow-focus:0 0 0 3px #2563eb33;--hdk-shadow-focus-sm:0 0 0 2px #2563eb33;--hdk-shadow-focus-alt:0 0 0 2px #2563eb1a;--transition-fast:.15s ease;--transition-smooth:.2s ease}:scope[data-theme=light]{--advanced-stop-1-l:.82;--advanced-stop-1-c:.18;--advanced-stop-1-h:55;--advanced-stop-2-l:.78;--advanced-stop-2-c:.2;--advanced-stop-2-h:40;--advanced-stop-3-l:.72;--advanced-stop-3-c:.22;--advanced-stop-3-h:25;--advanced-stop-4-l:.7;--advanced-stop-4-c:.18;--advanced-stop-4-h:10;--advanced-stop-5-l:.72;--advanced-stop-5-c:.16;--advanced-stop-5-h:355;--advanced-stop-6-l:.68;--advanced-stop-6-c:.14;--advanced-stop-6-h:190;--advanced-stop-7-l:.6;--advanced-stop-7-c:.16;--advanced-stop-7-h:200;--advanced-stop-8-l:.5;--advanced-stop-8-c:.18;--advanced-stop-8-h:210;--advanced-gradient:linear-gradient(135deg,oklch(var(--advanced-stop-1-l)var(--advanced-stop-1-c)var(--advanced-stop-1-h))0%,oklch(var(--advanced-stop-2-l)var(--advanced-stop-2-c)var(--advanced-stop-2-h))15%,oklch(var(--advanced-stop-3-l)var(--advanced-stop-3-c)var(--advanced-stop-3-h))30%,oklch(var(--advanced-stop-4-l)var(--advanced-stop-4-c)var(--advanced-stop-4-h))45%,oklch(var(--advanced-stop-5-l)var(--advanced-stop-5-c)var(--advanced-stop-5-h))55%,oklch(var(--advanced-stop-6-l)var(--advanced-stop-6-c)var(--advanced-stop-6-h))70%,oklch(var(--advanced-stop-7-l)var(--advanced-stop-7-c)var(--advanced-stop-7-h))85%,oklch(var(--advanced-stop-8-l)var(--advanced-stop-8-c)var(--advanced-stop-8-h))100%)}:scope[data-theme=dark]{color-scheme:dark only;--color-primary:#b793ff;--color-primary-dark:#a17de7;--color-primary-bg:#b793ff29;--color-primary-hover:#b793ff1f;--color-on-primary:black;--color-on-primary-bg:#b793ff;--color-success:#fcde3d;--color-success-dark:#e5c70e;--color-success-bg:#fcde3d26;--color-success-hover:#fcde3d1a;--color-on-success:black;--color-on-success-bg:#fcde3d;--color-warning:#ffb848;--color-warning-dark:#e7a22b;--color-warning-bg:#ffb84826;--color-warning-hover:#ffb8481a;--color-on-warning:black;--color-on-warning-bg:#ffb848;--color-danger:#f46a82;--color-danger-dark:#db536d;--color-danger-bg:#f46a8226;--color-danger-hover:#f46a821a;--color-on-danger:black;--color-on-danger-bg:#fb7088;--color-neutral:#9d9ea6;--color-neutral-dark:#888991;--color-neutral-bg:#2f3038;--color-neutral-hover:#9d9ea61a;--color-on-neutral:black;--color-on-neutral-bg:#9d9ea6;--color-text:#f4f2f9;--color-text-secondary:#dcd9e3;--color-text-tertiary:#c0bbcb;--color-text-muted:#948f9e;--color-border:#3c3d40;--color-border-light:#525256;--color-bg:#151618;--color-bg-card:#222325;--color-bg-alt:#101013;--color-bg-hover:#2c2c30;--color-bg-overlay:#000000b8;--hdk-shadow-sm:0 1px 2px #0006;--hdk-shadow-md:0 2px 4px #00000080;--hdk-shadow-lg:0 8px 24px #000000b3;--hdk-shadow-focus:0 0 0 3px #b793ff4d;--hdk-shadow-focus-sm:0 0 0 2px #b793ff4d;--hdk-shadow-focus-alt:0 0 0 2px #b793ff33}:scope[data-theme=strawberry-light]{color-scheme:light;--color-primary:#ff6b9d;--color-primary-dark:#ff477a;--color-primary-bg:#fff5f8;--color-primary-hover:#ff6b9d1a;--color-on-primary:black;--color-on-primary-bg:#c6356d;--color-success:#22c55e;--color-success-dark:#16a34a;--color-success-bg:#22c55e1a;--color-success-hover:#22c55e0f;--color-on-success:black;--color-on-success-bg:#006d00;--color-warning:#f59e0b;--color-warning-dark:#ce7a00;--color-warning-bg:#f59e0b1a;--color-warning-hover:#f59e0b0f;--color-on-warning:black;--color-on-warning-bg:#984700;--color-danger:#ec4899;--color-danger-dark:#db2777;--color-danger-bg:#ffe4e9;--color-danger-hover:#ec48990f;--color-on-danger:black;--color-on-danger-bg:#c51a78;--color-neutral:#94a3b8;--color-neutral-dark:#728094;--color-neutral-bg:#fdd5e0;--color-neutral-hover:#94a3b80f;--color-on-neutral:black;--color-on-neutral-bg:#546275;--color-text:#1f1c1e;--color-text-secondary:#6b5a5e;--color-text-tertiary:#a78b92;--color-text-muted:#c4a4ab;--color-border:#f3becf;--color-border-light:#f6d7e0;--color-bg:#f0e6e9;--color-bg-card:#fff9fc;--color-bg-alt:#ecdde1;--color-bg-hover:#eccdd6;--color-bg-overlay:#ff6b9d4d;--hdk-shadow-sm:0 1px 2px #ff6b9d14;--hdk-shadow-md:0 2px 4px #ff6b9d1f;--hdk-shadow-lg:0 8px 24px #ff6b9d33;--hdk-shadow-focus:0 0 0 3px #ff6b9d40;--hdk-shadow-focus-sm:0 0 0 2px #ff6b9d40;--hdk-shadow-focus-alt:0 0 0 2px #ff6b9d26}:scope[data-theme=strawberry-dark]{color-scheme:dark only;--color-primary:#f6526f;--color-primary-dark:#dd395b;--color-primary-bg:#f6526f29;--color-primary-hover:#f6526f1f;--color-on-primary:black;--color-on-primary-bg:#ff6781;--color-success:#6fda75;--color-success-dark:#57c35f;--color-success-bg:#6fda7526;--color-success-hover:#6fda751a;--color-on-success:black;--color-on-success-bg:#6fda75;--color-warning:#fdbe45;--color-warning-dark:#e5a725;--color-warning-bg:#fdbe4526;--color-warning-hover:#fdbe451a;--color-on-warning:black;--color-on-warning-bg:#fdbe45;--color-danger:#f2823b;--color-danger-dark:#da6c1f;--color-danger-bg:#f2823b26;--color-danger-hover:#f2823b1a;--color-on-danger:black;--color-on-danger-bg:#f2823b;--color-neutral:#889e94;--color-neutral-dark:#73897f;--color-neutral-bg:#20362c;--color-neutral-hover:#889e941a;--color-on-neutral:black;--color-on-neutral-bg:#8ba197;--color-text:#ffefef;--color-text-secondary:#edd4d6;--color-text-tertiary:#dbb2b7;--color-text-muted:#ab888c;--color-border:#2f5838;--color-border-light:#3f704a;--color-bg:#0a1a13;--color-bg-card:#16271f;--color-bg-alt:#05140e;--color-bg-hover:#1e3129;--color-bg-overlay:#000000b8;--hdk-shadow-sm:0 1px 2px #0006;--hdk-shadow-md:0 2px 4px #00000080;--hdk-shadow-lg:0 8px 24px #000000b3;--hdk-shadow-focus:0 0 0 3px #f6526f4d;--hdk-shadow-focus-sm:0 0 0 2px #f6526f4d;--hdk-shadow-focus-alt:0 0 0 2px #f6526f33}:scope[data-theme=ocean-light]{color-scheme:light;--color-primary:#06b6d4;--color-primary-dark:#0891b2;--color-primary-bg:#ecfeff;--color-primary-hover:#06b6d414;--color-on-primary:black;--color-on-primary-bg:#007b97;--color-success:#f97316;--color-success-dark:#ea580c;--color-success-bg:#f973161a;--color-success-hover:#f973160f;--color-on-success:black;--color-on-success-bg:#af2c00;--color-warning:#eab308;--color-warning-dark:#c48e00;--color-warning-bg:#eab3081a;--color-warning-hover:#eab3080f;--color-on-warning:black;--color-on-warning-bg:#8a5600;--color-danger:#ec4899;--color-danger-dark:#db2777;--color-danger-bg:#fce7f3;--color-danger-hover:#ec48990f;--color-on-danger:black;--color-on-danger-bg:#c51a78;--color-neutral:#637389;--color-neutral-dark:#435267;--color-neutral-bg:#e0f2fe;--color-neutral-hover:#64748b0f;--color-on-neutral:white;--color-on-neutral-bg:#5b6a80;--color-text:#0c4a6e;--color-text-secondary:#075985;--color-text-tertiary:#0ea5e9;--color-text-muted:#38bdf8;--color-border:#95e2eb;--color-border-light:#c4eef2;--color-bg:#e6eff5;--color-bg-card:#fff;--color-bg-alt:#d6e8f4;--color-bg-hover:#c5f0f4;--color-bg-overlay:#082f494d;--hdk-shadow-sm:0 1px 2px #06b6d414;--hdk-shadow-md:0 2px 4px #06b6d41f;--hdk-shadow-lg:0 8px 24px #06b6d433;--hdk-shadow-focus:0 0 0 3px #06b6d440;--hdk-shadow-focus-sm:0 0 0 2px #06b6d440;--hdk-shadow-focus-alt:0 0 0 2px #06b6d426}:scope[data-theme=ocean-dark]{color-scheme:dark only;--color-primary:#06b6d4;--color-primary-dark:#0891b2;--color-primary-bg:#0c4a6e;--color-primary-hover:#06b6d426;--color-on-primary:black;--color-on-primary-bg:#30c6e5;--color-success:#f97316;--color-success-dark:#ea580c;--color-success-bg:#f9731626;--color-success-hover:#f973161a;--color-on-success:black;--color-on-success-bg:#ffb568;--color-warning:#fbbf24;--color-warning-dark:#d59a00;--color-warning-bg:#fbbf2426;--color-warning-hover:#fbbf241a;--color-on-warning:black;--color-on-warning-bg:#ffc935;--color-danger:#ec4899;--color-danger-dark:#db2777;--color-danger-bg:#500724;--color-danger-hover:#ec48991a;--color-on-danger:black;--color-on-danger-bg:#f34f9f;--color-neutral:#637389;--color-neutral-dark:#435267;--color-neutral-bg:#1e3a52;--color-neutral-hover:#64748b1a;--color-on-neutral:white;--color-on-neutral-bg:#95a6be;--color-text:#e0f2fe;--color-text-secondary:#bae6fd;--color-text-tertiary:#7dd3fc;--color-text-muted:#0ea5e9;--color-border:#34697f;--color-border-light:#0e7490;--color-bg:#082f49;--color-bg-card:#0c4a6e;--color-bg-alt:#164e63;--color-bg-hover:#0e7490;--color-bg-overlay:#000000b3;--hdk-shadow-sm:0 1px 2px #00000080;--hdk-shadow-md:0 2px 4px #0009;--hdk-shadow-lg:0 8px 24px #000000b3;--hdk-shadow-focus:0 0 0 3px #06b6d44d;--hdk-shadow-focus-sm:0 0 0 2px #06b6d44d;--hdk-shadow-focus-alt:0 0 0 2px #06b6d433}:scope[data-theme=cyberpunk-light]{color-scheme:light;--color-primary:#ff006e;--color-primary-dark:#ea005e;--color-primary-bg:#fff0f7;--color-primary-hover:#ff006e14;--color-on-primary:black;--color-on-primary-bg:#d90052;--color-success:#00f5d4;--color-success-dark:#00d9c0;--color-success-bg:#00f5d41a;--color-success-hover:#00f5d40f;--color-on-success:black;--color-on-success-bg:#007158;--color-warning:#ff9f1c;--color-warning-dark:#d77b00;--color-warning-bg:#ff9f1c1a;--color-warning-hover:#ff9f1c0f;--color-on-warning:black;--color-on-warning-bg:#a04800;--color-danger:#fb5607;--color-danger-dark:#e04f06;--color-danger-bg:#ffe8dc;--color-danger-hover:#fb56070f;--color-on-danger:black;--color-on-danger-bg:#c20;--color-neutral:#9849d8;--color-neutral-dark:#761eb1;--color-neutral-bg:#e9d5ff;--color-neutral-hover:#9d4edd0f;--color-on-neutral:white;--color-on-neutral-bg:#8634c4;--color-text:#1a1a2e;--color-text-secondary:#3d3d5c;--color-text-tertiary:#6b6b8a;--color-text-muted:#9999b3;--color-border:#dacaf8;--color-border-light:#eadff9;--color-bg:#ececf5;--color-bg-card:#fff;--color-bg-alt:#e8e6f2;--color-bg-hover:#e3d8f2;--color-bg-overlay:#1a1a2e66;--hdk-shadow-sm:0 1px 3px #ff006e1f;--hdk-shadow-md:0 2px 6px #ff006e2e;--hdk-shadow-lg:0 8px 24px #ff006e40;--hdk-shadow-focus:0 0 0 3px #ff006e4d;--hdk-shadow-focus-sm:0 0 0 2px #ff006e4d;--hdk-shadow-focus-alt:0 0 0 2px #ff006e33}:scope[data-theme=cyberpunk-dark]{color-scheme:dark only;--color-primary:#22d3ee;--color-primary-dark:#06b6d4;--color-primary-bg:#0a1929;--color-primary-hover:#22d3ee26;--color-on-primary:black;--color-on-primary-bg:#22d3ee;--color-success:#39ff14;--color-success-dark:#0f0;--color-success-bg:#39ff1426;--color-success-hover:#39ff141a;--color-on-success:black;--color-on-success-bg:#39ff14;--color-warning:#ffbe0b;--color-warning-dark:#d89900;--color-warning-bg:#ffbe0b26;--color-warning-hover:#ffbe0b1a;--color-on-warning:black;--color-on-warning-bg:#ffbe0b;--color-danger:#ff10f0;--color-danger-dark:#f0f;--color-danger-bg:purple;--color-danger-hover:#ff10f01a;--color-on-danger:black;--color-on-danger-bg:#ff89ff;--color-neutral:#637389;--color-neutral-dark:#435267;--color-neutral-bg:#1e293b;--color-neutral-hover:#64748b1a;--color-on-neutral:white;--color-on-neutral-bg:#8394ab;--color-text:#e0f2fe;--color-text-secondary:#67e8f9;--color-text-tertiary:#22d3ee;--color-text-muted:#0891b2;--color-border:#164e63;--color-border-light:#0e7490;--color-bg:#020617;--color-bg-card:#0c1929;--color-bg-alt:#1e293b;--color-bg-hover:#0f172a;--color-bg-overlay:#000c;--hdk-shadow-sm:0 1px 2px #22d3ee33;--hdk-shadow-md:0 2px 4px #22d3ee4d;--hdk-shadow-lg:0 8px 24px #22d3ee66;--hdk-shadow-focus:0 0 0 3px #22d3ee66;--hdk-shadow-focus-sm:0 0 0 2px #22d3ee66;--hdk-shadow-focus-alt:0 0 0 2px #22d3ee4d;--advanced-stop-1-l:.32;--advanced-stop-1-c:.12;--advanced-stop-1-h:220;--advanced-stop-2-l:.42;--advanced-stop-2-c:.18;--advanced-stop-2-h:200;--advanced-stop-3-l:.5;--advanced-stop-3-c:.2;--advanced-stop-3-h:195;--advanced-stop-4-l:.45;--advanced-stop-4-c:.22;--advanced-stop-4-h:280;--advanced-stop-5-l:.4;--advanced-stop-5-c:.26;--advanced-stop-5-h:320;--advanced-stop-6-l:.46;--advanced-stop-6-c:.28;--advanced-stop-6-h:340;--advanced-stop-7-l:.38;--advanced-stop-7-c:.22;--advanced-stop-7-h:290;--advanced-stop-8-l:.28;--advanced-stop-8-c:.16;--advanced-stop-8-h:250;--advanced-gradient:linear-gradient(135deg,oklch(var(--advanced-stop-1-l)var(--advanced-stop-1-c)var(--advanced-stop-1-h))0%,oklch(var(--advanced-stop-2-l)var(--advanced-stop-2-c)var(--advanced-stop-2-h))15%,oklch(var(--advanced-stop-3-l)var(--advanced-stop-3-c)var(--advanced-stop-3-h))30%,oklch(var(--advanced-stop-4-l)var(--advanced-stop-4-c)var(--advanced-stop-4-h))45%,oklch(var(--advanced-stop-5-l)var(--advanced-stop-5-c)var(--advanced-stop-5-h))60%,oklch(var(--advanced-stop-6-l)var(--advanced-stop-6-c)var(--advanced-stop-6-h))75%,oklch(var(--advanced-stop-7-l)var(--advanced-stop-7-c)var(--advanced-stop-7-h))88%,oklch(var(--advanced-stop-8-l)var(--advanced-stop-8-c)var(--advanced-stop-8-h))100%)}:scope[data-theme=coffee-light]{color-scheme:light;--color-primary:#ea580c;--color-primary-dark:#d54300;--color-primary-bg:#fffbeb;--color-primary-hover:#ea580c14;--color-on-primary:black;--color-on-primary-bg:#cd3c00;--color-success:#eab308;--color-success-dark:#ca8a04;--color-success-bg:#eab3081a;--color-success-hover:#eab3080f;--color-on-success:black;--color-on-success-bg:#8a5600;--color-warning:#d97706;--color-warning-dark:#bb5b00;--color-warning-bg:#d977061a;--color-warning-hover:#d977060f;--color-on-warning:black;--color-on-warning-bg:#a04300;--color-danger:#78350f;--color-danger-dark:#92400e;--color-danger-bg:#fef3c7;--color-danger-hover:#78350f0f;--color-on-danger:white;--color-on-danger-bg:#78350f;--color-neutral:#77706b;--color-neutral-dark:#56504b;--color-neutral-bg:#fde68a;--color-neutral-hover:#78716c0f;--color-on-neutral:white;--color-on-neutral-bg:#6b6560;--color-text:#1c1917;--color-text-secondary:#44403c;--color-text-tertiary:#78716c;--color-text-muted:#a8a29e;--color-border:#ead377;--color-border-light:#f0e5ba;--color-bg:#f1edde;--color-bg-card:#fff;--color-bg-alt:#f0e5ba;--color-bg-hover:#f0ca9d;--color-bg-overlay:#1c19174d;--hdk-shadow-sm:0 1px 2px #ea580c14;--hdk-shadow-md:0 2px 4px #ea580c1f;--hdk-shadow-lg:0 8px 24px #ea580c33;--hdk-shadow-focus:0 0 0 3px #ea580c40;--hdk-shadow-focus-sm:0 0 0 2px #ea580c40;--hdk-shadow-focus-alt:0 0 0 2px #ea580c26}:scope[data-theme=coffee-dark]{color-scheme:dark only;--color-primary:#ea580c;--color-primary-dark:#d54300;--color-primary-bg:#292524;--color-primary-hover:#ea580c26;--color-on-primary:black;--color-on-primary-bg:#f15f1a;--color-success:#facc15;--color-success-dark:#eab308;--color-success-bg:#facc1526;--color-success-hover:#facc151a;--color-on-success:black;--color-on-success-bg:#facc15;--color-warning:#f59e0b;--color-warning-dark:#ce7a00;--color-warning-bg:#f59e0b26;--color-warning-hover:#f59e0b1a;--color-on-warning:black;--color-on-warning-bg:#ffab29;--color-danger:#57534e;--color-danger-dark:#44403c;--color-danger-bg:#1c1917;--color-danger-hover:#57534e1a;--color-on-danger:white;--color-on-danger-bg:#88847e;--color-neutral:#77706b;--color-neutral-dark:#56504b;--color-neutral-bg:#3e3933;--color-neutral-hover:#78716c1a;--color-on-neutral:white;--color-on-neutral-bg:#aea6a1;--color-text:#fef3c7;--color-text-secondary:#fde68a;--color-text-tertiary:#fbbf24;--color-text-muted:#d97706;--color-border:#44403c;--color-border-light:#57534e;--color-bg:#1c1917;--color-bg-card:#292524;--color-bg-alt:#3e3933;--color-bg-hover:#44403c;--color-bg-overlay:#000000bf;--hdk-shadow-sm:0 1px 2px #0009;--hdk-shadow-md:0 2px 4px #000000b3;--hdk-shadow-lg:0 8px 24px #000c;--hdk-shadow-focus:0 0 0 3px #ea580c4d;--hdk-shadow-focus-sm:0 0 0 2px #ea580c4d;--hdk-shadow-focus-alt:0 0 0 2px #ea580c33}:scope[data-theme=lavender-light]{color-scheme:light;--color-primary:#c084fc;--color-primary-dark:#a855f7;--color-primary-bg:#faf5ff;--color-primary-hover:#c084fc14;--color-on-primary:black;--color-on-primary-bg:#8e52c6;--color-success:#84cc16;--color-success-dark:#65a30d;--color-success-bg:#84cc161a;--color-success-hover:#84cc160f;--color-on-success:black;--color-on-success-bg:#256700;--color-warning:#f59e0b;--color-warning-dark:#ce7a00;--color-warning-bg:#f59e0b1a;--color-warning-hover:#f59e0b0f;--color-on-warning:black;--color-on-warning-bg:#914100;--color-danger:#5e60ea;--color-danger-dark:#4f46e5;--color-danger-bg:#e0e7ff;--color-danger-hover:#6366f10f;--color-on-danger:white;--color-on-danger-bg:#5453dc;--color-neutral:#71717a;--color-neutral-dark:#515159;--color-neutral-bg:#e4e4e7;--color-neutral-hover:#71717a0f;--color-on-neutral:white;--color-on-neutral-bg:#63636b;--color-text:#1f1f29;--color-text-secondary:#52525b;--color-text-tertiary:#71717a;--color-text-muted:#a1a1aa;--color-border:#d8c4ee;--color-border-light:#e6dbf1;--color-bg:#eae6ef;--color-bg-card:#fdf8ff;--color-bg-alt:#e1d6ed;--color-bg-hover:#d7c3ed;--color-bg-overlay:#c084fc40;--hdk-shadow-sm:0 1px 2px #c084fc0f;--hdk-shadow-md:0 2px 4px #c084fc1a;--hdk-shadow-lg:0 8px 24px #c084fc26;--hdk-shadow-focus:0 0 0 3px #c084fc33;--hdk-shadow-focus-sm:0 0 0 2px #c084fc33;--hdk-shadow-focus-alt:0 0 0 2px #c084fc26}:scope[data-theme=lavender-dark]{color-scheme:dark only;--color-primary:#cba1fa;--color-primary-dark:#b58be2;--color-primary-bg:#cba1fa29;--color-primary-hover:#cba1fa1f;--color-on-primary:black;--color-on-primary-bg:#d1a7ff;--color-success:#b3e14d;--color-success-dark:#9dca30;--color-success-bg:#b3e14d26;--color-success-hover:#b3e14d1a;--color-on-success:black;--color-on-success-bg:#b3e14d;--color-warning:#fdbe45;--color-warning-dark:#e5a725;--color-warning-bg:#fdbe4526;--color-warning-hover:#fdbe451a;--color-on-warning:black;--color-on-warning-bg:#fdbe45;--color-danger:#f06f84;--color-danger-dark:#d7596f;--color-danger-bg:#f06f8426;--color-danger-hover:#f06f841a;--color-on-danger:black;--color-on-danger-bg:#ff8c9f;--color-neutral:#9098ab;--color-neutral-dark:#7b8395;--color-neutral-bg:#253154;--color-neutral-hover:#9098ab1a;--color-on-neutral:black;--color-on-neutral-bg:#969eb1;--color-text:#f3f2fd;--color-text-secondary:#dad9e8;--color-text-tertiary:#bdbad4;--color-text-muted:#918fa6;--color-border:#333e5b;--color-border-light:#3b496e;--color-bg:#0e172f;--color-bg-card:#192341;--color-bg-alt:#27324e;--color-bg-hover:#222d4d;--color-bg-overlay:#000000b8;--hdk-shadow-sm:0 1px 2px #0006;--hdk-shadow-md:0 2px 4px #00000080;--hdk-shadow-lg:0 8px 24px #000000b3;--hdk-shadow-focus:0 0 0 3px #cba1fa4d;--hdk-shadow-focus-sm:0 0 0 2px #cba1fa4d;--hdk-shadow-focus-alt:0 0 0 2px #cba1fa33}:scope[data-theme=pink-light]{color-scheme:light;--color-primary:#ff69b4;--color-primary-dark:#ff1493;--color-primary-bg:#fff0f5;--color-primary-hover:#ff69b414;--color-on-primary:black;--color-on-primary-bg:#c32e80;--color-success:#ffb3d9;--color-success-dark:#ff8fcc;--color-success-bg:#ffb3d926;--color-success-hover:#ffb3d90f;--color-on-success:black;--color-on-success-bg:#8f4c70;--color-warning:#fbbf24;--color-warning-dark:#d59a00;--color-warning-bg:#fbbf241a;--color-warning-hover:#fbbf240f;--color-on-warning:black;--color-on-warning-bg:#8d5500;--color-danger:#9856b3;--color-danger-dark:#8e44ad;--color-danger-bg:#e8daef;--color-danger-hover:#9b59b60f;--color-on-danger:white;--color-on-danger-bg:#8644a0;--color-neutral:#d4a5a5;--color-neutral-dark:#af8282;--color-neutral-bg:#ffd9e0;--color-neutral-hover:#d4a5a50f;--color-on-neutral:black;--color-on-neutral-bg:#825858;--color-text:#4a2a3a;--color-text-secondary:#88576c;--color-text-tertiary:#c48ca0;--color-text-muted:#e5b3c8;--color-border:pink;--color-border-light:#ffd9e0;--color-bg:#f4eaee;--color-bg-card:#fff;--color-bg-alt:#f0e2e7;--color-bg-hover:#f0cbd2;--color-bg-overlay:#ff69b433;--hdk-shadow-sm:0 1px 2px #ff69b414;--hdk-shadow-md:0 2px 4px #ff69b41f;--hdk-shadow-lg:0 8px 24px #ff69b426;--hdk-shadow-focus:0 0 0 3px #ff69b440;--hdk-shadow-focus-sm:0 0 0 2px #ff69b440;--hdk-shadow-focus-alt:0 0 0 2px #ff69b426}:scope[data-theme=pink-dark]{color-scheme:dark only;--color-primary:#f799be;--color-primary-dark:#df83a8;--color-primary-bg:#f799be29;--color-primary-hover:#f799be1f;--color-on-primary:black;--color-on-primary-bg:#f799be;--color-success:#63e6be;--color-success-dark:#48cfa8;--color-success-bg:#63e6be26;--color-success-hover:#63e6be1a;--color-on-success:black;--color-on-success-bg:#63e6be;--color-warning:#fdbe45;--color-warning-dark:#e5a725;--color-warning-bg:#fdbe4526;--color-warning-hover:#fdbe451a;--color-on-warning:black;--color-on-warning-bg:#fdbe45;--color-danger:#ae7ee2;--color-danger-dark:#9869cb;--color-danger-bg:#ae7ee226;--color-danger-hover:#ae7ee21a;--color-on-danger:black;--color-on-danger-bg:#b787ec;--color-neutral:#a5929a;--color-neutral-dark:#907d85;--color-neutral-bg:#3b2b32;--color-neutral-hover:#a5929a1a;--color-on-neutral:black;--color-on-neutral-bg:#a8959d;--color-text:#ffeef5;--color-text-secondary:#ead4dd;--color-text-tertiary:#d7b2c2;--color-text-muted:#a88896;--color-border:#4a373f;--color-border-light:#624b55;--color-bg:#1d1217;--color-bg-card:#2c1e24;--color-bg-alt:#170d12;--color-bg-hover:#36282e;--color-bg-overlay:#000000b8;--hdk-shadow-sm:0 1px 2px #0006;--hdk-shadow-md:0 2px 4px #00000080;--hdk-shadow-lg:0 8px 24px #000000b3;--hdk-shadow-focus:0 0 0 3px #f799be66;--hdk-shadow-focus-sm:0 0 0 2px #f799be66;--hdk-shadow-focus-alt:0 0 0 2px #f799be4d}:scope[data-theme=nature-light]{color-scheme:light;--color-primary:#6bbe4e;--color-primary-dark:#5aa942;--color-primary-bg:#f4faf1;--color-primary-hover:#6bbe4e14;--color-on-primary:black;--color-on-primary-bg:#2c7f00;--color-success:#a7d88f;--color-success-dark:#8dc473;--color-success-bg:#a7d88f26;--color-success-hover:#a7d88f0f;--color-on-success:black;--color-on-success-bg:#426e29;--color-warning:#ca8a04;--color-warning-dark:#a66800;--color-warning-bg:#ca8a041a;--color-warning-hover:#ca8a040f;--color-on-warning:black;--color-on-warning-bg:#8d5100;--color-danger:#c8643b;--color-danger-dark:#bc5930;--color-danger-bg:#f9e8e1;--color-danger-hover:#c8643b0f;--color-on-danger:black;--color-on-danger-bg:#ad4c21;--color-neutral:#cfc6b0;--color-neutral-dark:#aaa28c;--color-neutral-bg:#ebe7dc;--color-neutral-hover:#cfc6b00f;--color-on-neutral:black;--color-on-neutral-bg:#6d6551;--color-text:#2c2c2c;--color-text-secondary:#5a5a5a;--color-text-tertiary:#8a8a8a;--color-text-muted:#acacac;--color-border:#dad3bf;--color-border-light:#e9e5da;--color-bg:#efede4;--color-bg-card:#fff;--color-bg-alt:#ebe8de;--color-bg-hover:#e1ddd2;--color-bg-overlay:#2c2c2c80;--hdk-shadow-sm:0 1px 2px #6bbe4e14;--hdk-shadow-md:0 2px 4px #6bbe4e1f;--hdk-shadow-lg:0 8px 24px #6bbe4e33;--hdk-shadow-focus:0 0 0 3px #6bbe4e40;--hdk-shadow-focus-sm:0 0 0 2px #6bbe4e40;--hdk-shadow-focus-alt:0 0 0 2px #6bbe4e26}:scope[data-theme=nature-dark]{color-scheme:dark only;--color-primary:#5ac041;--color-primary-dark:#42a926;--color-primary-bg:#5ac04129;--color-primary-hover:#5ac0411f;--color-on-primary:black;--color-on-primary-bg:#5ac041;--color-success:#52db9c;--color-success-dark:#34c487;--color-success-bg:#52db9c26;--color-success-hover:#52db9c1a;--color-on-success:black;--color-on-success-bg:#52db9c;--color-warning:#f7c243;--color-warning-dark:#e0ab22;--color-warning-bg:#f7c24326;--color-warning-hover:#f7c2431a;--color-on-warning:black;--color-on-warning-bg:#f7c243;--color-danger:#bc4f2b;--color-danger-dark:#a63b14;--color-danger-bg:#be512d26;--color-danger-hover:#be512d1a;--color-on-danger:white;--color-on-danger-bg:#ec7b56;--color-neutral:#8f8476;--color-neutral-dark:#807568;--color-neutral-bg:#3b3229;--color-neutral-hover:#8f84761a;--color-on-neutral:black;--color-on-neutral-bg:#a89c8e;--color-text:#f8f4e1;--color-text-secondary:#e2dbc1;--color-text-tertiary:#cabe92;--color-text-muted:#9d926c;--color-border:#604734;--color-border-light:#795c45;--color-bg:#1e1811;--color-bg-card:#2c251d;--color-bg-alt:#18130c;--color-bg-hover:#372f26;--color-bg-overlay:#000000b8;--hdk-shadow-sm:0 1px 2px #00000080;--hdk-shadow-md:0 2px 4px #0009;--hdk-shadow-lg:0 8px 24px #000c;--hdk-shadow-focus:0 0 0 3px #5ac04159;--hdk-shadow-focus-sm:0 0 0 2px #5ac04159;--hdk-shadow-focus-alt:0 0 0 2px #5ac04140}:scope[data-theme=izakaya-light]{color-scheme:light;--color-primary:#41baae;--color-primary-dark:#369c92;--color-primary-bg:#41baae0d;--color-primary-hover:#41baae14;--color-on-primary:black;--color-on-primary-bg:#00534a;--color-success:#a6d8ad;--color-success-dark:#8ccb94;--color-success-bg:#a6d8ad26;--color-success-hover:#a6d8ad0f;--color-on-success:black;--color-on-success-bg:#275530;--color-warning:#d4a574;--color-warning-dark:#af8252;--color-warning-bg:#d4a57426;--color-warning-hover:#d4a5740f;--color-on-warning:black;--color-on-warning-bg:#673e07;--color-danger:#c04731;--color-danger-dark:#a23b29;--color-danger-bg:#f97a6f;--color-danger-hover:#c047310f;--color-on-danger:white;--color-on-danger-bg:#720000;--color-neutral:#a8999e;--color-neutral-dark:#85777b;--color-neutral-bg:#d8c8b0;--color-neutral-hover:#a8999e0f;--color-on-neutral:black;--color-on-neutral-bg:#5e5055;--color-text:#2a2a2a;--color-text-secondary:#2a2a2acc;--color-text-tertiary:#2a2a2a99;--color-text-muted:#2a2a2a66;--color-border:#bfa1a0;--color-border-light:#ceaecb;--color-bg:#f2e6cd;--color-bg-card:#f1c8c8;--color-bg-alt:#d8b888;--color-bg-hover:#d1b1ce;--color-bg-overlay:#2a2a2a73;--hdk-shadow-sm:0 1px 2px #41baae14;--hdk-shadow-md:0 2px 4px #41baae1f;--hdk-shadow-lg:0 8px 24px #41baae33;--hdk-shadow-focus:0 0 0 3px #41baae40;--hdk-shadow-focus-sm:0 0 0 2px #41baae40;--hdk-shadow-focus-alt:0 0 0 2px #41baae26}:scope[data-theme=izakaya-dark]{color-scheme:dark only;--color-primary:#04c5b5;--color-primary-dark:#00ac9e;--color-primary-bg:#04c5b529;--color-primary-hover:#04c5b51f;--color-on-primary:black;--color-on-primary-bg:#04c5b5;--color-success:#61d19a;--color-success-dark:#48ba85;--color-success-bg:#61d19a26;--color-success-hover:#61d19a1a;--color-on-success:black;--color-on-success-bg:#61d19a;--color-warning:#fdbe45;--color-warning-dark:#e5a725;--color-warning-bg:#fdbe4526;--color-warning-hover:#fdbe451a;--color-on-warning:black;--color-on-warning-bg:#fdbe45;--color-danger:#c13e2e;--color-danger-dark:#a92517;--color-danger-bg:#c13e2e26;--color-danger-hover:#c13e2e1a;--color-on-danger:white;--color-on-danger-bg:#f66f5b;--color-neutral:#7f7f87;--color-neutral-dark:#76767e;--color-neutral-bg:#2d343b;--color-neutral-hover:#7f7f871a;--color-on-neutral:black;--color-on-neutral-bg:#9d9da5;--color-text:#f5f2f5;--color-text-secondary:#ddd9dd;--color-text-tertiary:#c1bcc1;--color-text-muted:#959095;--color-border:#71574b;--color-border-light:#8c6c5e;--color-bg:#14191e;--color-bg-card:#20262c;--color-bg-alt:#0f1318;--color-bg-hover:#2a3036;--color-bg-overlay:#000000b8;--hdk-shadow-sm:0 1px 2px #0006;--hdk-shadow-md:0 2px 4px #0000008c;--hdk-shadow-lg:0 8px 24px #000000b3;--hdk-shadow-focus:0 0 0 3px #04c5b552;--hdk-shadow-focus-sm:0 0 0 2px #04c5b552;--hdk-shadow-focus-alt:0 0 0 2px #04c5b52e}.hdk-icon{vertical-align:-.14em;flex:none;width:1em;height:1em}span.hdk-icon{display:inline-flex}span.hdk-icon>svg{width:100%;height:100%}.hdk-icon--primary{color:var(--color-primary)}.hdk-icon--success{color:var(--color-success)}.hdk-icon--warning{color:var(--color-warning)}.hdk-icon--danger{color:var(--color-danger)}.hdk-icon--neutral{color:var(--color-neutral)}.hdk-icon-tile{border-radius:var(--hdk-radius);flex:none;justify-content:center;align-items:center;width:2em;height:2em;display:inline-flex}.hdk-icon-tile--round{border-radius:50%}.hdk-icon-tile--tint.hdk-icon-tile--primary{background:var(--color-primary-bg);color:var(--color-on-primary-bg)}.hdk-icon-tile--tint.hdk-icon-tile--success{background:var(--color-success-bg);color:var(--color-on-success-bg)}.hdk-icon-tile--tint.hdk-icon-tile--warning{background:var(--color-warning-bg);color:var(--color-on-warning-bg)}.hdk-icon-tile--tint.hdk-icon-tile--danger{background:var(--color-danger-bg);color:var(--color-on-danger-bg)}.hdk-icon-tile--tint.hdk-icon-tile--neutral{background:var(--color-neutral-bg);color:var(--color-on-neutral-bg)}.hdk-icon-tile--filled.hdk-icon-tile--primary{background:var(--color-primary);color:var(--color-on-primary)}.hdk-icon-tile--filled.hdk-icon-tile--success{background:var(--color-success);color:var(--color-on-success)}.hdk-icon-tile--filled.hdk-icon-tile--warning{background:var(--color-warning);color:var(--color-on-warning)}.hdk-icon-tile--filled.hdk-icon-tile--danger{background:var(--color-danger);color:var(--color-on-danger)}.hdk-icon-tile--filled.hdk-icon-tile--neutral{background:var(--color-neutral);color:var(--color-on-neutral)}.chat-messages::-webkit-scrollbar{width:6px}.chat-messages::-webkit-scrollbar-track{background:0 0}.chat-messages::-webkit-scrollbar-thumb{background:#94a3b866;border-radius:3px}.chat-messages::-webkit-scrollbar-thumb:hover{background:#94a3b899}.chat-messages{scrollbar-width:thin;scrollbar-color:#94a3b866 transparent}.modal-input{background-color:var(--color-bg-alt);border:1px solid var(--color-border-light);border-radius:.25rem;width:100%;padding:.5rem .75rem;font-size:.875rem}.modal-input:focus{border-color:var(--color-text-secondary);background-color:var(--color-bg-card);outline:none}.modal-btn-update{background-color:var(--color-success);color:var(--color-on-success);cursor:pointer;border-radius:.25rem;padding:.25rem .75rem;font-size:.75rem;font-weight:500;transition:all .15s}.modal-btn-update:hover:not(.modal-btn-disabled){background-color:var(--color-success-dark)}.modal-btn-disabled{opacity:.5;cursor:not-allowed}}.wp-room-chrome-frame{width:100%;height:100%;min-height:0;display:flex;overflow:hidden}.wp-room-chrome-frame__content{flex:auto;min-width:0;min-height:0;position:relative;overflow:auto}.app-header:has(~.wp-room-chrome-frame,~* .wp-room-chrome-frame){margin-bottom:0}@scope (.wp-room-chrome){:scope{flex-direction:column;flex:0 0 var(--wp-room-chrome-width,20rem);width:var(--wp-room-chrome-width,20rem);box-sizing:border-box;background:var(--color-bg);min-height:0;color:var(--color-text);border-left:1px solid var(--color-border);font-family:inherit;line-height:1.5;display:flex;position:relative}}@media(max-width:767px){.wp-room-chrome-frame{flex-direction:column}@scope (.wp-room-chrome){:scope{border-left:none;border-top:1px solid var(--color-border);flex:none;width:auto;max-height:45vh}.wp-room-chrome__resize{display:none}}}@scope (.wp-room-chrome){*,:before,:after{box-sizing:border-box}img,svg,video,canvas{max-width:100%;display:block}button{font:inherit;color:inherit;background:0 0;border:none}input{font:inherit;color:inherit}button:focus{outline:none}button:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}.wp-room-chrome__leave{color:var(--color-text-secondary);cursor:pointer;border-radius:.25rem;padding:.25rem .625rem;font-size:.75rem;transition:color .15s,background-color .15s}.wp-room-chrome__leave:hover{color:var(--color-text);background-color:var(--color-bg-alt)}:scope[data-collapsed="1"]{align-items:center;padding-top:.375rem}.wp-room-chrome__icon-btn{color:var(--color-text-muted);cursor:pointer;border-radius:.25rem;justify-content:center;align-items:center;padding:.375rem;transition:color .15s,background-color .15s;display:inline-flex}.wp-room-chrome__icon-btn:hover{color:var(--color-text);background-color:var(--color-bg-alt)}.wp-room-chrome__resize{z-index:20;cursor:col-resize;touch-action:none;-webkit-user-select:none;user-select:none;width:8px;position:absolute;top:0;bottom:0;left:-4px}.wp-room-chrome__resize:after{content:"";background:0 0;width:2px;transition:background-color .15s;position:absolute;top:0;bottom:0;left:3px}.wp-room-chrome__resize:hover:after,.wp-room-chrome__resize:focus-visible:after,.wp-room-chrome__resize[data-dragging="1"]:after{background:var(--color-primary)}.wp-room-chrome__resize:focus-visible{outline:none}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}
2
+ @font-face{font-family:"hc-script";src:url("/cosmetics/fonts/caveat.woff2") format("woff2");font-display:swap}
3
+ @font-face{font-family:"hc-surf";src:url("/cosmetics/fonts/pacifico.woff2") format("woff2");font-display:swap}
4
+ @font-face{font-family:"hc-gothic";src:url("/cosmetics/fonts/gothic.woff2") format("woff2");font-display:swap}
5
+ @font-face{font-family:"hc-runic";src:url("/cosmetics/fonts/uncial.woff2") format("woff2");font-display:swap}
6
+ @font-face{font-family:"hc-corsair";src:url("/cosmetics/fonts/pirata.woff2") format("woff2");font-display:swap}
7
+ @font-face{font-family:"hc-metal";src:url("/cosmetics/fonts/metal.woff2") format("woff2");font-display:swap}
8
+ @font-face{font-family:"hc-comic";src:url("/cosmetics/fonts/bangers.woff2") format("woff2");font-display:swap}
9
+ @font-face{font-family:"hc-circuit";src:url("/cosmetics/fonts/orbitron.woff2") format("woff2");font-display:swap}
10
+ @font-face{font-family:"hc-hover";src:url("/cosmetics/fonts/audiowide.woff2") format("woff2");font-display:swap}
11
+ @font-face{font-family:"hc-retro";src:url("/cosmetics/fonts/righteous.woff2") format("woff2");font-display:swap}
12
+ @font-face{font-family:"hc-arcade";src:url("/cosmetics/fonts/bungee.woff2") format("woff2");font-display:swap}
13
+ @font-face{font-family:"hc-pixel";src:url("/cosmetics/fonts/pixel.woff2") format("woff2");font-display:swap}
14
+ /* Colours: everything derives from the character colour. */
15
+ @scope (.wp-room-chrome){
16
+ .hc-name,.hc-av,.hc-msg{--hc-b:var(--hc-base,currentColor);--hc-hi:color-mix(in oklch,var(--hc-b),white 50%);--hc-lo:color-mix(in oklch,var(--hc-b),black 50%);--hc-glow:color-mix(in oklch,var(--hc-b) 75%,transparent)}}
17
+
18
+ /* --- name ---
19
+ Bleed: a gradient is a background clipped to the letters, and a background covers only the box; script
20
+ swashes reach outside it. So the text box and every layer over it get room on all sides, cancelled by a
21
+ negative margin so nothing moves. */
22
+ @scope (.wp-room-chrome){
23
+ .hc-name{--hc-bx:.4em;--hc-by:.3em;--hc-grad:none;position:relative;display:inline-block;white-space:nowrap;color:var(--hc-b);font-weight:700;line-height:1.25}
24
+ .hc-name.hc-large{font-size:1.6em}
25
+ .hc-name .hc-tx{position:relative;z-index:1;display:inline-block;padding:var(--hc-by) var(--hc-bx);margin:calc(-1 * var(--hc-by)) calc(-1 * var(--hc-bx))}
26
+ .hc-name .hc-lyr{position:absolute;left:calc(-1 * var(--hc-bx));top:calc(-1 * var(--hc-by));padding:var(--hc-by) var(--hc-bx);pointer-events:none;white-space:nowrap}
27
+ .hc-name .hc-win{position:absolute;inset:calc(-1 * var(--hc-by)) calc(-1 * var(--hc-bx));overflow:hidden}
28
+ .hc-name .hc-win .hc-lyr{left:0;top:0}
29
+ .hc-name .hc-ch,.hc-name .hc-c{display:inline-block}
30
+ .hc-gr-gild{--hc-grad:linear-gradient(180deg,var(--hc-hi) 0%,var(--hc-b) 55%,var(--hc-lo) 100%)}
31
+ .hc-gr-gloss{--hc-grad:linear-gradient(180deg,color-mix(in oklch,var(--hc-hi),white 35%) 0%,var(--hc-hi) 36%,color-mix(in oklch,var(--hc-hi),var(--hc-b)) 48%,var(--hc-b) 60%,var(--hc-lo) 100%)}
32
+ .hc-gr-split{--hc-grad:linear-gradient(180deg,var(--hc-hi) 0 52%,var(--hc-b) 52% 100%)}
33
+ .hc-gr-brushed{--hc-grad:repeating-linear-gradient(90deg,transparent 0 1px,color-mix(in oklch,var(--hc-hi) 40%,transparent) 1px 2px),linear-gradient(180deg,var(--hc-hi),var(--hc-b) 55%,var(--hc-lo))}
34
+ .hc-gr-lumen{--hc-grad:radial-gradient(ellipse 60% 90% at 50% 35%,var(--hc-hi),var(--hc-b) 55%,var(--hc-lo) 100%)}
35
+ .hc-name:not(.hc-gr-none) .hc-tx{background:var(--hc-grad);-webkit-background-clip:text;background-clip:text;color:transparent}
36
+ .hc-name.hc-split:not(.hc-gr-none) .hc-tx{background:none}
37
+ .hc-name.hc-split:not(.hc-gr-none) .hc-tx .hc-c{padding:var(--hc-by) var(--hc-bx);margin:calc(-1 * var(--hc-by)) calc(-1 * var(--hc-bx));background:var(--hc-grad);-webkit-background-clip:text;background-clip:text;color:transparent}
38
+ .hc-fx-glint .hc-win{z-index:3;animation:hc-win-go 4.5s cubic-bezier(.4,0,.2,1) infinite;-webkit-mask-image:linear-gradient(100deg,transparent 35%,#000 50%,transparent 65%);mask-image:linear-gradient(100deg,transparent 35%,#000 50%,transparent 65%)}
39
+ .hc-fx-glint .hc-win .hc-lyr{color:var(--hc-hi);animation:hc-win-back 4.5s cubic-bezier(.4,0,.2,1) infinite}}
40
+ @keyframes hc-win-go{0%{transform:translateX(-100%)}55%,100%{transform:translateX(100%)}}
41
+ @keyframes hc-win-back{0%{transform:translateX(100%)}55%,100%{transform:translateX(-100%)}}
42
+ @scope (.wp-room-chrome){
43
+ .hc-fx-aura .hc-lyr.hc-g{color:transparent;text-shadow:0 0 6px var(--hc-glow),0 0 14px var(--hc-glow);animation:hc-breathe 3s ease-in-out infinite}
44
+ .hc-fx-outline .hc-lyr.hc-g{color:transparent;-webkit-text-stroke:3px var(--hc-hi);animation:hc-breathe 3.6s ease-in-out infinite}}
45
+ @keyframes hc-breathe{0%,100%{opacity:.25}50%{opacity:1}}
46
+ @scope (.wp-room-chrome){
47
+ .hc-fx-hop .hc-c{animation:hc-hop 3.6s ease-in-out calc(var(--hc-i) * 45ms) infinite}}
48
+ @keyframes hc-hop{0%,14%,100%{transform:none}5%{transform:translateY(-4px)}10%{transform:translateY(.5px)}}
49
+ @scope (.wp-room-chrome){
50
+ .hc-fx-twinkle .hc-tw{position:absolute;width:7px;height:7px;z-index:3;pointer-events:none;background:linear-gradient(var(--hc-hi),var(--hc-hi)) center/1.4px 100% no-repeat,linear-gradient(90deg,var(--hc-hi),var(--hc-hi)) center/100% 1.4px no-repeat;animation:hc-star 2.8s ease-in-out infinite both}}
51
+ @keyframes hc-star{0%,100%{opacity:0;transform:scale(.3) rotate(0deg)}25%{opacity:1;transform:scale(1) rotate(45deg)}50%{opacity:0;transform:scale(.3) rotate(90deg)}}
52
+ @scope (.wp-room-chrome){
53
+ .hc-font-caps{font-variant:small-caps;letter-spacing:.08em}
54
+ .hc-font-wide{letter-spacing:.22em;font-weight:600;text-transform:uppercase;font-size:.82em}
55
+ .hc-font-script{font-family:"hc-script","Caveat",cursive;font-weight:700;font-size:1.3em;line-height:1}
56
+ .hc-font-surf{font-family:"hc-surf","Pacifico",cursive;font-weight:400;font-size:1.02em;line-height:1.1}
57
+ .hc-font-gothic{font-family:"hc-gothic","Grenze Gotisch",serif;font-weight:600;font-size:1.15em}
58
+ .hc-font-runic{font-family:"hc-runic","Uncial Antiqua",serif;font-weight:400}
59
+ .hc-font-corsair{font-family:"hc-corsair","Pirata One",serif;font-weight:400;font-size:1.12em;letter-spacing:.02em}
60
+ .hc-font-metal{font-family:"hc-metal","Metal Mania",cursive;font-weight:400;font-size:1.12em;letter-spacing:.02em}
61
+ .hc-font-comic{font-family:"hc-comic","Bangers",cursive;font-weight:400;font-size:1.15em;letter-spacing:.05em}
62
+ .hc-font-circuit{font-family:"hc-circuit","Orbitron",sans-serif;font-weight:700;letter-spacing:.03em}
63
+ .hc-font-hover{font-family:"hc-hover","Audiowide",sans-serif;font-weight:400;font-size:.92em}
64
+ .hc-font-retro{font-family:"hc-retro","Righteous",sans-serif;font-weight:400}
65
+ .hc-font-arcade{font-family:"hc-arcade","Bungee",sans-serif;font-weight:400;font-size:.9em}
66
+ .hc-font-pixel{font-family:"hc-pixel","Press Start 2P",monospace;font-weight:400;font-size:.62em;line-height:1.6}}
67
+
68
+ /* --- avatar --- */
69
+ @scope (.wp-room-chrome){
70
+ .hc-av{--hc-sz:40px;position:relative;display:inline-block;width:var(--hc-sz);height:var(--hc-sz);flex:none;isolation:isolate;vertical-align:middle}
71
+ .hc-av .hc-pic{position:absolute;inset:0;border-radius:50%;overflow:hidden;z-index:1;background:color-mix(in oklch,var(--hc-b) 30%,transparent)}
72
+ .hc-av .hc-pic img{width:100%;height:100%;display:block;object-fit:cover;image-rendering:pixelated}
73
+ .hc-av .hc-ol{position:absolute;pointer-events:none}
74
+ .hc-ol.hc-art{display:block}
75
+ .hc-ol.hc-art .hc-adj{display:block;position:relative;transform-origin:50% 60%}
76
+ .hc-ol.hc-art img{display:block;width:100%;height:auto;filter:drop-shadow(0 1px 1px rgba(0,0,0,.45));animation:hc-art-bob 3.4s ease-in-out infinite;-webkit-user-drag:none;user-select:none}
77
+ .hc-ol.hc-art.hc-px img{image-rendering:pixelated}}
78
+ @keyframes hc-art-bob{0%,100%{transform:rotate(var(--hc-tilt,0deg)) translateY(0)}50%{transform:rotate(var(--hc-tilt,0deg)) translateY(-2px)}}
79
+ @scope (.wp-room-chrome){
80
+ .hc-ol.hc-runes{inset:-9px;z-index:2;color:var(--hc-hi);animation:hc-turn 14s linear infinite}
81
+ .hc-ol.hc-runes svg{width:100%;height:100%;display:block}}
82
+ @keyframes hc-turn{to{transform:rotate(360deg)}}
83
+ @scope (.wp-room-chrome){
84
+ .hc-ol.hc-comet{inset:-6px;z-index:2;border-radius:50%;background:conic-gradient(from 0deg,transparent 0 270deg,color-mix(in oklch,var(--hc-hi) 10%,transparent) 300deg,var(--hc-hi) 358deg,transparent 360deg);-webkit-mask:radial-gradient(farthest-side,transparent calc(100% - 3px),#000 calc(100% - 3px));mask:radial-gradient(farthest-side,transparent calc(100% - 3px),#000 calc(100% - 3px));animation:hc-turn 2.4s linear infinite}
85
+ .hc-ol.hc-clip{inset:0;border-radius:50%;overflow:hidden;z-index:2}
86
+ .hc-ol .hc-band{position:absolute;top:-30%;left:0;width:45%;height:160%;background:linear-gradient(90deg,transparent,color-mix(in oklch,var(--hc-hi) 70%,transparent),transparent);transform:translateX(-130%) rotate(20deg);animation:hc-band 5s ease-in-out infinite}}
87
+ @keyframes hc-band{0%{transform:translateX(-130%) rotate(20deg)}45%,100%{transform:translateX(260%) rotate(20deg)}}
88
+ @scope (.wp-room-chrome){
89
+ .hc-ol.hc-glow{inset:-30%;border-radius:50%;z-index:0;background:radial-gradient(circle,var(--hc-glow) 0,transparent 65%);animation:hc-glowp 3.2s ease-in-out infinite}}
90
+ @keyframes hc-glowp{0%,100%{opacity:.35;transform:scale(.92)}50%{opacity:.9;transform:scale(1.04)}}
91
+ @scope (.wp-room-chrome){
92
+ .hc-ol.hc-petal{top:-8%;height:100%;width:5px;z-index:2;animation:hc-fall 3.4s linear infinite both}
93
+ .hc-ol.hc-petal::after{content:"";position:absolute;left:0;top:0;width:5px;height:7px;border-radius:5px 0;background:#f7b6c8;transform:rotate(35deg)}}
94
+ @keyframes hc-fall{0%{transform:translate(0,0);opacity:0}12%{opacity:.9}100%{transform:translate(-6px,100%);opacity:0}}
95
+
96
+ /* --- message reveal: fill backwards only; the page swaps back to plain text when it ends --- */
97
+ @scope (.wp-room-chrome){
98
+ .hc-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
99
+ .hc-msg .hc-w{display:inline-block;white-space:nowrap}
100
+ .hc-msg .hc-ch{display:inline-block}
101
+ .hc-mt-type .hc-ch{animation:hc-in .01s linear calc(var(--hc-i) * var(--hc-step)) backwards}
102
+ .hc-mt-type .hc-caret{display:inline-block;width:2px;height:1em;margin-left:2px;vertical-align:-2px;background:var(--hc-b);opacity:0;animation:hc-caret 1.5s steps(1) calc(var(--hc-i) * var(--hc-step)) backwards}}
103
+ @keyframes hc-caret{0%{opacity:0}1%{opacity:1}20%{opacity:0}40%{opacity:1}60%{opacity:0}80%{opacity:1}100%{opacity:0}}
104
+ @scope (.wp-room-chrome){
105
+ .hc-mt-cascade .hc-ch{animation:hc-drop .35s cubic-bezier(.3,1.5,.5,1) calc(var(--hc-i) * var(--hc-step)) backwards}
106
+ .hc-mt-decode .hc-ch{position:relative}
107
+ .hc-mt-decode .hc-ch .hc-r{display:inline-block;animation:hc-in .01s linear calc(var(--hc-i) * var(--hc-step) + 260ms) backwards}
108
+ .hc-mt-decode .hc-ch::before,.hc-mt-decode .hc-ch::after{position:absolute;left:0;top:0;color:var(--hc-b);opacity:0;animation:hc-dec-a .26s steps(1) calc(var(--hc-i) * var(--hc-step)) backwards}
109
+ .hc-mt-decode .hc-ch::before{content:attr(data-g)}
110
+ .hc-mt-decode .hc-ch::after{content:attr(data-h);animation-name:hc-dec-b}}
111
+ @keyframes hc-dec-a{0%{opacity:.85}50%,100%{opacity:0}}
112
+ @keyframes hc-dec-b{0%{opacity:0}50%{opacity:.85}100%{opacity:0}}
113
+ @scope (.wp-room-chrome){
114
+ .hc-mt-words .hc-w{animation:hc-rise .4s ease-out calc(var(--hc-i) * var(--hc-step)) backwards}
115
+ .hc-mt-bubble .hc-w{animation:hc-pop .35s cubic-bezier(.3,1.6,.5,1) calc(var(--hc-i) * var(--hc-step)) backwards}
116
+ .hc-mt-drift .hc-w{animation:hc-slide .45s cubic-bezier(.2,.8,.2,1) calc(var(--hc-i) * var(--hc-step)) backwards}}
117
+ @keyframes hc-in{from{opacity:0}to{opacity:1}}
118
+ @keyframes hc-drop{0%{transform:translateY(-10px);opacity:0}100%{transform:none;opacity:1}}
119
+ @keyframes hc-rise{0%{transform:translateY(10px);opacity:0}100%{transform:none;opacity:1}}
120
+ @keyframes hc-pop{0%{transform:scale(.6);opacity:0}100%{transform:scale(1);opacity:1}}
121
+ @keyframes hc-slide{0%{transform:translateX(-18px);opacity:0}100%{transform:none;opacity:1}}
122
+
123
+ @scope (.wp-room-chrome){
124
+
125
+ .hc-idle .hc-name,.hc-idle .hc-name *,.hc-idle .hc-av *,.hc-idle .hc-av *::after{animation-play-state:paused!important}}
126
+ @media (prefers-reduced-motion:reduce){@scope (.wp-room-chrome){.hc-name *,.hc-av *,.hc-av *::after,.hc-msg *,.hc-msg *::before,.hc-msg *::after{animation:none!important}}}