@fixback/sdk 0.1.0 → 0.2.0
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.
- package/README.md +71 -0
- package/dist/annotation.d.ts +106 -0
- package/dist/auto-report-backoff.d.ts +47 -0
- package/dist/breadcrumbs.d.ts +165 -0
- package/dist/draw-surface.d.ts +51 -0
- package/dist/error-capture.d.ts +135 -0
- package/dist/fixback.umd.js +524 -28
- package/dist/fixback.umd.js.map +1 -1
- package/dist/index.d.ts +13 -3
- package/dist/index.mjs +2402 -467
- package/dist/index.mjs.map +1 -1
- package/dist/init.d.ts +66 -7
- package/dist/invite.d.ts +104 -0
- package/dist/launcher.d.ts +29 -9
- package/dist/onboarding-styles.d.ts +8 -0
- package/dist/onboarding.d.ts +44 -0
- package/dist/overlay-styles.d.ts +1 -1
- package/dist/overlay.d.ts +36 -6
- package/dist/region-capture.d.ts +40 -0
- package/dist/report.d.ts +33 -7
- package/dist/screenshot.d.ts +2 -0
- package/dist/scrub.d.ts +60 -0
- package/dist/styles.d.ts +11 -1
- package/dist/submit.d.ts +22 -4
- package/package.json +1 -1
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/boot.ts","../src/identity.ts","../src/styles.ts","../src/launcher.ts","../src/dom.ts","../src/element-picker.ts","../src/report.ts","../src/overlay-styles.ts","../src/screenshot.ts","../src/submit.ts","../src/version.ts","../src/overlay.ts","../src/init.ts"],"sourcesContent":["/**\n * The ingest **boot** wire-contract, vendored.\n *\n * The SDK deliberately does not import `@fixback/shared` — that package is\n * private and server-shaped (ticket #47). The boot request/response is small and\n * stable, so the exact slice the SDK needs is copied here. Keep it in lock-step\n * with the server: the request body accepted by `POST /api/ingest/boot`\n * (`apps/api/src/ingest/ingest.controller.ts`) and the `BootAnswer` returned by\n * `evaluateBoot` (`apps/api/src/ingest/reporter-identity.ts`).\n */\n\n/** A Project's Gate — who may submit. Mirrors the server's `ProjectGate`. */\nexport type ProjectGate = \"open\" | \"invited\" | \"internal\";\n\n/** The trust tier a Reporter holds. Mirrors the server's `ReporterTier`. */\nexport type ReporterTier = \"public\" | \"invited\" | \"internal\";\n\n/**\n * Optional identity evidence the SDK forwards to boot. None of it is a tier: the\n * server re-derives trust from this evidence and never honours a self-declared\n * tier, so the SDK does not send one.\n */\nexport interface IdentityInputs {\n readonly signedIdentity?: string;\n readonly reporterId?: string;\n readonly anonymousId?: string;\n}\n\n/** The JSON body `POST /api/ingest/boot` accepts. */\nexport interface BootRequest extends IdentityInputs {\n readonly key: string;\n}\n\n/**\n * The boot answer: whether this origin is allowlisted, the Project's Gate, the\n * caller's derived tier (`null` when a presented identity was refused), and\n * whether a submission would be accepted right now. The launcher shows only when\n * `canSubmit` is true.\n */\nexport interface BootAnswer {\n readonly originAllowed: boolean;\n readonly gate: ProjectGate;\n readonly tier: ReporterTier | null;\n readonly canSubmit: boolean;\n}\n\n/** Join an API base URL with the boot path, tolerating a trailing slash. */\nexport function bootEndpoint(apiUrl: string): string {\n return `${apiUrl.replace(/\\/+$/, \"\")}/api/ingest/boot`;\n}\n\n/** Narrow an unknown JSON body to a `BootAnswer` before the SDK trusts it. */\nfunction isBootAnswer(value: unknown): value is BootAnswer {\n if (typeof value !== \"object\" || value === null) return false;\n const v = value as Record<string, unknown>;\n return (\n typeof v.originAllowed === \"boolean\" &&\n typeof v.canSubmit === \"boolean\" &&\n typeof v.gate === \"string\" &&\n (v.tier === null || typeof v.tier === \"string\")\n );\n}\n\n/**\n * Ask ingest whether a submission would be accepted for this key / origin / Gate.\n * Resolves to the boot answer, or `null` when Fixback could not be reached, the\n * key was refused, or the response was not a boot answer. It never throws: any\n * non-answer is treated by the caller as \"do not show the launcher\", so a Fixback\n * outage stays invisible to the host page (ticket #47: \"fails quietly\").\n *\n * The browser attaches the `Origin` header itself on this cross-origin request —\n * the server reads it to decide `originAllowed` — so the SDK neither sets nor\n * needs to set it. `fetchImpl` is injectable purely so the boot call is testable.\n */\nexport async function requestBoot(\n apiUrl: string,\n request: BootRequest,\n fetchImpl: typeof fetch = fetch,\n): Promise<BootAnswer | null> {\n let response: Response;\n try {\n response = await fetchImpl(bootEndpoint(apiUrl), {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(request),\n });\n } catch {\n return null; // network error / Fixback unreachable\n }\n\n if (!response.ok) return null; // 401 unknown key, or any other refusal\n\n let body: unknown;\n try {\n body = await response.json();\n } catch {\n return null;\n }\n\n return isBootAnswer(body) ? body : null;\n}\n","/**\n * The Public Reporter's stable, first-party anonymous id.\n *\n * When the host site does not supply its own identity, the SDK establishes a\n * per-browser id and persists it, so repeated boots from the same browser are one\n * Reporter (the server counts distinct Reporters by it). Persistence is\n * best-effort: if storage is unavailable (private mode, blocked cookies), we fall\n * back to an ephemeral id rather than throw — the SDK must never disturb the host\n * page.\n */\n\nconst STORAGE_KEY = \"fixback.anonymousId\";\n\n/** A random id: a UUID where the platform offers one, else a compact fallback. */\nfunction generateId(): string {\n const c: Crypto | undefined = globalThis.crypto;\n if (c && typeof c.randomUUID === \"function\") return c.randomUUID();\n return `fb-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\n/** Return this browser's anonymous id, creating and persisting one on first use. */\nexport function ensureAnonymousId(): string {\n try {\n const store = globalThis.localStorage;\n const existing = store.getItem(STORAGE_KEY);\n if (existing) return existing;\n const id = generateId();\n store.setItem(STORAGE_KEY, id);\n return id;\n } catch {\n // Storage blocked or absent — an ephemeral id keeps this boot working.\n return generateId();\n }\n}\n","/**\n * The launcher's isolated styles.\n *\n * These live inside the launcher's Shadow DOM (see `launcher.ts`), so nothing\n * here can reach the host page and nothing on the host page can reach in. The\n * token values are **vendored** Signal design tokens — copied from\n * `packages/ui/src/tokens.css` rather than imported, because the SDK must not\n * depend on `@fixback/ui` at runtime (ticket #47). Keep them in sync by value.\n */\nexport const LAUNCHER_STYLES = `\n:host {\n /* Vendored Signal tokens (packages/ui/src/tokens.css). */\n --fb-color-accent: #2f6fed;\n --fb-color-accent-hover: #245fd0;\n --fb-color-on-emphasis: #ffffff;\n --fb-color-text: #0f1720;\n --fb-font-sans: \"IBM Plex Sans\", system-ui, -apple-system, \"Segoe UI\", Roboto,\n Helvetica, Arial, sans-serif;\n\n display: block;\n color: var(--fb-color-text);\n font-family: var(--fb-font-sans);\n font-size: 13px;\n line-height: 1.4;\n -webkit-font-smoothing: antialiased;\n}\n\n.fb-launcher {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n box-sizing: border-box;\n height: 40px;\n margin: 0;\n padding: 0 16px;\n border: 0;\n border-radius: 999px;\n background: var(--fb-color-accent);\n color: var(--fb-color-on-emphasis);\n font-family: inherit;\n font-size: 13px;\n font-weight: 600;\n letter-spacing: 0.01em;\n cursor: pointer;\n box-shadow:\n 0 6px 18px rgba(15, 23, 32, 0.16),\n 0 1px 2px rgba(15, 23, 32, 0.12);\n transition:\n background-color 120ms ease,\n transform 120ms ease;\n}\n\n.fb-launcher:hover {\n background: var(--fb-color-accent-hover);\n}\n\n.fb-launcher:active {\n transform: translateY(1px);\n}\n\n.fb-launcher:focus-visible {\n outline: 2px solid var(--fb-color-accent);\n outline-offset: 2px;\n}\n\n.fb-launcher__icon {\n display: block;\n flex: none;\n width: 16px;\n height: 16px;\n}\n\n.fb-launcher__label {\n white-space: nowrap;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .fb-launcher {\n transition: none;\n }\n}\n`;\n","import { LAUNCHER_STYLES } from \"./styles\";\n\n/** Marks the SDK's host element in the light DOM, so it is findable and unique. */\nexport const ROOT_ATTRIBUTE = \"data-fixback-root\";\n\n/**\n * Dispatched from the host element when the launcher is activated. The report\n * overlay this opens is a later ticket (#54); for now the launcher is the mounted\n * entry point, and this event is the seam the overlay will hang off. It is\n * `composed` so host-page listeners outside the Shadow DOM can hear it.\n */\nexport const LAUNCH_EVENT = \"fixback:launch\";\n\nconst ICON =\n '<svg class=\"fb-launcher__icon\" viewBox=\"0 0 16 16\" aria-hidden=\"true\" focusable=\"false\">' +\n '<path fill=\"currentColor\" d=\"M3.25 2h9.5A1.25 1.25 0 0 1 14 3.25v6.5A1.25 1.25 0 0 1 12.75 11H7.6l-3.19 2.55A.6.6 0 0 1 3.4 13.1V11h-.15A1.25 1.25 0 0 1 2 9.75v-6.5A1.25 1.25 0 0 1 3.25 2Z\"/>' +\n \"</svg>\";\n\n/** A mounted launcher and the handle needed to remove it again. */\nexport interface Launcher {\n readonly host: HTMLElement;\n}\n\n/**\n * Mount the launcher into `target` (typically `document.body`). The visible\n * button lives inside an open Shadow DOM so its styles are fully isolated from\n * the host page and vice-versa; the host element itself is fixed-positioned and\n * out of flow, so mounting never shifts the host page's layout. Only one launcher\n * can exist at a time — an earlier one is removed first.\n */\nexport function mountLauncher(target: HTMLElement): Launcher {\n const doc = target.ownerDocument;\n\n const previous = doc.querySelector(`[${ROOT_ATTRIBUTE}]`);\n if (previous) previous.remove();\n\n const host = doc.createElement(\"div\");\n host.setAttribute(ROOT_ATTRIBUTE, \"\");\n // Positioning is inlined on the host (not in the Shadow DOM sheet) so it holds\n // even against a hostile host-page reset, and so the launcher is anchored to\n // the viewport without ever participating in the page's flow.\n host.style.cssText =\n \"position:fixed;right:20px;bottom:20px;z-index:2147483000;margin:0;padding:0;border:0;\";\n\n const shadow = host.attachShadow({ mode: \"open\" });\n\n const style = doc.createElement(\"style\");\n style.textContent = LAUNCHER_STYLES;\n shadow.appendChild(style);\n\n const button = doc.createElement(\"button\");\n button.type = \"button\";\n button.className = \"fb-launcher\";\n button.setAttribute(\"aria-haspopup\", \"dialog\");\n button.setAttribute(\"aria-label\", \"Give feedback\");\n button.innerHTML = `${ICON}<span class=\"fb-launcher__label\">Feedback</span>`;\n button.addEventListener(\"click\", () => {\n host.dispatchEvent(\n new CustomEvent(LAUNCH_EVENT, { bubbles: true, composed: true }),\n );\n });\n shadow.appendChild(button);\n\n target.appendChild(host);\n return { host };\n}\n\n/** Remove a mounted launcher. Safe to call more than once. */\nexport function unmountLauncher(launcher: Launcher): void {\n launcher.host.remove();\n}\n","/**\n * Small DOM helpers shared across the SDK's report surfaces.\n *\n * The SDK mounts its own host elements into the host page — the launcher and the\n * overlay panel — each tagged with a `data-fixback-*` marker attribute (the\n * element-picker's highlight lives inside the overlay's shadow, so it is covered\n * by the shadow-boundary walk below). These helpers let the picker skip the SDK's\n * own UI and let the screenshot exclude it, so a report never captures or targets\n * Fixback's chrome.\n */\n\n/** Marker attributes on the SDK's own host elements in the light DOM. */\nexport const FIXBACK_HOST_MARKERS = [\n \"data-fixback-root\",\n \"data-fixback-overlay\",\n] as const;\n\nconst HOST_SELECTOR = FIXBACK_HOST_MARKERS.map((m) => `[${m}]`).join(\",\");\n\n/**\n * Is this node part of the SDK's own UI? Walks up parents and out through any\n * Shadow DOM boundary (via `getRootNode().host`), so a click inside the overlay's\n * shadow is recognised as Fixback's own — not a host-page element to pick.\n */\nexport function isFixbackNode(node: EventTarget | null | undefined): boolean {\n let current: Node | null = node instanceof Node ? node : null;\n while (current) {\n if (current instanceof Element) {\n for (const marker of FIXBACK_HOST_MARKERS) {\n if (current.hasAttribute(marker)) return true;\n }\n }\n const root = current.getRootNode();\n if (root instanceof ShadowRoot && root !== current) {\n current = root.host;\n continue;\n }\n current = current.parentNode;\n }\n return false;\n}\n\n/**\n * Remove the SDK's own host elements from a (cloned) subtree, so a captured\n * screenshot never contains the launcher, overlay, or picker highlight.\n */\nexport function stripFixbackNodes(root: ParentNode): void {\n for (const el of Array.from(root.querySelectorAll(HOST_SELECTOR))) {\n el.remove();\n }\n}\n","import { isFixbackNode } from \"./dom\";\nimport type { ElementRect, SelectedElement } from \"./report\";\n\n/**\n * Escape a string for use in a CSS selector, preferring the platform's\n * `CSS.escape` and falling back to a conservative backslash escape where it is\n * unavailable (older engines, some test environments).\n */\nfunction cssEscape(value: string): string {\n const api = (globalThis as { CSS?: { escape?: (v: string) => string } }).CSS;\n if (api?.escape) return api.escape(value);\n return value.replace(/[^\\w-]/g, (ch) => `\\\\${ch}`);\n}\n\n/** The 1-based position of `el` among its same-tag siblings (`:nth-of-type`). */\nfunction nthOfType(el: Element): number {\n let n = 1;\n let sib = el.previousElementSibling;\n while (sib) {\n if (sib.tagName === el.tagName) n += 1;\n sib = sib.previousElementSibling;\n }\n return n;\n}\n\n/**\n * A stable CSS selector for `el`: its own id when it has one, otherwise a path of\n * tag (with `:nth-of-type` to disambiguate same-tag siblings) climbing until it\n * reaches an ancestor with an id — the shortest anchor that still resolves.\n */\nexport function cssSelectorFor(el: Element): string {\n if (el.id) return `#${cssEscape(el.id)}`;\n\n const parts: string[] = [];\n let node: Element | null = el;\n while (node && node.tagName.toLowerCase() !== \"html\") {\n if (node.id) {\n parts.unshift(`#${cssEscape(node.id)}`);\n break;\n }\n const tag = node.tagName.toLowerCase();\n const parent: Element | null = node.parentElement;\n if (parent) {\n const sameTag = Array.from(parent.children).filter(\n (c) => c.tagName === node!.tagName,\n );\n parts.unshift(\n sameTag.length > 1 ? `${tag}:nth-of-type(${nthOfType(node)})` : tag,\n );\n } else {\n parts.unshift(tag);\n }\n node = parent;\n }\n return parts.join(\" > \");\n}\n\n/** One node's readable label: `tag`, `tag#id`, or `tag.firstClass`. */\nfunction describeNode(el: Element): string {\n const tag = el.tagName.toLowerCase();\n if (el.id) return `${tag}#${el.id}`;\n const firstClass = el.classList[0];\n return firstClass ? `${tag}.${firstClass}` : tag;\n}\n\n/** A readable ancestry path from the document root down to `el`. */\nexport function domPathFor(el: Element): string {\n const chain: string[] = [];\n let node: Element | null = el;\n while (node) {\n chain.unshift(describeNode(node));\n node = node.parentElement;\n }\n return chain.join(\" > \");\n}\n\nfunction roundedRect(el: Element): ElementRect {\n const r = el.getBoundingClientRect();\n return {\n x: Math.round(r.x),\n y: Math.round(r.y),\n width: Math.round(r.width),\n height: Math.round(r.height),\n };\n}\n\n/**\n * Describe a picked element as the Annotation ingest stores: a stable selector, a\n * readable DOM path, the tag, and the viewport bounding rect (spec MVP §B).\n */\nexport function describeElement(el: Element): SelectedElement {\n return {\n selector: cssSelectorFor(el),\n domPath: domPathFor(el),\n tag: el.tagName.toLowerCase(),\n rect: roundedRect(el),\n };\n}\n\n/** Options for {@link startElementPicker}. */\nexport interface ElementPickerOptions {\n /** Document to attach to. Defaults to the global `document`. */\n readonly doc?: Document;\n /** Called with the element being hovered (or `null` over nothing pickable). */\n readonly onHover?: (element: Element | null) => void;\n /** Called with the element the Reporter clicked to pin. */\n readonly onPick: (element: Element) => void;\n /** Called when the Reporter presses Escape to abandon the pick. */\n readonly onCancel?: () => void;\n /** Return true to skip a node (defaults to skipping the SDK's own UI). */\n readonly ignore?: (element: Element) => boolean;\n}\n\n/** A running element-picker; call {@link ElementPicker.stop} to detach it. */\nexport interface ElementPicker {\n stop(): void;\n}\n\n/**\n * Enter element-select mode: as the Reporter moves over the host page the element\n * under the pointer is reported through `onHover` (the overlay draws a highlight),\n * a click pins it through `onPick`, and Escape abandons through `onCancel`. The\n * click that pins is swallowed (prevent-default + stop-propagation) so it never\n * also activates the host page. The SDK's own UI is skipped by default, so the\n * Reporter can never pick the overlay itself.\n *\n * Listeners are attached in the capture phase so the pick is intercepted before\n * any host-page handler sees it; `stop()` (called automatically on pick/cancel)\n * detaches them all.\n */\nexport function startElementPicker(options: ElementPickerOptions): ElementPicker {\n const doc = options.doc ?? document;\n const ignore = options.ignore ?? isFixbackNode;\n let current: Element | null = null;\n let stopped = false;\n\n function resolveTarget(event: Event): Element | null {\n const path =\n typeof event.composedPath === \"function\" ? event.composedPath() : [];\n const deepest = path[0] ?? event.target;\n if (deepest instanceof Element) return ignore(deepest) ? null : deepest;\n return null;\n }\n\n function onOver(event: Event): void {\n if (stopped) return;\n current = resolveTarget(event);\n options.onHover?.(current);\n }\n\n function onClick(event: MouseEvent): void {\n if (stopped) return;\n const el = resolveTarget(event) ?? current;\n if (!el) return; // nothing pickable under the pointer — let the click pass\n event.preventDefault();\n event.stopPropagation();\n stop();\n options.onPick(el);\n }\n\n function onKey(event: KeyboardEvent): void {\n if (stopped) return;\n if (event.key === \"Escape\") {\n event.preventDefault();\n stop();\n options.onCancel?.();\n }\n }\n\n function stop(): void {\n if (stopped) return;\n stopped = true;\n doc.removeEventListener(\"mouseover\", onOver, true);\n doc.removeEventListener(\"click\", onClick, true);\n doc.removeEventListener(\"keydown\", onKey, true);\n current = null;\n }\n\n doc.addEventListener(\"mouseover\", onOver, true);\n doc.addEventListener(\"click\", onClick, true);\n doc.addEventListener(\"keydown\", onKey, true);\n\n return { stop };\n}\n","/**\n * The ingest **feedback** content wire-contract, vendored, plus the helpers that\n * assemble it from what a Reporter composed in the overlay.\n *\n * Like `boot.ts`, the SDK does not import `@fixback/shared`; the slice of the\n * submission shape it needs is copied here. Keep it in lock-step with the server:\n * the JSON `payload` part accepted by `POST /api/ingest/feedback`\n * (`apps/api/src/ingest/ingest.controller.ts` — `feedbackContentBody`) and the\n * `SubmissionContent` it parses (`apps/api/src/ingest/reporter-identity.ts`).\n */\n\n/** The Kind a Reporter tags a report with. Mirrors the server's `ISSUE_KINDS`. */\nexport type IssueKind = \"bug\" | \"improvement\" | \"idea\";\n\n/** The picked element's viewport rectangle, as the server's annotation `rect`. */\nexport interface ElementRect {\n readonly x: number;\n readonly y: number;\n readonly width: number;\n readonly height: number;\n}\n\n/**\n * The Annotation — the element a Reporter pointed at: a stable CSS selector, a\n * readable DOM path, the tag, and the bounding rect. Exactly the server's\n * `annotation` object shape.\n */\nexport interface SelectedElement {\n readonly selector: string;\n readonly domPath: string;\n readonly tag: string;\n readonly rect: ElementRect;\n}\n\n/** The capture environment recorded alongside a report. */\nexport interface CaptureEnvironment {\n readonly viewportWidth?: number;\n readonly viewportHeight?: number;\n readonly browser?: string;\n readonly sdkVersion?: string;\n}\n\n/**\n * The JSON content of a feedback submission — the object serialised into the\n * multipart `payload` part next to the `key` and identity evidence. Every field\n * is optional: none of it feeds the server's trust decision, so a submission may\n * carry any subset. `annotation` is the selected element; the screenshot is a\n * separate binary part, never part of this JSON.\n */\nexport interface ReportContent {\n readonly comment?: string;\n readonly kind?: IssueKind;\n readonly url?: string;\n readonly environment?: CaptureEnvironment;\n readonly annotation?: SelectedElement;\n}\n\n/** What the overlay hands to {@link assembleContent} when the Reporter sends. */\nexport interface ReportDraft {\n readonly kind?: IssueKind;\n readonly comment?: string;\n readonly element?: SelectedElement;\n readonly url?: string;\n readonly environment?: CaptureEnvironment;\n}\n\n/**\n * Read the capture environment off a window: the viewport size, the browser's\n * user-agent, and the SDK version. A dimension is recorded only when it is a\n * positive number (a headless/zero viewport records nothing rather than an\n * invalid `0`, which the server would reject).\n */\nexport function collectEnvironment(\n win: Window,\n sdkVersion: string,\n): CaptureEnvironment {\n const env: {\n viewportWidth?: number;\n viewportHeight?: number;\n browser?: string;\n sdkVersion?: string;\n } = {};\n\n const width = win.innerWidth;\n if (typeof width === \"number\" && width > 0) env.viewportWidth = Math.round(width);\n const height = win.innerHeight;\n if (typeof height === \"number\" && height > 0) {\n env.viewportHeight = Math.round(height);\n }\n\n const ua = win.navigator?.userAgent;\n if (typeof ua === \"string\" && ua.length > 0) env.browser = ua;\n\n if (sdkVersion.length > 0) env.sdkVersion = sdkVersion;\n\n return env;\n}\n\n/**\n * Assemble the ingest content from what the Reporter composed. Empty pieces are\n * dropped rather than sent as blanks: a whitespace-only comment, an absent\n * element, or an empty environment are simply omitted, so the payload carries\n * only what was actually provided (mirroring the server's all-optional content).\n */\nexport function assembleContent(draft: ReportDraft): ReportContent {\n const content: {\n comment?: string;\n kind?: IssueKind;\n url?: string;\n environment?: CaptureEnvironment;\n annotation?: SelectedElement;\n } = {};\n\n if (draft.kind) content.kind = draft.kind;\n\n const comment = draft.comment?.trim();\n if (comment) content.comment = comment;\n\n if (draft.url) content.url = draft.url;\n\n if (draft.environment && Object.keys(draft.environment).length > 0) {\n content.environment = draft.environment;\n }\n\n if (draft.element) content.annotation = draft.element;\n\n return content;\n}\n","/**\n * The report overlay's isolated styles, built to the frozen Signal Reporter\n * overlay (`docs/design/Fixback Visual Identity.dc.html`, badge `1d`). Like the\n * launcher's styles these live inside the overlay's Shadow DOM, so nothing here\n * reaches the host page and nothing on the host page reaches in. Token values are\n * vendored Signal tokens (copied from `packages/ui/src/tokens.css`, not imported —\n * the SDK must not depend on `@fixback/ui` at runtime). Keep them in sync by value.\n */\nexport const OVERLAY_STYLES = `\n:host {\n --fb-color-accent: #2f6fed;\n --fb-color-accent-hover: #245fd0;\n --fb-color-on-emphasis: #ffffff;\n --fb-color-ink: #0f1720;\n --fb-color-text: #1a2530;\n --fb-color-muted: #5a6875;\n --fb-color-faint: #9aa7b2;\n --fb-color-border: #e0e6ec;\n --fb-color-border-soft: #e6ebf0;\n --fb-color-surface: #ffffff;\n --fb-color-bug: #e5484d;\n --fb-color-bug-bg: #fdecec;\n --fb-color-impr: #2f6fed;\n --fb-color-impr-bg: #eaf1fe;\n --fb-color-idea: #8b5cf6;\n --fb-color-idea-bg: #f2ecfe;\n --fb-color-success: #2f9e5b;\n --fb-font-sans: \"IBM Plex Sans\", system-ui, -apple-system, \"Segoe UI\", Roboto,\n Helvetica, Arial, sans-serif;\n --fb-font-mono: \"IBM Plex Mono\", ui-monospace, \"SFMono-Regular\", Menlo, Consolas,\n monospace;\n\n display: block;\n color: var(--fb-color-text);\n font-family: var(--fb-font-sans);\n font-size: 13px;\n line-height: 1.45;\n -webkit-font-smoothing: antialiased;\n}\n\n* { box-sizing: border-box; }\n\n.fb-ov-panel {\n width: 300px;\n max-width: calc(100vw - 40px);\n background: var(--fb-color-surface);\n border: 1px solid var(--fb-color-border);\n border-radius: 13px;\n box-shadow: 0 18px 44px rgba(20, 40, 70, 0.2);\n overflow: hidden;\n}\n\n.fb-ov-head {\n display: flex;\n align-items: center;\n gap: 9px;\n padding: 12px 14px;\n border-bottom: 1px solid var(--fb-color-border-soft);\n}\n.fb-ov-mark-sq {\n width: 13px;\n height: 13px;\n border-radius: 4px;\n background: var(--fb-color-accent);\n flex: none;\n}\n.fb-ov-brand { font-size: 14px; font-weight: 600; color: var(--fb-color-ink); }\n.fb-ov-close {\n margin-left: auto;\n border: 0;\n background: none;\n color: var(--fb-color-faint);\n font-size: 16px;\n line-height: 1;\n padding: 2px 4px;\n cursor: pointer;\n border-radius: 6px;\n}\n.fb-ov-close:hover { color: var(--fb-color-muted); background: #f4f7fa; }\n\n.fb-ov-tabs { display: flex; gap: 6px; padding: 12px 14px 6px; }\n.fb-ov-tab {\n flex: 1;\n text-align: center;\n font-size: 11px;\n font-weight: 600;\n padding: 6px;\n border-radius: 7px;\n border: 1px solid var(--fb-color-border);\n background: var(--fb-color-surface);\n color: var(--fb-color-muted);\n cursor: pointer;\n font-family: inherit;\n}\n.fb-ov-tab:hover { border-color: #cfd8e2; }\n.fb-ov-tab.is-active[data-kind=\"bug\"] {\n background: var(--fb-color-bug-bg); color: var(--fb-color-bug); border-color: transparent;\n}\n.fb-ov-tab.is-active[data-kind=\"improvement\"] {\n background: var(--fb-color-impr-bg); color: var(--fb-color-impr); border-color: transparent;\n}\n.fb-ov-tab.is-active[data-kind=\"idea\"] {\n background: var(--fb-color-idea-bg); color: var(--fb-color-idea); border-color: transparent;\n}\n\n.fb-ov-mark {\n margin: 10px 14px;\n min-height: 52px;\n border-radius: 9px;\n border: 1px solid var(--fb-color-border-soft);\n background: repeating-linear-gradient(135deg, #f4f7fa, #f4f7fa 7px, #eaeff4 7px, #eaeff4 14px);\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n justify-content: center;\n gap: 6px;\n padding: 10px 12px;\n}\n.fb-ov-selector {\n display: none;\n max-width: 100%;\n font-family: var(--fb-font-mono);\n font-size: 10px;\n color: var(--fb-color-on-emphasis);\n background: var(--fb-color-accent);\n padding: 3px 8px;\n border-radius: 5px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.fb-ov-mark.has-element .fb-ov-selector { display: inline-block; }\n.fb-ov-mark-caption { font-size: 10px; color: var(--fb-color-faint); font-family: var(--fb-font-mono); }\n\n.fb-ov-comment {\n display: block;\n width: calc(100% - 28px);\n margin: 0 14px 10px;\n min-height: 62px;\n resize: vertical;\n font-family: inherit;\n font-size: 13px;\n color: var(--fb-color-text);\n border: 1px solid var(--fb-color-border-soft);\n border-radius: 9px;\n padding: 10px 11px;\n}\n.fb-ov-comment::placeholder { color: var(--fb-color-faint); }\n.fb-ov-comment:focus-visible { outline: 2px solid var(--fb-color-accent); outline-offset: 1px; }\n\n.fb-ov-status { padding: 0 14px; font-size: 11px; min-height: 0; }\n.fb-ov-status.is-error { color: var(--fb-color-bug); }\n\n.fb-ov-tools { display: flex; align-items: center; gap: 8px; padding: 8px 14px 14px; }\n.fb-ov-pickbtn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-family: inherit;\n font-size: 12px;\n color: var(--fb-color-muted);\n background: var(--fb-color-surface);\n border: 1px solid var(--fb-color-border);\n border-radius: 8px;\n padding: 8px 11px;\n cursor: pointer;\n}\n.fb-ov-pickbtn:hover { border-color: #cfd8e2; }\n.fb-ov-pickbtn.is-active {\n color: var(--fb-color-accent);\n border-color: var(--fb-color-accent);\n background: var(--fb-color-impr-bg);\n}\n.fb-ov-pickbtn__glyph { font-size: 14px; line-height: 1; }\n\n.fb-ov-send {\n margin-left: auto;\n font-family: inherit;\n font-size: 13px;\n font-weight: 600;\n color: var(--fb-color-on-emphasis);\n background: var(--fb-color-accent);\n border: 0;\n border-radius: 8px;\n padding: 9px 18px;\n cursor: pointer;\n}\n.fb-ov-send:hover { background: var(--fb-color-accent-hover); }\n.fb-ov-send:disabled { opacity: 0.6; cursor: default; }\n\n.fb-ov-done { display: none; padding: 24px 18px; text-align: center; }\n.fb-ov-panel.is-sent .fb-ov-form { display: none; }\n.fb-ov-panel.is-sent .fb-ov-done { display: block; }\n.fb-ov-done__check {\n width: 40px; height: 40px; margin: 0 auto 12px;\n border-radius: 50%;\n background: #e7f6ee; color: var(--fb-color-success);\n display: flex; align-items: center; justify-content: center;\n font-size: 20px; font-weight: 700;\n}\n.fb-ov-done__title { font-size: 15px; font-weight: 600; color: var(--fb-color-ink); }\n.fb-ov-done__sub { font-size: 12px; color: var(--fb-color-muted); margin-top: 4px; }\n\n.fb-ov-pick { position: fixed; inset: 0; pointer-events: none; z-index: 2147483002; display: none; }\n.fb-ov-panel.is-picking + .fb-ov-pick { display: block; }\n.fb-ov-panel.is-picking { visibility: hidden; }\n.fb-ov-highlight {\n position: absolute;\n border: 2px dashed var(--fb-color-accent);\n border-radius: 6px;\n box-shadow: 0 0 0 3px rgba(47, 111, 237, 0.14);\n transition: all 60ms ease;\n}\n.fb-ov-hint {\n position: absolute;\n top: 16px;\n left: 50%;\n transform: translateX(-50%);\n font-family: var(--fb-font-mono);\n font-size: 11px;\n color: var(--fb-color-on-emphasis);\n background: var(--fb-color-ink);\n padding: 6px 12px;\n border-radius: 7px;\n box-shadow: 0 8px 20px rgba(20, 40, 70, 0.25);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .fb-ov-highlight { transition: none; }\n}\n`;\n","import { stripFixbackNodes } from \"./dom\";\n\n/**\n * Client-side masked screenshot capture (spec MVP §E; ticket #54).\n *\n * The capture is **private-by-default**: input values are masked and the SDK's\n * own UI is removed from a *clone* of the view **before** anything is rasterised,\n * so no unmasked text and none of Fixback's chrome ever reaches the image. The\n * approach is dependency-free — the cloned, masked DOM is serialised into an SVG\n * `<foreignObject>` and drawn onto a `<canvas>` — honouring the SDK's \"no runtime\n * dependencies / no host-page disturbance\" constraints. The raster step is\n * injectable so the pipeline (and the masking-before-capture guarantee) is\n * testable without a real canvas, and it fails quietly: any problem resolves to\n * `null` and the report is simply sent without a screenshot.\n */\n\n/** The character private input content is replaced with. */\nexport const MASK_CHAR = \"•\";\n\n/** Upper bound on mask length, so a huge field doesn't produce a huge string. */\nconst MAX_MASK_LENGTH = 32;\n\n/** Input `type`s that carry no free-text private data, so are left as-is. */\nconst NON_TEXT_INPUT_TYPES = new Set([\n \"button\",\n \"submit\",\n \"reset\",\n \"checkbox\",\n \"radio\",\n \"range\",\n \"color\",\n \"file\",\n \"image\",\n \"hidden\",\n]);\n\nfunction maskString(length: number): string {\n const n = Math.min(Math.max(length, 1), MAX_MASK_LENGTH);\n return MASK_CHAR.repeat(n);\n}\n\n/**\n * Mask the private, user-entered content in a subtree: text `input` values,\n * `textarea` content, and `contenteditable` text. Developer-authored text\n * (placeholders, button labels, non-text controls) is left untouched. Operates in\n * place — call it on a *clone* of the view, never the live page.\n */\nexport function maskInputs(root: ParentNode): void {\n for (const input of Array.from(root.querySelectorAll(\"input\"))) {\n const type = (input.getAttribute(\"type\") ?? \"text\").toLowerCase();\n if (NON_TEXT_INPUT_TYPES.has(type)) continue;\n const value = input.value;\n if (!value) continue;\n const masked = maskString(value.length);\n input.value = masked;\n input.setAttribute(\"value\", masked);\n }\n\n for (const area of Array.from(root.querySelectorAll(\"textarea\"))) {\n const value = area.value || area.textContent || \"\";\n if (!value) continue;\n const masked = maskString(value.length);\n area.value = masked;\n area.textContent = masked;\n }\n\n for (const editable of Array.from(\n root.querySelectorAll<HTMLElement>(\"[contenteditable]\"),\n )) {\n if (editable.getAttribute(\"contenteditable\") === \"false\") continue;\n const text = editable.textContent ?? \"\";\n if (!text.trim()) continue;\n editable.textContent = maskString(text.length);\n }\n}\n\n/** A captured screenshot: the image bytes plus its dimensions and content type. */\nexport interface Capture {\n readonly blob: Blob;\n readonly width: number;\n readonly height: number;\n readonly type: string;\n}\n\n/** Turns a serialised SVG of the view into image bytes (injectable for tests). */\nexport type Rasterize = (\n svg: string,\n meta: { width: number; height: number; type: string },\n) => Promise<Blob | null>;\n\n/** Options for {@link captureView}. */\nexport interface CaptureOptions {\n /** The element to capture. Defaults to the document element (the full view). */\n readonly target?: Element;\n readonly doc?: Document;\n readonly win?: Window;\n /** Output content type. Defaults to `image/png`. */\n readonly type?: string;\n /** Override the raster step (the default draws via an SVG + `<canvas>`). */\n readonly rasterize?: Rasterize;\n}\n\nfunction viewportExtent(\n win: Window | undefined,\n doc: Document,\n axis: \"Width\" | \"Height\",\n): number {\n const inner = win?.[`inner${axis}`];\n const client = doc.documentElement[`client${axis}`];\n const value = typeof inner === \"number\" && inner > 0 ? inner : client;\n return Math.max(1, Math.round(value || 0) || 1);\n}\n\nfunction serializeToSvg(node: Element, width: number, height: number): string {\n const serialized = new XMLSerializer().serializeToString(node);\n return (\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\">` +\n `<foreignObject x=\"0\" y=\"0\" width=\"100%\" height=\"100%\">` +\n `<div xmlns=\"http://www.w3.org/1999/xhtml\">${serialized}</div>` +\n `</foreignObject></svg>`\n );\n}\n\n/** How long the default rasteriser waits for the SVG image before giving up. */\nconst RASTER_TIMEOUT_MS = 4000;\n\n/**\n * The default raster step: load the serialised SVG as an image and draw it onto a\n * canvas. Guarded at every turn — a missing 2D context, a tainted canvas (a\n * cross-origin image on the page), an image that never loads, or any thrown error\n * resolves to `null` rather than rejecting, so a capture failure never breaks the\n * Send.\n */\nconst rasterizeViaCanvas: Rasterize = (svg, { width, height, type }) =>\n new Promise((resolve) => {\n let settled = false;\n const done = (blob: Blob | null): void => {\n if (settled) return;\n settled = true;\n resolve(blob);\n };\n try {\n const image = new Image();\n const timer = setTimeout(() => done(null), RASTER_TIMEOUT_MS);\n image.onload = () => {\n try {\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) {\n clearTimeout(timer);\n done(null);\n return;\n }\n ctx.drawImage(image, 0, 0);\n canvas.toBlob((blob) => {\n clearTimeout(timer);\n done(blob);\n }, type);\n } catch {\n clearTimeout(timer);\n done(null);\n }\n };\n image.onerror = () => {\n clearTimeout(timer);\n done(null);\n };\n image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;\n } catch {\n done(null);\n }\n });\n\n/**\n * Capture the current view as a masked screenshot. Clones the target, removes the\n * SDK's own UI, masks input values — **all before** serialising and rasterising —\n * then returns the image bytes, or `null` if capture wasn't possible.\n */\nexport async function captureView(\n options: CaptureOptions = {},\n): Promise<Capture | null> {\n try {\n const doc = options.doc ?? document;\n const win = options.win ?? doc.defaultView ?? undefined;\n const target = options.target ?? doc.documentElement;\n const type = options.type ?? \"image/png\";\n const width = viewportExtent(win, doc, \"Width\");\n const height = viewportExtent(win, doc, \"Height\");\n\n const clone = target.cloneNode(true) as Element;\n stripFixbackNodes(clone);\n maskInputs(clone);\n\n const svg = serializeToSvg(clone, width, height);\n const rasterize = options.rasterize ?? rasterizeViaCanvas;\n const blob = await rasterize(svg, { width, height, type });\n if (!blob) return null;\n return { blob, width, height, type };\n } catch {\n return null;\n }\n}\n","import type { IdentityInputs, ReporterTier } from \"./boot\";\nimport type { ReportContent } from \"./report\";\n\n/**\n * The multipart feedback submission — the transport half of the capture loop.\n *\n * `POST /api/ingest/feedback` (spec MVP §C; tickets #50/#53) is a multipart\n * request: a single text part `payload` carrying the JSON (publishable key +\n * identity evidence + content) and an optional binary part `screenshot`. The\n * content-type header is deliberately not set, so the runtime writes the\n * `multipart/form-data` boundary itself. Like `requestBoot`, this never throws:\n * an unreachable Fixback or a refusal is a quiet result, so a Fixback problem\n * never breaks the host page.\n */\n\n/** The multipart field the screenshot binary is sent under (mirrors the server). */\nconst SCREENSHOT_FIELD = \"screenshot\";\n\n/** What ingest returns for an accepted submission (vendored server shape). */\nexport interface RecordedFeedback {\n readonly feedbackId: string;\n readonly issueId: string;\n readonly reporterId: string;\n readonly tier: ReporterTier;\n}\n\n/** The captured screenshot to attach, if any. */\nexport interface SubmitScreenshot {\n readonly blob: Blob;\n readonly filename?: string;\n}\n\n/** Everything a single feedback submission carries. */\nexport interface SubmitInput {\n readonly key: string;\n readonly identity?: IdentityInputs;\n readonly content: ReportContent;\n readonly screenshot?: SubmitScreenshot | null;\n}\n\n/** The outcome of a submission — never an exception. */\nexport type SubmitResult =\n | { readonly ok: true; readonly feedback: RecordedFeedback | null }\n | {\n readonly ok: false;\n readonly reason: \"unreachable\" | \"refused\";\n readonly status?: number;\n };\n\n/** Join an API base URL with the feedback path, tolerating a trailing slash. */\nexport function feedbackEndpoint(apiUrl: string): string {\n return `${apiUrl.replace(/\\/+$/, \"\")}/api/ingest/feedback`;\n}\n\n/** Drop `undefined` identity fields so the payload carries only what was given. */\nfunction compactIdentity(identity: IdentityInputs | undefined): IdentityInputs {\n if (!identity) return {};\n const out: { signedIdentity?: string; reporterId?: string; anonymousId?: string } = {};\n if (identity.signedIdentity) out.signedIdentity = identity.signedIdentity;\n if (identity.reporterId) out.reporterId = identity.reporterId;\n if (identity.anonymousId) out.anonymousId = identity.anonymousId;\n return out;\n}\n\n/**\n * Submit a report to ingest. Assembles the `payload` JSON (key + identity +\n * content) and the optional `screenshot` file into a `FormData`, posts it, and\n * resolves to the recorded Feedback on success or a named failure otherwise. The\n * `fetchImpl` seam exists purely so the call is testable.\n */\nexport async function submitReport(\n apiUrl: string,\n input: SubmitInput,\n fetchImpl: typeof fetch = fetch,\n): Promise<SubmitResult> {\n const payload = {\n key: input.key,\n ...compactIdentity(input.identity),\n ...input.content,\n };\n\n const form = new FormData();\n form.append(\"payload\", JSON.stringify(payload));\n if (input.screenshot?.blob) {\n form.append(\n SCREENSHOT_FIELD,\n input.screenshot.blob,\n input.screenshot.filename ?? \"screenshot.png\",\n );\n }\n\n let response: Response;\n try {\n response = await fetchImpl(feedbackEndpoint(apiUrl), {\n method: \"POST\",\n body: form,\n });\n } catch {\n return { ok: false, reason: \"unreachable\" };\n }\n\n if (!response.ok) {\n return { ok: false, reason: \"refused\", status: response.status };\n }\n\n try {\n const feedback = (await response.json()) as RecordedFeedback;\n return { ok: true, feedback };\n } catch {\n // Accepted, but the body wasn't the expected JSON — still a success.\n return { ok: true, feedback: null };\n }\n}\n","/**\n * The SDK's own version string, reported to ingest as `environment.sdkVersion`\n * (spec MVP §B/§E). Kept as a hand-maintained constant rather than imported from\n * `package.json`, so the bundle stays a single self-contained file with no JSON\n * import — keep it in step with `package.json` and the Changesets bump.\n */\nexport const SDK_VERSION = \"0.1.0\";\n","import type { IdentityInputs } from \"./boot\";\nimport {\n describeElement,\n startElementPicker,\n type ElementPicker,\n type ElementPickerOptions,\n} from \"./element-picker\";\nimport {\n assembleContent,\n collectEnvironment,\n type IssueKind,\n type SelectedElement,\n} from \"./report\";\nimport { OVERLAY_STYLES } from \"./overlay-styles\";\nimport { captureView, type Capture, type CaptureOptions } from \"./screenshot\";\nimport { submitReport, type SubmitInput, type SubmitResult } from \"./submit\";\nimport { SDK_VERSION } from \"./version\";\n\n/** Marks the overlay's host element in the light DOM (skipped by capture/picker). */\nexport const OVERLAY_ATTRIBUTE = \"data-fixback-overlay\";\n\n/** The Kind tabs, in order — value is the ingest Kind, label is the overlay text. */\nconst KIND_TABS: ReadonlyArray<{ value: IssueKind; label: string }> = [\n { value: \"bug\", label: \"Bug\" },\n { value: \"improvement\", label: \"Improve\" },\n { value: \"idea\", label: \"Idea\" },\n];\n\n/** How long the confirmation shows before the overlay closes itself. */\nconst AUTO_CLOSE_MS = 1800;\n\ntype CaptureViewFn = (options?: CaptureOptions) => Promise<Capture | null>;\ntype SubmitReportFn = (\n apiUrl: string,\n input: SubmitInput,\n fetchImpl?: typeof fetch,\n) => Promise<SubmitResult>;\ntype StartPickerFn = (options: ElementPickerOptions) => ElementPicker;\n\n/** Injectable collaborators, defaulted to the real implementations. */\nexport interface OverlayDeps {\n readonly captureView: CaptureViewFn;\n readonly submitReport: SubmitReportFn;\n readonly startElementPicker: StartPickerFn;\n}\n\n/** Configuration for {@link createOverlay}. */\nexport interface OverlayConfig {\n readonly apiUrl: string;\n readonly key: string;\n readonly identity?: IdentityInputs;\n readonly sdkVersion?: string;\n /** Where to mount the overlay host. Defaults to `document.body`. */\n readonly target?: HTMLElement;\n readonly doc?: Document;\n readonly win?: Window;\n readonly deps?: Partial<OverlayDeps>;\n}\n\n/** A mounted overlay the launcher opens. */\nexport interface OverlayController {\n open(): void;\n close(): void;\n destroy(): void;\n readonly isOpen: boolean;\n}\n\ninterface Refs {\n readonly panel: HTMLElement;\n readonly tabs: Map<IssueKind, HTMLButtonElement>;\n readonly mark: HTMLElement;\n readonly selector: HTMLElement;\n readonly caption: HTMLElement;\n readonly comment: HTMLTextAreaElement;\n readonly status: HTMLElement;\n readonly pickBtn: HTMLButtonElement;\n readonly sendBtn: HTMLButtonElement;\n readonly highlight: HTMLElement;\n}\n\nfunction h<K extends keyof HTMLElementTagNameMap>(\n doc: Document,\n tag: K,\n props: Partial<Record<string, string>> = {},\n children: ReadonlyArray<Node | string> = [],\n): HTMLElementTagNameMap[K] {\n const node = doc.createElement(tag);\n for (const [name, value] of Object.entries(props)) {\n if (value !== undefined) node.setAttribute(name, value);\n }\n for (const child of children) {\n node.appendChild(typeof child === \"string\" ? doc.createTextNode(child) : child);\n }\n return node;\n}\n\nconst EXTENSIONS: Readonly<Record<string, string>> = {\n \"image/png\": \"png\",\n \"image/jpeg\": \"jpg\",\n \"image/webp\": \"webp\",\n \"image/gif\": \"gif\",\n};\n\nfunction extensionFor(type: string): string {\n return EXTENSIONS[type] ?? \"png\";\n}\n\n/**\n * Create the report overlay — the on-page panel a Reporter files a report from,\n * built to the frozen Signal overlay (`1d`). It mounts lazily inside its own\n * Shadow DOM (isolated from the host page, and marked so the screenshot and\n * element-picker skip it), opens on the launcher's `fixback:launch` seam, and on\n * Send captures a masked screenshot, assembles the Annotation, and submits to\n * ingest — showing a confirmation on success and failing quietly otherwise.\n */\nexport function createOverlay(config: OverlayConfig): OverlayController {\n const doc = config.doc ?? document;\n const win = config.win ?? doc.defaultView ?? window;\n const sdkVersion = config.sdkVersion ?? SDK_VERSION;\n const deps: OverlayDeps = {\n captureView: config.deps?.captureView ?? captureView,\n submitReport: config.deps?.submitReport ?? submitReport,\n startElementPicker: config.deps?.startElementPicker ?? startElementPicker,\n };\n\n let host: HTMLElement | null = null;\n let refs: Refs | null = null;\n let picker: ElementPicker | null = null;\n let autoClose: ReturnType<typeof setTimeout> | null = null;\n let open = false;\n\n const state: {\n kind: IssueKind;\n selectedElement: SelectedElement | null;\n phase: \"compose\" | \"sending\" | \"sent\";\n } = { kind: \"bug\", selectedElement: null, phase: \"compose\" };\n\n function setStatus(message: string, isError: boolean): void {\n if (!refs) return;\n refs.status.textContent = message;\n refs.status.classList.toggle(\"is-error\", isError);\n }\n\n function setKind(kind: IssueKind): void {\n state.kind = kind;\n if (!refs) return;\n for (const [value, button] of refs.tabs) {\n const active = value === kind;\n button.classList.toggle(\"is-active\", active);\n button.setAttribute(\"aria-pressed\", String(active));\n }\n }\n\n function renderElement(): void {\n if (!refs) return;\n if (state.selectedElement) {\n refs.mark.classList.add(\"has-element\");\n refs.selector.textContent = state.selectedElement.selector;\n refs.caption.textContent = \"Element selected · masked screenshot on Send\";\n refs.pickBtn.textContent = \"\";\n refs.pickBtn.append(glyph(doc), doc.createTextNode(\"Change element\"));\n } else {\n refs.mark.classList.remove(\"has-element\");\n refs.selector.textContent = \"\";\n refs.caption.textContent = \"Masked screenshot attached on Send\";\n refs.pickBtn.textContent = \"\";\n refs.pickBtn.append(glyph(doc), doc.createTextNode(\"Pick element\"));\n }\n }\n\n function positionHighlight(element: Element | null): void {\n if (!refs) return;\n if (!element) {\n refs.highlight.style.display = \"none\";\n return;\n }\n const rect = element.getBoundingClientRect();\n refs.highlight.style.display = \"block\";\n refs.highlight.style.left = `${rect.left}px`;\n refs.highlight.style.top = `${rect.top}px`;\n refs.highlight.style.width = `${rect.width}px`;\n refs.highlight.style.height = `${rect.height}px`;\n }\n\n function stopPicker(): void {\n picker?.stop();\n picker = null;\n }\n\n function endPick(): void {\n if (refs) {\n refs.panel.classList.remove(\"is-picking\");\n refs.pickBtn.classList.remove(\"is-active\");\n refs.highlight.style.display = \"none\";\n }\n stopPicker();\n }\n\n function beginPick(): void {\n if (!refs) return;\n stopPicker();\n refs.panel.classList.add(\"is-picking\");\n refs.pickBtn.classList.add(\"is-active\");\n picker = deps.startElementPicker({\n doc,\n onHover: positionHighlight,\n onPick: (element) => {\n state.selectedElement = describeElement(element);\n endPick();\n renderElement();\n },\n onCancel: endPick,\n });\n }\n\n function clearAutoClose(): void {\n if (autoClose !== null) {\n clearTimeout(autoClose);\n autoClose = null;\n }\n }\n\n async function send(): Promise<void> {\n if (!refs || state.phase === \"sending\") return;\n state.phase = \"sending\";\n refs.sendBtn.disabled = true;\n refs.sendBtn.textContent = \"Sending…\";\n setStatus(\"\", false);\n try {\n const content = assembleContent({\n kind: state.kind,\n comment: refs.comment.value,\n element: state.selectedElement ?? undefined,\n url: win.location?.href,\n environment: collectEnvironment(win, sdkVersion),\n });\n const capture = await deps.captureView({ doc, win });\n const screenshot = capture\n ? { blob: capture.blob, filename: `screenshot.${extensionFor(capture.type)}` }\n : null;\n const result = await deps.submitReport(config.apiUrl, {\n key: config.key,\n identity: config.identity,\n content,\n screenshot,\n });\n if (result.ok) {\n state.phase = \"sent\";\n refs.panel.classList.add(\"is-sent\");\n autoClose = setTimeout(close, AUTO_CLOSE_MS);\n } else {\n resetSendControl();\n setStatus(\n result.reason === \"unreachable\"\n ? \"Couldn't reach Fixback — try again.\"\n : \"Fixback couldn't accept this report.\",\n true,\n );\n }\n } catch {\n resetSendControl();\n setStatus(\"Something went wrong — try again.\", true);\n }\n }\n\n function resetSendControl(): void {\n state.phase = \"compose\";\n if (!refs) return;\n refs.sendBtn.disabled = false;\n refs.sendBtn.textContent = \"Send\";\n }\n\n function build(): void {\n host = h(doc, \"div\", { [OVERLAY_ATTRIBUTE]: \"\" });\n host.style.cssText =\n \"position:fixed;right:20px;bottom:84px;z-index:2147483001;margin:0;padding:0;border:0;display:none;\";\n const shadow = host.attachShadow({ mode: \"open\" });\n\n const style = doc.createElement(\"style\");\n style.textContent = OVERLAY_STYLES;\n shadow.appendChild(style);\n\n // Header\n const closeBtn = h(doc, \"button\", { type: \"button\", class: \"fb-ov-close\", \"aria-label\": \"Close\" }, [\"✕\"]);\n const head = h(doc, \"div\", { class: \"fb-ov-head\" }, [\n h(doc, \"span\", { class: \"fb-ov-mark-sq\" }),\n h(doc, \"span\", { class: \"fb-ov-brand\" }, [\"Fixback\"]),\n closeBtn,\n ]);\n\n // Kind tabs\n const tabs = new Map<IssueKind, HTMLButtonElement>();\n const tabRow = h(doc, \"div\", { class: \"fb-ov-tabs\" });\n for (const { value, label } of KIND_TABS) {\n const tab = h(doc, \"button\", {\n type: \"button\",\n class: \"fb-ov-tab\",\n \"data-kind\": value,\n }, [label]);\n tab.addEventListener(\"click\", () => setKind(value));\n tabs.set(value, tab);\n tabRow.appendChild(tab);\n }\n\n // Annotation / screenshot marking region\n const selector = h(doc, \"span\", { class: \"fb-ov-selector\" });\n const caption = h(doc, \"span\", { class: \"fb-ov-mark-caption\" });\n const mark = h(doc, \"div\", { class: \"fb-ov-mark\" }, [selector, caption]);\n\n // Comment\n const comment = h(doc, \"textarea\", {\n class: \"fb-ov-comment\",\n placeholder: \"Describe what you saw or want…\",\n \"aria-label\": \"Comment\",\n });\n\n // Status + tools\n const status = h(doc, \"div\", { class: \"fb-ov-status\", role: \"status\", \"aria-live\": \"polite\" });\n const pickBtn = h(doc, \"button\", { type: \"button\", class: \"fb-ov-pickbtn\" });\n pickBtn.addEventListener(\"click\", () => {\n if (state.phase === \"sending\") return;\n if (refs?.panel.classList.contains(\"is-picking\")) endPick();\n else beginPick();\n });\n const sendBtn = h(doc, \"button\", { type: \"button\", class: \"fb-ov-send\" }, [\"Send\"]);\n sendBtn.addEventListener(\"click\", () => void send());\n const tools = h(doc, \"div\", { class: \"fb-ov-tools\" }, [pickBtn, sendBtn]);\n\n const form = h(doc, \"div\", { class: \"fb-ov-form\" }, [tabRow, mark, comment, status, tools]);\n\n // Confirmation\n const done = h(doc, \"div\", { class: \"fb-ov-done\" }, [\n h(doc, \"div\", { class: \"fb-ov-done__check\" }, [\"✓\"]),\n h(doc, \"div\", { class: \"fb-ov-done__title\" }, [\"Report sent\"]),\n h(doc, \"div\", { class: \"fb-ov-done__sub\" }, [\"Thanks — the team can see it now.\"]),\n ]);\n\n const panel = h(doc, \"div\", {\n class: \"fb-ov-panel\",\n role: \"dialog\",\n \"aria-label\": \"Fixback feedback\",\n }, [head, form, done]);\n\n // Element-pick highlight layer (visual only; never intercepts host events)\n const highlight = h(doc, \"div\", { class: \"fb-ov-highlight\" });\n highlight.style.display = \"none\";\n const pickLayer = h(doc, \"div\", { class: \"fb-ov-pick\" }, [\n h(doc, \"div\", { class: \"fb-ov-hint\" }, [\"Click the element you mean · Esc to cancel\"]),\n highlight,\n ]);\n\n shadow.appendChild(panel);\n shadow.appendChild(pickLayer);\n\n closeBtn.addEventListener(\"click\", close);\n\n (config.target ?? doc.body).appendChild(host);\n refs = { panel, tabs, mark, selector, caption, comment, status, pickBtn, sendBtn, highlight };\n }\n\n function ensureBuilt(): void {\n if (!host || !refs) build();\n }\n\n function openOverlay(): void {\n ensureBuilt();\n if (!host || !refs) return;\n clearAutoClose();\n endPick();\n state.phase = \"compose\";\n state.selectedElement = null;\n refs.panel.classList.remove(\"is-sent\");\n refs.comment.value = \"\";\n resetSendControl();\n setStatus(\"\", false);\n setKind(\"bug\");\n renderElement();\n host.style.display = \"block\";\n open = true;\n try {\n refs.comment.focus();\n } catch {\n /* focus is best-effort — never let it throw into the host page */\n }\n }\n\n function close(): void {\n clearAutoClose();\n endPick();\n if (host) host.style.display = \"none\";\n open = false;\n }\n\n function destroy(): void {\n clearAutoClose();\n stopPicker();\n host?.remove();\n host = null;\n refs = null;\n open = false;\n }\n\n return {\n open: openOverlay,\n close,\n destroy,\n get isOpen() {\n return open;\n },\n };\n}\n\n/** The crosshair glyph on the element-pick control (matches the `⌖` in `1d`). */\nfunction glyph(doc: Document): HTMLElement {\n return h(doc, \"span\", { class: \"fb-ov-pickbtn__glyph\", \"aria-hidden\": \"true\" }, [\"⌖\"]);\n}\n","import { type IdentityInputs, requestBoot } from \"./boot\";\nimport { ensureAnonymousId } from \"./identity\";\nimport { LAUNCH_EVENT, type Launcher, mountLauncher, unmountLauncher } from \"./launcher\";\nimport { createOverlay } from \"./overlay\";\nimport { SDK_VERSION } from \"./version\";\n\n/**\n * The hosted Fixback API origin the SDK talks to by default. A self-hosted or\n * local deployment overrides it with `apiUrl` (the dashboard's install snippet\n * pre-fills the right value for the Project).\n */\nexport const DEFAULT_API_URL = \"https://api.fixback.dev\";\n\n/** Options for {@link init}. Only `key` is required. */\nexport interface InitOptions extends IdentityInputs {\n /** The Project's **publishable** key — an identifier that ships in the page. */\n readonly key: string;\n /** The Fixback API origin. Defaults to {@link DEFAULT_API_URL}. */\n readonly apiUrl?: string;\n /** Where to mount the launcher. Defaults to `document.body`. */\n readonly target?: HTMLElement;\n}\n\n/** A running SDK instance. */\nexport interface FixbackInstance {\n /** Remove the launcher and release its DOM. Safe to call more than once. */\n destroy(): void;\n}\n\nconst NOOP_INSTANCE: FixbackInstance = { destroy() {} };\n\n/**\n * Boot the SDK and mount the launcher when — and only when — a submission would\n * be accepted for this key, origin, and Gate.\n *\n * On load the SDK calls the ingest boot endpoint; it mounts the launcher solely\n * when the answer's `canSubmit` is true, so a Reporter is never shown a launcher\n * a submission would be refused (origin off the allowlist, or the Gate turns their\n * tier away). Activating the launcher opens the report overlay (ticket #54) — wired\n * to the launcher's `fixback:launch` seam — from which a Reporter files a complete\n * report. Everything is wrapped so a Fixback problem — unreachable, refused, or an\n * unexpected error — resolves to a no-op instance and never surfaces on the host page.\n */\nexport async function init(options: InitOptions): Promise<FixbackInstance> {\n try {\n // No DOM (server-side render, worker) — nothing to mount.\n if (typeof document === \"undefined\") return NOOP_INSTANCE;\n\n const key = options?.key;\n if (typeof key !== \"string\" || key.length === 0) return NOOP_INSTANCE;\n\n const apiUrl = options.apiUrl ?? DEFAULT_API_URL;\n const anonymousId = options.anonymousId ?? ensureAnonymousId();\n\n const answer = await requestBoot(apiUrl, {\n key,\n signedIdentity: options.signedIdentity,\n reporterId: options.reporterId,\n anonymousId,\n });\n\n // Unreachable, refused, or the Gate/origin says no — show nothing.\n if (!answer?.canSubmit) return NOOP_INSTANCE;\n\n const target = options.target ?? document.body;\n const launcher: Launcher = mountLauncher(target);\n\n // The overlay the launcher opens — carrying the same key and identity, so a\n // filed report is attributed to the very Reporter boot recognised.\n const overlay = createOverlay({\n apiUrl,\n key,\n identity: {\n signedIdentity: options.signedIdentity,\n reporterId: options.reporterId,\n anonymousId,\n },\n sdkVersion: SDK_VERSION,\n target,\n });\n const onLaunch = (): void => overlay.open();\n launcher.host.addEventListener(LAUNCH_EVENT, onLaunch);\n\n return {\n destroy: () => {\n launcher.host.removeEventListener(LAUNCH_EVENT, onLaunch);\n overlay.destroy();\n unmountLauncher(launcher);\n },\n };\n } catch {\n // A Fixback failure must never break the host page.\n return NOOP_INSTANCE;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/boot.ts","../src/dom.ts","../src/element-picker.ts","../src/scrub.ts","../src/breadcrumbs.ts","../src/annotation.ts","../src/report.ts","../src/screenshot.ts","../src/auto-report-backoff.ts","../src/submit.ts","../src/version.ts","../src/error-capture.ts","../src/identity.ts","../src/invite.ts","../src/styles.ts","../src/launcher.ts","../src/onboarding-styles.ts","../src/onboarding.ts","../src/draw-surface.ts","../src/region-capture.ts","../src/overlay-styles.ts","../src/overlay.ts","../src/init.ts"],"sourcesContent":["/**\n * The ingest **boot** wire-contract, vendored.\n *\n * The SDK deliberately does not import `@fixback/shared` — that package is\n * private and server-shaped (ticket #47). The boot request/response is small and\n * stable, so the exact slice the SDK needs is copied here. Keep it in lock-step\n * with the server: the request body accepted by `POST /api/ingest/boot`\n * (`apps/api/src/ingest/ingest.controller.ts`) and the `BootAnswer` returned by\n * `evaluateBoot` (`apps/api/src/ingest/reporter-identity.ts`).\n */\n\n/** A Project's Gate — who may submit. Mirrors the server's `ProjectGate`. */\nexport type ProjectGate = \"open\" | \"invited\" | \"internal\";\n\n/** The trust tier a Reporter holds. Mirrors the server's `ReporterTier`. */\nexport type ReporterTier = \"public\" | \"invited\" | \"internal\";\n\n/**\n * Optional identity evidence the SDK forwards to boot. None of it is a tier: the\n * server re-derives trust from this evidence and never honours a self-declared\n * tier, so the SDK does not send one.\n */\nexport interface IdentityInputs {\n readonly signedIdentity?: string;\n readonly reporterId?: string;\n readonly anonymousId?: string;\n}\n\n/** The JSON body `POST /api/ingest/boot` accepts. */\nexport interface BootRequest extends IdentityInputs {\n readonly key: string;\n}\n\n/**\n * The boot answer: whether this origin is allowlisted, the Project's Gate, the\n * caller's derived tier (`null` when a presented identity was refused), and\n * whether a submission would be accepted right now. The launcher shows only when\n * `canSubmit` is true.\n */\nexport interface BootAnswer {\n readonly originAllowed: boolean;\n readonly gate: ProjectGate;\n readonly tier: ReporterTier | null;\n readonly canSubmit: boolean;\n}\n\n/** Join an API base URL with the boot path, tolerating a trailing slash. */\nexport function bootEndpoint(apiUrl: string): string {\n return `${apiUrl.replace(/\\/+$/, \"\")}/api/ingest/boot`;\n}\n\n/** Narrow an unknown JSON body to a `BootAnswer` before the SDK trusts it. */\nfunction isBootAnswer(value: unknown): value is BootAnswer {\n if (typeof value !== \"object\" || value === null) return false;\n const v = value as Record<string, unknown>;\n return (\n typeof v.originAllowed === \"boolean\" &&\n typeof v.canSubmit === \"boolean\" &&\n typeof v.gate === \"string\" &&\n (v.tier === null || typeof v.tier === \"string\")\n );\n}\n\n/**\n * Ask ingest whether a submission would be accepted for this key / origin / Gate.\n * Resolves to the boot answer, or `null` when Fixback could not be reached, the\n * key was refused, or the response was not a boot answer. It never throws: any\n * non-answer is treated by the caller as \"do not show the launcher\", so a Fixback\n * outage stays invisible to the host page (ticket #47: \"fails quietly\").\n *\n * The browser attaches the `Origin` header itself on this cross-origin request —\n * the server reads it to decide `originAllowed` — so the SDK neither sets nor\n * needs to set it. `fetchImpl` is injectable purely so the boot call is testable.\n */\nexport async function requestBoot(\n apiUrl: string,\n request: BootRequest,\n fetchImpl: typeof fetch = fetch,\n): Promise<BootAnswer | null> {\n let response: Response;\n try {\n response = await fetchImpl(bootEndpoint(apiUrl), {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(request),\n });\n } catch {\n return null; // network error / Fixback unreachable\n }\n\n if (!response.ok) return null; // 401 unknown key, or any other refusal\n\n let body: unknown;\n try {\n body = await response.json();\n } catch {\n return null;\n }\n\n return isBootAnswer(body) ? body : null;\n}\n","/**\n * Small DOM helpers shared across the SDK's report surfaces.\n *\n * The SDK mounts its own host elements into the host page — the launcher and the\n * overlay panel — each tagged with a `data-fixback-*` marker attribute (the\n * element-picker's highlight lives inside the overlay's shadow, so it is covered\n * by the shadow-boundary walk below). These helpers let the picker skip the SDK's\n * own UI and let the screenshot exclude it, so a report never captures or targets\n * Fixback's chrome.\n */\n\n/** Marker attributes on the SDK's own host elements in the light DOM. */\nexport const FIXBACK_HOST_MARKERS = [\n \"data-fixback-root\",\n \"data-fixback-overlay\",\n] as const;\n\nconst HOST_SELECTOR = FIXBACK_HOST_MARKERS.map((m) => `[${m}]`).join(\",\");\n\n/**\n * Is this node part of the SDK's own UI? Walks up parents and out through any\n * Shadow DOM boundary (via `getRootNode().host`), so a click inside the overlay's\n * shadow is recognised as Fixback's own — not a host-page element to pick.\n */\nexport function isFixbackNode(node: EventTarget | null | undefined): boolean {\n let current: Node | null = node instanceof Node ? node : null;\n while (current) {\n if (current instanceof Element) {\n for (const marker of FIXBACK_HOST_MARKERS) {\n if (current.hasAttribute(marker)) return true;\n }\n }\n const root = current.getRootNode();\n if (root instanceof ShadowRoot && root !== current) {\n current = root.host;\n continue;\n }\n current = current.parentNode;\n }\n return false;\n}\n\n/**\n * Remove the SDK's own host elements from a (cloned) subtree, so a captured\n * screenshot never contains the launcher, overlay, or picker highlight.\n */\nexport function stripFixbackNodes(root: ParentNode): void {\n for (const el of Array.from(root.querySelectorAll(HOST_SELECTOR))) {\n el.remove();\n }\n}\n","import { isFixbackNode } from \"./dom\";\nimport type { ElementRect, SelectedElement } from \"./report\";\n\n/**\n * Escape a string for use in a CSS selector, preferring the platform's\n * `CSS.escape` and falling back to a conservative backslash escape where it is\n * unavailable (older engines, some test environments).\n */\nfunction cssEscape(value: string): string {\n const api = (globalThis as { CSS?: { escape?: (v: string) => string } }).CSS;\n if (api?.escape) return api.escape(value);\n return value.replace(/[^\\w-]/g, (ch) => `\\\\${ch}`);\n}\n\n/** The 1-based position of `el` among its same-tag siblings (`:nth-of-type`). */\nfunction nthOfType(el: Element): number {\n let n = 1;\n let sib = el.previousElementSibling;\n while (sib) {\n if (sib.tagName === el.tagName) n += 1;\n sib = sib.previousElementSibling;\n }\n return n;\n}\n\n/**\n * A stable CSS selector for `el`: its own id when it has one, otherwise a path of\n * tag (with `:nth-of-type` to disambiguate same-tag siblings) climbing until it\n * reaches an ancestor with an id — the shortest anchor that still resolves.\n */\nexport function cssSelectorFor(el: Element): string {\n if (el.id) return `#${cssEscape(el.id)}`;\n\n const parts: string[] = [];\n let node: Element | null = el;\n while (node && node.tagName.toLowerCase() !== \"html\") {\n if (node.id) {\n parts.unshift(`#${cssEscape(node.id)}`);\n break;\n }\n const tag = node.tagName.toLowerCase();\n const parent: Element | null = node.parentElement;\n if (parent) {\n const sameTag = Array.from(parent.children).filter(\n (c) => c.tagName === node!.tagName,\n );\n parts.unshift(\n sameTag.length > 1 ? `${tag}:nth-of-type(${nthOfType(node)})` : tag,\n );\n } else {\n parts.unshift(tag);\n }\n node = parent;\n }\n return parts.join(\" > \");\n}\n\n/** One node's readable label: `tag`, `tag#id`, or `tag.firstClass`. */\nfunction describeNode(el: Element): string {\n const tag = el.tagName.toLowerCase();\n if (el.id) return `${tag}#${el.id}`;\n const firstClass = el.classList[0];\n return firstClass ? `${tag}.${firstClass}` : tag;\n}\n\n/** A readable ancestry path from the document root down to `el`. */\nexport function domPathFor(el: Element): string {\n const chain: string[] = [];\n let node: Element | null = el;\n while (node) {\n chain.unshift(describeNode(node));\n node = node.parentElement;\n }\n return chain.join(\" > \");\n}\n\nfunction roundedRect(el: Element): ElementRect {\n const r = el.getBoundingClientRect();\n return {\n x: Math.round(r.x),\n y: Math.round(r.y),\n width: Math.round(r.width),\n height: Math.round(r.height),\n };\n}\n\n/**\n * Describe a picked element as the Annotation ingest stores: a stable selector, a\n * readable DOM path, the tag, and the viewport bounding rect (spec MVP §B).\n */\nexport function describeElement(el: Element): SelectedElement {\n return {\n selector: cssSelectorFor(el),\n domPath: domPathFor(el),\n tag: el.tagName.toLowerCase(),\n rect: roundedRect(el),\n };\n}\n\n/** Options for {@link startElementPicker}. */\nexport interface ElementPickerOptions {\n /** Document to attach to. Defaults to the global `document`. */\n readonly doc?: Document;\n /** Called with the element being hovered (or `null` over nothing pickable). */\n readonly onHover?: (element: Element | null) => void;\n /** Called with the element the Reporter clicked to pin. */\n readonly onPick: (element: Element) => void;\n /** Called when the Reporter presses Escape to abandon the pick. */\n readonly onCancel?: () => void;\n /** Return true to skip a node (defaults to skipping the SDK's own UI). */\n readonly ignore?: (element: Element) => boolean;\n}\n\n/** A running element-picker; call {@link ElementPicker.stop} to detach it. */\nexport interface ElementPicker {\n stop(): void;\n}\n\n/**\n * Enter element-select mode: as the Reporter moves over the host page the element\n * under the pointer is reported through `onHover` (the overlay draws a highlight),\n * a click pins it through `onPick`, and Escape abandons through `onCancel`. The\n * click that pins is swallowed (prevent-default + stop-propagation) so it never\n * also activates the host page. The SDK's own UI is skipped by default, so the\n * Reporter can never pick the overlay itself.\n *\n * Listeners are attached in the capture phase so the pick is intercepted before\n * any host-page handler sees it; `stop()` (called automatically on pick/cancel)\n * detaches them all.\n */\nexport function startElementPicker(options: ElementPickerOptions): ElementPicker {\n const doc = options.doc ?? document;\n const ignore = options.ignore ?? isFixbackNode;\n let current: Element | null = null;\n let stopped = false;\n\n function resolveTarget(event: Event): Element | null {\n const path =\n typeof event.composedPath === \"function\" ? event.composedPath() : [];\n const deepest = path[0] ?? event.target;\n if (deepest instanceof Element) return ignore(deepest) ? null : deepest;\n return null;\n }\n\n function onOver(event: Event): void {\n if (stopped) return;\n current = resolveTarget(event);\n options.onHover?.(current);\n }\n\n function onClick(event: MouseEvent): void {\n if (stopped) return;\n const el = resolveTarget(event) ?? current;\n if (!el) return; // nothing pickable under the pointer — let the click pass\n event.preventDefault();\n event.stopPropagation();\n stop();\n options.onPick(el);\n }\n\n function onKey(event: KeyboardEvent): void {\n if (stopped) return;\n if (event.key === \"Escape\") {\n event.preventDefault();\n stop();\n options.onCancel?.();\n }\n }\n\n function stop(): void {\n if (stopped) return;\n stopped = true;\n doc.removeEventListener(\"mouseover\", onOver, true);\n doc.removeEventListener(\"click\", onClick, true);\n doc.removeEventListener(\"keydown\", onKey, true);\n current = null;\n }\n\n doc.addEventListener(\"mouseover\", onOver, true);\n doc.addEventListener(\"click\", onClick, true);\n doc.addEventListener(\"keydown\", onKey, true);\n\n return { stop };\n}\n","/**\n * The single client-side **scrub choke point** every report passes through\n * before transport (spec 0003 §C; research `sentry-error-capture-findings.md`\n * §7.4) — the SDK's `beforeSend` equivalent.\n *\n * Masking is the SDK's job, done in the browser before anything leaves the page.\n * The screenshot is masked at capture, and breadcrumbs never record a value or a\n * body at the source; `runBeforeSend` is the **last** gate over the assembled\n * report. Its default scrubbers are **on**: they strip credentials, query\n * strings, and bearer tokens from URLs, and redact obvious PII (emails, long\n * digit runs, bearer tokens) from crumb and error text. The result is then handed\n * to an optional per-project hook that can mutate it further or drop the whole\n * report by returning `null`.\n *\n * The hook is **synchronous and network-free** by contract, and both manual\n * (overlay) and automatic (error-capture) reports run through the very same\n * choke point. A project relaxes the defaults with `scrub: false`, or reshapes\n * the draft in its own hook — never a silent raw send.\n */\n\nimport type { Breadcrumb, BreadcrumbData } from \"./breadcrumbs\";\nimport type { ReportContent } from \"./report\";\n\n/** The per-project client scrub hook. Return `null` to drop the whole report. */\nexport type BeforeSend = (draft: ReportContent) => ReportContent | null;\n\n/** Options for {@link runBeforeSend}. */\nexport interface BeforeSendOptions {\n /** The per-project hook, run **after** the default scrubbers. */\n readonly hook?: BeforeSend | null;\n /** Run the built-in default scrubbers first. Defaults to `true`. */\n readonly scrub?: boolean;\n}\n\n/** A digit run at least this long is treated as sensitive (phone, card, id). */\nconst MIN_DIGIT_RUN = 7;\nconst EMAIL_RE = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}/gi;\nconst DIGIT_RUN_RE = new RegExp(`\\\\d{${MIN_DIGIT_RUN},}`, \"g\");\nconst BEARER_RE = /\\b(bearer|token)\\s+[\\w.\\-~+/]+=*/gi;\n/** An http(s) URL embedded in free text (e.g. a console log line). */\nconst URL_IN_TEXT_RE = /\\bhttps?:\\/\\/[^\\s\"'<>]+/gi;\n\n/**\n * Redact obvious PII from free text: email addresses, `Bearer <token>` /\n * `token <value>` pairs, and long digit runs. Conservative by design — it keeps\n * the shape of the message readable while removing the sensitive spans.\n */\nexport function redactPii(text: string): string {\n if (typeof text !== \"string\" || text.length === 0) return text;\n return text\n .replace(EMAIL_RE, \"[redacted-email]\")\n .replace(BEARER_RE, (_match, keyword: string) => `${keyword} [redacted]`)\n .replace(DIGIT_RUN_RE, \"[redacted-number]\");\n}\n\n/**\n * Strip the sensitive parts of a URL: userinfo credentials\n * (`scheme://user:pass@host`), the entire query string, and a token-bearing\n * fragment (one that carries `key=value`). Plain hash routes (`#/checkout`) are\n * kept. Works on absolute and relative URLs alike, with no dependency and no\n * throw.\n */\nexport function scrubUrl(url: string): string {\n if (typeof url !== \"string\" || url.length === 0) return url;\n let out = url;\n // Drop userinfo credentials: scheme://user:pass@host → scheme://host\n out = out.replace(/(^[a-z][a-z0-9+.-]*:\\/\\/)[^/@?#]*@/i, \"$1\");\n // Drop the query string entirely (everything from '?' up to a '#').\n out = out.replace(/\\?[^#]*/, \"\");\n // Drop a token-bearing fragment (it carries '='); keep plain hash routes.\n out = out.replace(/#.*$/, (fragment) =>\n fragment.includes(\"=\") ? \"\" : fragment,\n );\n return out;\n}\n\ntype MutableBreadcrumbData = { -readonly [K in keyof BreadcrumbData]: BreadcrumbData[K] };\ntype MutableBreadcrumb = { -readonly [K in keyof Breadcrumb]: Breadcrumb[K] };\ntype MutableContent = { -readonly [K in keyof ReportContent]: ReportContent[K] };\n\nfunction scrubCrumbData(data: BreadcrumbData): BreadcrumbData {\n const next: MutableBreadcrumbData = { ...data };\n if (typeof next.url === \"string\") next.url = scrubUrl(next.url);\n if (typeof next.from === \"string\") next.from = scrubUrl(next.from);\n if (typeof next.to === \"string\") next.to = scrubUrl(next.to);\n return next;\n}\n\n/** Scrub a crumb's free-text message: strip URL query strings, then redact PII. */\nfunction scrubMessage(message: string): string {\n return redactPii(message.replace(URL_IN_TEXT_RE, (url) => scrubUrl(url)));\n}\n\nfunction scrubCrumb(crumb: Breadcrumb): Breadcrumb {\n const next: MutableBreadcrumb = { ...crumb };\n if (typeof next.message === \"string\") next.message = scrubMessage(next.message);\n if (next.data) next.data = scrubCrumbData(next.data);\n return next;\n}\n\n/**\n * Apply the built-in default scrubbers to a report draft: strip the page URL,\n * and scrub every crumb's URLs and redact PII from its text. The Reporter's own\n * `comment` is intentionally left untouched — it is authored on purpose, not\n * scraped. The screenshot and input values are masked elsewhere (at capture and\n * at crumb creation); this is the final URL/PII sweep.\n */\nexport function applyDefaultScrub(draft: ReportContent): ReportContent {\n const next: MutableContent = { ...draft };\n if (typeof next.url === \"string\") next.url = scrubUrl(next.url);\n if (next.trace && next.trace.length > 0) {\n next.trace = next.trace.map(scrubCrumb);\n }\n return next;\n}\n\n/**\n * Run the report draft through the client scrub choke point: the default\n * scrubbers first (unless `scrub` is `false`), then the optional per-project\n * hook. Returns the scrubbed (and possibly hook-mutated) draft, or `null` when\n * the hook drops the report. A hook that throws is treated as a no-op — the\n * already-scrubbed draft is kept, so a buggy hook never breaks the report path\n * nor leaks unscrubbed data.\n */\nexport function runBeforeSend(\n draft: ReportContent,\n options: BeforeSendOptions = {},\n): ReportContent | null {\n const current =\n options.scrub === false ? draft : applyDefaultScrub(draft);\n const hook = options.hook;\n if (!hook) return current;\n try {\n const result = hook(current);\n return result ?? null;\n } catch {\n return current;\n }\n}\n","/**\n * The thin trace **breadcrumb ring buffer** and its capture instrumentation\n * (spec 0003 §C; research `sentry-error-capture-findings.md` §7.5).\n *\n * A fixed-size FIFO buffer of the most recent activity — console, navigation,\n * network metadata, masked user actions, and the failing error — that rides on a\n * report as Evidence. It is trimmed exactly like Sentry (`crumbs.slice(-N)`),\n * defaults to a deliberately thin `N ≈ 30` because it ships on **every** payload,\n * and takes an optional age cap. A `beforeBreadcrumb(crumb) => crumb | null`\n * filter lets a Project mute or edit crumbs before they enter the buffer.\n *\n * Everything private is kept out **at the source**: `ui.input` records that an\n * input changed, never its value; `fetch`/`xhr` crumbs carry method + URL +\n * status only, **never** bodies; URLs are scrubbed as the crumb is built. The\n * `beforeSend` choke point (`scrub.ts`) is the final gate over the whole report.\n *\n * The SDK stays dependency-free and must never throw into the host page, so every\n * instrumentation hook is wrapped: a capture failure is swallowed and the original\n * behaviour (the real `console`, `fetch`, navigation) always runs.\n */\n\nimport { isFixbackNode } from \"./dom\";\nimport { cssSelectorFor } from \"./element-picker\";\nimport { scrubUrl } from \"./scrub\";\n\n/** Console-style severity a `console` crumb records. */\nexport type BreadcrumbLevel =\n | \"log\"\n | \"info\"\n | \"warn\"\n | \"error\"\n | \"assert\"\n | \"debug\";\n\n/** The kind of activity a crumb records. */\nexport type BreadcrumbCategory =\n | \"console\"\n | \"navigation\"\n | \"fetch\"\n | \"xhr\"\n | \"ui.click\"\n | \"ui.input\"\n | \"error\";\n\n/**\n * A crumb's structured detail. Deliberately narrow: there is **no** field for a\n * request/response body or an input value, so those can never be recorded.\n */\nexport interface BreadcrumbData {\n readonly url?: string;\n readonly method?: string;\n readonly status?: number;\n /** A masked CSS selector for a `ui.*` target — never its text or value. */\n readonly target?: string;\n readonly from?: string;\n readonly to?: string;\n readonly errorType?: string;\n}\n\n/** One entry in the trace buffer. */\nexport interface Breadcrumb {\n readonly category: BreadcrumbCategory;\n readonly message?: string;\n readonly level?: BreadcrumbLevel;\n /** Epoch milliseconds when the crumb was recorded. */\n readonly timestamp: number;\n readonly data?: BreadcrumbData;\n}\n\n/** Default ring size — thinner than Sentry's 100 (it rides on every payload). */\nexport const DEFAULT_MAX_BREADCRUMBS = 30;\n\n/** Console levels captured by default (warn/error/assert first, per research). */\nexport const DEFAULT_CONSOLE_LEVELS: readonly BreadcrumbLevel[] = [\n \"warn\",\n \"error\",\n \"assert\",\n];\n\n/** Filters or edits each crumb before it enters the buffer; `null` drops it. */\nexport type BeforeBreadcrumb = (crumb: Breadcrumb) => Breadcrumb | null;\n\n/** Configuration for {@link createBreadcrumbBuffer}. All values are optional. */\nexport interface BreadcrumbBufferConfig {\n /** Keep at most this many crumbs (oldest drop). Defaults to {@link DEFAULT_MAX_BREADCRUMBS}. */\n readonly maxBreadcrumbs?: number;\n /** Optional age cap in ms: crumbs older than this are dropped. Off by default. */\n readonly maxAgeMs?: number;\n /** A per-crumb filter (mute a category, edit, or drop by returning `null`). */\n readonly beforeBreadcrumb?: BeforeBreadcrumb | null;\n /** Clock source, injectable for tests. Defaults to `Date.now`. */\n readonly now?: () => number;\n}\n\n/** A live trace buffer. */\nexport interface BreadcrumbBuffer {\n /** Record a crumb (subject to `beforeBreadcrumb`, size, and age trimming). */\n add(crumb: Breadcrumb): void;\n /** The current crumbs, oldest first — a fresh array, safe to keep. */\n snapshot(): Breadcrumb[];\n /** Drop every crumb. */\n clear(): void;\n}\n\nfunction normalizeMax(value: number | undefined): number {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value < 1) {\n return DEFAULT_MAX_BREADCRUMBS;\n }\n return Math.floor(value);\n}\n\n/**\n * Create a FIFO ring buffer. On `add`, the crumb passes through\n * `beforeBreadcrumb`, is appended, then the buffer is trimmed to the newest\n * `maxBreadcrumbs` (`slice(-N)`) and — when an age cap is set — pruned of stale\n * crumbs. `snapshot` prunes by age again at read time so an idle tab never ships\n * stale context.\n */\nexport function createBreadcrumbBuffer(\n config: BreadcrumbBufferConfig = {},\n): BreadcrumbBuffer {\n const max = normalizeMax(config.maxBreadcrumbs);\n const maxAgeMs = config.maxAgeMs;\n const beforeBreadcrumb = config.beforeBreadcrumb;\n const now = config.now ?? Date.now;\n let crumbs: Breadcrumb[] = [];\n\n function pruneByAge(): void {\n if (typeof maxAgeMs === \"number\" && maxAgeMs > 0 && crumbs.length > 0) {\n const cutoff = now() - maxAgeMs;\n crumbs = crumbs.filter((crumb) => crumb.timestamp >= cutoff);\n }\n }\n\n return {\n add(crumb: Breadcrumb): void {\n let entry: Breadcrumb | null = crumb;\n if (beforeBreadcrumb) {\n try {\n entry = beforeBreadcrumb(crumb);\n } catch {\n // A throwing filter must never break capture; keep the (already\n // masked) crumb rather than silently erasing the trace.\n entry = crumb;\n }\n }\n if (!entry) return;\n crumbs.push(entry);\n pruneByAge();\n if (crumbs.length > max) crumbs = crumbs.slice(-max);\n },\n snapshot(): Breadcrumb[] {\n pruneByAge();\n return crumbs.slice();\n },\n clear(): void {\n crumbs = [];\n },\n };\n}\n\n// --- Pure crumb builders -----------------------------------------------------\n\n/** Longest crumb message kept; a huge log line is truncated, never dropped. */\nconst MAX_MESSAGE_LENGTH = 300;\n\nfunction stringifyArg(arg: unknown): string {\n if (typeof arg === \"string\") return arg;\n if (arg instanceof Error) return `${arg.name}: ${arg.message}`;\n if (arg === null || arg === undefined) return String(arg);\n if (typeof arg === \"number\" || typeof arg === \"boolean\") return String(arg);\n try {\n return JSON.stringify(arg) ?? String(arg);\n } catch {\n return \"[object]\";\n }\n}\n\nfunction joinArgs(args: readonly unknown[]): string {\n const text = args.map(stringifyArg).join(\" \");\n return text.length > MAX_MESSAGE_LENGTH\n ? `${text.slice(0, MAX_MESSAGE_LENGTH)}…`\n : text;\n}\n\n/** A `console` crumb from a captured call's level and arguments. */\nexport function consoleCrumb(\n level: BreadcrumbLevel,\n args: readonly unknown[],\n timestamp: number,\n): Breadcrumb {\n return { category: \"console\", level, message: joinArgs(args), timestamp };\n}\n\n/** A `navigation` crumb; both URLs are scrubbed as the crumb is built. */\nexport function navigationCrumb(\n from: string,\n to: string,\n timestamp: number,\n): Breadcrumb {\n const fromUrl = scrubUrl(from);\n const toUrl = scrubUrl(to);\n return {\n category: \"navigation\",\n message: `${fromUrl} → ${toUrl}`,\n timestamp,\n data: { from: fromUrl, to: toUrl },\n };\n}\n\n/**\n * A network crumb (`fetch` or `xhr`): method + scrubbed URL + status only. The\n * shape has no field for a request/response body, so a body can never be\n * recorded.\n */\nfunction networkCrumb(\n category: \"fetch\" | \"xhr\",\n method: string,\n url: string,\n status: number | undefined,\n timestamp: number,\n): Breadcrumb {\n const scrubbed = scrubUrl(url);\n const data: BreadcrumbData = status\n ? { method, url: scrubbed, status }\n : { method, url: scrubbed };\n return {\n category,\n message: `${method} ${scrubbed}${status ? ` [${status}]` : \"\"}`,\n timestamp,\n data,\n };\n}\n\n/** A `fetch` crumb: method + scrubbed URL + status only. No body, ever. */\nexport function fetchCrumb(\n method: string,\n url: string,\n status: number | undefined,\n timestamp: number,\n): Breadcrumb {\n return networkCrumb(\"fetch\", method, url, status, timestamp);\n}\n\n/** An `xhr` crumb: method + scrubbed URL + status only. No body, ever. */\nexport function xhrCrumb(\n method: string,\n url: string,\n status: number | undefined,\n timestamp: number,\n): Breadcrumb {\n return networkCrumb(\"xhr\", method, url, status, timestamp);\n}\n\nfunction safeSelector(target: Element): string {\n try {\n return cssSelectorFor(target);\n } catch {\n return target.tagName ? target.tagName.toLowerCase() : \"unknown\";\n }\n}\n\n/** A `ui.click` crumb: a masked target selector only — no text or value. */\nexport function clickCrumb(target: Element, timestamp: number): Breadcrumb {\n const selector = safeSelector(target);\n return {\n category: \"ui.click\",\n message: `click ${selector}`,\n timestamp,\n data: { target: selector },\n };\n}\n\n/**\n * A `ui.input` crumb: records **that** an input changed and which field, never\n * the value typed into it. The `target` element's `.value` is never read.\n */\nexport function inputCrumb(target: Element, timestamp: number): Breadcrumb {\n const selector = safeSelector(target);\n return {\n category: \"ui.input\",\n message: `input ${selector}`,\n timestamp,\n data: { target: selector },\n };\n}\n\nfunction describeError(error: unknown): { name: string; message: string } {\n if (error instanceof Error) {\n return { name: error.name || \"Error\", message: error.message };\n }\n if (typeof error === \"string\") return { name: \"Error\", message: error };\n return { name: \"Error\", message: stringifyArg(error) };\n}\n\n/** An `error` crumb for the failing exception or rejection that ends the trace. */\nexport function errorCrumb(error: unknown, timestamp: number): Breadcrumb {\n const { name, message } = describeError(error);\n return {\n category: \"error\",\n level: \"error\",\n message: message ? `${name}: ${message}` : name,\n timestamp,\n data: { errorType: name },\n };\n}\n\n// --- Instrumentation ---------------------------------------------------------\n\n/** Detaches an installed instrumentation, restoring the original behaviour. */\nexport type Teardown = () => void;\n\ntype AnyFn = (...args: unknown[]) => unknown;\ntype ConsoleLike = Partial<Record<BreadcrumbLevel, AnyFn>>;\ntype FetchFn = (\n input: string | URL | Request,\n init?: RequestInit,\n) => Promise<Response>;\n\ninterface HistoryLike {\n pushState(data: unknown, unused: string, url?: string | URL | null): void;\n replaceState(data: unknown, unused: string, url?: string | URL | null): void;\n}\n\ninterface XhrInstance {\n status: number;\n open(method: string, url: string | URL, ...rest: unknown[]): void;\n send(body?: unknown): void;\n addEventListener(type: string, listener: () => void): void;\n removeEventListener(type: string, listener: () => void): void;\n}\ninterface XhrConstructor {\n new (): XhrInstance;\n prototype: XhrInstance;\n}\n\n/** The structural window surface the instrumentation reaches into. */\nexport interface InstrumentWindow {\n fetch?: FetchFn;\n history?: HistoryLike;\n location?: { href: string };\n XMLHttpRequest?: XhrConstructor;\n addEventListener(\n type: string,\n listener: (event: Event) => void,\n options?: boolean | AddEventListenerOptions,\n ): void;\n removeEventListener(\n type: string,\n listener: (event: Event) => void,\n options?: boolean | EventListenerOptions,\n ): void;\n}\n\n/** Options for {@link instrumentBreadcrumbs} and the individual installers. */\nexport interface InstrumentOptions {\n readonly win?: InstrumentWindow;\n readonly doc?: Document;\n readonly consoleObj?: ConsoleLike;\n readonly consoleLevels?: readonly BreadcrumbLevel[];\n /** Skip URLs (e.g. the SDK's own ingest calls) so they never become crumbs. */\n readonly ignoreUrl?: (url: string) => boolean;\n readonly now?: () => number;\n}\n\nfunction noop(): void {\n /* nothing installed */\n}\n\n/** Wrap `console` methods so calls at the captured levels become crumbs. */\nexport function instrumentConsole(\n buffer: BreadcrumbBuffer,\n consoleObj: ConsoleLike,\n levels: readonly BreadcrumbLevel[],\n now: () => number,\n): Teardown {\n const restores: Teardown[] = [];\n for (const level of levels) {\n const original = consoleObj[level];\n if (typeof original !== \"function\") continue;\n const wrapper = (...args: unknown[]): unknown => {\n try {\n if (level === \"assert\") {\n // console.assert records only when the asserted condition is falsy.\n if (!args[0]) buffer.add(consoleCrumb(\"assert\", args.slice(1), now()));\n } else {\n buffer.add(consoleCrumb(level, args, now()));\n }\n } catch {\n // Capture must never throw into the host page.\n }\n return original.apply(consoleObj, args);\n };\n consoleObj[level] = wrapper;\n restores.push(() => {\n consoleObj[level] = original;\n });\n }\n return () => {\n for (const restore of restores) restore();\n };\n}\n\nfunction requestUrl(input: string | URL | Request): string {\n if (typeof input === \"string\") return input;\n if (input instanceof URL) return input.toString();\n return input.url;\n}\n\nfunction requestMethod(\n input: string | URL | Request,\n init: RequestInit | undefined,\n): string {\n const method =\n init?.method ??\n (typeof input === \"object\" && \"method\" in input ? input.method : undefined) ??\n \"GET\";\n return method.toUpperCase();\n}\n\n/**\n * Wrap `fetch` to record a crumb on settlement. The **original** outcome is\n * returned untouched — the response is passed through without its body being\n * read, and a rejection is re-thrown so the caller's `unhandledrejection`\n * semantics are preserved.\n */\nexport function instrumentFetch(\n buffer: BreadcrumbBuffer,\n win: InstrumentWindow,\n ignoreUrl: (url: string) => boolean,\n now: () => number,\n): Teardown {\n const original = win.fetch;\n if (typeof original !== \"function\") return noop;\n const wrapper: FetchFn = (\n input: string | URL | Request,\n init?: RequestInit,\n ): Promise<Response> => {\n const url = requestUrl(input);\n const method = requestMethod(input, init);\n // Native `fetch` must run against the window, so bind the receiver rather\n // than forward whatever `this` a free call carries.\n const promise = original.call(win, input, init);\n if (ignoreUrl(url)) return promise;\n return promise.then(\n (response) => {\n try {\n buffer.add(fetchCrumb(method, url, response.status, now()));\n } catch {\n /* never throw into the host page */\n }\n return response;\n },\n (error: unknown) => {\n try {\n buffer.add(fetchCrumb(method, url, undefined, now()));\n } catch {\n /* never throw into the host page */\n }\n throw error;\n },\n );\n };\n win.fetch = wrapper;\n return () => {\n win.fetch = original;\n };\n}\n\ninterface XhrMeta {\n __fixbackMeta?: { method: string; url: string };\n}\n\n/**\n * Patch `XMLHttpRequest` to record a crumb when a request settles. Only the\n * method, URL, and final status are read; the `send` body argument is ignored,\n * so a body can never reach the buffer.\n */\nexport function instrumentXhr(\n buffer: BreadcrumbBuffer,\n win: InstrumentWindow,\n ignoreUrl: (url: string) => boolean,\n now: () => number,\n): Teardown {\n const ctor = win.XMLHttpRequest;\n if (typeof ctor !== \"function\") return noop;\n const proto = ctor.prototype;\n const originalOpen = proto.open;\n const originalSend = proto.send;\n\n proto.open = function (\n this: XhrInstance & XhrMeta,\n method: string,\n url: string | URL,\n ...rest: unknown[]\n ): void {\n this.__fixbackMeta = { method: String(method).toUpperCase(), url: String(url) };\n return originalOpen.call(this, method, url, ...rest);\n };\n\n proto.send = function (this: XhrInstance & XhrMeta, body?: unknown): void {\n const meta = this.__fixbackMeta;\n if (meta && !ignoreUrl(meta.url)) {\n // An arrow captures the request instance as `this` without aliasing it.\n const onDone = (): void => {\n try {\n buffer.add(xhrCrumb(meta.method, meta.url, this.status || undefined, now()));\n } catch {\n /* never throw into the host page */\n }\n this.removeEventListener(\"loadend\", onDone);\n };\n this.addEventListener(\"loadend\", onDone);\n }\n return originalSend.call(this, body);\n };\n\n return () => {\n proto.open = originalOpen;\n proto.send = originalSend;\n };\n}\n\n/** Record a `navigation` crumb on `pushState`/`replaceState`/pop/hash changes. */\nexport function instrumentNavigation(\n buffer: BreadcrumbBuffer,\n win: InstrumentWindow,\n now: () => number,\n): Teardown {\n const history = win.history;\n const location = win.location;\n if (!history || !location) return noop;\n\n let last = location.href;\n const record = (to: string): void => {\n try {\n buffer.add(navigationCrumb(last, to, now()));\n } catch {\n /* never throw into the host page */\n }\n last = to;\n };\n\n const originalPush = history.pushState;\n const originalReplace = history.replaceState;\n\n history.pushState = function (\n this: HistoryLike,\n data: unknown,\n unused: string,\n url?: string | URL | null,\n ): void {\n const result = originalPush.call(this, data, unused, url);\n record(location.href);\n return result;\n };\n history.replaceState = function (\n this: HistoryLike,\n data: unknown,\n unused: string,\n url?: string | URL | null,\n ): void {\n const result = originalReplace.call(this, data, unused, url);\n record(location.href);\n return result;\n };\n\n const onPopState = (): void => record(location.href);\n const onHashChange = (): void => record(location.href);\n win.addEventListener(\"popstate\", onPopState);\n win.addEventListener(\"hashchange\", onHashChange);\n\n return () => {\n history.pushState = originalPush;\n history.replaceState = originalReplace;\n win.removeEventListener(\"popstate\", onPopState);\n win.removeEventListener(\"hashchange\", onHashChange);\n };\n}\n\nfunction eventTargetElement(event: Event): Element | null {\n const path =\n typeof event.composedPath === \"function\" ? event.composedPath() : [];\n const deepest = path[0] ?? event.target;\n return deepest instanceof Element ? deepest : null;\n}\n\n/** Listen (capture-phase) for clicks and input changes as masked crumbs. */\nexport function instrumentUiEvents(\n buffer: BreadcrumbBuffer,\n doc: Document,\n now: () => number,\n): Teardown {\n const onClick = (event: Event): void => {\n const target = eventTargetElement(event);\n if (!target || isFixbackNode(target)) return;\n try {\n buffer.add(clickCrumb(target, now()));\n } catch {\n /* never throw into the host page */\n }\n };\n const onInput = (event: Event): void => {\n const target = eventTargetElement(event);\n if (!target || isFixbackNode(target)) return;\n try {\n buffer.add(inputCrumb(target, now()));\n } catch {\n /* never throw into the host page */\n }\n };\n doc.addEventListener(\"click\", onClick, true);\n doc.addEventListener(\"input\", onInput, true);\n return () => {\n doc.removeEventListener(\"click\", onClick, true);\n doc.removeEventListener(\"input\", onInput, true);\n };\n}\n\n/**\n * Install every capture hook onto a window/document and return a single teardown\n * that removes them all. Each hook is independent and defensive: a failure in one\n * never blocks the others, and none can throw into the host page.\n */\nexport function instrumentBreadcrumbs(\n buffer: BreadcrumbBuffer,\n options: InstrumentOptions = {},\n): Teardown {\n const win =\n options.win ?? (globalThis as unknown as InstrumentWindow | undefined);\n const doc =\n options.doc ??\n (typeof document !== \"undefined\" ? document : undefined);\n const consoleObj =\n options.consoleObj ??\n (typeof console !== \"undefined\"\n ? (console as unknown as ConsoleLike)\n : undefined);\n const levels = options.consoleLevels ?? DEFAULT_CONSOLE_LEVELS;\n const ignoreUrl = options.ignoreUrl ?? (() => false);\n const now = options.now ?? Date.now;\n\n const teardowns: Teardown[] = [];\n if (consoleObj) {\n teardowns.push(instrumentConsole(buffer, consoleObj, levels, now));\n }\n if (win) {\n teardowns.push(instrumentFetch(buffer, win, ignoreUrl, now));\n teardowns.push(instrumentXhr(buffer, win, ignoreUrl, now));\n teardowns.push(instrumentNavigation(buffer, win, now));\n }\n if (doc) {\n teardowns.push(instrumentUiEvents(buffer, doc, now));\n }\n\n return () => {\n for (const teardown of teardowns) {\n try {\n teardown();\n } catch {\n /* teardown is best-effort */\n }\n }\n };\n}\n","/**\n * The Annotation & vector-marks model (spec 0003 §B/§D).\n *\n * An Annotation is everything a Reporter marked on the page, as three\n * **composable, optional** layers over one full masked screenshot: the picked\n * `element` (from the element-picker), a drag-captured `region`, and vector\n * `marks` — arrow / box / pen / text. Marks live in the **screenshot's coordinate\n * space** (the full viewport the screenshot is rasterised at), never baked into\n * the PNG: the dashboard composites them at view time (#91).\n *\n * This module is the marks' single home — the pure types and geometry used by the\n * region-capture and draw controllers and by the overlay's Send assembly. Like the\n * rest of the SDK's wire types it is vendored (no `@fixback/shared` import); keep\n * `Annotation` in lock-step with the server's ingest contract (spec §D/§H).\n */\n\nimport type { SelectedElement } from \"./report\";\n\n/** A point in screenshot (viewport) coordinate space. */\nexport interface Point {\n readonly x: number;\n readonly y: number;\n}\n\n/** A rectangle in screenshot coordinates — the same shape as an element's rect. */\nexport interface Rect {\n readonly x: number;\n readonly y: number;\n readonly width: number;\n readonly height: number;\n}\n\n/** The draw tools the overlay offers, in toolbar order (spec §B, prototype). */\nexport type DrawTool = \"arrow\" | \"box\" | \"pen\" | \"text\";\n\n/** Fields shared by every vector mark. */\ninterface MarkBase {\n /** Stroke/fill colour, as a CSS colour string. */\n readonly color: string;\n}\n\n/** A directional arrow from `(x0,y0)` to its tip at `(x1,y1)`. */\nexport interface ArrowMark extends MarkBase {\n readonly type: \"arrow\";\n readonly x0: number;\n readonly y0: number;\n readonly x1: number;\n readonly y1: number;\n}\n\n/** A rectangle spanning the drag from `(x0,y0)` to `(x1,y1)`. */\nexport interface BoxMark extends MarkBase {\n readonly type: \"box\";\n readonly x0: number;\n readonly y0: number;\n readonly x1: number;\n readonly y1: number;\n}\n\n/** A freehand polyline through `points` (in order). */\nexport interface PenMark extends MarkBase {\n readonly type: \"pen\";\n readonly points: ReadonlyArray<Point>;\n}\n\n/** A text label anchored at `(x,y)` (its baseline-left, as SVG text). */\nexport interface TextMark extends MarkBase {\n readonly type: \"text\";\n readonly x: number;\n readonly y: number;\n readonly text: string;\n}\n\n/** A single vector mark, in screenshot coordinates. */\nexport type Mark = ArrowMark | BoxMark | PenMark | TextMark;\n\n/**\n * The structured Annotation carried on a report's content (spec §D): the three\n * optional layers. Every field is optional — a report may carry any subset or\n * none (a bare Kind + comment is a valid Send).\n */\nexport interface Annotation {\n readonly element?: SelectedElement;\n readonly region?: Rect;\n readonly marks?: ReadonlyArray<Mark>;\n}\n\n/**\n * The default mark colour — Signal's danger red, matching the frozen Reporter\n * prototype's draw layer (`docs/design/Fixback Reporter.dc.html`).\n */\nexport const MARK_COLOR = \"#e5484d\";\n\n/**\n * Normalise a drag from a start to an end point into a positive-extent rect\n * (top-left origin, non-negative width/height), rounded to whole pixels — the\n * screenshot is a pixel raster, so sub-pixel extents carry no meaning. Shared by\n * region-capture and the box mark.\n */\nexport function normalizeRect(x0: number, y0: number, x1: number, y1: number): Rect {\n return {\n x: Math.round(Math.min(x0, x1)),\n y: Math.round(Math.min(y0, y1)),\n width: Math.round(Math.abs(x1 - x0)),\n height: Math.round(Math.abs(y1 - y0)),\n };\n}\n\n/**\n * The three points of an arrow's head, given its line `(x0,y0)→(x1,y1)` and a head\n * length: the tip, then the two barbs splayed ±30° behind it. Pure geometry the\n * draw surface renders as a filled triangle at the arrow's tip.\n */\nexport function arrowHeadPoints(\n x0: number,\n y0: number,\n x1: number,\n y1: number,\n size = 14,\n): [Point, Point, Point] {\n const angle = Math.atan2(y1 - y0, x1 - x0);\n return [\n { x: x1, y: y1 },\n {\n x: x1 - size * Math.cos(angle - Math.PI / 6),\n y: y1 - size * Math.sin(angle - Math.PI / 6),\n },\n {\n x: x1 - size * Math.cos(angle + Math.PI / 6),\n y: y1 - size * Math.sin(angle + Math.PI / 6),\n },\n ];\n}\n\n/**\n * Assemble the structured Annotation from whatever a Reporter marked, dropping\n * empty layers: no element, no region, and an empty marks list are omitted, so the\n * result is `undefined` when nothing was marked. Marks are copied into a plain,\n * independent array so the annotation is a serialisable snapshot decoupled from the\n * live draw state.\n */\nexport function assembleAnnotation(parts: {\n readonly element?: SelectedElement | null;\n readonly region?: Rect | null;\n readonly marks?: ReadonlyArray<Mark> | null;\n}): Annotation | undefined {\n const annotation: {\n element?: SelectedElement;\n region?: Rect;\n marks?: ReadonlyArray<Mark>;\n } = {};\n if (parts.element) annotation.element = parts.element;\n if (parts.region) annotation.region = parts.region;\n if (parts.marks && parts.marks.length > 0) annotation.marks = parts.marks.slice();\n return Object.keys(annotation).length > 0 ? annotation : undefined;\n}\n","/**\n * The ingest **feedback** content wire-contract, vendored, plus the helpers that\n * assemble it from what a Reporter composed in the overlay.\n *\n * Like `boot.ts`, the SDK does not import `@fixback/shared`; the slice of the\n * submission shape it needs is copied here. Keep it in lock-step with the server:\n * the JSON `payload` part accepted by `POST /api/ingest/feedback`\n * (`apps/api/src/ingest/ingest.controller.ts` — `feedbackContentBody`) and the\n * `SubmissionContent` it parses (`apps/api/src/ingest/reporter-identity.ts`).\n */\n\nimport { assembleAnnotation, type Annotation, type Mark, type Rect } from \"./annotation\";\nimport type { Breadcrumb } from \"./breadcrumbs\";\n\n/** The Kind a Reporter tags a report with. Mirrors the server's `ISSUE_KINDS`. */\nexport type IssueKind = \"bug\" | \"improvement\" | \"idea\";\n\n/**\n * Where a Feedback came from — a human in the overlay (`reporter`, the default) or\n * the SDK's automatic error capture (`auto`). Mirrors the server's `FEEDBACK_SOURCES`;\n * the server derives trust independently and ignores anything else the client claims.\n */\nexport type FeedbackSource = \"reporter\" | \"auto\";\n\n/** The picked element's viewport rectangle, as the server's annotation `rect`. */\nexport interface ElementRect {\n readonly x: number;\n readonly y: number;\n readonly width: number;\n readonly height: number;\n}\n\n/**\n * The picked element — the `element` layer of an {@link Annotation}: a stable CSS\n * selector, a readable DOM path, the tag, and the bounding rect. Exactly the\n * server's annotation `element` object shape.\n */\nexport interface SelectedElement {\n readonly selector: string;\n readonly domPath: string;\n readonly tag: string;\n readonly rect: ElementRect;\n}\n\n/** The capture environment recorded alongside a report. */\nexport interface CaptureEnvironment {\n readonly viewportWidth?: number;\n readonly viewportHeight?: number;\n readonly browser?: string;\n readonly sdkVersion?: string;\n}\n\n/**\n * The JSON content of a feedback submission — the object serialised into the\n * multipart `payload` part next to the `key` and identity evidence. Every field\n * is optional: none of it feeds the server's trust decision, so a submission may\n * carry any subset. `annotation` is the structured `{ element?, region?, marks? }`\n * (spec §D); the screenshot is a separate binary part, never part of this JSON.\n */\nexport interface ReportContent {\n readonly comment?: string;\n readonly kind?: IssueKind;\n readonly url?: string;\n readonly environment?: CaptureEnvironment;\n readonly annotation?: Annotation;\n /** The masked breadcrumb trace buffer that rode on this report (spec §C). */\n readonly trace?: readonly Breadcrumb[];\n /**\n * Provenance (spec §D/§E). Omitted for a manual report — the transport stamps the\n * `reporter` default on the wire; set to `auto` by the SDK's error capture.\n */\n readonly source?: FeedbackSource;\n /** For `source: auto` only — the SDK's per-session error fingerprint (spec §E). */\n readonly errorSignature?: string;\n /** For `source: auto` only — the running occurrence count within the session (spec §E). */\n readonly occurrences?: number;\n}\n\n/**\n * What the overlay hands to {@link assembleContent} when the Reporter sends. The\n * three marking layers arrive flat (`element` / `region` / `marks`); `assembleContent`\n * folds whatever is present into the structured {@link Annotation}.\n */\nexport interface ReportDraft {\n readonly kind?: IssueKind;\n readonly comment?: string;\n readonly element?: SelectedElement;\n readonly region?: Rect;\n readonly marks?: ReadonlyArray<Mark>;\n readonly url?: string;\n readonly environment?: CaptureEnvironment;\n readonly trace?: readonly Breadcrumb[];\n}\n\n/**\n * Read the capture environment off a window: the viewport size, the browser's\n * user-agent, and the SDK version. A dimension is recorded only when it is a\n * positive number (a headless/zero viewport records nothing rather than an\n * invalid `0`, which the server would reject).\n */\nexport function collectEnvironment(\n win: Window,\n sdkVersion: string,\n): CaptureEnvironment {\n const env: {\n viewportWidth?: number;\n viewportHeight?: number;\n browser?: string;\n sdkVersion?: string;\n } = {};\n\n const width = win.innerWidth;\n if (typeof width === \"number\" && width > 0) env.viewportWidth = Math.round(width);\n const height = win.innerHeight;\n if (typeof height === \"number\" && height > 0) {\n env.viewportHeight = Math.round(height);\n }\n\n const ua = win.navigator?.userAgent;\n if (typeof ua === \"string\" && ua.length > 0) env.browser = ua;\n\n if (sdkVersion.length > 0) env.sdkVersion = sdkVersion;\n\n return env;\n}\n\n/**\n * Assemble the ingest content from what the Reporter composed. Empty pieces are\n * dropped rather than sent as blanks: a whitespace-only comment, an absent\n * element, or an empty environment are simply omitted, so the payload carries\n * only what was actually provided (mirroring the server's all-optional content).\n */\nexport function assembleContent(draft: ReportDraft): ReportContent {\n const content: {\n comment?: string;\n kind?: IssueKind;\n url?: string;\n environment?: CaptureEnvironment;\n annotation?: Annotation;\n trace?: readonly Breadcrumb[];\n } = {};\n\n if (draft.kind) content.kind = draft.kind;\n\n const comment = draft.comment?.trim();\n if (comment) content.comment = comment;\n\n if (draft.url) content.url = draft.url;\n\n if (draft.environment && Object.keys(draft.environment).length > 0) {\n content.environment = draft.environment;\n }\n\n const annotation = assembleAnnotation({\n element: draft.element,\n region: draft.region,\n marks: draft.marks,\n });\n if (annotation) content.annotation = annotation;\n\n // Only a non-empty trace rides along — an empty buffer is simply omitted.\n if (draft.trace && draft.trace.length > 0) content.trace = draft.trace;\n\n return content;\n}\n","import { stripFixbackNodes } from \"./dom\";\n\n/**\n * Client-side masked screenshot capture (spec MVP §E; ticket #54).\n *\n * The capture is **private-by-default**: input values are masked and the SDK's\n * own UI is removed from a *clone* of the view **before** anything is rasterised,\n * so no unmasked text and none of Fixback's chrome ever reaches the image. The\n * approach is dependency-free — the cloned, masked DOM is serialised into an SVG\n * `<foreignObject>` and drawn onto a `<canvas>` — honouring the SDK's \"no runtime\n * dependencies / no host-page disturbance\" constraints. The raster step is\n * injectable so the pipeline (and the masking-before-capture guarantee) is\n * testable without a real canvas, and it fails quietly: any problem resolves to\n * `null` and the report is simply sent without a screenshot.\n */\n\n/** The character private input content is replaced with. */\nexport const MASK_CHAR = \"•\";\n\n/** Upper bound on mask length, so a huge field doesn't produce a huge string. */\nconst MAX_MASK_LENGTH = 32;\n\n/** Input `type`s that carry no free-text private data, so are left as-is. */\nconst NON_TEXT_INPUT_TYPES = new Set([\n \"button\",\n \"submit\",\n \"reset\",\n \"checkbox\",\n \"radio\",\n \"range\",\n \"color\",\n \"file\",\n \"image\",\n \"hidden\",\n]);\n\nfunction maskString(length: number): string {\n const n = Math.min(Math.max(length, 1), MAX_MASK_LENGTH);\n return MASK_CHAR.repeat(n);\n}\n\n/**\n * Mask the private, user-entered content in a subtree: text `input` values,\n * `textarea` content, and `contenteditable` text. Developer-authored text\n * (placeholders, button labels, non-text controls) is left untouched. Operates in\n * place — call it on a *clone* of the view, never the live page.\n */\nexport function maskInputs(root: ParentNode): void {\n for (const input of Array.from(root.querySelectorAll(\"input\"))) {\n const type = (input.getAttribute(\"type\") ?? \"text\").toLowerCase();\n if (NON_TEXT_INPUT_TYPES.has(type)) continue;\n const value = input.value;\n if (!value) continue;\n const masked = maskString(value.length);\n input.value = masked;\n input.setAttribute(\"value\", masked);\n }\n\n for (const area of Array.from(root.querySelectorAll(\"textarea\"))) {\n const value = area.value || area.textContent || \"\";\n if (!value) continue;\n const masked = maskString(value.length);\n area.value = masked;\n area.textContent = masked;\n }\n\n for (const editable of Array.from(\n root.querySelectorAll<HTMLElement>(\"[contenteditable]\"),\n )) {\n if (editable.getAttribute(\"contenteditable\") === \"false\") continue;\n const text = editable.textContent ?? \"\";\n if (!text.trim()) continue;\n editable.textContent = maskString(text.length);\n }\n}\n\n/** A captured screenshot: the image bytes plus its dimensions and content type. */\nexport interface Capture {\n readonly blob: Blob;\n readonly width: number;\n readonly height: number;\n readonly type: string;\n}\n\n/** Content type → screenshot file extension. */\nconst SCREENSHOT_EXTENSIONS: Readonly<Record<string, string>> = {\n \"image/png\": \"png\",\n \"image/jpeg\": \"jpg\",\n \"image/webp\": \"webp\",\n \"image/gif\": \"gif\",\n};\n\n/** The screenshot filename extension for a {@link Capture}'s content type (defaults to `png`). */\nexport function extensionFor(type: string): string {\n return SCREENSHOT_EXTENSIONS[type] ?? \"png\";\n}\n\n/** Turns a serialised SVG of the view into image bytes (injectable for tests). */\nexport type Rasterize = (\n svg: string,\n meta: { width: number; height: number; type: string },\n) => Promise<Blob | null>;\n\n/** Options for {@link captureView}. */\nexport interface CaptureOptions {\n /** The element to capture. Defaults to the document element (the full view). */\n readonly target?: Element;\n readonly doc?: Document;\n readonly win?: Window;\n /** Output content type. Defaults to `image/png`. */\n readonly type?: string;\n /** Override the raster step (the default draws via an SVG + `<canvas>`). */\n readonly rasterize?: Rasterize;\n}\n\nfunction viewportExtent(\n win: Window | undefined,\n doc: Document,\n axis: \"Width\" | \"Height\",\n): number {\n const inner = win?.[`inner${axis}`];\n const client = doc.documentElement[`client${axis}`];\n const value = typeof inner === \"number\" && inner > 0 ? inner : client;\n return Math.max(1, Math.round(value || 0) || 1);\n}\n\nfunction serializeToSvg(node: Element, width: number, height: number): string {\n const serialized = new XMLSerializer().serializeToString(node);\n return (\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\">` +\n `<foreignObject x=\"0\" y=\"0\" width=\"100%\" height=\"100%\">` +\n `<div xmlns=\"http://www.w3.org/1999/xhtml\">${serialized}</div>` +\n `</foreignObject></svg>`\n );\n}\n\n/** How long the default rasteriser waits for the SVG image before giving up. */\nconst RASTER_TIMEOUT_MS = 4000;\n\n/**\n * The default raster step: load the serialised SVG as an image and draw it onto a\n * canvas. Guarded at every turn — a missing 2D context, a tainted canvas (a\n * cross-origin image on the page), an image that never loads, or any thrown error\n * resolves to `null` rather than rejecting, so a capture failure never breaks the\n * Send.\n */\nconst rasterizeViaCanvas: Rasterize = (svg, { width, height, type }) =>\n new Promise((resolve) => {\n let settled = false;\n const done = (blob: Blob | null): void => {\n if (settled) return;\n settled = true;\n resolve(blob);\n };\n try {\n const image = new Image();\n const timer = setTimeout(() => done(null), RASTER_TIMEOUT_MS);\n image.onload = () => {\n try {\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) {\n clearTimeout(timer);\n done(null);\n return;\n }\n ctx.drawImage(image, 0, 0);\n canvas.toBlob((blob) => {\n clearTimeout(timer);\n done(blob);\n }, type);\n } catch {\n clearTimeout(timer);\n done(null);\n }\n };\n image.onerror = () => {\n clearTimeout(timer);\n done(null);\n };\n image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;\n } catch {\n done(null);\n }\n });\n\n/**\n * Capture the current view as a masked screenshot. Clones the target, removes the\n * SDK's own UI, masks input values — **all before** serialising and rasterising —\n * then returns the image bytes, or `null` if capture wasn't possible.\n */\nexport async function captureView(\n options: CaptureOptions = {},\n): Promise<Capture | null> {\n try {\n const doc = options.doc ?? document;\n const win = options.win ?? doc.defaultView ?? undefined;\n const target = options.target ?? doc.documentElement;\n const type = options.type ?? \"image/png\";\n const width = viewportExtent(win, doc, \"Width\");\n const height = viewportExtent(win, doc, \"Height\");\n\n const clone = target.cloneNode(true) as Element;\n stripFixbackNodes(clone);\n maskInputs(clone);\n\n const svg = serializeToSvg(clone, width, height);\n const rasterize = options.rasterize ?? rasterizeViaCanvas;\n const blob = await rasterize(svg, { width, height, type });\n if (!blob) return null;\n return { blob, width, height, type };\n } catch {\n return null;\n }\n}\n","/**\n * Client-side backpressure for **automatic** error reports (spec 0003 §E/§H,\n * ticket #89). When ingest sheds `source: auto` load it answers `429` with a\n * `Retry-After`; the SDK honours it by holding a pause window during which further\n * `source: auto` reports are dropped without touching the network. **Manual**\n * reports — a human clicking Send — never consult this gate.\n *\n * This is the transport's counterpart to the server's per-Project token bucket\n * (`apps/api/src/ingest/auto-report-rate-limiter.ts`): one shared window per page,\n * so a 429 from one auto-report shed applies to the auto-reports that follow it.\n * The clock is injectable so the window is unit-tested deterministically, never on\n * wall time — mirroring the server limiter's `Clock`.\n */\n\n/** A source of the current time in epoch milliseconds — injectable for tests. */\nexport type Clock = () => number;\n\n/** The hold window applied when a `429` carries no usable `Retry-After` (spec §E). */\nexport const DEFAULT_RETRY_AFTER_SECONDS = 60;\n\n/**\n * Parse a `Retry-After` header into whole seconds to hold for. Handles both HTTP\n * forms — a delta-seconds integer and an HTTP-date (measured from `now`, rounded up\n * and clamped at zero) — and falls back to {@link DEFAULT_RETRY_AFTER_SECONDS} when\n * the header is absent, blank, or unparseable. Ingest sends the delta-seconds form;\n * the date form is handled for spec-completeness.\n */\nexport function parseRetryAfter(\n header: string | null | undefined,\n now: number,\n): number {\n if (header == null) return DEFAULT_RETRY_AFTER_SECONDS;\n const value = header.trim();\n if (value === \"\") return DEFAULT_RETRY_AFTER_SECONDS;\n\n if (/^\\d+$/.test(value)) {\n return Number(value);\n }\n\n const when = Date.parse(value);\n if (!Number.isNaN(when)) {\n return Math.max(0, Math.ceil((when - now) / 1000));\n }\n\n return DEFAULT_RETRY_AFTER_SECONDS;\n}\n\n/**\n * A single pause window for `source: auto` reports. `hold` opens (or extends) it\n * from a `429`'s `Retry-After`; `isPaused` reports whether it is still open. The\n * default instance in `submit.ts` is shared across a page's reports so the hold\n * persists across successive auto submissions.\n */\nexport class AutoReportBackoff {\n /** Epoch ms until which `source: auto` reports are held; `0` when clear. */\n private pausedUntil = 0;\n\n constructor(private readonly now: Clock = Date.now) {}\n\n /** Is the `source: auto` pause window currently open? */\n isPaused(): boolean {\n return this.now() < this.pausedUntil;\n }\n\n /** Whole seconds remaining in the pause window (`0` when clear). */\n retryAfterSeconds(): number {\n return Math.max(0, Math.ceil((this.pausedUntil - this.now()) / 1000));\n }\n\n /**\n * Open (or extend) the window from a `429`'s `Retry-After` value, returning the\n * seconds it will hold for. The window only ever grows — a shorter later hold\n * never clips a longer one already in effect.\n */\n hold(retryAfterHeader: string | null | undefined): number {\n const now = this.now();\n const seconds = parseRetryAfter(retryAfterHeader, now);\n const until = now + seconds * 1000;\n if (until > this.pausedUntil) this.pausedUntil = until;\n return seconds;\n }\n}\n","import { AutoReportBackoff } from \"./auto-report-backoff\";\nimport type { IdentityInputs, ReporterTier } from \"./boot\";\nimport type { FeedbackSource, ReportContent } from \"./report\";\n\n/**\n * The multipart feedback submission — the transport half of the capture loop.\n *\n * `POST /api/ingest/feedback` (spec MVP §C; tickets #50/#53) is a multipart\n * request: a single text part `payload` carrying the JSON (publishable key +\n * identity evidence + content) and an optional binary part `screenshot`. The\n * content-type header is deliberately not set, so the runtime writes the\n * `multipart/form-data` boundary itself. Like `requestBoot`, this never throws:\n * an unreachable Fixback or a refusal is a quiet result, so a Fixback problem\n * never breaks the host page.\n *\n * The `payload` carries the full report content — the structured `annotation`\n * (element + region + marks), the masked `trace` buffer, and the `source`\n * provenance (spec §D); the `screenshot` part is the full masked PNG, never\n * region-cropped. Automatic reports (`source: auto`) additionally honour ingest's\n * `429` + `Retry-After` backpressure: while the hold window is open they are\n * dropped without touching the network. Manual reports are never gated (spec §E/§H).\n */\n\n/** The multipart field the screenshot binary is sent under (mirrors the server). */\nconst SCREENSHOT_FIELD = \"screenshot\";\n\n/** What ingest returns for an accepted submission (vendored server shape). */\nexport interface RecordedFeedback {\n readonly feedbackId: string;\n readonly issueId: string;\n readonly reporterId: string;\n readonly tier: ReporterTier;\n}\n\n/** The captured screenshot to attach, if any. */\nexport interface SubmitScreenshot {\n readonly blob: Blob;\n readonly filename?: string;\n}\n\n/** Everything a single feedback submission carries. */\nexport interface SubmitInput {\n readonly key: string;\n readonly identity?: IdentityInputs;\n readonly content: ReportContent;\n readonly screenshot?: SubmitScreenshot | null;\n}\n\n/** The outcome of a submission — never an exception. */\nexport type SubmitResult =\n | { readonly ok: true; readonly feedback: RecordedFeedback | null }\n | {\n readonly ok: false;\n readonly reason: \"unreachable\" | \"refused\";\n readonly status?: number;\n }\n | {\n /**\n * A `source: auto` report shed under ingest backpressure (spec §E/§H) — either\n * held locally because the window is still open, or answered `429` by ingest,\n * which opened/extended the window. Carries the seconds left to wait. Manual\n * reports never produce this.\n */\n readonly ok: false;\n readonly reason: \"backpressure\";\n readonly retryAfterSeconds: number;\n };\n\n/**\n * The shared `source: auto` backpressure window for the page. One instance so a\n * `429` from an auto-report holds the auto-reports that follow it; tests inject\n * their own {@link AutoReportBackoff} to isolate the clock.\n */\nconst defaultBackoff = new AutoReportBackoff();\n\n/** Join an API base URL with the feedback path, tolerating a trailing slash. */\nexport function feedbackEndpoint(apiUrl: string): string {\n return `${apiUrl.replace(/\\/+$/, \"\")}/api/ingest/feedback`;\n}\n\n/** Drop `undefined` identity fields so the payload carries only what was given. */\nfunction compactIdentity(identity: IdentityInputs | undefined): IdentityInputs {\n if (!identity) return {};\n const out: { signedIdentity?: string; reporterId?: string; anonymousId?: string } = {};\n if (identity.signedIdentity) out.signedIdentity = identity.signedIdentity;\n if (identity.reporterId) out.reporterId = identity.reporterId;\n if (identity.anonymousId) out.anonymousId = identity.anonymousId;\n return out;\n}\n\n/**\n * Submit a report to ingest. Assembles the `payload` JSON (key + identity +\n * content, with an explicit `source` stamped) and the optional full masked\n * `screenshot` file into a `FormData`, posts it, and resolves to the recorded\n * Feedback on success or a named failure otherwise.\n *\n * A `source: auto` report first consults the shared backpressure window: while it\n * is open the report is dropped without a request (spec §E/§H). Ingest's `429` +\n * `Retry-After` opens/extends that window (default 60 s if the header is absent).\n * Manual reports (`source: reporter`, the default) never consult the window and a\n * non-2xx for them stays a plain refusal. The `fetchImpl` and `backoff` seams exist\n * purely so the call is testable.\n */\nexport async function submitReport(\n apiUrl: string,\n input: SubmitInput,\n fetchImpl: typeof fetch = fetch,\n backoff: AutoReportBackoff = defaultBackoff,\n): Promise<SubmitResult> {\n const source: FeedbackSource = input.content.source ?? \"reporter\";\n const isAuto = source === \"auto\";\n\n // Client-side backpressure: shed auto-reports while the hold window is open,\n // without touching the network. Manual reports are never gated.\n if (isAuto && backoff.isPaused()) {\n return {\n ok: false,\n reason: \"backpressure\",\n retryAfterSeconds: backoff.retryAfterSeconds(),\n };\n }\n\n const payload = {\n key: input.key,\n ...compactIdentity(input.identity),\n ...input.content,\n source,\n };\n\n const form = new FormData();\n form.append(\"payload\", JSON.stringify(payload));\n if (input.screenshot?.blob) {\n form.append(\n SCREENSHOT_FIELD,\n input.screenshot.blob,\n input.screenshot.filename ?? \"screenshot.png\",\n );\n }\n\n let response: Response;\n try {\n response = await fetchImpl(feedbackEndpoint(apiUrl), {\n method: \"POST\",\n body: form,\n });\n } catch {\n return { ok: false, reason: \"unreachable\" };\n }\n\n if (!response.ok) {\n // A `429` for an auto-report is backpressure, not a refusal: open/extend the\n // hold window from `Retry-After` and report the wait. Manual reports never 429\n // here, so any non-2xx for them stays a plain refusal.\n if (isAuto && response.status === 429) {\n const retryAfterSeconds = backoff.hold(response.headers.get(\"Retry-After\"));\n return { ok: false, reason: \"backpressure\", retryAfterSeconds };\n }\n return { ok: false, reason: \"refused\", status: response.status };\n }\n\n try {\n const feedback = (await response.json()) as RecordedFeedback;\n return { ok: true, feedback };\n } catch {\n // Accepted, but the body wasn't the expected JSON — still a success.\n return { ok: true, feedback: null };\n }\n}\n","/**\n * The SDK's own version string, reported to ingest as `environment.sdkVersion`\n * (spec MVP §B/§E). Kept as a hand-maintained constant rather than imported from\n * `package.json`, so the bundle stays a single self-contained file with no JSON\n * import — keep it in step with `package.json` and the Changesets bump.\n */\nexport const SDK_VERSION = \"0.1.0\";\n","/**\n * Automatic error capture — the SDK's signature capability: **errors report\n * themselves, no prompt** (spec 0003 §E, ADR-0011; research\n * `sentry-error-capture-findings.md` §7.1–7.3, §7.6).\n *\n * Exactly **two capture-phase listeners** (`window` `error` +\n * `unhandledrejection`) turn uncaught exceptions and unhandled rejections into\n * `source: auto`, `Kind = bug` Feedback for the current session's Reporter — no\n * native-API monkeypatching, no library. Each firing is deduped by a per-session\n * fingerprint, rate-limited by a token-bucket burst limiter and a per-session cap,\n * scrubbed through the same `beforeSend` choke point as manual reports (§C), and\n * shipped through the same transport (which honours ingest's `429` / `Retry-After`\n * backpressure, ticket #89). `console.error` is **not** promoted — it stays\n * breadcrumb-only Evidence.\n *\n * Per-firing order (research §7.1): **`canSubmit`/Gate → dedup → rate-limit/cap →\n * `beforeSend` scrub → enqueue Feedback**. The Gate is honoured by construction:\n * `init` installs this only when boot returned `canSubmit`, so an auto-error is\n * never filed where a manual report would be refused, and it inherits the session\n * Reporter's server-derived tier.\n *\n * The whole module is defensive — every handler is wrapped so a Fixback problem\n * (or an error thrown while capturing an error) never surfaces on the host page.\n * The fingerprint/`normalize` shape and the limiter numbers are **starting points**\n * from research (ticket #92), exposed as config — tunable, not frozen.\n */\n\nimport type { IdentityInputs } from \"./boot\";\nimport { type BreadcrumbBuffer, errorCrumb, type Teardown } from \"./breadcrumbs\";\nimport { assembleContent, collectEnvironment, type ReportContent } from \"./report\";\nimport { runBeforeSend, type BeforeSend } from \"./scrub\";\nimport { captureView, extensionFor, type Capture, type CaptureOptions } from \"./screenshot\";\nimport { submitReport, type SubmitInput, type SubmitResult } from \"./submit\";\nimport { SDK_VERSION } from \"./version\";\n\nexport type { Teardown };\n\n/** Burst limiter capacity — how many auto-reports may fire back-to-back (§E). */\nexport const DEFAULT_BURST_CAPACITY = 5;\n/** Burst limiter refill — one token returns every this-many ms (§E). */\nexport const DEFAULT_BURST_REFILL_MS = 2_000;\n/** Per-session ceiling on distinct auto-Feedback; beyond it, only a dropped-count (§E). */\nexport const DEFAULT_MAX_DISTINCT_AUTO = 20;\n\n/** How many top stack frames feed the fingerprint (research §7.2). */\nconst FINGERPRINT_FRAME_LIMIT = 5;\n\n// --- Fingerprint (per-session flood-guard key, research §7.2) -----------------\n\nconst UUID_RE =\n /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi;\nconst URL_RE = /\\bhttps?:\\/\\/[^\\s\"')]+/gi;\nconst HEX_0X_RE = /\\b0x[0-9a-f]+\\b/gi;\nconst HEX_RUN_RE = /\\b[0-9a-f]{8,}\\b/gi;\nconst DIGIT_RUN_RE = /\\d{4,}/g;\n\n/**\n * Collapse the volatile parts of an error message so a changing string doesn't\n * split one bug: UUIDs, URLs, `0x…` and long hex runs, and long digit runs are\n * replaced with stable placeholders. Short numbers and stable text are kept so\n * genuinely distinct bugs stay distinct. A small, dependency-free regex set —\n * tunable per ticket #92, never a frozen magic set.\n */\nexport function normalize(value: string): string {\n if (typeof value !== \"string\" || value.length === 0) return \"\";\n return value\n .replace(UUID_RE, \"<uuid>\")\n .replace(URL_RE, \"<url>\")\n .replace(HEX_0X_RE, \"<hex>\")\n .replace(HEX_RUN_RE, \"<hex>\")\n .replace(DIGIT_RUN_RE, \"<n>\")\n .trim();\n}\n\n/**\n * A dependency-free FNV-1a hash rendered in base-36. It only has to be stable and\n * well-distributed within one session (the client key is a flood guard; the server\n * does canonical cross-session clustering), so a non-cryptographic hash is right.\n */\nexport function hashString(input: string): string {\n let h = 0x811c9dc5;\n for (let i = 0; i < input.length; i++) {\n h ^= input.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return (h >>> 0).toString(36);\n}\n\n/** Reduce a frame location to `basename:line:col`, dropping origin and query. */\nfunction compactLocation(location: string): string {\n const noQuery = location.replace(/\\?[^:]*/, \"\");\n const lastSlash = noQuery.lastIndexOf(\"/\");\n return lastSlash >= 0 ? noQuery.slice(lastSlash + 1) : noQuery;\n}\n\n/** Parse one stack line into a compact `function@basename:line:col` frame id. */\nfunction parseFrame(line: string): string | null {\n // V8: \"at fn (loc)\" | \"at loc\"\n const v8Named = line.match(/^at\\s+(.+?)\\s+\\((.+)\\)$/);\n if (v8Named) return `${v8Named[1] ?? \"\"}@${compactLocation(v8Named[2] ?? \"\")}`;\n const v8Bare = line.match(/^at\\s+(.+)$/);\n if (v8Bare) return `@${compactLocation(v8Bare[1] ?? \"\")}`;\n // Firefox / Safari: \"fn@loc\" | \"@loc\"\n const at = line.indexOf(\"@\");\n if (at >= 0) {\n const fn = line.slice(0, at);\n return `${fn}@${compactLocation(line.slice(at + 1))}`;\n }\n return null;\n}\n\n/**\n * Extract a compact, stable signature of the top in-app frames of a stack: up to\n * {@link FINGERPRINT_FRAME_LIMIT} frames as `function@basename:line:col`, origin\n * and cache-busting query stripped so a per-deploy asset hash doesn't matter within\n * a session. Returns `\"\"` when there is no usable stack (message-only fallback).\n */\nexport function extractTopFrames(\n stack: string | undefined,\n limit = FINGERPRINT_FRAME_LIMIT,\n): string {\n if (typeof stack !== \"string\" || stack.length === 0) return \"\";\n const frames: string[] = [];\n for (const raw of stack.split(\"\\n\")) {\n const frame = parseFrame(raw.trim());\n if (frame) {\n frames.push(frame);\n if (frames.length >= limit) break;\n }\n }\n return frames.join(\" < \");\n}\n\n/**\n * The per-session fingerprint (research §7.2):\n * `hash(errorType + \"|\" + normalize(value) + \"|\" + topFrames)`. Stack frames\n * dominate when present; otherwise it falls back to type + normalized value.\n */\nexport function computeFingerprint(\n type: string,\n value: string,\n stack?: string,\n): string {\n return hashString(`${type}|${normalize(value)}|${extractTopFrames(stack)}`);\n}\n\n// --- Burst limiter (token bucket, research §7.3) ------------------------------\n\n/** Configuration for {@link TokenBucket}. */\nexport interface TokenBucketOptions {\n readonly capacity: number;\n readonly refillIntervalMs: number;\n /** Clock source, injectable for tests. Defaults to `Date.now`. */\n readonly now?: () => number;\n}\n\n/**\n * A token bucket: starts full at `capacity`, refills one token every\n * `refillIntervalMs`, and refuses (`take() === false`) when empty. So a fast error\n * loop that dodges dedup with distinct fingerprints still can't machine-gun ingest.\n * The clock is injectable so the window is unit-tested deterministically.\n */\nexport class TokenBucket {\n private tokens: number;\n private last: number;\n private readonly now: () => number;\n\n constructor(private readonly options: TokenBucketOptions) {\n this.now = options.now ?? Date.now;\n this.tokens = Math.max(0, options.capacity);\n this.last = this.now();\n }\n\n /** Consume a token if one is available (refilling first), else refuse. */\n take(): boolean {\n const now = this.now();\n const { capacity, refillIntervalMs } = this.options;\n if (refillIntervalMs > 0 && now > this.last) {\n const refill = Math.floor((now - this.last) / refillIntervalMs);\n if (refill > 0) {\n this.tokens = Math.min(capacity, this.tokens + refill);\n this.last += refill * refillIntervalMs;\n }\n }\n if (this.tokens >= 1) {\n this.tokens -= 1;\n return true;\n }\n return false;\n }\n}\n\n// --- Error extraction ---------------------------------------------------------\n\n/** The distilled shape a fingerprint + report is built from. */\ninterface ExtractedError {\n readonly type: string;\n readonly value: string;\n readonly stack?: string;\n /** The original throwable (Error or rejection reason) for the trace crumb. */\n readonly original: unknown;\n}\n\ninterface ErrorLike {\n name?: unknown;\n message?: unknown;\n stack?: unknown;\n}\n\nfunction asString(value: unknown): string {\n if (typeof value === \"string\") return value;\n if (value == null) return \"\";\n try {\n return String(value);\n } catch {\n return \"\";\n }\n}\n\n/** Distil an uncaught `error` event into a fingerprint-able shape, or `null`. */\nfunction extractFromErrorEvent(event: Event): ExtractedError | null {\n const e = event as ErrorEvent & { error?: unknown };\n const error = e.error;\n if (error && typeof error === \"object\") {\n const err = error as ErrorLike;\n return {\n type: asString(err.name) || \"Error\",\n value: asString(err.message) || asString(e.message),\n stack: typeof err.stack === \"string\" ? err.stack : undefined,\n original: error,\n };\n }\n // No Error object: a message-only event (some browsers) is still capturable.\n const message = asString(e.message);\n if (message) return { type: \"Error\", value: message, original: message };\n // A resource-load error (img/script) has neither — never an uncaught exception.\n return null;\n}\n\n/** Distil an `unhandledrejection` event into a fingerprint-able shape, or `null`. */\nfunction extractFromRejectionEvent(event: Event): ExtractedError | null {\n const reason = (event as Event & { reason?: unknown }).reason;\n if (reason && typeof reason === \"object\") {\n const err = reason as ErrorLike;\n const type = asString(err.name) || \"UnhandledRejection\";\n const value = asString(err.message) || asString(reason);\n return {\n type,\n value,\n stack: typeof err.stack === \"string\" ? err.stack : undefined,\n original: reason,\n };\n }\n const value = asString(reason);\n return { type: \"UnhandledRejection\", value, original: reason ?? value };\n}\n\n// --- The capture controller ---------------------------------------------------\n\n/** Injectable collaborators, defaulted to the real implementations. */\nexport interface AutoCaptureDeps {\n readonly captureView: (options?: CaptureOptions) => Promise<Capture | null>;\n readonly submitReport: (\n apiUrl: string,\n input: SubmitInput,\n fetchImpl?: typeof fetch,\n ) => Promise<SubmitResult>;\n}\n\n/** Configuration for {@link installErrorCapture}. */\nexport interface AutoCaptureConfig {\n readonly apiUrl: string;\n readonly key: string;\n readonly identity?: IdentityInputs;\n /** The window whose global handlers are installed. Defaults to `window`. */\n readonly win?: Window;\n /** The document used for capture + environment. Defaults to the window's. */\n readonly doc?: Document;\n readonly sdkVersion?: string;\n /** The shared trace buffer; the failing error is added to it before filing. */\n readonly buffer?: BreadcrumbBuffer | null;\n /** Per-project client scrub hook, run at the `beforeSend` choke point (§C). */\n readonly beforeSend?: BeforeSend;\n /** Run the built-in default scrubbers. Defaults to `true` (private-by-default). */\n readonly scrub?: boolean;\n readonly deps?: Partial<AutoCaptureDeps>;\n /** Burst limiter capacity. Defaults to {@link DEFAULT_BURST_CAPACITY}. */\n readonly burstCapacity?: number;\n /** Burst limiter refill interval (ms). Defaults to {@link DEFAULT_BURST_REFILL_MS}. */\n readonly burstRefillMs?: number;\n /** Per-session distinct-Feedback cap. Defaults to {@link DEFAULT_MAX_DISTINCT_AUTO}. */\n readonly maxDistinct?: number;\n /** Clock source, injectable for tests. Defaults to `Date.now`. */\n readonly now?: () => number;\n}\n\n/** A per-session record of one fingerprinted error. */\ninterface AutoEntry {\n count: number;\n firstAt: number;\n lastAt: number;\n /** The occurrence count last shipped to ingest (`0` until the first send). */\n sentCount: number;\n}\n\n/** What to include when filing — a first report is rich; a flush is a light count update. */\ninterface FileOptions {\n readonly screenshot: boolean;\n readonly trace: boolean;\n}\n\n/** A running auto-capture: its teardown, plus the local dropped-count (spec §E). */\nexport interface AutoCaptureHandle {\n /** Remove the two global handlers and the flush hooks. Safe to call repeatedly. */\n readonly destroy: Teardown;\n /**\n * Distinct auto-Feedback dropped by the burst limiter or the per-session cap —\n * the \"keep only a local dropped-count\" fallback beyond the guardrails (spec §E).\n */\n droppedCount(): number;\n}\n\nconst NOOP_HANDLE: AutoCaptureHandle = { destroy: () => {}, droppedCount: () => 0 };\n\n/**\n * Install automatic error capture on a window and return a handle whose `destroy`\n * removes every listener it added (the two global handlers plus the flush hooks).\n * The caller (`init`) installs this only after boot returned `canSubmit`, so the\n * Gate is respected and the auto-error inherits the session Reporter's tier.\n */\nexport function installErrorCapture(config: AutoCaptureConfig): AutoCaptureHandle {\n const maybeWin =\n config.win ?? (typeof window !== \"undefined\" ? window : undefined);\n if (!maybeWin) return NOOP_HANDLE;\n const win: Window = maybeWin;\n const doc: Document | undefined =\n config.doc ??\n win.document ??\n (typeof document !== \"undefined\" ? document : undefined);\n\n const now = config.now ?? Date.now;\n const sdkVersion = config.sdkVersion ?? SDK_VERSION;\n const maxDistinct = config.maxDistinct ?? DEFAULT_MAX_DISTINCT_AUTO;\n const captureViewFn = config.deps?.captureView ?? captureView;\n const submitReportFn = config.deps?.submitReport ?? submitReport;\n\n const bucket = new TokenBucket({\n capacity: config.burstCapacity ?? DEFAULT_BURST_CAPACITY,\n refillIntervalMs: config.burstRefillMs ?? DEFAULT_BURST_REFILL_MS,\n now,\n });\n\n const seen = new Map<string, AutoEntry>();\n /** Distinct auto-Feedback dropped by the burst limiter or the session cap. */\n let dropped = 0;\n\n /** Build the `source: auto` content for a fingerprint at a given occurrence count. */\n function buildContent(fp: string, occurrences: number, opts: FileOptions): ReportContent {\n const base = assembleContent({\n kind: \"bug\",\n url: win.location?.href,\n environment: collectEnvironment(win, sdkVersion),\n trace: opts.trace ? config.buffer?.snapshot() : undefined,\n });\n return { ...base, source: \"auto\", errorSignature: fp, occurrences };\n }\n\n /**\n * Assemble, scrub, (optionally) screenshot, and ship one auto-Feedback. Sets the\n * entry's `sentCount` optimistically so a concurrent flush never double-sends,\n * and rolls it back on a failed send so the flush can retry the count update. A\n * `beforeSend` that returns `null` drops the report without transport.\n */\n async function file(fp: string, occurrences: number, opts: FileOptions): Promise<void> {\n const entry = seen.get(fp);\n if (!entry) return;\n const previouslySent = entry.sentCount;\n entry.sentCount = occurrences;\n try {\n const draft = buildContent(fp, occurrences, opts);\n const content = runBeforeSend(draft, {\n hook: config.beforeSend,\n scrub: config.scrub,\n });\n if (!content) return; // dropped at the client scrub choke point — no transport.\n\n let screenshot: SubmitInput[\"screenshot\"] = null;\n if (opts.screenshot) {\n const capture = await captureViewFn({ doc, win });\n screenshot = capture\n ? { blob: capture.blob, filename: `screenshot.${extensionFor(capture.type)}` }\n : null;\n }\n\n const result = await submitReportFn(config.apiUrl, {\n key: config.key,\n identity: config.identity,\n content,\n screenshot,\n });\n if (!result.ok && entry.sentCount === occurrences) {\n // Held under backpressure, refused, or unreachable — let a later flush retry.\n entry.sentCount = previouslySent;\n }\n } catch {\n if (entry.sentCount === occurrences) entry.sentCount = previouslySent;\n // The error path must never throw into the host page.\n }\n }\n\n /** Handle one distilled error: dedup → burst limiter → session cap → file. */\n function handle(extracted: ExtractedError): void {\n const fp = computeFingerprint(extracted.type, extracted.value, extracted.stack);\n const existing = seen.get(fp);\n if (existing) {\n // Dedup: one Feedback per fingerprint per session; count locally, flush later.\n existing.count += 1;\n existing.lastAt = now();\n return;\n }\n if (!bucket.take()) {\n dropped += 1; // burst limiter\n return;\n }\n if (seen.size >= maxDistinct) {\n dropped += 1; // per-session cap — keep only the local dropped-count\n return;\n }\n const at = now();\n seen.set(fp, { count: 1, firstAt: at, lastAt: at, sentCount: 0 });\n // The failing error joins the trace so the lead-up and the failure both show.\n try {\n config.buffer?.add(errorCrumb(extracted.original, at));\n } catch {\n /* capture must never throw into the host page */\n }\n void file(fp, 1, { screenshot: true, trace: true });\n }\n\n const onError = (event: Event): void => {\n try {\n const extracted = extractFromErrorEvent(event);\n if (extracted) handle(extracted);\n } catch {\n /* never throw into the host page */\n }\n };\n\n const onRejection = (event: Event): void => {\n try {\n const extracted = extractFromRejectionEvent(event);\n if (extracted) handle(extracted);\n } catch {\n /* never throw into the host page */\n }\n };\n\n /** Flush the final occurrence count of every fingerprint that grew since its send. */\n const flush = (): void => {\n try {\n for (const [fp, entry] of seen) {\n if (entry.count > entry.sentCount) {\n void file(fp, entry.count, { screenshot: false, trace: false });\n }\n }\n } catch {\n /* best-effort on unload */\n }\n };\n\n const onVisibility = (): void => {\n if (doc?.visibilityState === \"hidden\") flush();\n };\n\n win.addEventListener(\"error\", onError, true);\n win.addEventListener(\"unhandledrejection\", onRejection, true);\n win.addEventListener(\"pagehide\", flush);\n doc?.addEventListener(\"visibilitychange\", onVisibility);\n\n return {\n destroy: () => {\n win.removeEventListener(\"error\", onError, true);\n win.removeEventListener(\"unhandledrejection\", onRejection, true);\n win.removeEventListener(\"pagehide\", flush);\n doc?.removeEventListener(\"visibilitychange\", onVisibility);\n },\n droppedCount: () => dropped,\n };\n}\n","/**\n * The Public Reporter's stable, first-party anonymous id.\n *\n * When the host site does not supply its own identity, the SDK establishes a\n * per-browser id and persists it, so repeated boots from the same browser are one\n * Reporter (the server counts distinct Reporters by it). Persistence is\n * best-effort: if storage is unavailable (private mode, blocked cookies), we fall\n * back to an ephemeral id rather than throw — the SDK must never disturb the host\n * page.\n */\n\nconst STORAGE_KEY = \"fixback.anonymousId\";\n\n/** A random id: a UUID where the platform offers one, else a compact fallback. */\nfunction generateId(): string {\n const c: Crypto | undefined = globalThis.crypto;\n if (c && typeof c.randomUUID === \"function\") return c.randomUUID();\n return `fb-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\n/** Return this browser's anonymous id, creating and persisting one on first use. */\nexport function ensureAnonymousId(): string {\n try {\n const store = globalThis.localStorage;\n const existing = store.getItem(STORAGE_KEY);\n if (existing) return existing;\n const id = generateId();\n store.setItem(STORAGE_KEY, id);\n return id;\n } catch {\n // Storage blocked or absent — an ephemeral id keeps this boot working.\n return generateId();\n }\n}\n","/**\n * Invite redemption & reporter identity (spec §F).\n *\n * The zero-integration path for an invited tester: detect an invite token on the\n * page (`?fixback_invite=<token>`) or an explicit `redeem()` call, read the\n * Invite's public status, and — on confirmation — redeem it into a persisted\n * `reporterId` the SDK thereafter presents as an identity input.\n *\n * This module is the wire + storage half (pure enough to unit-test directly): the\n * two invite endpoints, the localStorage persistence scoped by publishable key,\n * and the URL token detection / one-time consumption. The onboarding modal is\n * `onboarding.ts`; `init` wires the two together.\n *\n * Like `boot.ts`, the wire types are a **vendored** slice of the server contract\n * (`apps/api/src/invites/invite-redemption.controller.ts`) — the SDK never imports\n * the private server package. Keep them in lock-step with that controller.\n */\n\nimport type { ReporterTier } from \"./boot\";\n\n/** The Invite's shape. Mirrors the server's `InviteKind`. */\nexport type InviteKind = \"targeted\" | \"shared\";\n\n/** The dead/unknown states a status or redeem read can resolve to. */\nexport type InviteDeadStatus = \"revoked\" | \"expired\" | \"exhausted\" | \"not_found\";\n\n/**\n * The public status of an Invite (`GET /api/invites/:token`). A live (`pending`)\n * Invite reveals the minimal facts the onboarding modal needs — the Project, the\n * Invite's shape, and the **server-derived** tier the redemption would grant; a\n * dead or unknown token reveals only its state.\n */\nexport type InviteStatusAnswer =\n | {\n readonly status: \"pending\";\n readonly projectId: string;\n readonly kind: InviteKind;\n readonly tier: ReporterTier;\n }\n | { readonly status: InviteDeadStatus };\n\n/**\n * The result of redeeming an Invite (`POST /api/invites/:token/redeem`): the\n * minted Reporter (its handle + server-derived tier + Project), or why it was\n * refused.\n */\nexport type RedeemAnswer =\n | {\n readonly status: \"redeemed\";\n readonly reporterId: string;\n readonly tier: ReporterTier;\n readonly projectId: string;\n }\n | { readonly status: InviteDeadStatus };\n\n/**\n * The self-provided display fields captured in the onboarding modal. They ride\n * along as a Reporter's chosen name / email — **never** a trust signal (the tier\n * is always server-derived, spec §F).\n */\nexport interface ReporterDisplay {\n readonly name?: string;\n readonly email?: string;\n}\n\n/** A persisted redeemed Reporter: the server handle plus the display fields. */\nexport interface StoredReporter extends ReporterDisplay {\n readonly reporterId: string;\n}\n\n/** The URL query parameter that carries an invite token. */\nexport const INVITE_QUERY_PARAM = \"fixback_invite\";\n\n/** localStorage namespace for a redeemed Reporter, scoped by publishable key. */\nconst REPORTER_STORAGE_PREFIX = \"fixback:reporter:\";\n\n// ---------------------------------------------------------------------------\n// URL token detection & one-time consumption\n// ---------------------------------------------------------------------------\n\n/**\n * Read the invite token from a page URL's `?fixback_invite=` param. Returns the\n * token, or `null` when absent, empty, or the URL cannot be parsed — never throws.\n */\nexport function readInviteToken(href: string): string | null {\n try {\n const token = new URL(href).searchParams.get(INVITE_QUERY_PARAM);\n return token && token.length > 0 ? token : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Strip the invite token from the address bar (one-time consumption, spec §F) via\n * `history.replaceState`, so a reload or a shared link cannot re-trigger — or\n * re-consume — an already-redeemed Invite. Other params, the path, and the hash\n * are preserved. Best-effort: it never throws into the host page.\n */\nexport function stripInviteToken(win: Window): void {\n try {\n const url = new URL(win.location.href);\n if (!url.searchParams.has(INVITE_QUERY_PARAM)) return;\n url.searchParams.delete(INVITE_QUERY_PARAM);\n const query = url.searchParams.toString();\n const next = `${url.pathname}${query ? `?${query}` : \"\"}${url.hash}`;\n win.history.replaceState(win.history.state, \"\", next);\n } catch {\n // Never disturb the host page over a cosmetic URL rewrite.\n }\n}\n\n// ---------------------------------------------------------------------------\n// Persistence (scoped by publishable key)\n// ---------------------------------------------------------------------------\n\nfunction reporterStorageKey(key: string): string {\n return `${REPORTER_STORAGE_PREFIX}${key}`;\n}\n\n/** The ambient localStorage, or `null` when it is absent or blocked. */\nfunction safeStorage(): Storage | null {\n try {\n return globalThis.localStorage ?? null;\n } catch {\n return null;\n }\n}\n\n/** A trimmed non-empty string, or `undefined` — so blanks are never stored. */\nfunction cleanField(value: string | undefined): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\n/**\n * Persist a redeemed Reporter for `key`. The `reporterId` is the identity the SDK\n * presents on later boots; the display name / email ride along as chosen fields.\n * Best-effort — storage being unavailable (private mode) is never fatal.\n */\nexport function persistReporter(\n key: string,\n reporter: StoredReporter,\n store: Storage | null = safeStorage(),\n): void {\n if (!store) return;\n const name = cleanField(reporter.name);\n const email = cleanField(reporter.email);\n const payload: StoredReporter = {\n reporterId: reporter.reporterId,\n ...(name ? { name } : {}),\n ...(email ? { email } : {}),\n };\n try {\n store.setItem(reporterStorageKey(key), JSON.stringify(payload));\n } catch {\n // Storage blocked or full — the redemption still succeeded in this session.\n }\n}\n\n/**\n * Read the redeemed Reporter persisted for `key`, or `null` when none is stored,\n * the record is malformed, or it carries no `reporterId`. Never throws.\n */\nexport function readStoredReporter(\n key: string,\n store: Storage | null = safeStorage(),\n): StoredReporter | null {\n if (!store) return null;\n try {\n const raw = store.getItem(reporterStorageKey(key));\n if (!raw) return null;\n const parsed = JSON.parse(raw) as Partial<StoredReporter>;\n if (typeof parsed?.reporterId !== \"string\" || parsed.reporterId.length === 0) {\n return null;\n }\n const name = cleanField(parsed.name);\n const email = cleanField(parsed.email);\n return {\n reporterId: parsed.reporterId,\n ...(name ? { name } : {}),\n ...(email ? { email } : {}),\n };\n } catch {\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Wire client\n// ---------------------------------------------------------------------------\n\n/** Join an API base URL with the invite status path, tolerating a trailing slash. */\nexport function inviteStatusEndpoint(apiUrl: string, token: string): string {\n return `${apiUrl.replace(/\\/+$/, \"\")}/api/invites/${encodeURIComponent(token)}`;\n}\n\n/** Join an API base URL with the redeem path for a token. */\nexport function redeemEndpoint(apiUrl: string, token: string): string {\n return `${inviteStatusEndpoint(apiUrl, token)}/redeem`;\n}\n\nconst DEAD_STATUSES = new Set<InviteDeadStatus>([\n \"revoked\",\n \"expired\",\n \"exhausted\",\n \"not_found\",\n]);\n\nfunction isTier(value: unknown): value is ReporterTier {\n return value === \"public\" || value === \"invited\" || value === \"internal\";\n}\n\n/** Narrow an unknown JSON body to an {@link InviteStatusAnswer}. */\nfunction isInviteStatusAnswer(value: unknown): value is InviteStatusAnswer {\n if (typeof value !== \"object\" || value === null) return false;\n const v = value as Record<string, unknown>;\n if (v.status === \"pending\") {\n return (\n typeof v.projectId === \"string\" &&\n (v.kind === \"targeted\" || v.kind === \"shared\") &&\n isTier(v.tier)\n );\n }\n return typeof v.status === \"string\" && DEAD_STATUSES.has(v.status as InviteDeadStatus);\n}\n\n/** Narrow an unknown JSON body to a {@link RedeemAnswer}. */\nfunction isRedeemAnswer(value: unknown): value is RedeemAnswer {\n if (typeof value !== \"object\" || value === null) return false;\n const v = value as Record<string, unknown>;\n if (v.status === \"redeemed\") {\n return (\n typeof v.reporterId === \"string\" &&\n typeof v.projectId === \"string\" &&\n isTier(v.tier)\n );\n }\n return typeof v.status === \"string\" && DEAD_STATUSES.has(v.status as InviteDeadStatus);\n}\n\nasync function readJson(response: Response): Promise<unknown> {\n try {\n return await response.json();\n } catch {\n return null;\n }\n}\n\n/**\n * Read an Invite's public status. Resolves to the answer, or `null` when Fixback\n * could not be reached or the body was not a recognised answer. The endpoint\n * answers 200 for a live or dead Invite and 404 (with a JSON body) for an unknown\n * token, so the body — not the HTTP status — is what the caller narrows on. Never\n * throws: an outage stays invisible to the host page.\n */\nexport async function fetchInviteStatus(\n apiUrl: string,\n token: string,\n fetchImpl: typeof fetch = fetch,\n): Promise<InviteStatusAnswer | null> {\n let response: Response;\n try {\n response = await fetchImpl(inviteStatusEndpoint(apiUrl, token), {\n method: \"GET\",\n headers: { accept: \"application/json\" },\n });\n } catch {\n return null;\n }\n const body = await readJson(response);\n return isInviteStatusAnswer(body) ? body : null;\n}\n\n/**\n * Redeem an Invite by token, minting and returning a Reporter. Resolves to the\n * answer, or `null` on an unreachable API or an unrecognised body. Never throws.\n */\nexport async function redeemInviteToken(\n apiUrl: string,\n token: string,\n fetchImpl: typeof fetch = fetch,\n): Promise<RedeemAnswer | null> {\n let response: Response;\n try {\n response = await fetchImpl(redeemEndpoint(apiUrl, token), {\n method: \"POST\",\n headers: { accept: \"application/json\" },\n });\n } catch {\n return null;\n }\n const body = await readJson(response);\n return isRedeemAnswer(body) ? body : null;\n}\n","/**\n * The launcher's isolated styles.\n *\n * These live inside the launcher's Shadow DOM (see `launcher.ts`), so nothing\n * here can reach the host page and nothing on the host page can reach in. The\n * token values are **vendored** Signal design tokens — copied from\n * `packages/ui/src/tokens.css` rather than imported, because the SDK must not\n * depend on `@fixback/ui` at runtime (ticket #47). Keep them in sync by value.\n *\n * The launcher is the prototype's polished pill (spec 0003 §A): a bottom-right\n * **Feedback** pill that **hover-peeks**, can be **tucked away** (sliding off\n * behind an edge nub, with a corner hover-zone to bring it back), a first-visit\n * **welcome toast**, and a **reduce-motion** mode that stills the pulse. Every\n * piece — pill, nub, corner zone, toasts — lives in this one Shadow DOM.\n *\n * Motion is driven by two host-element attributes the mount toggles:\n * `data-fb-hidden` (tucked away) and `data-fb-peeking` (peeked back on hover);\n * `data-fb-reduce-motion` (or the OS `prefers-reduced-motion`) stills it all.\n */\nexport const LAUNCHER_STYLES = `\n:host {\n /* Vendored Signal tokens (packages/ui/src/tokens.css). */\n --fb-color-accent: #2f6fed;\n --fb-color-accent-hover: #245fd0;\n --fb-color-on-emphasis: #ffffff;\n --fb-color-text: #0f1720;\n /* Inverse (dark) surface for the toasts — Signal --fb-ink-900. */\n --fb-color-surface-inverse: #0f1720;\n --fb-font-sans: \"IBM Plex Sans\", system-ui, -apple-system, \"Segoe UI\", Roboto,\n Helvetica, Arial, sans-serif;\n\n display: block;\n color: var(--fb-color-text);\n font-family: var(--fb-font-sans);\n font-size: 13px;\n line-height: 1.4;\n -webkit-font-smoothing: antialiased;\n}\n\n/* The pill: Feedback button + divider + tuck control, anchored bottom-right. */\n.fb-launcher {\n position: fixed;\n right: 20px;\n bottom: 20px;\n z-index: 2;\n display: inline-flex;\n align-items: stretch;\n box-sizing: border-box;\n height: 40px;\n margin: 0;\n border-radius: 999px;\n background: var(--fb-color-accent);\n box-shadow:\n 0 6px 18px rgba(15, 23, 32, 0.16),\n 0 1px 2px rgba(15, 23, 32, 0.12);\n transform: translateX(0);\n transition:\n transform 340ms cubic-bezier(0.2, 0.8, 0.3, 1),\n background-color 120ms ease;\n}\n\n.fb-launcher:hover {\n background: var(--fb-color-accent-hover);\n}\n\n.fb-launcher__button {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n box-sizing: border-box;\n height: 100%;\n margin: 0;\n padding: 0 6px 0 16px;\n border: 0;\n background: transparent;\n color: var(--fb-color-on-emphasis);\n font-family: inherit;\n font-size: 13px;\n font-weight: 600;\n letter-spacing: 0.01em;\n cursor: pointer;\n}\n\n.fb-launcher__button:focus-visible,\n.fb-launcher__tuck:focus-visible {\n outline: 2px solid var(--fb-color-on-emphasis);\n outline-offset: -3px;\n border-radius: 999px;\n}\n\n.fb-launcher__icon {\n display: block;\n flex: none;\n width: 16px;\n height: 16px;\n}\n\n.fb-launcher__label {\n white-space: nowrap;\n}\n\n.fb-launcher__divider {\n width: 1px;\n margin: 9px 0;\n flex: none;\n background: rgba(255, 255, 255, 0.28);\n}\n\n.fb-launcher__tuck {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n width: 32px;\n height: 100%;\n padding: 0;\n border: 0;\n background: transparent;\n color: rgba(255, 255, 255, 0.82);\n cursor: pointer;\n}\n\n.fb-launcher__tuck:hover {\n color: var(--fb-color-on-emphasis);\n}\n\n.fb-launcher__tuck-icon {\n display: block;\n width: 14px;\n height: 14px;\n}\n\n/* The corner hover-zone that peeks a tucked launcher back — inert until tucked,\n * so it never swallows the host page's own bottom-right clicks. */\n.fb-launcher__peekzone {\n position: fixed;\n right: 0;\n bottom: 0;\n z-index: 1;\n width: 160px;\n height: 160px;\n pointer-events: none;\n}\n\n/* The edge nub: the visible re-reveal affordance, hidden until tucked. */\n.fb-launcher__nub {\n position: fixed;\n right: 0;\n bottom: 22px;\n z-index: 2;\n width: 13px;\n height: 42px;\n border-radius: 9px 0 0 9px;\n background: var(--fb-color-accent);\n box-shadow: -6px 5px 18px rgba(47, 111, 237, 0.4);\n cursor: pointer;\n opacity: 0;\n transform: translateX(10px);\n pointer-events: none;\n transition:\n opacity 220ms ease,\n transform 220ms ease;\n}\n\n/* Tucked and not peeking: slide the pill off, reveal the nub, arm the zone. */\n:host([data-fb-hidden]:not([data-fb-peeking])) .fb-launcher {\n transform: translateX(calc(100% + 30px));\n}\n\n:host([data-fb-hidden]:not([data-fb-peeking])) .fb-launcher__nub {\n opacity: 1;\n transform: none;\n pointer-events: auto;\n}\n\n:host([data-fb-hidden]) .fb-launcher__peekzone {\n pointer-events: auto;\n}\n\n/* Toasts: the first-visit welcome (above the pill) and the tuck-away hint. */\n.fb-launcher__welcome,\n.fb-launcher__hint {\n position: fixed;\n right: 20px;\n z-index: 5;\n box-sizing: border-box;\n color: #fff;\n font-size: 12px;\n line-height: 1.45;\n background: var(--fb-color-surface-inverse);\n border-radius: 10px;\n box-shadow: 0 12px 30px rgba(15, 40, 70, 0.32);\n animation: fbToast 300ms ease both;\n}\n\n.fb-launcher__welcome {\n bottom: 70px;\n max-width: 210px;\n padding: 9px 13px;\n}\n\n.fb-launcher__welcome strong {\n color: #8fc0ff;\n font-weight: 600;\n}\n\n.fb-launcher__hint {\n bottom: 20px;\n max-width: 232px;\n padding: 10px 13px;\n}\n\n/* The pulse that draws the eye while the welcome shows. */\n.fb-launcher--pulse {\n animation: fbPulse 1.8s ease-in-out 2;\n}\n\n@keyframes fbPulse {\n 0%,\n 100% {\n box-shadow:\n 0 6px 18px rgba(15, 23, 32, 0.16),\n 0 0 0 0 rgba(47, 111, 237, 0.45);\n }\n 50% {\n box-shadow:\n 0 6px 18px rgba(15, 23, 32, 0.16),\n 0 0 0 12px rgba(47, 111, 237, 0);\n }\n}\n\n@keyframes fbToast {\n from {\n opacity: 0;\n transform: translateY(14px);\n }\n to {\n opacity: 1;\n transform: none;\n }\n}\n\n/* Reduce motion — the explicit opt-in and the OS setting both still it all. */\n:host([data-fb-reduce-motion]) .fb-launcher,\n:host([data-fb-reduce-motion]) .fb-launcher__nub {\n transition: none;\n}\n\n:host([data-fb-reduce-motion]) .fb-launcher--pulse,\n:host([data-fb-reduce-motion]) .fb-launcher__welcome,\n:host([data-fb-reduce-motion]) .fb-launcher__hint {\n animation: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .fb-launcher,\n .fb-launcher__nub {\n transition: none;\n }\n .fb-launcher--pulse,\n .fb-launcher__welcome,\n .fb-launcher__hint {\n animation: none;\n }\n}\n`;\n","import { LAUNCHER_STYLES } from \"./styles\";\n\n/** Marks the SDK's host element in the light DOM, so it is findable and unique. */\nexport const ROOT_ATTRIBUTE = \"data-fixback-root\";\n\n/**\n * Dispatched from the host element when the launcher is activated. The report\n * overlay this opens is wired in `init`; this event is the seam it hangs off. It\n * is `composed` so host-page listeners outside the Shadow DOM can hear it.\n */\nexport const LAUNCH_EVENT = \"fixback:launch\";\n\n/** Storage-key namespace for the first-visit welcome flag, scoped per key. */\nconst WELCOME_STORAGE_PREFIX = \"fixback:welcomed:\";\n\n/** How long the welcome toast (and its pulse) linger before self-dismissing. */\nconst WELCOME_MS = 5000;\n/** How long the tuck-away hint lingers before fading out. */\nconst HINT_MS = 4400;\n/** Grace period before a peeked-open launcher tucks itself back on pointer-out. */\nconst PEEK_OUT_MS = 280;\n\nconst FEEDBACK_ICON =\n '<svg class=\"fb-launcher__icon\" viewBox=\"0 0 16 16\" aria-hidden=\"true\" focusable=\"false\">' +\n '<path fill=\"currentColor\" d=\"M3.25 2h9.5A1.25 1.25 0 0 1 14 3.25v6.5A1.25 1.25 0 0 1 12.75 11H7.6l-3.19 2.55A.6.6 0 0 1 3.4 13.1V11h-.15A1.25 1.25 0 0 1 2 9.75v-6.5A1.25 1.25 0 0 1 3.25 2Z\"/>' +\n \"</svg>\";\n\nconst TUCK_ICON =\n '<svg class=\"fb-launcher__tuck-icon\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\">' +\n '<path d=\"M4 6l4 4 4-4\"/>' +\n \"</svg>\";\n\n/** Options that shape a mounted launcher. */\nexport interface LauncherOptions {\n /**\n * The Project's publishable key. Scopes the first-visit welcome flag so the\n * welcome greets once per key per browser.\n */\n readonly key?: string;\n /**\n * Still the pulse and motion. An explicit opt-in that complements the OS-level\n * `prefers-reduced-motion` (which the styles already honour on their own).\n */\n readonly reduceMotion?: boolean;\n}\n\n/** A mounted launcher and the handle needed to remove it again. */\nexport interface Launcher {\n readonly host: HTMLElement;\n /** Clears the launcher's pending timers. Called by {@link unmountLauncher}. */\n readonly dispose: () => void;\n}\n\n/**\n * Read the first-visit welcome flag and mark it seen, returning whether the\n * welcome should greet now. Never throws: when storage is unavailable (SSR,\n * sandboxed frame, blocked cookies) it greets without persisting.\n */\nfunction claimFirstVisitWelcome(win: Window | null, key: string | undefined): boolean {\n const flag = WELCOME_STORAGE_PREFIX + (key ?? \"default\");\n try {\n const storage = win?.localStorage;\n if (!storage) return true;\n if (storage.getItem(flag) === \"1\") return false;\n storage.setItem(flag, \"1\");\n return true;\n } catch {\n return true;\n }\n}\n\n/**\n * Mount the launcher into `target` (typically `document.body`). Everything the\n * launcher draws — the **Feedback** pill, its **tuck** control, the edge **nub**\n * and corner **hover-zone** that peek it back, and the first-visit **welcome**\n * and tuck-away **hint** toasts — lives inside one open Shadow DOM, so its styles\n * are fully isolated from the host page and vice-versa. The host element is\n * fixed-positioned and out of flow, so mounting never shifts the host page's\n * layout. Only one launcher can exist at a time — an earlier one is removed first.\n *\n * Tuck/peek state is expressed as `data-fb-hidden` / `data-fb-peeking` attributes\n * on the host, which the Shadow DOM stylesheet animates; `reduceMotion` sets\n * `data-fb-reduce-motion`, which (with the OS `prefers-reduced-motion`) stills it.\n */\nexport function mountLauncher(\n target: HTMLElement,\n options: LauncherOptions = {},\n): Launcher {\n const doc = target.ownerDocument;\n const win = doc.defaultView;\n\n const previous = doc.querySelector(`[${ROOT_ATTRIBUTE}]`);\n if (previous) previous.remove();\n\n const host = doc.createElement(\"div\");\n host.setAttribute(ROOT_ATTRIBUTE, \"\");\n // Positioning is inlined on the host (not in the Shadow DOM sheet) so it holds\n // even against a hostile host-page reset, and so the launcher is anchored to\n // the viewport without ever participating in the page's flow.\n host.style.cssText =\n \"position:fixed;right:20px;bottom:20px;z-index:2147483000;margin:0;padding:0;border:0;\";\n if (options.reduceMotion) host.setAttribute(\"data-fb-reduce-motion\", \"true\");\n\n const shadow = host.attachShadow({ mode: \"open\" });\n\n const style = doc.createElement(\"style\");\n style.textContent = LAUNCHER_STYLES;\n shadow.appendChild(style);\n\n // --- The pill: Feedback button + divider + tuck control ---\n const pill = doc.createElement(\"div\");\n pill.className = \"fb-launcher\";\n\n const button = doc.createElement(\"button\");\n button.type = \"button\";\n button.className = \"fb-launcher__button\";\n button.setAttribute(\"aria-haspopup\", \"dialog\");\n button.setAttribute(\"aria-label\", \"Give feedback\");\n button.innerHTML = `${FEEDBACK_ICON}<span class=\"fb-launcher__label\">Feedback</span>`;\n button.addEventListener(\"click\", () => {\n host.dispatchEvent(\n new CustomEvent(LAUNCH_EVENT, { bubbles: true, composed: true }),\n );\n });\n\n const divider = doc.createElement(\"span\");\n divider.className = \"fb-launcher__divider\";\n divider.setAttribute(\"aria-hidden\", \"true\");\n\n const tuck = doc.createElement(\"button\");\n tuck.type = \"button\";\n tuck.className = \"fb-launcher__tuck\";\n tuck.setAttribute(\"data-fb-tuck\", \"\");\n tuck.setAttribute(\"aria-label\", \"Hide feedback button\");\n tuck.title = \"Tuck away — hover the corner to bring it back\";\n tuck.innerHTML = TUCK_ICON;\n\n pill.append(button, divider, tuck);\n\n // --- The peek chrome: corner hover-zone + edge nub ---\n const peekzone = doc.createElement(\"div\");\n peekzone.className = \"fb-launcher__peekzone\";\n peekzone.setAttribute(\"aria-hidden\", \"true\");\n\n const nub = doc.createElement(\"div\");\n nub.className = \"fb-launcher__nub\";\n nub.setAttribute(\"role\", \"button\");\n nub.setAttribute(\"tabindex\", \"0\");\n nub.setAttribute(\"aria-label\", \"Show feedback button\");\n\n shadow.append(pill, peekzone, nub);\n\n // --- Tuck / peek / welcome state ---\n let hidden = false;\n let peeking = false;\n let peekTimer: ReturnType<typeof setTimeout> | undefined;\n let welcomeTimer: ReturnType<typeof setTimeout> | undefined;\n let hintTimer: ReturnType<typeof setTimeout> | undefined;\n let welcome: HTMLElement | undefined;\n let hint: HTMLElement | undefined;\n\n const syncState = (): void => {\n if (hidden) host.setAttribute(\"data-fb-hidden\", \"true\");\n else host.removeAttribute(\"data-fb-hidden\");\n if (hidden && peeking) host.setAttribute(\"data-fb-peeking\", \"true\");\n else host.removeAttribute(\"data-fb-peeking\");\n };\n\n const dismissWelcome = (): void => {\n if (welcomeTimer !== undefined) {\n clearTimeout(welcomeTimer);\n welcomeTimer = undefined;\n }\n pill.classList.remove(\"fb-launcher--pulse\");\n welcome?.remove();\n welcome = undefined;\n };\n\n const dismissHint = (): void => {\n if (hintTimer !== undefined) {\n clearTimeout(hintTimer);\n hintTimer = undefined;\n }\n hint?.remove();\n hint = undefined;\n };\n\n const peekIn = (): void => {\n if (peekTimer !== undefined) {\n clearTimeout(peekTimer);\n peekTimer = undefined;\n }\n if (!peeking) {\n peeking = true;\n syncState();\n }\n };\n\n const peekOut = (): void => {\n if (peekTimer !== undefined) clearTimeout(peekTimer);\n peekTimer = setTimeout(() => {\n peekTimer = undefined;\n peeking = false;\n syncState();\n }, PEEK_OUT_MS);\n };\n\n // Hovering the pill, the nub, or the corner zone all keep the launcher peeked.\n for (const el of [pill, peekzone, nub]) {\n el.addEventListener(\"mouseenter\", peekIn);\n el.addEventListener(\"mouseleave\", peekOut);\n }\n\n const showLauncher = (): void => {\n if (peekTimer !== undefined) {\n clearTimeout(peekTimer);\n peekTimer = undefined;\n }\n hidden = false;\n peeking = false;\n syncState();\n dismissHint();\n };\n\n const hideLauncher = (): void => {\n dismissWelcome();\n hidden = true;\n peeking = false;\n syncState();\n\n dismissHint();\n hint = doc.createElement(\"div\");\n hint.className = \"fb-launcher__hint\";\n hint.setAttribute(\"role\", \"status\");\n hint.textContent =\n \"Tucked away down here. Hover the bottom-right corner and I'll bring it back.\";\n shadow.appendChild(hint);\n hintTimer = setTimeout(dismissHint, HINT_MS);\n };\n\n tuck.addEventListener(\"click\", hideLauncher);\n\n // The nub is a real re-reveal control for pointers that can't hover (touch,\n // keyboard): clicking or Enter/Space brings the launcher fully back.\n nub.addEventListener(\"click\", showLauncher);\n nub.addEventListener(\"keydown\", (event) => {\n if (event.key === \"Enter\" || event.key === \" \" || event.key === \"Spacebar\") {\n event.preventDefault();\n showLauncher();\n }\n });\n\n // --- First-visit welcome + its pulse ---\n if (claimFirstVisitWelcome(win, options.key)) {\n welcome = doc.createElement(\"div\");\n welcome.className = \"fb-launcher__welcome\";\n welcome.setAttribute(\"role\", \"status\");\n welcome.innerHTML =\n \"You're all set — tap <strong>Feedback</strong> any time to report something.\";\n shadow.appendChild(welcome);\n if (!options.reduceMotion) pill.classList.add(\"fb-launcher--pulse\");\n welcomeTimer = setTimeout(dismissWelcome, WELCOME_MS);\n }\n\n target.appendChild(host);\n\n const dispose = (): void => {\n if (peekTimer !== undefined) clearTimeout(peekTimer);\n if (welcomeTimer !== undefined) clearTimeout(welcomeTimer);\n if (hintTimer !== undefined) clearTimeout(hintTimer);\n };\n\n return { host, dispose };\n}\n\n/** Remove a mounted launcher. Safe to call more than once. */\nexport function unmountLauncher(launcher: Launcher): void {\n launcher.dispose();\n launcher.host.remove();\n}\n","/**\n * Styles for the invite onboarding modal (spec §F) — the Signal look of the\n * frozen Reporter prototype (`docs/design/Fixback Reporter.dc.html`), scoped to\n * the modal's own Shadow DOM so the host page is never touched and never touches\n * it. Tokens mirror `overlay-styles.ts` so launcher, overlay, and modal read as\n * one system.\n */\nexport const ONBOARDING_STYLES = `\n:host {\n --fb-color-accent: #2f6fed;\n --fb-color-accent-hover: #245fd0;\n --fb-color-on-emphasis: #ffffff;\n --fb-color-ink: #0f1720;\n --fb-color-text: #1a2530;\n --fb-color-muted: #5a6875;\n --fb-color-faint: #9aa7b2;\n --fb-color-hint: #b7c1cb;\n --fb-color-border: #dce3ea;\n --fb-color-border-soft: #e6ebf0;\n --fb-color-surface: #ffffff;\n --fb-color-card: #f6f8fa;\n --fb-color-accent-bg: #eaf1fe;\n --fb-color-success: #2f9e5b;\n --fb-font-sans: \"IBM Plex Sans\", system-ui, -apple-system, \"Segoe UI\", Roboto,\n Helvetica, Arial, sans-serif;\n --fb-font-mono: \"IBM Plex Mono\", ui-monospace, \"SFMono-Regular\", Menlo, Consolas,\n monospace;\n all: initial;\n}\n\n*, *::before, *::after { box-sizing: border-box; }\n\n.fb-ob-backdrop {\n position: fixed;\n inset: 0;\n z-index: 2147483010;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n background: rgba(15, 23, 32, 0.55);\n backdrop-filter: blur(3px);\n font-family: var(--fb-font-sans);\n color: var(--fb-color-text);\n animation: fb-ob-fade 0.25s ease both;\n}\n\n.fb-ob-panel {\n width: 400px;\n max-width: 100%;\n background: var(--fb-color-surface);\n border-radius: 16px;\n box-shadow: 0 30px 70px rgba(15, 40, 70, 0.4);\n overflow: hidden;\n animation: fb-ob-pop 0.32s cubic-bezier(0.2, 0.8, 0.3, 1) both;\n}\n\n.fb-ob-head {\n padding: 22px 24px 0;\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.fb-ob-mark {\n width: 22px;\n height: 22px;\n border-radius: 6px;\n background: var(--fb-color-accent);\n flex: none;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.fb-ob-mark::after {\n content: \"\";\n width: 7px;\n height: 7px;\n border-radius: 2px;\n background: #fff;\n}\n.fb-ob-brand { font-size: 15px; font-weight: 600; color: var(--fb-color-ink); }\n.fb-ob-chip {\n margin-left: auto;\n font-family: var(--fb-font-mono);\n font-size: 10px;\n color: #8a97a3;\n border: 1px solid var(--fb-color-border-soft);\n border-radius: 6px;\n padding: 2px 7px;\n}\n\n.fb-ob-body { padding: 16px 24px 8px; }\n.fb-ob-title {\n font-size: 19px;\n font-weight: 700;\n color: var(--fb-color-ink);\n letter-spacing: -0.01em;\n}\n.fb-ob-lede {\n font-size: 13.5px;\n color: var(--fb-color-muted);\n line-height: 1.5;\n margin: 7px 0 0;\n}\n.fb-ob-lede strong { color: var(--fb-color-text); }\n\n.fb-ob-card {\n margin: 16px 0;\n padding: 13px 14px;\n background: var(--fb-color-card);\n border: 1px solid var(--fb-color-border-soft);\n border-radius: 10px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n.fb-ob-row { display: flex; align-items: center; gap: 10px; }\n.fb-ob-row + .fb-ob-row {\n border-top: 1px solid #eef2f6;\n padding-top: 10px;\n}\n.fb-ob-rowlabel {\n font-family: var(--fb-font-mono);\n font-size: 10px;\n color: var(--fb-color-faint);\n width: 70px;\n flex: none;\n}\n.fb-ob-site { font-size: 13px; color: var(--fb-color-text); font-weight: 500; }\n.fb-ob-tier {\n font-size: 11px;\n font-weight: 600;\n color: var(--fb-color-accent);\n background: var(--fb-color-accent-bg);\n padding: 3px 9px;\n border-radius: 6px;\n}\n.fb-ob-tiernote { font-size: 11.5px; color: #8a97a3; }\n\n.fb-ob-fieldlabel {\n display: block;\n font-family: var(--fb-font-mono);\n font-size: 10px;\n letter-spacing: 0.05em;\n text-transform: uppercase;\n color: var(--fb-color-faint);\n margin-bottom: 6px;\n}\n.fb-ob-fields { display: flex; gap: 8px; }\n.fb-ob-input {\n height: 38px;\n padding: 0 12px;\n border: 1px solid var(--fb-color-border);\n border-radius: 9px;\n font-family: inherit;\n font-size: 13px;\n color: var(--fb-color-text);\n outline: none;\n min-width: 0;\n}\n.fb-ob-input:focus { border-color: var(--fb-color-accent); }\n.fb-ob-name { flex: 1; }\n.fb-ob-email { flex: 1.3; font-family: var(--fb-font-mono); font-size: 12.5px; color: var(--fb-color-muted); }\n\n.fb-ob-privacy {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n margin-top: 14px;\n font-size: 11.5px;\n color: #8a97a3;\n line-height: 1.5;\n}\n.fb-ob-privacy svg { flex: none; margin-top: 1px; }\n.fb-ob-privacy strong { color: var(--fb-color-muted); }\n\n.fb-ob-foot { padding: 16px 24px 22px; }\n.fb-ob-confirm {\n width: 100%;\n height: 44px;\n border: 0;\n border-radius: 11px;\n background: var(--fb-color-accent);\n color: var(--fb-color-on-emphasis);\n font-family: inherit;\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n}\n.fb-ob-confirm:hover { background: var(--fb-color-accent-hover); }\n.fb-ob-confirm:disabled { opacity: 0.6; cursor: default; }\n\n@keyframes fb-ob-fade { from { opacity: 0; } to { opacity: 1; } }\n@keyframes fb-ob-pop {\n from { opacity: 0; transform: translateY(8px) scale(0.98); }\n to { opacity: 1; transform: none; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .fb-ob-backdrop, .fb-ob-panel { animation: none; }\n}\n`;\n","/**\n * The invite onboarding modal (spec §F) — the SDK-rendered redemption screen an\n * invited tester sees when a page carries an invite token. It shows the site\n * they were invited to, the **server-derived** access tier the redemption grants,\n * a private-by-default note, and a \"Continue as\" name / email, then confirms.\n *\n * Built to the frozen Signal Reporter prototype\n * (`docs/design/Fixback Reporter.dc.html`). It mounts lazily inside its own open\n * Shadow DOM so the host page is fully isolated, and — like the launcher and\n * overlay — never throws into the host page. It renders only data the SDK\n * truthfully holds: the site's own origin and the server-derived tier. The name /\n * email are **self-provided display fields**, never a trust signal (§F).\n */\n\nimport type { ReporterTier } from \"./boot\";\nimport type { ReporterDisplay } from \"./invite\";\nimport { ONBOARDING_STYLES } from \"./onboarding-styles\";\n\n/** Marks the modal's host element in the light DOM, so it is findable and unique. */\nexport const ONBOARDING_ATTRIBUTE = \"data-fixback-onboard\";\n\n/** The access badge label for each tier (the redemption grants invited or internal). */\nconst TIER_LABEL: Record<ReporterTier, string> = {\n invited: \"Invited tester\",\n internal: \"Internal\",\n public: \"Public\",\n};\n\n/** A short, plain-language note on what the tier means for a submission. */\nconst TIER_NOTE: Record<ReporterTier, string> = {\n invited: \"reviewed before it ships\",\n internal: \"ships straight through\",\n public: \"tracked, not auto-shipped\",\n};\n\nconst SHIELD_ICON =\n '<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"#2f9e5b\" stroke-width=\"1.5\" aria-hidden=\"true\">' +\n '<path d=\"M8 1.8 13.5 4v3.5c0 3.6-2.4 5.6-5.5 6.7C4.9 13.1 2.5 11.1 2.5 7.5V4z\"/>' +\n '<path d=\"M5.8 8 7.3 9.5 10.3 6.2\"/></svg>';\n\n/** Configuration for {@link createOnboardingModal}. */\nexport interface OnboardingConfig {\n /** The site the tester was invited to — its origin (`window.location.host`). */\n readonly origin: string;\n /** The **server-derived** tier the redemption grants (from the invite status). */\n readonly tier: ReporterTier;\n /** Prefill the Continue-as fields from a previously stored display identity. */\n readonly defaults?: ReporterDisplay;\n /** Called with the self-provided display fields when the tester confirms. */\n readonly onConfirm: (display: ReporterDisplay) => void;\n /** Where to mount the modal host. Defaults to `document.body`. */\n readonly target?: HTMLElement;\n /** The document to build in. Defaults to the target's owner document. */\n readonly doc?: Document;\n}\n\n/** A mounted onboarding modal. */\nexport interface OnboardingModal {\n destroy(): void;\n readonly host: HTMLElement;\n}\n\n/** Minimal typed `createElement` helper — attributes plus string / node children. */\nfunction h<K extends keyof HTMLElementTagNameMap>(\n doc: Document,\n tag: K,\n props: Partial<Record<string, string>> = {},\n children: ReadonlyArray<Node | string> = [],\n): HTMLElementTagNameMap[K] {\n const node = doc.createElement(tag);\n for (const [name, value] of Object.entries(props)) {\n if (value !== undefined) node.setAttribute(name, value);\n }\n for (const child of children) {\n node.appendChild(typeof child === \"string\" ? doc.createTextNode(child) : child);\n }\n return node;\n}\n\n/** A trimmed, non-empty field or `undefined` — display fields are never blanks. */\nfunction field(value: string): string | undefined {\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\n/**\n * Render the onboarding modal into `target` (default `document.body`) and return a\n * handle to remove it. Confirming reads the name / email, fires `onConfirm` once\n * (further clicks are ignored while the caller redeems), and leaves teardown to\n * the caller so it can strip the token and mount the launcher first.\n */\nexport function createOnboardingModal(config: OnboardingConfig): OnboardingModal {\n const target = config.target ?? document.body;\n const doc = config.doc ?? target.ownerDocument;\n\n // Only one onboarding modal can exist at a time — an earlier one is removed\n // first, so a repeated trigger never stacks two dialogs.\n doc.querySelector(`[${ONBOARDING_ATTRIBUTE}]`)?.remove();\n\n const host = h(doc, \"div\", { [ONBOARDING_ATTRIBUTE]: \"\" });\n const shadow = host.attachShadow({ mode: \"open\" });\n\n const style = doc.createElement(\"style\");\n style.textContent = ONBOARDING_STYLES;\n shadow.appendChild(style);\n\n // Header — brand + \"invite link\" chip.\n const head = h(doc, \"div\", { class: \"fb-ob-head\" }, [\n h(doc, \"span\", { class: \"fb-ob-mark\" }),\n h(doc, \"span\", { class: \"fb-ob-brand\" }, [\"Fixback\"]),\n h(doc, \"span\", { class: \"fb-ob-chip\" }, [\"invite link\"]),\n ]);\n\n // Lede — names the site by its origin (no inviter identity is fetched or shown).\n const lede = h(doc, \"p\", { class: \"fb-ob-lede\" }, [\n \"You've been invited to give feedback on \",\n h(doc, \"strong\", {}, [config.origin]),\n \". Point, mark up, send.\",\n ]);\n\n // Info card — the site and the server-derived access tier.\n const siteRow = h(doc, \"div\", { class: \"fb-ob-row\" }, [\n h(doc, \"span\", { class: \"fb-ob-rowlabel\" }, [\"SITE\"]),\n h(doc, \"span\", { class: \"fb-ob-site\" }, [config.origin]),\n ]);\n const accessRow = h(doc, \"div\", { class: \"fb-ob-row\" }, [\n h(doc, \"span\", { class: \"fb-ob-rowlabel\" }, [\"ACCESS\"]),\n h(doc, \"span\", { class: \"fb-ob-tier\" }, [TIER_LABEL[config.tier]]),\n h(doc, \"span\", { class: \"fb-ob-tiernote\" }, [TIER_NOTE[config.tier]]),\n ]);\n const card = h(doc, \"div\", { class: \"fb-ob-card\" }, [siteRow, accessRow]);\n\n // Continue-as fields.\n const nameInput = h(doc, \"input\", {\n class: \"fb-ob-input fb-ob-name\",\n type: \"text\",\n \"aria-label\": \"Name\",\n placeholder: \"Name\",\n });\n const emailInput = h(doc, \"input\", {\n class: \"fb-ob-input fb-ob-email\",\n type: \"email\",\n \"aria-label\": \"Email\",\n placeholder: \"email@example.com\",\n });\n if (config.defaults?.name) nameInput.value = config.defaults.name;\n if (config.defaults?.email) emailInput.value = config.defaults.email;\n\n const privacy = h(doc, \"div\", { class: \"fb-ob-privacy\" });\n const icon = h(doc, \"span\");\n icon.innerHTML = SHIELD_ICON;\n privacy.appendChild(icon.firstChild ?? doc.createTextNode(\"\"));\n privacy.appendChild(\n h(doc, \"span\", {}, [\n h(doc, \"strong\", {}, [\"Private by default.\"]),\n \" Anything you type is masked before it leaves your browser.\",\n ]),\n );\n\n const body = h(doc, \"div\", { class: \"fb-ob-body\" }, [\n h(doc, \"div\", { class: \"fb-ob-title\" }, [\"You're invited to give feedback\"]),\n lede,\n card,\n h(doc, \"label\", { class: \"fb-ob-fieldlabel\", for: \"fb-ob-name-input\" }, [\"Continue as\"]),\n h(doc, \"div\", { class: \"fb-ob-fields\" }, [nameInput, emailInput]),\n privacy,\n ]);\n nameInput.id = \"fb-ob-name-input\";\n\n const confirm = h(doc, \"button\", { type: \"button\", class: \"fb-ob-confirm\" }, [\n \"Start giving feedback\",\n ]);\n const foot = h(doc, \"div\", { class: \"fb-ob-foot\" }, [confirm]);\n\n const panel = h(\n doc,\n \"div\",\n { class: \"fb-ob-panel\", role: \"dialog\", \"aria-modal\": \"true\", \"aria-label\": \"Fixback invite\" },\n [head, body, foot],\n );\n const backdrop = h(doc, \"div\", { class: \"fb-ob-backdrop\" }, [panel]);\n shadow.appendChild(backdrop);\n\n let confirmed = false;\n confirm.addEventListener(\"click\", () => {\n if (confirmed) return;\n confirmed = true;\n confirm.disabled = true;\n const display: ReporterDisplay = {\n ...(field(nameInput.value) ? { name: field(nameInput.value) } : {}),\n ...(field(emailInput.value) ? { email: field(emailInput.value) } : {}),\n };\n config.onConfirm(display);\n });\n\n target.appendChild(host);\n return {\n host,\n destroy: () => host.remove(),\n };\n}\n","/**\n * The draw surface — arrow / box / pen / text marking over the captured frame\n * (spec 0003 §B, the Reporter prototype's draw toolbar). It is the interactive\n * editor behind the overlay's draw mode: a tool, a live preview, a committed list\n * of {@link Mark}s with undo, and an SVG it renders into. Marks are recorded in\n * **screenshot (viewport) coordinates** so they compose over the full masked\n * screenshot; the overlay reads {@link DrawSurface.marks} when the Reporter\n * attaches.\n *\n * Pointer listeners are capture-phase on the document, so a stroke is intercepted\n * before the host page; a stroke that begins on interactive chrome (the toolbar,\n * the label input) is ignored so the toolbar stays clickable. The surface draws\n * nothing into the host page — only into the SVG the overlay hands it inside its\n * own Shadow DOM.\n */\n\nimport {\n arrowHeadPoints,\n MARK_COLOR,\n type ArrowMark,\n type BoxMark,\n type DrawTool,\n type Mark,\n type PenMark,\n type Point,\n} from \"./annotation\";\n\n/** A mark mid-drag — always a stroke (arrow/box/pen); text is entered via the input. */\ntype StrokePreview = ArrowMark | BoxMark | PenMark;\n\nconst SVG_NS = \"http://www.w3.org/2000/svg\";\n\n/** Stroke width for every mark, in px (matches the prototype). */\nconst STROKE_WIDTH = \"3\";\n\n/** The smallest arrow/box drag that commits, in px — smaller is a stray click. */\nconst MIN_DRAG = 4;\n\n/** The label font, vendored to match the overlay's Signal sans stack. */\nconst LABEL_FONT =\n '600 16px \"IBM Plex Sans\", system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif';\n\n/** Options for {@link startDrawSurface}. */\nexport interface DrawSurfaceOptions {\n /** Document to attach to. Defaults to the global `document`. */\n readonly doc?: Document;\n /** The (viewport-filling) SVG the surface renders committed marks + preview into. */\n readonly svg: SVGSVGElement;\n /** The inline label input the text tool shows, positions, and reads. */\n readonly textInput: HTMLInputElement;\n /** Mark colour. Defaults to {@link MARK_COLOR}. */\n readonly color?: string;\n /** Marks to seed from, so re-entering draw continues an existing annotation. */\n readonly initialMarks?: ReadonlyArray<Mark>;\n /** Notified with a fresh snapshot whenever the committed marks change. */\n readonly onChange?: (marks: ReadonlyArray<Mark>) => void;\n}\n\n/** A running draw surface. */\nexport interface DrawSurface {\n /** Switch the active tool. */\n setTool(tool: DrawTool): void;\n /** The active tool. */\n getTool(): DrawTool;\n /** Remove the most recently committed mark. */\n undo(): void;\n /** A snapshot of the committed marks, in screenshot coordinates. */\n marks(): ReadonlyArray<Mark>;\n /** Detach all listeners and hide the label input. */\n stop(): void;\n}\n\n/**\n * Begin drawing. The returned surface tracks a tool, a live preview stroke, and the\n * committed marks; `marks()` snapshots them for the overlay's Send assembly. Text\n * labels are entered through the provided input (Enter commits, Escape/blur-empty\n * discards). `stop()` detaches everything.\n */\nexport function startDrawSurface(options: DrawSurfaceOptions): DrawSurface {\n const doc = options.doc ?? document;\n const { svg, textInput } = options;\n const color = options.color ?? MARK_COLOR;\n\n let tool: DrawTool = \"arrow\";\n let committed: Mark[] = options.initialMarks ? options.initialMarks.slice() : [];\n let preview: StrokePreview | null = null;\n let drawing = false;\n let pendingText: Point | null = null;\n let stopped = false;\n\n function emitChange(): void {\n options.onChange?.(committed.slice());\n }\n\n function point(event: MouseEvent): Point {\n return { x: Math.round(event.clientX), y: Math.round(event.clientY) };\n }\n\n /** Does the event begin on interactive chrome (a button/input in the path)? */\n function hitsChrome(event: Event): boolean {\n const path =\n typeof event.composedPath === \"function\" ? event.composedPath() : [event.target];\n for (const node of path) {\n if (node === doc) break;\n if (node instanceof Element) {\n const tag = node.tagName;\n if (tag === \"BUTTON\" || tag === \"INPUT\" || tag === \"TEXTAREA\") return true;\n }\n }\n return false;\n }\n\n function meaningful(mark: Mark): boolean {\n if (mark.type === \"pen\") return mark.points.length > 1;\n if (mark.type === \"text\") return mark.text.trim().length > 0;\n return Math.abs(mark.x1 - mark.x0) > MIN_DRAG || Math.abs(mark.y1 - mark.y0) > MIN_DRAG;\n }\n\n function onDown(event: MouseEvent): void {\n if (stopped || drawing || hitsChrome(event)) return;\n const p = point(event);\n if (tool === \"text\") {\n openText(p);\n return;\n }\n if (event.cancelable) event.preventDefault();\n drawing = true;\n preview =\n tool === \"pen\"\n ? { type: \"pen\", points: [p], color }\n : { type: tool, x0: p.x, y0: p.y, x1: p.x, y1: p.y, color };\n render();\n }\n\n function onMove(event: MouseEvent): void {\n if (stopped || !drawing || !preview) return;\n const p = point(event);\n if (preview.type === \"pen\") {\n preview = { type: \"pen\", points: [...preview.points, p], color: preview.color };\n } else {\n preview = { ...preview, x1: p.x, y1: p.y };\n }\n render();\n }\n\n function onUp(): void {\n if (stopped || !drawing) return;\n drawing = false;\n const pv = preview;\n preview = null;\n if (pv && meaningful(pv)) {\n committed = [...committed, pv];\n emitChange();\n }\n render();\n }\n\n function openText(p: Point): void {\n pendingText = p;\n textInput.value = \"\";\n textInput.style.display = \"block\";\n textInput.style.left = `${p.x}px`;\n textInput.style.top = `${p.y}px`;\n textInput.onkeydown = onTextKey;\n textInput.onblur = commitText;\n // Focus on the next tick so the current click doesn't immediately blur it.\n setTimeout(() => {\n try {\n textInput.focus();\n } catch {\n /* focus is best-effort — never throw into the host page */\n }\n }, 0);\n }\n\n function onTextKey(event: KeyboardEvent): void {\n if (event.key === \"Enter\") {\n event.preventDefault();\n event.stopPropagation();\n commitText();\n } else if (event.key === \"Escape\") {\n event.preventDefault();\n event.stopPropagation();\n discardText();\n }\n }\n\n function commitText(): void {\n if (!pendingText) return;\n const text = textInput.value.trim();\n const at = pendingText;\n discardText();\n if (text.length > 0) {\n committed = [...committed, { type: \"text\", x: at.x, y: at.y, text, color }];\n emitChange();\n render();\n }\n }\n\n function discardText(): void {\n pendingText = null;\n textInput.onkeydown = null;\n textInput.onblur = null;\n textInput.value = \"\";\n textInput.style.display = \"none\";\n }\n\n function render(): void {\n while (svg.firstChild) svg.removeChild(svg.firstChild);\n for (const mark of committed) svg.appendChild(renderMark(doc, mark));\n if (preview) svg.appendChild(renderMark(doc, preview));\n }\n\n function stop(): void {\n if (stopped) return;\n stopped = true;\n doc.removeEventListener(\"mousedown\", onDown, true);\n doc.removeEventListener(\"mousemove\", onMove, true);\n doc.removeEventListener(\"mouseup\", onUp, true);\n discardText();\n }\n\n doc.addEventListener(\"mousedown\", onDown, true);\n doc.addEventListener(\"mousemove\", onMove, true);\n doc.addEventListener(\"mouseup\", onUp, true);\n render();\n\n return {\n setTool: (next) => {\n tool = next;\n },\n getTool: () => tool,\n undo: () => {\n if (committed.length === 0) return;\n committed = committed.slice(0, -1);\n emitChange();\n render();\n },\n marks: () => committed.slice(),\n stop,\n };\n}\n\n/** Build the SVG element(s) for one mark, in the mark's own coordinate space. */\nfunction renderMark(doc: Document, mark: Mark): SVGElement {\n switch (mark.type) {\n case \"box\": {\n const rect = svgEl(doc, \"rect\", {\n x: String(Math.min(mark.x0, mark.x1)),\n y: String(Math.min(mark.y0, mark.y1)),\n width: String(Math.abs(mark.x1 - mark.x0)),\n height: String(Math.abs(mark.y1 - mark.y0)),\n rx: \"2\",\n fill: \"none\",\n stroke: mark.color,\n \"stroke-width\": STROKE_WIDTH,\n });\n return rect;\n }\n case \"pen\": {\n return svgEl(doc, \"polyline\", {\n points: mark.points.map((p) => `${p.x},${p.y}`).join(\" \"),\n fill: \"none\",\n stroke: mark.color,\n \"stroke-width\": STROKE_WIDTH,\n \"stroke-linecap\": \"round\",\n \"stroke-linejoin\": \"round\",\n });\n }\n case \"text\": {\n const text = svgEl(doc, \"text\", {\n x: String(mark.x),\n y: String(mark.y),\n fill: mark.color,\n style: `font:${LABEL_FONT}`,\n });\n text.textContent = mark.text;\n return text;\n }\n case \"arrow\": {\n const group = svgEl(doc, \"g\", {});\n group.appendChild(\n svgEl(doc, \"line\", {\n x1: String(mark.x0),\n y1: String(mark.y0),\n x2: String(mark.x1),\n y2: String(mark.y1),\n stroke: mark.color,\n \"stroke-width\": STROKE_WIDTH,\n \"stroke-linecap\": \"round\",\n }),\n );\n const head = arrowHeadPoints(mark.x0, mark.y0, mark.x1, mark.y1);\n group.appendChild(\n svgEl(doc, \"polygon\", {\n points: head.map((p) => `${p.x},${p.y}`).join(\" \"),\n fill: mark.color,\n }),\n );\n return group;\n }\n }\n}\n\n/** Create a namespaced SVG element with the given attributes. */\nfunction svgEl(\n doc: Document,\n tag: string,\n attrs: Record<string, string>,\n): SVGElement {\n const node = doc.createElementNS(SVG_NS, tag) as SVGElement;\n for (const [name, value] of Object.entries(attrs)) node.setAttribute(name, value);\n return node;\n}\n","/**\n * Region-capture — drag a rectangle over the page to focus the report on one area\n * (spec 0003 §B, the Reporter prototype's capture layer). It produces a `region`\n * rect in screenshot (viewport) coordinates; Escape or a too-small drag abandons\n * it. One of the overlay's three composable, optional marking layers, alongside\n * element-pick and draw.\n *\n * Listeners are attached in the **capture phase** so the drag is intercepted before\n * any host-page handler sees it. It is armed deliberately — the overlay lays a\n * full-screen capture layer over the page (and hides the panel) while it runs — so\n * it captures whatever the Reporter drags, its own layer included. Purely a producer\n * of a `Rect`: it draws nothing; the overlay renders the live selection box from\n * `onProgress`.\n */\n\nimport { normalizeRect, type Rect } from \"./annotation\";\n\n/** Options for {@link startRegionCapture}. */\nexport interface RegionCaptureOptions {\n /** Document to attach to. Defaults to the global `document`. */\n readonly doc?: Document;\n /** Called on each drag update with the current rect (to draw a selection box). */\n readonly onProgress?: (rect: Rect) => void;\n /** Called with the captured rect when the drag ends large enough to keep. */\n readonly onComplete: (rect: Rect) => void;\n /** Called when the capture is abandoned (Escape, or a drag below the floor). */\n readonly onCancel?: () => void;\n /** Minimum width and height, in px, to count as a capture. Defaults to 12. */\n readonly minSize?: number;\n}\n\n/** A running region-capture; call {@link RegionCapture.stop} to detach it. */\nexport interface RegionCapture {\n stop(): void;\n}\n\n/** The smallest drag that counts as a region (matches the prototype's 12px floor). */\nconst DEFAULT_MIN_SIZE = 12;\n\n/**\n * Enter region-capture mode: the next drag on the host page draws out a rectangle,\n * reported live through `onProgress` and committed through `onComplete` on release\n * (when it clears the size floor). Escape, or a drag too small to be meaningful,\n * calls `onCancel`. `stop()` (called automatically on complete/cancel) detaches all\n * listeners.\n */\nexport function startRegionCapture(options: RegionCaptureOptions): RegionCapture {\n const doc = options.doc ?? document;\n const minSize = options.minSize ?? DEFAULT_MIN_SIZE;\n let start: { x: number; y: number } | null = null;\n let stopped = false;\n\n function onDown(event: MouseEvent): void {\n if (stopped || start) return;\n start = { x: event.clientX, y: event.clientY };\n if (event.cancelable) event.preventDefault();\n }\n\n function onMove(event: MouseEvent): void {\n if (stopped || !start) return;\n options.onProgress?.(normalizeRect(start.x, start.y, event.clientX, event.clientY));\n }\n\n function onUp(event: MouseEvent): void {\n if (stopped || !start) return;\n const rect = normalizeRect(start.x, start.y, event.clientX, event.clientY);\n start = null;\n stop();\n if (rect.width < minSize || rect.height < minSize) {\n options.onCancel?.();\n return;\n }\n options.onComplete(rect);\n }\n\n function onKey(event: KeyboardEvent): void {\n if (stopped) return;\n if (event.key === \"Escape\") {\n event.preventDefault();\n stop();\n options.onCancel?.();\n }\n }\n\n function stop(): void {\n if (stopped) return;\n stopped = true;\n doc.removeEventListener(\"mousedown\", onDown, true);\n doc.removeEventListener(\"mousemove\", onMove, true);\n doc.removeEventListener(\"mouseup\", onUp, true);\n doc.removeEventListener(\"keydown\", onKey, true);\n start = null;\n }\n\n doc.addEventListener(\"mousedown\", onDown, true);\n doc.addEventListener(\"mousemove\", onMove, true);\n doc.addEventListener(\"mouseup\", onUp, true);\n doc.addEventListener(\"keydown\", onKey, true);\n\n return { stop };\n}\n","/**\n * The report overlay's isolated styles, built to the frozen Signal Reporter\n * overlay (`docs/design/Fixback Visual Identity.dc.html`, badge `1d`). Like the\n * launcher's styles these live inside the overlay's Shadow DOM, so nothing here\n * reaches the host page and nothing on the host page reaches in. Token values are\n * vendored Signal tokens (copied from `packages/ui/src/tokens.css`, not imported —\n * the SDK must not depend on `@fixback/ui` at runtime). Keep them in sync by value.\n */\nexport const OVERLAY_STYLES = `\n:host {\n --fb-color-accent: #2f6fed;\n --fb-color-accent-hover: #245fd0;\n --fb-color-on-emphasis: #ffffff;\n --fb-color-ink: #0f1720;\n --fb-color-text: #1a2530;\n --fb-color-muted: #5a6875;\n --fb-color-faint: #9aa7b2;\n --fb-color-border: #e0e6ec;\n --fb-color-border-soft: #e6ebf0;\n --fb-color-surface: #ffffff;\n --fb-color-bug: #e5484d;\n --fb-color-bug-bg: #fdecec;\n --fb-color-impr: #2f6fed;\n --fb-color-impr-bg: #eaf1fe;\n --fb-color-idea: #8b5cf6;\n --fb-color-idea-bg: #f2ecfe;\n --fb-color-success: #2f9e5b;\n --fb-font-sans: \"IBM Plex Sans\", system-ui, -apple-system, \"Segoe UI\", Roboto,\n Helvetica, Arial, sans-serif;\n --fb-font-mono: \"IBM Plex Mono\", ui-monospace, \"SFMono-Regular\", Menlo, Consolas,\n monospace;\n\n display: block;\n color: var(--fb-color-text);\n font-family: var(--fb-font-sans);\n font-size: 13px;\n line-height: 1.45;\n -webkit-font-smoothing: antialiased;\n}\n\n* { box-sizing: border-box; }\n\n.fb-ov-panel {\n width: 300px;\n max-width: calc(100vw - 40px);\n background: var(--fb-color-surface);\n border: 1px solid var(--fb-color-border);\n border-radius: 13px;\n box-shadow: 0 18px 44px rgba(20, 40, 70, 0.2);\n overflow: hidden;\n}\n\n.fb-ov-head {\n display: flex;\n align-items: center;\n gap: 9px;\n padding: 12px 14px;\n border-bottom: 1px solid var(--fb-color-border-soft);\n}\n.fb-ov-mark-sq {\n width: 13px;\n height: 13px;\n border-radius: 4px;\n background: var(--fb-color-accent);\n flex: none;\n}\n.fb-ov-brand { font-size: 14px; font-weight: 600; color: var(--fb-color-ink); }\n.fb-ov-tier {\n flex: none;\n font-family: var(--fb-font-mono);\n font-size: 9.5px;\n color: var(--fb-color-faint);\n border: 1px solid var(--fb-color-border-soft);\n border-radius: 5px;\n padding: 1px 6px;\n}\n.fb-ov-close {\n margin-left: auto;\n border: 0;\n background: none;\n color: var(--fb-color-faint);\n font-size: 16px;\n line-height: 1;\n padding: 2px 4px;\n cursor: pointer;\n border-radius: 6px;\n}\n.fb-ov-close:hover { color: var(--fb-color-muted); background: #f4f7fa; }\n\n.fb-ov-tabs { display: flex; gap: 6px; padding: 12px 14px 6px; }\n.fb-ov-tab {\n flex: 1;\n text-align: center;\n font-size: 11px;\n font-weight: 600;\n padding: 6px;\n border-radius: 7px;\n border: 1px solid var(--fb-color-border);\n background: var(--fb-color-surface);\n color: var(--fb-color-muted);\n cursor: pointer;\n font-family: inherit;\n}\n.fb-ov-tab:hover { border-color: #cfd8e2; }\n.fb-ov-tab.is-active[data-kind=\"bug\"] {\n background: var(--fb-color-bug-bg); color: var(--fb-color-bug); border-color: transparent;\n}\n.fb-ov-tab.is-active[data-kind=\"improvement\"] {\n background: var(--fb-color-impr-bg); color: var(--fb-color-impr); border-color: transparent;\n}\n.fb-ov-tab.is-active[data-kind=\"idea\"] {\n background: var(--fb-color-idea-bg); color: var(--fb-color-idea); border-color: transparent;\n}\n\n.fb-ov-mark {\n margin: 10px 14px;\n min-height: 52px;\n border-radius: 9px;\n border: 1px solid var(--fb-color-border-soft);\n background: repeating-linear-gradient(135deg, #f4f7fa, #f4f7fa 7px, #eaeff4 7px, #eaeff4 14px);\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n justify-content: center;\n gap: 6px;\n padding: 10px 12px;\n}\n.fb-ov-selector {\n display: none;\n max-width: 100%;\n font-family: var(--fb-font-mono);\n font-size: 10px;\n color: var(--fb-color-on-emphasis);\n background: var(--fb-color-accent);\n padding: 3px 8px;\n border-radius: 5px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.fb-ov-mark.has-element .fb-ov-selector { display: inline-block; }\n.fb-ov-mark-caption { font-size: 10px; color: var(--fb-color-faint); font-family: var(--fb-font-mono); }\n\n.fb-ov-comment {\n display: block;\n width: calc(100% - 28px);\n margin: 0 14px 10px;\n min-height: 62px;\n resize: vertical;\n font-family: inherit;\n font-size: 13px;\n color: var(--fb-color-text);\n border: 1px solid var(--fb-color-border-soft);\n border-radius: 9px;\n padding: 10px 11px;\n}\n.fb-ov-comment::placeholder { color: var(--fb-color-faint); }\n.fb-ov-comment:focus-visible { outline: 2px solid var(--fb-color-accent); outline-offset: 1px; }\n\n.fb-ov-status { padding: 0 14px; font-size: 11px; min-height: 0; }\n.fb-ov-status.is-error { color: var(--fb-color-bug); }\n\n.fb-ov-tools { display: flex; align-items: center; gap: 7px; padding: 8px 14px 14px; }\n.fb-ov-pickbtn,\n.fb-ov-capturebtn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-family: inherit;\n font-size: 12px;\n color: var(--fb-color-muted);\n background: var(--fb-color-surface);\n border: 1px solid var(--fb-color-border);\n border-radius: 8px;\n padding: 8px 11px;\n cursor: pointer;\n}\n.fb-ov-capturebtn { padding: 8px 9px; }\n.fb-ov-pickbtn:hover,\n.fb-ov-capturebtn:hover { border-color: #cfd8e2; }\n.fb-ov-pickbtn.is-active,\n.fb-ov-capturebtn.is-active,\n.fb-ov-capturebtn.is-attached {\n color: var(--fb-color-accent);\n border-color: var(--fb-color-accent);\n background: var(--fb-color-impr-bg);\n}\n.fb-ov-pickbtn__glyph { font-size: 14px; line-height: 1; }\n.fb-ov-icon { display: block; }\n\n.fb-ov-send {\n margin-left: auto;\n font-family: inherit;\n font-size: 13px;\n font-weight: 600;\n color: var(--fb-color-on-emphasis);\n background: var(--fb-color-accent);\n border: 0;\n border-radius: 8px;\n padding: 9px 18px;\n cursor: pointer;\n}\n.fb-ov-send:hover { background: var(--fb-color-accent-hover); }\n.fb-ov-send:disabled { opacity: 0.6; cursor: default; }\n\n.fb-ov-done { display: none; padding: 24px 18px; text-align: center; }\n.fb-ov-panel.is-sent .fb-ov-form { display: none; }\n.fb-ov-panel.is-sent .fb-ov-done { display: block; }\n.fb-ov-done__check {\n width: 40px; height: 40px; margin: 0 auto 12px;\n border-radius: 50%;\n background: #e7f6ee; color: var(--fb-color-success);\n display: flex; align-items: center; justify-content: center;\n font-size: 20px; font-weight: 700;\n}\n.fb-ov-done__title { font-size: 15px; font-weight: 600; color: var(--fb-color-ink); }\n.fb-ov-done__sub { font-size: 12px; color: var(--fb-color-muted); margin-top: 4px; }\n\n/* The panel is hidden while any full-screen marking layer is active. */\n.fb-ov-panel.is-marking { visibility: hidden; }\n\n.fb-ov-pick,\n.fb-ov-capture,\n.fb-ov-draw { position: fixed; inset: 0; z-index: 2147483002; display: none; }\n.fb-ov-pick.is-visible,\n.fb-ov-capture.is-visible,\n.fb-ov-draw.is-visible { display: block; }\n\n/* Element-pick highlight layer — visual only, never intercepts host events. */\n.fb-ov-pick { pointer-events: none; }\n.fb-ov-highlight {\n position: absolute;\n border: 2px dashed var(--fb-color-accent);\n border-radius: 6px;\n box-shadow: 0 0 0 3px rgba(47, 111, 237, 0.14);\n transition: all 60ms ease;\n}\n.fb-ov-hint {\n position: absolute;\n top: 16px;\n left: 50%;\n transform: translateX(-50%);\n font-family: var(--fb-font-mono);\n font-size: 11px;\n color: var(--fb-color-on-emphasis);\n background: var(--fb-color-ink);\n padding: 6px 12px;\n border-radius: 7px;\n box-shadow: 0 8px 20px rgba(20, 40, 70, 0.25);\n}\n\n/* Region-capture layer — a dim wash with a live selection box. */\n.fb-ov-capture { cursor: crosshair; }\n.fb-ov-capture-dim { position: absolute; inset: 0; background: rgba(15, 23, 32, 0.28); }\n.fb-ov-capture-sel {\n position: absolute;\n border: 2px solid var(--fb-color-accent);\n border-radius: 4px;\n box-shadow: 0 0 0 100vmax rgba(15, 23, 32, 0.32);\n}\n\n/* Draw layer — the mark SVG, the region frame, the label input, and the toolbar. */\n.fb-ov-draw-svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none; }\n.fb-ov-draw-frame {\n position: absolute;\n border: 1px solid rgba(47, 111, 237, 0.5);\n border-radius: 4px;\n box-shadow: 0 0 0 100vmax rgba(15, 23, 32, 0.18);\n pointer-events: none;\n}\n.fb-ov-draw-text {\n position: absolute;\n display: none;\n font-family: var(--fb-font-sans);\n font-size: 15px;\n font-weight: 600;\n color: var(--fb-color-bug);\n background: rgba(255, 255, 255, 0.92);\n border: 1px dashed var(--fb-color-bug);\n border-radius: 4px;\n padding: 2px 6px;\n outline: none;\n z-index: 3;\n}\n.fb-ov-draw-toolbar {\n position: fixed;\n left: 50%;\n bottom: 26px;\n transform: translateX(-50%);\n display: flex;\n align-items: center;\n gap: 5px;\n background: var(--fb-color-ink);\n border-radius: 12px;\n padding: 7px;\n box-shadow: 0 16px 40px rgba(15, 40, 70, 0.4);\n}\n.fb-ov-drawtool {\n width: 34px;\n height: 34px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 0;\n border-radius: 8px;\n background: transparent;\n color: #c4ccd4;\n cursor: pointer;\n font-family: inherit;\n}\n.fb-ov-drawtool:hover { background: rgba(255, 255, 255, 0.08); color: #fff; }\n.fb-ov-drawtool.is-active { background: var(--fb-color-accent); color: var(--fb-color-on-emphasis); }\n.fb-ov-drawtool__t { font-weight: 700; font-size: 14px; line-height: 1; }\n.fb-ov-draw-divider { width: 1px; height: 22px; background: #2a343e; margin: 0 3px; }\n.fb-ov-draw-undo {\n width: 34px;\n height: 34px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 0;\n border-radius: 8px;\n background: transparent;\n color: #c4ccd4;\n cursor: pointer;\n}\n.fb-ov-draw-undo:hover:not(:disabled) { background: rgba(255, 255, 255, 0.08); color: #fff; }\n.fb-ov-draw-undo:disabled { opacity: 0.4; cursor: default; }\n.fb-ov-draw-cancel {\n height: 34px;\n padding: 0 12px;\n border: 0;\n border-radius: 8px;\n background: transparent;\n color: var(--fb-color-faint);\n font-family: inherit;\n font-size: 12.5px;\n cursor: pointer;\n}\n.fb-ov-draw-cancel:hover { color: #fff; }\n.fb-ov-draw-attach {\n height: 34px;\n padding: 0 15px;\n border: 0;\n border-radius: 8px;\n background: var(--fb-color-accent);\n color: var(--fb-color-on-emphasis);\n font-family: inherit;\n font-size: 12.5px;\n font-weight: 600;\n cursor: pointer;\n}\n.fb-ov-draw-attach:hover { background: var(--fb-color-accent-hover); }\n\n@media (prefers-reduced-motion: reduce) {\n .fb-ov-highlight { transition: none; }\n}\n`;\n","import type { DrawTool, Mark, Rect } from \"./annotation\";\nimport type { IdentityInputs, ProjectGate, ReporterTier } from \"./boot\";\nimport type { Breadcrumb } from \"./breadcrumbs\";\nimport { startDrawSurface, type DrawSurface } from \"./draw-surface\";\nimport {\n describeElement,\n startElementPicker,\n type ElementPicker,\n type ElementPickerOptions,\n} from \"./element-picker\";\nimport {\n assembleContent,\n collectEnvironment,\n type IssueKind,\n type SelectedElement,\n} from \"./report\";\nimport { startRegionCapture, type RegionCapture } from \"./region-capture\";\nimport { OVERLAY_STYLES } from \"./overlay-styles\";\nimport { runBeforeSend, type BeforeSend } from \"./scrub\";\nimport { captureView, extensionFor, type Capture, type CaptureOptions } from \"./screenshot\";\nimport { submitReport, type SubmitInput, type SubmitResult } from \"./submit\";\nimport { SDK_VERSION } from \"./version\";\n\n/** Marks the overlay's host element in the light DOM (skipped by capture/picker). */\nexport const OVERLAY_ATTRIBUTE = \"data-fixback-overlay\";\n\n/** The SVG namespace, for the draw surface's mark layer. */\nconst SVG_NS = \"http://www.w3.org/2000/svg\";\n\n/** The Kind tabs, in order — value is the ingest Kind, label is the overlay text. */\nconst KIND_TABS: ReadonlyArray<{ value: IssueKind; label: string }> = [\n { value: \"bug\", label: \"Bug\" },\n { value: \"improvement\", label: \"Improve\" },\n { value: \"idea\", label: \"Idea\" },\n];\n\n/** The draw tools, in toolbar order (spec §B, the Reporter prototype). */\nconst DRAW_TOOLS: ReadonlyArray<{ tool: DrawTool; label: string }> = [\n { tool: \"arrow\", label: \"Arrow\" },\n { tool: \"box\", label: \"Box\" },\n { tool: \"pen\", label: \"Pen\" },\n { tool: \"text\", label: \"Text label\" },\n];\n\n/** How long the confirmation shows before the overlay closes itself. */\nconst AUTO_CLOSE_MS = 1800;\n\ntype CaptureViewFn = (options?: CaptureOptions) => Promise<Capture | null>;\ntype SubmitReportFn = (\n apiUrl: string,\n input: SubmitInput,\n fetchImpl?: typeof fetch,\n) => Promise<SubmitResult>;\ntype StartPickerFn = (options: ElementPickerOptions) => ElementPicker;\n\n/** Injectable collaborators, defaulted to the real implementations. */\nexport interface OverlayDeps {\n readonly captureView: CaptureViewFn;\n readonly submitReport: SubmitReportFn;\n readonly startElementPicker: StartPickerFn;\n}\n\n/** A read-only view of the trace buffer the overlay attaches to a report. */\nexport interface BreadcrumbSource {\n snapshot(): readonly Breadcrumb[];\n}\n\n/** Configuration for {@link createOverlay}. */\nexport interface OverlayConfig {\n readonly apiUrl: string;\n readonly key: string;\n readonly identity?: IdentityInputs;\n /**\n * The Project's Gate, forwarded verbatim from the boot answer. Reserved for\n * Gate-aware launcher / redemption behaviour (spec §A/§F); the overlay itself\n * does not read it yet, but `init` plumbs it through here (ticket #85 / §G).\n */\n readonly gate?: ProjectGate;\n /**\n * The Reporter's **server-derived** trust tier from the boot answer, or `null`\n * when a presented identity was refused. Rendered as a **display-only** chip in\n * the overlay header (spec §G) — never a trust signal, never read for a decision,\n * and never sent back to the server. When `null`/absent no tier is asserted.\n */\n readonly tier?: ReporterTier | null;\n readonly sdkVersion?: string;\n /** Where to mount the overlay host. Defaults to `document.body`. */\n readonly target?: HTMLElement;\n readonly doc?: Document;\n readonly win?: Window;\n readonly deps?: Partial<OverlayDeps>;\n /** The trace buffer whose snapshot rides on each report (spec §C). */\n readonly buffer?: BreadcrumbSource | null;\n /** Per-project client scrub hook, run at the `beforeSend` choke point. */\n readonly beforeSend?: BeforeSend;\n /** Run the built-in default scrubbers. Defaults to `true` (private-by-default). */\n readonly scrub?: boolean;\n}\n\n/** A mounted overlay the launcher opens. */\nexport interface OverlayController {\n open(): void;\n close(): void;\n destroy(): void;\n readonly isOpen: boolean;\n}\n\n/** Which full-screen marking mode is active, if any. */\ntype MarkMode = \"compose\" | \"picking\" | \"capturing\" | \"drawing\";\n\ninterface Refs {\n readonly panel: HTMLElement;\n readonly tabs: Map<IssueKind, HTMLButtonElement>;\n readonly mark: HTMLElement;\n readonly selector: HTMLElement;\n readonly caption: HTMLElement;\n readonly comment: HTMLTextAreaElement;\n readonly status: HTMLElement;\n readonly pickBtn: HTMLButtonElement;\n readonly captureBtn: HTMLButtonElement;\n readonly sendBtn: HTMLButtonElement;\n readonly highlight: HTMLElement;\n readonly pickLayer: HTMLElement;\n readonly captureLayer: HTMLElement;\n readonly selBox: HTMLElement;\n readonly drawLayer: HTMLElement;\n readonly drawSvg: SVGSVGElement;\n readonly drawText: HTMLInputElement;\n readonly drawFrame: HTMLElement;\n readonly drawTools: Map<DrawTool, HTMLButtonElement>;\n readonly undoBtn: HTMLButtonElement;\n}\n\nfunction h<K extends keyof HTMLElementTagNameMap>(\n doc: Document,\n tag: K,\n props: Partial<Record<string, string>> = {},\n children: ReadonlyArray<Node | string> = [],\n): HTMLElementTagNameMap[K] {\n const node = doc.createElement(tag);\n for (const [name, value] of Object.entries(props)) {\n if (value !== undefined) node.setAttribute(name, value);\n }\n for (const child of children) {\n node.appendChild(typeof child === \"string\" ? doc.createTextNode(child) : child);\n }\n return node;\n}\n\n/** Create a namespaced SVG element with attributes and optional SVG children. */\nfunction svgNode(\n doc: Document,\n tag: string,\n attrs: Record<string, string> = {},\n children: ReadonlyArray<SVGElement> = [],\n): SVGElement {\n const node = doc.createElementNS(SVG_NS, tag) as SVGElement;\n for (const [name, value] of Object.entries(attrs)) node.setAttribute(name, value);\n for (const child of children) node.appendChild(child);\n return node;\n}\n\n/**\n * Create the report overlay — the on-page panel a Reporter files a report from,\n * built to the frozen Signal Reporter prototype (`docs/design/Fixback Reporter.dc.html`).\n * It mounts lazily inside its own Shadow DOM (isolated from the host page, and\n * marked so the screenshot and element-picker skip it), opens on the launcher's\n * `fixback:launch` seam, and offers three **composable, optional** marking layers\n * over one full masked screenshot (spec §B): **element-pick**, **region-capture**\n * (drag), and **draw** (arrow / box / pen / text, with undo / cancel / attach). On\n * Send it captures the masked screenshot, assembles the structured Annotation\n * (`{ element?, region?, marks? }`, spec §D — marks stay vector, never baked into\n * the PNG), and submits to ingest — showing a confirmation on success and failing\n * quietly otherwise.\n */\nexport function createOverlay(config: OverlayConfig): OverlayController {\n const doc = config.doc ?? document;\n const win = config.win ?? doc.defaultView ?? window;\n const sdkVersion = config.sdkVersion ?? SDK_VERSION;\n const deps: OverlayDeps = {\n captureView: config.deps?.captureView ?? captureView,\n submitReport: config.deps?.submitReport ?? submitReport,\n startElementPicker: config.deps?.startElementPicker ?? startElementPicker,\n };\n\n let host: HTMLElement | null = null;\n let refs: Refs | null = null;\n let picker: ElementPicker | null = null;\n let regionCapture: RegionCapture | null = null;\n let drawSurface: DrawSurface | null = null;\n let pendingRegion: Rect | null = null;\n let autoClose: ReturnType<typeof setTimeout> | null = null;\n let open = false;\n let mode: MarkMode = \"compose\";\n\n const state: {\n kind: IssueKind;\n selectedElement: SelectedElement | null;\n region: Rect | null;\n marks: Mark[];\n phase: \"compose\" | \"sending\" | \"sent\";\n } = { kind: \"bug\", selectedElement: null, region: null, marks: [], phase: \"compose\" };\n\n function setStatus(message: string, isError: boolean): void {\n if (!refs) return;\n refs.status.textContent = message;\n refs.status.classList.toggle(\"is-error\", isError);\n }\n\n function setKind(kind: IssueKind): void {\n state.kind = kind;\n if (!refs) return;\n for (const [value, button] of refs.tabs) {\n const active = value === kind;\n button.classList.toggle(\"is-active\", active);\n button.setAttribute(\"aria-pressed\", String(active));\n }\n }\n\n /** Show exactly one full-screen marking layer, hiding the panel while it runs. */\n function setMode(next: MarkMode): void {\n mode = next;\n if (!refs) return;\n const marking = next !== \"compose\";\n refs.panel.classList.toggle(\"is-marking\", marking);\n refs.pickLayer.classList.toggle(\"is-visible\", next === \"picking\");\n refs.captureLayer.classList.toggle(\"is-visible\", next === \"capturing\");\n refs.drawLayer.classList.toggle(\"is-visible\", next === \"drawing\");\n refs.pickBtn.classList.toggle(\"is-active\", next === \"picking\");\n refs.captureBtn.classList.toggle(\"is-active\", next === \"capturing\" || next === \"drawing\");\n }\n\n /** Reflect the current marking state in the compose panel's mark region. */\n function renderMarking(): void {\n if (!refs) return;\n const hasElement = !!state.selectedElement;\n const hasRegion = !!state.region;\n const markCount = state.marks.length;\n\n refs.mark.classList.toggle(\"has-element\", hasElement);\n refs.selector.textContent = hasElement ? state.selectedElement!.selector : \"\";\n\n refs.pickBtn.textContent = \"\";\n refs.pickBtn.append(\n glyph(doc),\n doc.createTextNode(hasElement ? \"Change element\" : \"Pick element\"),\n );\n refs.captureBtn.classList.toggle(\"is-attached\", hasRegion || markCount > 0);\n\n const bits: string[] = [];\n if (hasElement) bits.push(\"Element\");\n if (hasRegion) bits.push(\"Region\");\n if (markCount > 0) bits.push(`${markCount} mark${markCount === 1 ? \"\" : \"s\"}`);\n refs.caption.textContent = bits.length\n ? `${bits.join(\" · \")} · masked screenshot on Send`\n : \"Masked screenshot attached on Send\";\n }\n\n function positionHighlight(element: Element | null): void {\n if (!refs) return;\n if (!element) {\n refs.highlight.style.display = \"none\";\n return;\n }\n const rect = element.getBoundingClientRect();\n refs.highlight.style.display = \"block\";\n refs.highlight.style.left = `${rect.left}px`;\n refs.highlight.style.top = `${rect.top}px`;\n refs.highlight.style.width = `${rect.width}px`;\n refs.highlight.style.height = `${rect.height}px`;\n }\n\n // ---- element-pick -------------------------------------------------------\n\n function stopPicker(): void {\n picker?.stop();\n picker = null;\n }\n\n function endPick(): void {\n if (refs) refs.highlight.style.display = \"none\";\n stopPicker();\n if (mode === \"picking\") setMode(\"compose\");\n }\n\n function beginPick(): void {\n if (!refs) return;\n closeMarkingModes();\n setMode(\"picking\");\n picker = deps.startElementPicker({\n doc,\n onHover: positionHighlight,\n onPick: (element) => {\n state.selectedElement = describeElement(element);\n endPick();\n renderMarking();\n },\n onCancel: endPick,\n });\n }\n\n // ---- region-capture -----------------------------------------------------\n\n function positionSelBox(rect: Rect | null): void {\n if (!refs) return;\n const box = refs.selBox;\n if (!rect) {\n box.style.display = \"none\";\n return;\n }\n box.style.display = \"block\";\n box.style.left = `${rect.x}px`;\n box.style.top = `${rect.y}px`;\n box.style.width = `${rect.width}px`;\n box.style.height = `${rect.height}px`;\n }\n\n function stopRegionCapture(): void {\n regionCapture?.stop();\n regionCapture = null;\n }\n\n function beginCapture(): void {\n if (!refs) return;\n closeMarkingModes();\n positionSelBox(null);\n setMode(\"capturing\");\n regionCapture = startRegionCapture({\n doc,\n onProgress: positionSelBox,\n onComplete: (rect) => {\n stopRegionCapture();\n positionSelBox(null);\n beginDraw(rect);\n },\n onCancel: () => {\n stopRegionCapture();\n positionSelBox(null);\n setMode(\"compose\");\n },\n });\n }\n\n // ---- draw ---------------------------------------------------------------\n\n function positionDrawFrame(region: Rect): void {\n if (!refs) return;\n refs.drawFrame.style.left = `${region.x}px`;\n refs.drawFrame.style.top = `${region.y}px`;\n refs.drawFrame.style.width = `${region.width}px`;\n refs.drawFrame.style.height = `${region.height}px`;\n }\n\n function setDrawTool(tool: DrawTool): void {\n drawSurface?.setTool(tool);\n if (!refs) return;\n for (const [value, button] of refs.drawTools) {\n button.classList.toggle(\"is-active\", value === tool);\n }\n }\n\n function onMarksChange(marks: ReadonlyArray<Mark>): void {\n if (refs) refs.undoBtn.disabled = marks.length === 0;\n }\n\n function stopDrawSurface(): void {\n drawSurface?.stop();\n drawSurface = null;\n }\n\n function beginDraw(region: Rect): void {\n if (!refs) return;\n pendingRegion = region;\n positionDrawFrame(region);\n setMode(\"drawing\");\n drawSurface = startDrawSurface({\n doc,\n svg: refs.drawSvg,\n textInput: refs.drawText,\n initialMarks: state.marks,\n onChange: onMarksChange,\n });\n setDrawTool(\"arrow\");\n onMarksChange(state.marks);\n }\n\n function attachDraw(): void {\n if (drawSurface) {\n state.marks = drawSurface.marks().slice();\n if (pendingRegion) state.region = pendingRegion;\n }\n endDraw();\n renderMarking();\n }\n\n function cancelDraw(): void {\n // Discard the in-flight capture; previously attached marking is untouched.\n endDraw();\n renderMarking();\n }\n\n function endDraw(): void {\n stopDrawSurface();\n pendingRegion = null;\n if (mode === \"drawing\") setMode(\"compose\");\n }\n\n /** Tear down any active marking mode (before starting another, or on close). */\n function closeMarkingModes(): void {\n stopPicker();\n stopRegionCapture();\n stopDrawSurface();\n pendingRegion = null;\n positionSelBox(null);\n if (refs) refs.highlight.style.display = \"none\";\n if (mode !== \"compose\") setMode(\"compose\");\n }\n\n // ---- send ---------------------------------------------------------------\n\n function clearAutoClose(): void {\n if (autoClose !== null) {\n clearTimeout(autoClose);\n autoClose = null;\n }\n }\n\n async function send(): Promise<void> {\n if (!refs || state.phase === \"sending\") return;\n closeMarkingModes();\n state.phase = \"sending\";\n refs.sendBtn.disabled = true;\n refs.sendBtn.textContent = \"Sending…\";\n setStatus(\"\", false);\n try {\n const assembled = assembleContent({\n kind: state.kind,\n comment: refs.comment.value,\n element: state.selectedElement ?? undefined,\n region: state.region ?? undefined,\n marks: state.marks.length > 0 ? state.marks : undefined,\n url: win.location?.href,\n environment: collectEnvironment(win, sdkVersion),\n trace: config.buffer?.snapshot(),\n });\n // The single client-side scrub choke point (spec §C): default scrubbers +\n // the per-project hook run before transport; a `null` result drops the\n // report entirely.\n const content = runBeforeSend(assembled, {\n hook: config.beforeSend,\n scrub: config.scrub,\n });\n if (!content) {\n state.phase = \"sent\";\n refs.panel.classList.add(\"is-sent\");\n autoClose = setTimeout(close, AUTO_CLOSE_MS);\n return;\n }\n const capture = await deps.captureView({ doc, win });\n const screenshot = capture\n ? { blob: capture.blob, filename: `screenshot.${extensionFor(capture.type)}` }\n : null;\n const result = await deps.submitReport(config.apiUrl, {\n key: config.key,\n identity: config.identity,\n content,\n screenshot,\n });\n if (result.ok) {\n state.phase = \"sent\";\n refs.panel.classList.add(\"is-sent\");\n autoClose = setTimeout(close, AUTO_CLOSE_MS);\n } else {\n resetSendControl();\n setStatus(\n result.reason === \"unreachable\"\n ? \"Couldn't reach Fixback — try again.\"\n : \"Fixback couldn't accept this report.\",\n true,\n );\n }\n } catch {\n resetSendControl();\n setStatus(\"Something went wrong — try again.\", true);\n }\n }\n\n function resetSendControl(): void {\n state.phase = \"compose\";\n if (!refs) return;\n refs.sendBtn.disabled = false;\n refs.sendBtn.textContent = \"Send\";\n }\n\n // ---- build --------------------------------------------------------------\n\n function build(): void {\n host = h(doc, \"div\", { [OVERLAY_ATTRIBUTE]: \"\" });\n host.style.cssText =\n \"position:fixed;right:20px;bottom:84px;z-index:2147483001;margin:0;padding:0;border:0;display:none;\";\n const shadow = host.attachShadow({ mode: \"open\" });\n\n const style = doc.createElement(\"style\");\n style.textContent = OVERLAY_STYLES;\n shadow.appendChild(style);\n\n // Header\n const closeBtn = h(doc, \"button\", { type: \"button\", class: \"fb-ov-close\", \"aria-label\": \"Close\" }, [\"✕\"]);\n const head = h(doc, \"div\", { class: \"fb-ov-head\" }, [\n h(doc, \"span\", { class: \"fb-ov-mark-sq\" }),\n h(doc, \"span\", { class: \"fb-ov-brand\" }, [\"Fixback\"]),\n ]);\n // Display-only trust tier chip — present only when boot derived a tier. It\n // reflects the server-derived value verbatim; the client never asserts trust.\n if (config.tier != null) {\n head.appendChild(\n h(doc, \"span\", { class: \"fb-ov-tier\", \"aria-label\": `Trust tier: ${config.tier}` }, [config.tier]),\n );\n }\n head.appendChild(closeBtn);\n\n // Kind tabs\n const tabs = new Map<IssueKind, HTMLButtonElement>();\n const tabRow = h(doc, \"div\", { class: \"fb-ov-tabs\" });\n for (const { value, label } of KIND_TABS) {\n const tab = h(doc, \"button\", {\n type: \"button\",\n class: \"fb-ov-tab\",\n \"data-kind\": value,\n }, [label]);\n tab.addEventListener(\"click\", () => setKind(value));\n tabs.set(value, tab);\n tabRow.appendChild(tab);\n }\n\n // Annotation / screenshot marking region\n const selector = h(doc, \"span\", { class: \"fb-ov-selector\" });\n const caption = h(doc, \"span\", { class: \"fb-ov-mark-caption\" });\n const mark = h(doc, \"div\", { class: \"fb-ov-mark\" }, [selector, caption]);\n\n // Comment\n const comment = h(doc, \"textarea\", {\n class: \"fb-ov-comment\",\n placeholder: \"Describe what you saw or want…\",\n \"aria-label\": \"Comment\",\n });\n\n // Status + tools\n const status = h(doc, \"div\", { class: \"fb-ov-status\", role: \"status\", \"aria-live\": \"polite\" });\n const pickBtn = h(doc, \"button\", { type: \"button\", class: \"fb-ov-pickbtn\", title: \"Point at an element\" });\n pickBtn.addEventListener(\"click\", () => {\n if (state.phase === \"sending\") return;\n if (mode === \"picking\") endPick();\n else beginPick();\n });\n const captureBtn = h(doc, \"button\", {\n type: \"button\",\n class: \"fb-ov-capturebtn\",\n title: \"Capture a region and draw\",\n \"aria-label\": \"Capture a region and draw\",\n });\n captureBtn.appendChild(captureGlyph(doc));\n captureBtn.addEventListener(\"click\", () => {\n if (state.phase === \"sending\") return;\n beginCapture();\n });\n const sendBtn = h(doc, \"button\", { type: \"button\", class: \"fb-ov-send\" }, [\"Send\"]);\n sendBtn.addEventListener(\"click\", () => void send());\n const tools = h(doc, \"div\", { class: \"fb-ov-tools\" }, [pickBtn, captureBtn, sendBtn]);\n\n const form = h(doc, \"div\", { class: \"fb-ov-form\" }, [tabRow, mark, comment, status, tools]);\n\n // Confirmation\n const done = h(doc, \"div\", { class: \"fb-ov-done\" }, [\n h(doc, \"div\", { class: \"fb-ov-done__check\" }, [\"✓\"]),\n h(doc, \"div\", { class: \"fb-ov-done__title\" }, [\"Report sent\"]),\n h(doc, \"div\", { class: \"fb-ov-done__sub\" }, [\"Thanks — the team can see it now.\"]),\n ]);\n\n const panel = h(doc, \"div\", {\n class: \"fb-ov-panel\",\n role: \"dialog\",\n \"aria-label\": \"Fixback feedback\",\n }, [head, form, done]);\n\n // Element-pick highlight layer (visual only; never intercepts host events)\n const highlight = h(doc, \"div\", { class: \"fb-ov-highlight\" });\n highlight.style.display = \"none\";\n const pickLayer = h(doc, \"div\", { class: \"fb-ov-pick\" }, [\n h(doc, \"div\", { class: \"fb-ov-hint\" }, [\"Click the element you mean · Esc to cancel\"]),\n highlight,\n ]);\n\n // Region-capture layer (a dim + a live selection box)\n const selBox = h(doc, \"div\", { class: \"fb-ov-capture-sel\" });\n selBox.style.display = \"none\";\n const captureLayer = h(doc, \"div\", { class: \"fb-ov-capture\" }, [\n h(doc, \"div\", { class: \"fb-ov-capture-dim\" }),\n h(doc, \"div\", { class: \"fb-ov-hint\" }, [\"Drag to capture an area · Esc to cancel\"]),\n selBox,\n ]);\n\n // Draw layer: the mark SVG, the label input, the region frame, and the toolbar.\n const drawSvg = svgNode(doc, \"svg\", {\n class: \"fb-ov-draw-svg\",\n width: \"100%\",\n height: \"100%\",\n }) as SVGSVGElement;\n const drawText = h(doc, \"input\", {\n class: \"fb-ov-draw-text\",\n placeholder: \"label…\",\n \"aria-label\": \"Mark label\",\n });\n drawText.style.display = \"none\";\n const drawFrame = h(doc, \"div\", { class: \"fb-ov-draw-frame\" });\n\n const drawTools = new Map<DrawTool, HTMLButtonElement>();\n const toolButtons: HTMLButtonElement[] = [];\n for (const { tool, label } of DRAW_TOOLS) {\n const button = h(doc, \"button\", {\n type: \"button\",\n class: \"fb-ov-drawtool\",\n \"data-tool\": tool,\n title: label,\n \"aria-label\": label,\n });\n button.appendChild(drawToolGlyph(doc, tool));\n button.addEventListener(\"click\", () => setDrawTool(tool));\n drawTools.set(tool, button);\n toolButtons.push(button);\n }\n const undoBtn = h(doc, \"button\", { type: \"button\", class: \"fb-ov-draw-undo\", title: \"Undo\", \"aria-label\": \"Undo\" });\n undoBtn.appendChild(undoGlyph(doc));\n undoBtn.addEventListener(\"click\", () => drawSurface?.undo());\n const cancelDrawBtn = h(doc, \"button\", { type: \"button\", class: \"fb-ov-draw-cancel\" }, [\"Cancel\"]);\n cancelDrawBtn.addEventListener(\"click\", () => cancelDraw());\n const attachBtn = h(doc, \"button\", { type: \"button\", class: \"fb-ov-draw-attach\" }, [\"Attach\"]);\n attachBtn.addEventListener(\"click\", () => attachDraw());\n const drawToolbar = h(doc, \"div\", { class: \"fb-ov-draw-toolbar\" }, [\n ...toolButtons,\n h(doc, \"span\", { class: \"fb-ov-draw-divider\" }),\n undoBtn,\n cancelDrawBtn,\n attachBtn,\n ]);\n const drawLayer = h(doc, \"div\", { class: \"fb-ov-draw\" }, [drawFrame, drawSvg, drawText, drawToolbar]);\n\n shadow.append(panel, pickLayer, captureLayer, drawLayer);\n\n closeBtn.addEventListener(\"click\", close);\n\n (config.target ?? doc.body).appendChild(host);\n refs = {\n panel,\n tabs,\n mark,\n selector,\n caption,\n comment,\n status,\n pickBtn,\n captureBtn,\n sendBtn,\n highlight,\n pickLayer,\n captureLayer,\n selBox,\n drawLayer,\n drawSvg,\n drawText,\n drawFrame,\n drawTools,\n undoBtn,\n };\n }\n\n function ensureBuilt(): void {\n if (!host || !refs) build();\n }\n\n function openOverlay(): void {\n ensureBuilt();\n if (!host || !refs) return;\n clearAutoClose();\n closeMarkingModes();\n state.phase = \"compose\";\n state.selectedElement = null;\n state.region = null;\n state.marks = [];\n refs.panel.classList.remove(\"is-sent\");\n refs.comment.value = \"\";\n resetSendControl();\n setStatus(\"\", false);\n setKind(\"bug\");\n renderMarking();\n host.style.display = \"block\";\n open = true;\n try {\n refs.comment.focus();\n } catch {\n /* focus is best-effort — never let it throw into the host page */\n }\n }\n\n function close(): void {\n clearAutoClose();\n closeMarkingModes();\n if (host) host.style.display = \"none\";\n open = false;\n }\n\n function destroy(): void {\n clearAutoClose();\n closeMarkingModes();\n host?.remove();\n host = null;\n refs = null;\n open = false;\n }\n\n return {\n open: openOverlay,\n close,\n destroy,\n get isOpen() {\n return open;\n },\n };\n}\n\n/** The crosshair glyph on the element-pick control (matches the `⌖` in the prototype). */\nfunction glyph(doc: Document): HTMLElement {\n return h(doc, \"span\", { class: \"fb-ov-pickbtn__glyph\", \"aria-hidden\": \"true\" }, [\"⌖\"]);\n}\n\n/** The frame glyph on the region-capture control (matches the prototype's crop icon). */\nfunction captureGlyph(doc: Document): SVGElement {\n return svgNode(\n doc,\n \"svg\",\n { class: \"fb-ov-icon\", width: \"14\", height: \"14\", viewBox: \"0 0 16 16\", fill: \"none\", stroke: \"currentColor\", \"stroke-width\": \"1.5\", \"aria-hidden\": \"true\" },\n [svgNode(doc, \"path\", { d: \"M2 5V2h3M14 5V2h-3M2 11v3h3M14 11v3h-3\" })],\n );\n}\n\n/** The undo glyph on the draw toolbar. */\nfunction undoGlyph(doc: Document): SVGElement {\n return svgNode(\n doc,\n \"svg\",\n { class: \"fb-ov-icon\", width: \"15\", height: \"15\", viewBox: \"0 0 16 16\", fill: \"none\", stroke: \"currentColor\", \"stroke-width\": \"1.6\", \"aria-hidden\": \"true\" },\n [svgNode(doc, \"path\", { d: \"M6 4 3 7l3 3M3 7h6a4 4 0 0 1 0 8H7\" })],\n );\n}\n\n/** The per-tool glyph on the draw toolbar (matches the prototype's icons). */\nfunction drawToolGlyph(doc: Document, tool: DrawTool): Node {\n if (tool === \"text\") {\n return h(doc, \"span\", { class: \"fb-ov-drawtool__t\", \"aria-hidden\": \"true\" }, [\"T\"]);\n }\n const paths: Record<Exclude<DrawTool, \"text\">, SVGElement> = {\n arrow: svgNode(doc, \"path\", { d: \"M3 13 13 3M7 3h6v6\" }),\n box: svgNode(doc, \"rect\", { x: \"2.5\", y: \"3.5\", width: \"11\", height: \"9\", rx: \"1\" }),\n pen: svgNode(doc, \"path\", { d: \"M3 13l1-3 6-6 2 2-6 6-3 1Z\" }),\n };\n return svgNode(\n doc,\n \"svg\",\n { class: \"fb-ov-icon\", width: \"15\", height: \"15\", viewBox: \"0 0 16 16\", fill: \"none\", stroke: \"currentColor\", \"stroke-width\": \"1.6\", \"aria-hidden\": \"true\" },\n [paths[tool]],\n );\n}\n","import { type BootAnswer, type IdentityInputs, requestBoot } from \"./boot\";\nimport {\n createBreadcrumbBuffer,\n instrumentBreadcrumbs,\n type BeforeBreadcrumb,\n type BreadcrumbBuffer,\n type BreadcrumbLevel,\n type Teardown,\n} from \"./breadcrumbs\";\nimport { installErrorCapture } from \"./error-capture\";\nimport { ensureAnonymousId } from \"./identity\";\nimport {\n fetchInviteStatus,\n persistReporter,\n readInviteToken,\n readStoredReporter,\n redeemInviteToken,\n type ReporterDisplay,\n stripInviteToken,\n} from \"./invite\";\nimport { LAUNCH_EVENT, type Launcher, mountLauncher, unmountLauncher } from \"./launcher\";\nimport { createOnboardingModal } from \"./onboarding\";\nimport { createOverlay } from \"./overlay\";\nimport type { BeforeSend } from \"./scrub\";\nimport { SDK_VERSION } from \"./version\";\n\n/**\n * The hosted Fixback API origin the SDK talks to by default. A self-hosted or\n * local deployment overrides it with `apiUrl` (the dashboard's install snippet\n * pre-fills the right value for the Project).\n */\nexport const DEFAULT_API_URL = \"https://api.fixback.dev\";\n\n/**\n * Trace buffer tuning (spec §C). These are configurable **starting points** from\n * research (N ≈ 30, `warn`/`error`/`assert`, optional ~60 s age cap) — never\n * frozen magic numbers. Pass `false` for {@link InitOptions.trace} to disable\n * capture entirely.\n */\nexport interface TraceOptions {\n /** Keep at most this many crumbs (oldest drop). Defaults to 30. */\n readonly maxBreadcrumbs?: number;\n /** Optional age cap in ms (e.g. `60000`); off by default. */\n readonly maxAgeMs?: number;\n /** Console levels captured. Defaults to `warn`/`error`/`assert`. */\n readonly consoleLevels?: readonly BreadcrumbLevel[];\n /** Per-crumb filter: mute a category, edit a crumb, or drop it (`null`). */\n readonly beforeBreadcrumb?: BeforeBreadcrumb;\n}\n\n/** Options for {@link init}. Only `key` is required. */\nexport interface InitOptions extends IdentityInputs {\n /** The Project's **publishable** key — an identifier that ships in the page. */\n readonly key: string;\n /** The Fixback API origin. Defaults to {@link DEFAULT_API_URL}. */\n readonly apiUrl?: string;\n /** Where to mount the launcher. Defaults to `document.body`. */\n readonly target?: HTMLElement;\n /**\n * Still the launcher's pulse and motion. An explicit opt-in that complements\n * the visitor's OS-level `prefers-reduced-motion`, which the launcher already\n * honours on its own.\n */\n readonly reduceMotion?: boolean;\n /**\n * The synchronous, network-free client scrub hook every report passes through\n * before transport (spec §C). Mutate the draft to scrub further, or return\n * `null` to drop the report. Runs after the default scrubbers.\n */\n readonly beforeSend?: BeforeSend;\n /** Run the built-in default scrubbers. Defaults to `true` (private-by-default). */\n readonly scrub?: boolean;\n /** Trace buffer tuning, or `false` to turn the buffer off entirely. */\n readonly trace?: TraceOptions | false;\n /**\n * Automatic error capture — the SDK files uncaught exceptions / unhandled\n * rejections as `source: auto` Feedback with no prompt (spec §E, ADR-0011).\n * **Default-on across all Gates**; set `false` for the per-project toggle that\n * turns it off. It is gated by boot's `canSubmit` either way, so an auto-error is\n * never filed where a manual report would be refused.\n */\n readonly autoCapture?: boolean;\n}\n\n/**\n * Options for {@link redeem} — the explicit form of the `?fixback_invite=` URL\n * detection {@link init} performs. Identical to {@link InitOptions} plus the\n * `token` to redeem (the value the invite link carried).\n */\nexport interface RedeemOptions extends InitOptions {\n /** The invite token to redeem — the `?fixback_invite=` value from the link. */\n readonly token: string;\n}\n\n/** A running SDK instance. */\nexport interface FixbackInstance {\n /** Remove the launcher and release its DOM. Safe to call more than once. */\n destroy(): void;\n}\n\nconst NOOP_INSTANCE: FixbackInstance = { destroy() {} };\n\n/** The host site's origin (for the onboarding modal); never throws. */\nfunction originHost(): string {\n try {\n const loc = window.location;\n return loc.host || loc.hostname || \"this site\";\n } catch {\n return \"this site\";\n }\n}\n\n/** The current page URL, or `\"\"` when it cannot be read; never throws. */\nfunction safeHref(): string {\n try {\n return window.location.href;\n } catch {\n return \"\";\n }\n}\n\n/** Context for mounting the launcher + overlay + trace once boot says yes. */\ninterface MountContext {\n readonly options: InitOptions;\n readonly apiUrl: string;\n readonly key: string;\n readonly identity: IdentityInputs;\n readonly target: HTMLElement;\n readonly answer: BootAnswer;\n}\n\n/**\n * Mount the launcher and the report overlay it opens, the thin trace buffer\n * (spec §C), and automatic error capture (spec §E). Returns a teardown that removes\n * them all. The boot answer's `gate` + `tier` ride into the overlay (no second\n * call): `tier` drives the display-only header chip (§G), `gate` is reserved for\n * Gate-aware behaviour. The overlay and the auto-capture handlers carry the resolved\n * identity, so both a filed report and an auto-captured error are attributed to the\n * very Reporter boot recognised.\n */\nfunction mountReporter(ctx: MountContext): Teardown {\n const { options, apiUrl, key, identity, target, answer } = ctx;\n const launcher: Launcher = mountLauncher(target, {\n key,\n reduceMotion: options.reduceMotion,\n });\n\n const apiBase = apiUrl.replace(/\\/+$/, \"\");\n let buffer: BreadcrumbBuffer | null = null;\n let teardownTrace: Teardown = () => {};\n if (options.trace !== false) {\n const trace = options.trace ?? {};\n buffer = createBreadcrumbBuffer({\n maxBreadcrumbs: trace.maxBreadcrumbs,\n maxAgeMs: trace.maxAgeMs,\n beforeBreadcrumb: trace.beforeBreadcrumb,\n });\n teardownTrace = instrumentBreadcrumbs(buffer, {\n consoleLevels: trace.consoleLevels,\n ignoreUrl: (url) => url.startsWith(apiBase),\n });\n }\n\n const overlay = createOverlay({\n apiUrl,\n key,\n identity,\n gate: answer.gate,\n tier: answer.tier,\n sdkVersion: SDK_VERSION,\n target,\n buffer,\n beforeSend: options.beforeSend,\n scrub: options.scrub,\n });\n const onLaunch = (): void => overlay.open();\n launcher.host.addEventListener(LAUNCH_EVENT, onLaunch);\n\n // Automatic error capture (spec §E, ADR-0011): two global handlers file uncaught\n // errors as `source: auto` Feedback for this session's Reporter, with no prompt.\n // Default-on; `autoCapture: false` is the per-project toggle. Reaching here means\n // boot already returned `canSubmit`, so the Gate is respected and the auto-error\n // inherits the very tier boot derived — a Public crash stays tracked-not-shipped.\n let teardownAutoCapture: Teardown = () => {};\n if (options.autoCapture !== false) {\n teardownAutoCapture = installErrorCapture({\n apiUrl,\n key,\n identity,\n sdkVersion: SDK_VERSION,\n buffer,\n beforeSend: options.beforeSend,\n scrub: options.scrub,\n }).destroy;\n }\n\n return () => {\n launcher.host.removeEventListener(LAUNCH_EVENT, onLaunch);\n overlay.destroy();\n unmountLauncher(launcher);\n teardownTrace();\n teardownAutoCapture();\n };\n}\n\n/**\n * The shared boot + identity flow behind {@link init} and {@link redeem}.\n *\n * When an invite `token` is present and its Invite is live, the SDK renders the\n * onboarding modal (spec §F) instead of mounting immediately; on confirm it\n * redeems the Invite, persists the returned `reporterId` (scoped by publishable\n * key), strips the token from the URL (one-time consumption), and only then boots\n * with that identity and mounts the launcher. With no token — or a dead one — it\n * boots with any previously stored `reporterId` (a returning invited tester) or\n * an anonymous id, and mounts solely when boot's `canSubmit` is true. So on an\n * Invited / Internal Gate the launcher is absent until redemption, while an Open\n * Gate shows it to an anonymous visitor. Everything is wrapped so a Fixback\n * problem resolves to a no-op instance and never surfaces on the host page.\n */\nasync function run(\n options: InitOptions,\n inviteToken: string | null,\n): Promise<FixbackInstance> {\n try {\n // No DOM (server-side render, worker) — nothing to mount.\n if (typeof document === \"undefined\") return NOOP_INSTANCE;\n\n const key = options?.key;\n if (typeof key !== \"string\" || key.length === 0) return NOOP_INSTANCE;\n\n const apiUrl = options.apiUrl ?? DEFAULT_API_URL;\n const anonymousId = options.anonymousId ?? ensureAnonymousId();\n const target = options.target ?? document.body;\n\n // The launcher / overlay and the modal mount at different times (the modal\n // now, the launcher after redemption). `destroy()` tears down whichever exist.\n let disposed = false;\n let teardownMounted: Teardown = () => {};\n let teardownModal: Teardown = () => {};\n const instance: FixbackInstance = {\n destroy: () => {\n disposed = true;\n teardownModal();\n teardownModal = () => {};\n teardownMounted();\n teardownMounted = () => {};\n },\n };\n\n /** The identity to present when there is no fresh redemption to honour. */\n const storedIdentity = (): IdentityInputs => {\n const stored = readStoredReporter(key);\n return {\n signedIdentity: options.signedIdentity,\n reporterId: options.reporterId ?? stored?.reporterId,\n anonymousId,\n };\n };\n\n /** Boot with a resolved identity and mount the launcher when accepted. */\n const bootAndMount = async (identity: IdentityInputs): Promise<void> => {\n const answer = await requestBoot(apiUrl, { key, ...identity });\n if (disposed || !answer?.canSubmit) return;\n teardownMounted = mountReporter({ options, apiUrl, key, identity, target, answer });\n };\n\n if (inviteToken) {\n const status = await fetchInviteStatus(apiUrl, inviteToken);\n if (disposed) return instance;\n if (status && status.status === \"pending\") {\n const stored = readStoredReporter(key);\n\n const confirmRedeem = async (display: ReporterDisplay): Promise<void> => {\n const result = await redeemInviteToken(apiUrl, inviteToken);\n if (disposed) return;\n teardownModal();\n teardownModal = () => {};\n if (result && result.status === \"redeemed\") {\n persistReporter(key, {\n reporterId: result.reporterId,\n name: display.name,\n email: display.email,\n });\n stripInviteToken(window);\n await bootAndMount({\n signedIdentity: options.signedIdentity,\n reporterId: result.reporterId,\n anonymousId,\n });\n } else {\n // The Invite died between reading its status and redeeming it (a\n // race, or an already-spent shared link) — fall back to whatever\n // identity we already hold; on a gated Project that means no launcher.\n await bootAndMount(storedIdentity());\n }\n };\n\n const modal = createOnboardingModal({\n origin: originHost(),\n tier: status.tier,\n defaults: stored ? { name: stored.name, email: stored.email } : undefined,\n target,\n onConfirm: (display) => {\n void confirmRedeem(display);\n },\n });\n teardownModal = () => modal.destroy();\n return instance;\n }\n // A dead, unknown, or unreachable Invite shows no modal — the SDK proceeds\n // as an ordinary boot (a returning Reporter's stored identity, or anonymous).\n }\n\n await bootAndMount(storedIdentity());\n return instance;\n } catch {\n // A Fixback failure must never break the host page.\n return NOOP_INSTANCE;\n }\n}\n\n/**\n * Boot the SDK and mount the launcher when — and only when — a submission would\n * be accepted for this key, origin, and Gate.\n *\n * On load the SDK first checks the page URL for an invite token\n * (`?fixback_invite=`, spec §F); when one is present and live it renders the\n * onboarding modal and defers the launcher until the tester redeems. Otherwise it\n * calls the ingest boot endpoint and mounts the launcher solely when the answer's\n * `canSubmit` is true — so a Reporter is never shown a launcher a submission would\n * be refused, and on an Invited / Internal Gate the launcher stays absent until\n * redemption. Everything is wrapped so a Fixback problem — unreachable, refused,\n * or an unexpected error — resolves to a no-op instance.\n */\nexport function init(options: InitOptions): Promise<FixbackInstance> {\n const token = typeof window !== \"undefined\" ? readInviteToken(safeHref()) : null;\n return run(options, token);\n}\n\n/**\n * Redeem an invite token explicitly, the programmatic equivalent of landing on a\n * page with `?fixback_invite=<token>`. Renders the onboarding modal, and on\n * confirm redeems, persists the `reporterId`, and mounts the launcher (spec §F).\n * Use it when the token reaches the page some way other than the URL.\n */\nexport function redeem(options: RedeemOptions): Promise<FixbackInstance> {\n const token =\n typeof options?.token === \"string\" && options.token.length > 0\n ? options.token\n : null;\n return run(options, token);\n}\n"],"mappings":"AA+CA,SAAgB,GAAa,GAAwB;AACnD,SAAO,GAAG,EAAO,QAAQ,QAAQ,EAAE,CAAA;AACrC;AAGA,SAAS,GAAa,GAAqC;AACzD,MAAI,OAAO,KAAU,YAAY,MAAU,KAAM,QAAO;AACxD,QAAM,IAAI;AACV,SACE,OAAO,EAAE,iBAAkB,aAC3B,OAAO,EAAE,aAAc,aACvB,OAAO,EAAE,QAAS,aACjB,EAAE,SAAS,QAAQ,OAAO,EAAE,QAAS;AAE1C;AAaA,eAAsB,GACpB,GACA,GACA,IAA0B,OACE;AAC5B,MAAI;AACJ,MAAI;AACF,IAAA,IAAW,MAAM,EAAU,GAAa,CAAM,GAAG;AAAA,MAC/C,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,CAAO;AAAA,IAC9B,CAAC;AAAA,EACH,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,EAAS,GAAI,QAAO;AAEzB,MAAI;AACJ,MAAI;AACF,IAAA,IAAO,MAAM,EAAS,KAAK;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,SAAO,GAAa,CAAI,IAAI,IAAO;AACrC;ACxFA,IAAa,KAAuB,CAClC,qBACA,sBACF,GAEM,KAAgB,GAAqB,IAAA,CAAK,MAAM,IAAI,CAAA,GAAI,EAAE,KAAK,GAAG;AAOxE,SAAgB,GAAc,GAA+C;AAC3E,MAAI,IAAuB,aAAgB,OAAO,IAAO;AACzD,SAAO,KAAS;AACd,QAAI,aAAmB;AAChB,iBAAM,KAAU,GACnB,KAAI,EAAQ,aAAa,CAAM,EAAG,QAAO;AAAA;AAG7C,UAAM,IAAO,EAAQ,YAAY;AACjC,QAAI,aAAgB,cAAc,MAAS,GAAS;AAClD,MAAA,IAAU,EAAK;AACf;AAAA,IACF;AACA,IAAA,IAAU,EAAQ;AAAA,EACpB;AACA,SAAO;AACT;AAMA,SAAgB,GAAkB,GAAwB;AACxD,aAAW,KAAM,MAAM,KAAK,EAAK,iBAAiB,EAAa,CAAC,EAC9D,CAAA,EAAG,OAAO;AAEd;AC1CA,SAAS,GAAU,GAAuB;AACxC,QAAM,IAAO,WAA4D;AACzE,SAAA,KAAA,QAAI,EAAK,SAAe,EAAI,OAAO,CAAK,IACjC,EAAM,QAAQ,WAAA,CAAY,MAAO,KAAK,CAAA,EAAI;AACnD;AAGA,SAAS,GAAU,GAAqB;AACtC,MAAI,IAAI,GACJ,IAAM,EAAG;AACb,SAAO;AACL,IAAI,EAAI,YAAY,EAAG,YAAS,KAAK,IACrC,IAAM,EAAI;AAEZ,SAAO;AACT;AAOA,SAAgB,GAAe,GAAqB;AAClD,MAAI,EAAG,GAAI,QAAO,IAAI,GAAU,EAAG,EAAE,CAAA;AAErC,QAAM,IAAkB,CAAC;AACzB,MAAI,IAAuB;AAC3B,SAAO,KAAQ,EAAK,QAAQ,YAAY,MAAM,UAAQ;AACpD,QAAI,EAAK,IAAI;AACX,MAAA,EAAM,QAAQ,IAAI,GAAU,EAAK,EAAE,CAAA,EAAG;AACtC;AAAA,IACF;AACA,UAAM,IAAM,EAAK,QAAQ,YAAY,GAC/B,IAAyB,EAAK;AACpC,QAAI,GAAQ;AACV,YAAM,IAAU,MAAM,KAAK,EAAO,QAAQ,EAAE,OAAA,CACzC,MAAM,EAAE,YAAY,EAAM,OAC7B;AACA,MAAA,EAAM,QACJ,EAAQ,SAAS,IAAI,GAAG,CAAA,gBAAmB,GAAU,CAAI,CAAA,MAAO,CAClE;AAAA,IACF,MACE,CAAA,EAAM,QAAQ,CAAG;AAEnB,IAAA,IAAO;AAAA,EACT;AACA,SAAO,EAAM,KAAK,KAAK;AACzB;AAGA,SAAS,GAAa,GAAqB;AACzC,QAAM,IAAM,EAAG,QAAQ,YAAY;AACnC,MAAI,EAAG,GAAI,QAAO,GAAG,CAAA,IAAO,EAAG,EAAA;AAC/B,QAAM,IAAa,EAAG,UAAU,CAAA;AAChC,SAAO,IAAa,GAAG,CAAA,IAAO,CAAA,KAAe;AAC/C;AAGA,SAAgB,GAAW,GAAqB;AAC9C,QAAM,IAAkB,CAAC;AACzB,MAAI,IAAuB;AAC3B,SAAO;AACL,IAAA,EAAM,QAAQ,GAAa,CAAI,CAAC,GAChC,IAAO,EAAK;AAEd,SAAO,EAAM,KAAK,KAAK;AACzB;AAEA,SAAS,GAAY,GAA0B;AAC7C,QAAM,IAAI,EAAG,sBAAsB;AACnC,SAAO;AAAA,IACL,GAAG,KAAK,MAAM,EAAE,CAAC;AAAA,IACjB,GAAG,KAAK,MAAM,EAAE,CAAC;AAAA,IACjB,OAAO,KAAK,MAAM,EAAE,KAAK;AAAA,IACzB,QAAQ,KAAK,MAAM,EAAE,MAAM;AAAA,EAC7B;AACF;AAMA,SAAgB,GAAgB,GAA8B;AAC5D,SAAO;AAAA,IACL,UAAU,GAAe,CAAE;AAAA,IAC3B,SAAS,GAAW,CAAE;AAAA,IACtB,KAAK,EAAG,QAAQ,YAAY;AAAA,IAC5B,MAAM,GAAY,CAAE;AAAA,EACtB;AACF;AAiCA,SAAgB,GAAmB,GAA8C;;AAC/E,QAAM,KAAA,IAAM,EAAQ,SAAA,QAAA,MAAA,SAAA,IAAO,UACrB,KAAA,IAAS,EAAQ,YAAA,QAAA,MAAA,SAAA,IAAU;AACjC,MAAI,IAA0B,MAC1B,IAAU;AAEd,WAAS,EAAc,GAA8B;;AAGnD,UAAM,KAAA,KADJ,OAAO,EAAM,gBAAiB,aAAa,EAAM,aAAa,IAAI,CAAC,GAChD,CAAA,OAAA,QAAA,MAAA,SAAA,IAAM,EAAM;AACjC,WAAI,aAAmB,UAAgB,EAAO,CAAO,IAAI,OAAO,IACzD;AAAA,EACT;AAEA,WAAS,EAAO,GAAoB;;AAClC,IAAI,MACJ,IAAU,EAAc,CAAK,IAC7B,IAAA,EAAQ,aAAA,QAAA,MAAA,UAAA,EAAA,KAAA,GAAU,CAAO;AAAA,EAC3B;AAEA,WAAS,EAAQ,GAAyB;;AACxC,QAAI,EAAS;AACb,UAAM,KAAA,IAAK,EAAc,CAAK,OAAA,QAAA,MAAA,SAAA,IAAK;AACnC,IAAK,MACL,EAAM,eAAe,GACrB,EAAM,gBAAgB,GACtB,EAAK,GACL,EAAQ,OAAO,CAAE;AAAA,EACnB;AAEA,WAAS,EAAM,GAA4B;AACzC,QAAI,CAAA,KACA,EAAM,QAAQ,UAAU;;AAC1B,MAAA,EAAM,eAAe,GACrB,EAAK,IACL,IAAA,EAAQ,cAAA,QAAA,MAAA,UAAA,EAAA,KAAA,CAAW;AAAA,IACrB;AAAA,EACF;AAEA,WAAS,IAAa;AACpB,IAAI,MACJ,IAAU,IACV,EAAI,oBAAoB,aAAa,GAAQ,EAAI,GACjD,EAAI,oBAAoB,SAAS,GAAS,EAAI,GAC9C,EAAI,oBAAoB,WAAW,GAAO,EAAI,GAC9C,IAAU;AAAA,EACZ;AAEA,SAAA,EAAI,iBAAiB,aAAa,GAAQ,EAAI,GAC9C,EAAI,iBAAiB,SAAS,GAAS,EAAI,GAC3C,EAAI,iBAAiB,WAAW,GAAO,EAAI,GAEpC,EAAE,MAAA,EAAK;AAChB;ACpJA,IAAM,KAAgB,GAChB,KAAW,2CACX,KAAe,IAAI,OAAO,OAAO,EAAA,MAAmB,GAAG,GACvD,KAAY,sCAEZ,KAAiB;AAOvB,SAAgB,GAAU,GAAsB;AAC9C,SAAI,OAAO,KAAS,YAAY,EAAK,WAAW,IAAU,IACnD,EACJ,QAAQ,IAAU,kBAAkB,EACpC,QAAQ,IAAA,CAAY,GAAQ,MAAoB,GAAG,CAAA,aAAoB,EACvE,QAAQ,IAAc,mBAAmB;AAC9C;AASA,SAAgB,EAAS,GAAqB;AAC5C,MAAI,OAAO,KAAQ,YAAY,EAAI,WAAW,EAAG,QAAO;AACxD,MAAI,IAAM;AAEV,SAAA,IAAM,EAAI,QAAQ,uCAAuC,IAAI,GAE7D,IAAM,EAAI,QAAQ,WAAW,EAAE,GAE/B,IAAM,EAAI,QAAQ,QAAA,CAAS,MACzB,EAAS,SAAS,GAAG,IAAI,KAAK,CAChC,GACO;AACT;AAMA,SAAS,GAAe,GAAsC;AAC5D,QAAM,IAA8B,EAAE,GAAG,EAAK;AAC9C,SAAI,OAAO,EAAK,OAAQ,aAAU,EAAK,MAAM,EAAS,EAAK,GAAG,IAC1D,OAAO,EAAK,QAAS,aAAU,EAAK,OAAO,EAAS,EAAK,IAAI,IAC7D,OAAO,EAAK,MAAO,aAAU,EAAK,KAAK,EAAS,EAAK,EAAE,IACpD;AACT;AAGA,SAAS,GAAa,GAAyB;AAC7C,SAAO,GAAU,EAAQ,QAAQ,IAAA,CAAiB,MAAQ,EAAS,CAAG,CAAC,CAAC;AAC1E;AAEA,SAAS,GAAW,GAA+B;AACjD,QAAM,IAA0B,EAAE,GAAG,EAAM;AAC3C,SAAI,OAAO,EAAK,WAAY,aAAU,EAAK,UAAU,GAAa,EAAK,OAAO,IAC1E,EAAK,SAAM,EAAK,OAAO,GAAe,EAAK,IAAI,IAC5C;AACT;AASA,SAAgB,GAAkB,GAAqC;AACrE,QAAM,IAAuB,EAAE,GAAG,EAAM;AACxC,SAAI,OAAO,EAAK,OAAQ,aAAU,EAAK,MAAM,EAAS,EAAK,GAAG,IAC1D,EAAK,SAAS,EAAK,MAAM,SAAS,MACpC,EAAK,QAAQ,EAAK,MAAM,IAAI,EAAU,IAEjC;AACT;AAUA,SAAgB,GACd,GACA,IAA6B,CAAC,GACR;AACtB,QAAM,IACJ,EAAQ,UAAU,KAAQ,IAAQ,GAAkB,CAAK,GACrD,IAAO,EAAQ;AACrB,MAAI,CAAC,EAAM,QAAO;AAClB,MAAI;AACF,UAAM,IAAS,EAAK,CAAO;AAC3B,WAAO,KAAA,OAAA,IAAU;AAAA,EACnB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;ACjEA,IAAa,KAAqD;AAAA,EAChE;AAAA,EACA;AAAA,EACA;AACF;AA2BA,SAAS,GAAa,GAAmC;AACvD,SAAI,OAAO,KAAU,YAAY,CAAC,OAAO,SAAS,CAAK,KAAK,IAAQ,IAClE,KAEK,KAAK,MAAM,CAAK;AACzB;AASA,SAAgB,GACd,IAAiC,CAAC,GAChB;;AAClB,QAAM,IAAM,GAAa,EAAO,cAAc,GACxC,IAAW,EAAO,UAClB,IAAmB,EAAO,kBAC1B,KAAA,IAAM,EAAO,SAAA,QAAA,MAAA,SAAA,IAAO,KAAK;AAC/B,MAAI,IAAuB,CAAC;AAE5B,WAAS,IAAmB;AAC1B,QAAI,OAAO,KAAa,YAAY,IAAW,KAAK,EAAO,SAAS,GAAG;AACrE,YAAM,IAAS,EAAI,IAAI;AACvB,MAAA,IAAS,EAAO,OAAA,CAAQ,MAAU,EAAM,aAAa,CAAM;AAAA,IAC7D;AAAA,EACF;AAEA,SAAO;AAAA,IACL,IAAI,GAAyB;AAC3B,UAAI,IAA2B;AAC/B,UAAI,EACF,KAAI;AACF,QAAA,IAAQ,EAAiB,CAAK;AAAA,MAChC,QAAQ;AAGN,QAAA,IAAQ;AAAA,MACV;AAEF,MAAK,MACL,EAAO,KAAK,CAAK,GACjB,EAAW,GACP,EAAO,SAAS,MAAK,IAAS,EAAO,MAAM,CAAC,CAAG;AAAA,IACrD;AAAA,IACA,WAAyB;AACvB,aAAA,EAAW,GACJ,EAAO,MAAM;AAAA,IACtB;AAAA,IACA,QAAc;AACZ,MAAA,IAAS,CAAC;AAAA,IACZ;AAAA,EACF;AACF;AAKA,IAAM,KAAqB;AAE3B,SAAS,GAAa,GAAsB;AAC1C,MAAI,OAAO,KAAQ,SAAU,QAAO;AACpC,MAAI,aAAe,MAAO,QAAO,GAAG,EAAI,IAAA,KAAS,EAAI,OAAA;AAErD,MADI,KAAQ,QACR,OAAO,KAAQ,YAAY,OAAO,KAAQ,UAAW,QAAO,OAAO,CAAG;AAC1E,MAAI;;AACF,YAAA,IAAO,KAAK,UAAU,CAAG,OAAA,QAAA,MAAA,SAAA,IAAK,OAAO,CAAG;AAAA,EAC1C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,GAAS,GAAkC;AAClD,QAAM,IAAO,EAAK,IAAI,EAAY,EAAE,KAAK,GAAG;AAC5C,SAAO,EAAK,SAAS,KACjB,GAAG,EAAK,MAAM,GAAG,EAAkB,CAAA,MACnC;AACN;AAGA,SAAgB,GACd,GACA,GACA,GACY;AACZ,SAAO;AAAA,IAAE,UAAU;AAAA,IAAW,OAAA;AAAA,IAAO,SAAS,GAAS,CAAI;AAAA,IAAG,WAAA;AAAA,EAAU;AAC1E;AAGA,SAAgB,GACd,GACA,GACA,GACY;AACZ,QAAM,IAAU,EAAS,CAAI,GACvB,IAAQ,EAAS,CAAE;AACzB,SAAO;AAAA,IACL,UAAU;AAAA,IACV,SAAS,GAAG,CAAA,MAAa,CAAA;AAAA,IACzB,WAAA;AAAA,IACA,MAAM;AAAA,MAAE,MAAM;AAAA,MAAS,IAAI;AAAA,IAAM;AAAA,EACnC;AACF;AAOA,SAAS,GACP,GACA,GACA,GACA,GACA,GACY;AACZ,QAAM,IAAW,EAAS,CAAG,GACvB,IAAuB,IACzB;AAAA,IAAE,QAAA;AAAA,IAAQ,KAAK;AAAA,IAAU,QAAA;AAAA,EAAO,IAChC;AAAA,IAAE,QAAA;AAAA,IAAQ,KAAK;AAAA,EAAS;AAC5B,SAAO;AAAA,IACL,UAAA;AAAA,IACA,SAAS,GAAG,CAAA,IAAU,CAAA,GAAW,IAAS,KAAK,CAAA,MAAY,EAAA;AAAA,IAC3D,WAAA;AAAA,IACA,MAAA;AAAA,EACF;AACF;AAGA,SAAgB,GACd,GACA,GACA,GACA,GACY;AACZ,SAAO,GAAa,SAAS,GAAQ,GAAK,GAAQ,CAAS;AAC7D;AAGA,SAAgB,GACd,GACA,GACA,GACA,GACY;AACZ,SAAO,GAAa,OAAO,GAAQ,GAAK,GAAQ,CAAS;AAC3D;AAEA,SAAS,GAAa,GAAyB;AAC7C,MAAI;AACF,WAAO,GAAe,CAAM;AAAA,EAC9B,QAAQ;AACN,WAAO,EAAO,UAAU,EAAO,QAAQ,YAAY,IAAI;AAAA,EACzD;AACF;AAGA,SAAgB,GAAW,GAAiB,GAA+B;AACzE,QAAM,IAAW,GAAa,CAAM;AACpC,SAAO;AAAA,IACL,UAAU;AAAA,IACV,SAAS,SAAS,CAAA;AAAA,IAClB,WAAA;AAAA,IACA,MAAM,EAAE,QAAQ,EAAS;AAAA,EAC3B;AACF;AAMA,SAAgB,GAAW,GAAiB,GAA+B;AACzE,QAAM,IAAW,GAAa,CAAM;AACpC,SAAO;AAAA,IACL,UAAU;AAAA,IACV,SAAS,SAAS,CAAA;AAAA,IAClB,WAAA;AAAA,IACA,MAAM,EAAE,QAAQ,EAAS;AAAA,EAC3B;AACF;AAEA,SAAS,GAAc,GAAmD;AACxE,SAAI,aAAiB,QACZ;AAAA,IAAE,MAAM,EAAM,QAAQ;AAAA,IAAS,SAAS,EAAM;AAAA,EAAQ,IAE3D,OAAO,KAAU,WAAiB;AAAA,IAAE,MAAM;AAAA,IAAS,SAAS;AAAA,EAAM,IAC/D;AAAA,IAAE,MAAM;AAAA,IAAS,SAAS,GAAa,CAAK;AAAA,EAAE;AACvD;AAGA,SAAgB,GAAW,GAAgB,GAA+B;AACxE,QAAM,EAAE,MAAA,GAAM,SAAA,EAAA,IAAY,GAAc,CAAK;AAC7C,SAAO;AAAA,IACL,UAAU;AAAA,IACV,OAAO;AAAA,IACP,SAAS,IAAU,GAAG,CAAA,KAAS,CAAA,KAAY;AAAA,IAC3C,WAAA;AAAA,IACA,MAAM,EAAE,WAAW,EAAK;AAAA,EAC1B;AACF;AA4DA,SAAS,KAAa;AAEtB;AAGA,SAAgB,GACd,GACA,GACA,GACA,GACU;AACV,QAAM,IAAuB,CAAC;AAC9B,aAAW,KAAS,GAAQ;AAC1B,UAAM,IAAW,EAAW,CAAA;AAC5B,QAAI,OAAO,KAAa,WAAY;AACpC,UAAM,IAAA,IAAc,MAA6B;AAC/C,UAAI;AACF,QAAI,MAAU,WAEP,EAAK,CAAA,KAAI,EAAO,IAAI,GAAa,UAAU,EAAK,MAAM,CAAC,GAAG,EAAI,CAAC,CAAC,IAErE,EAAO,IAAI,GAAa,GAAO,GAAM,EAAI,CAAC,CAAC;AAAA,MAE/C,QAAQ;AAAA,MAER;AACA,aAAO,EAAS,MAAM,GAAY,CAAI;AAAA,IACxC;AACA,IAAA,EAAW,CAAA,IAAS,GACpB,EAAS,KAAA,MAAW;AAClB,MAAA,EAAW,CAAA,IAAS;AAAA,IACtB,CAAC;AAAA,EACH;AACA,SAAA,MAAa;AACX,eAAW,KAAW,EAAU,CAAA,EAAQ;AAAA,EAC1C;AACF;AAEA,SAAS,GAAW,GAAuC;AACzD,SAAI,OAAO,KAAU,WAAiB,IAClC,aAAiB,MAAY,EAAM,SAAS,IACzC,EAAM;AACf;AAEA,SAAS,GACP,GACA,GACQ;;AAKR,WAAA,KAAA,IAAA,KAAA,OAAA,SAHE,EAAM,YAAA,QAAA,MAAA,SAAA,IACL,OAAO,KAAU,YAAY,YAAY,IAAQ,EAAM,SAAS,YAAA,QAAA,MAAA,SAAA,IACjE,OACY,YAAY;AAC5B;AAQA,SAAgB,GACd,GACA,GACA,GACA,GACU;AACV,QAAM,IAAW,EAAI;AACrB,MAAI,OAAO,KAAa,WAAY,QAAO;AAC3C,QAAM,IAAA,CACJ,GACA,MACsB;AACtB,UAAM,IAAM,GAAW,CAAK,GACtB,IAAS,GAAc,GAAO,CAAI,GAGlC,IAAU,EAAS,KAAK,GAAK,GAAO,CAAI;AAC9C,WAAI,EAAU,CAAG,IAAU,IACpB,EAAQ,KAAA,CACZ,MAAa;AACZ,UAAI;AACF,QAAA,EAAO,IAAI,GAAW,GAAQ,GAAK,EAAS,QAAQ,EAAI,CAAC,CAAC;AAAA,MAC5D,QAAQ;AAAA,MAER;AACA,aAAO;AAAA,IACT,GAAA,CACC,MAAmB;AAClB,UAAI;AACF,QAAA,EAAO,IAAI,GAAW,GAAQ,GAAK,QAAW,EAAI,CAAC,CAAC;AAAA,MACtD,QAAQ;AAAA,MAER;AACA,YAAM;AAAA,IACR,CACF;AAAA,EACF;AACA,SAAA,EAAI,QAAQ,GACZ,MAAa;AACX,IAAA,EAAI,QAAQ;AAAA,EACd;AACF;AAWA,SAAgB,GACd,GACA,GACA,GACA,GACU;AACV,QAAM,IAAO,EAAI;AACjB,MAAI,OAAO,KAAS,WAAY,QAAO;AACvC,QAAM,IAAQ,EAAK,WACb,IAAe,EAAM,MACrB,IAAe,EAAM;AAE3B,SAAA,EAAM,OAAO,SAEX,GACA,MACG,GACG;AACN,gBAAK,gBAAgB;AAAA,MAAE,QAAQ,OAAO,CAAM,EAAE,YAAY;AAAA,MAAG,KAAK,OAAO,CAAG;AAAA,IAAE,GACvE,EAAa,KAAK,MAAM,GAAQ,GAAK,GAAG,CAAI;AAAA,EACrD,GAEA,EAAM,OAAO,SAAuC,GAAsB;AACxE,UAAM,IAAO,KAAK;AAClB,QAAI,KAAQ,CAAC,EAAU,EAAK,GAAG,GAAG;AAEhC,YAAM,IAAA,MAAqB;AACzB,YAAI;AACF,UAAA,EAAO,IAAI,GAAS,EAAK,QAAQ,EAAK,KAAK,KAAK,UAAU,QAAW,EAAI,CAAC,CAAC;AAAA,QAC7E,QAAQ;AAAA,QAER;AACA,aAAK,oBAAoB,WAAW,CAAM;AAAA,MAC5C;AACA,WAAK,iBAAiB,WAAW,CAAM;AAAA,IACzC;AACA,WAAO,EAAa,KAAK,MAAM,CAAI;AAAA,EACrC,GAEA,MAAa;AACX,IAAA,EAAM,OAAO,GACb,EAAM,OAAO;AAAA,EACf;AACF;AAGA,SAAgB,GACd,GACA,GACA,GACU;AACV,QAAM,IAAU,EAAI,SACd,IAAW,EAAI;AACrB,MAAI,CAAC,KAAW,CAAC,EAAU,QAAO;AAElC,MAAI,IAAO,EAAS;AACpB,QAAM,IAAA,CAAU,MAAqB;AACnC,QAAI;AACF,MAAA,EAAO,IAAI,GAAgB,GAAM,GAAI,EAAI,CAAC,CAAC;AAAA,IAC7C,QAAQ;AAAA,IAER;AACA,IAAA,IAAO;AAAA,EACT,GAEM,IAAe,EAAQ,WACvB,IAAkB,EAAQ;AAEhC,EAAA,EAAQ,YAAY,SAElB,GACA,GACA,GACM;AACN,UAAM,IAAS,EAAa,KAAK,MAAM,GAAM,GAAQ,CAAG;AACxD,WAAA,EAAO,EAAS,IAAI,GACb;AAAA,EACT,GACA,EAAQ,eAAe,SAErB,GACA,GACA,GACM;AACN,UAAM,IAAS,EAAgB,KAAK,MAAM,GAAM,GAAQ,CAAG;AAC3D,WAAA,EAAO,EAAS,IAAI,GACb;AAAA,EACT;AAEA,QAAM,IAAA,MAAyB,EAAO,EAAS,IAAI,GAC7C,IAAA,MAA2B,EAAO,EAAS,IAAI;AACrD,SAAA,EAAI,iBAAiB,YAAY,CAAU,GAC3C,EAAI,iBAAiB,cAAc,CAAY,GAE/C,MAAa;AACX,IAAA,EAAQ,YAAY,GACpB,EAAQ,eAAe,GACvB,EAAI,oBAAoB,YAAY,CAAU,GAC9C,EAAI,oBAAoB,cAAc,CAAY;AAAA,EACpD;AACF;AAEA,SAAS,GAAmB,GAA8B;;AAGxD,QAAM,KAAA,KADJ,OAAO,EAAM,gBAAiB,aAAa,EAAM,aAAa,IAAI,CAAC,GAChD,CAAA,OAAA,QAAA,MAAA,SAAA,IAAM,EAAM;AACjC,SAAO,aAAmB,UAAU,IAAU;AAChD;AAGA,SAAgB,GACd,GACA,GACA,GACU;AACV,QAAM,IAAA,CAAW,MAAuB;AACtC,UAAM,IAAS,GAAmB,CAAK;AACvC,QAAI,GAAC,KAAU,GAAc,CAAM;AACnC,UAAI;AACF,QAAA,EAAO,IAAI,GAAW,GAAQ,EAAI,CAAC,CAAC;AAAA,MACtC,QAAQ;AAAA,MAER;AAAA,EACF,GACM,IAAA,CAAW,MAAuB;AACtC,UAAM,IAAS,GAAmB,CAAK;AACvC,QAAI,GAAC,KAAU,GAAc,CAAM;AACnC,UAAI;AACF,QAAA,EAAO,IAAI,GAAW,GAAQ,EAAI,CAAC,CAAC;AAAA,MACtC,QAAQ;AAAA,MAER;AAAA,EACF;AACA,SAAA,EAAI,iBAAiB,SAAS,GAAS,EAAI,GAC3C,EAAI,iBAAiB,SAAS,GAAS,EAAI,GAC3C,MAAa;AACX,IAAA,EAAI,oBAAoB,SAAS,GAAS,EAAI,GAC9C,EAAI,oBAAoB,SAAS,GAAS,EAAI;AAAA,EAChD;AACF;AAOA,SAAgB,GACd,GACA,IAA6B,CAAC,GACpB;;AACV,QAAM,KAAA,IACJ,EAAQ,SAAA,QAAA,MAAA,SAAA,IAAQ,YACZ,KAAA,IACJ,EAAQ,SAAA,QAAA,MAAA,SAAA,IACP,OAAO,YAAa,cAAc,WAAW,QAC1C,KAAA,IACJ,EAAQ,gBAAA,QAAA,MAAA,SAAA,IACP,OAAO,WAAY,cACf,UACD,QACA,KAAA,IAAS,EAAQ,mBAAA,QAAA,MAAA,SAAA,IAAiB,IAClC,KAAA,IAAY,EAAQ,eAAA,QAAA,MAAA,SAAA,KAAA,MAAoB,KACxC,KAAA,IAAM,EAAQ,SAAA,QAAA,MAAA,SAAA,IAAO,KAAK,KAE1B,IAAwB,CAAC;AAC/B,SAAI,KACF,EAAU,KAAK,GAAkB,GAAQ,GAAY,GAAQ,CAAG,CAAC,GAE/D,MACF,EAAU,KAAK,GAAgB,GAAQ,GAAK,GAAW,CAAG,CAAC,GAC3D,EAAU,KAAK,GAAc,GAAQ,GAAK,GAAW,CAAG,CAAC,GACzD,EAAU,KAAK,GAAqB,GAAQ,GAAK,CAAG,CAAC,IAEnD,KACF,EAAU,KAAK,GAAmB,GAAQ,GAAK,CAAG,CAAC,GAGrD,MAAa;AACX,eAAW,KAAY,EACrB,KAAI;AACF,MAAA,EAAS;AAAA,IACX,QAAQ;AAAA,IAER;AAAA,EAEJ;AACF;AC7jBA,IAAa,KAAa;AAQ1B,SAAgB,GAAc,GAAY,GAAY,GAAY,GAAkB;AAClF,SAAO;AAAA,IACL,GAAG,KAAK,MAAM,KAAK,IAAI,GAAI,CAAE,CAAC;AAAA,IAC9B,GAAG,KAAK,MAAM,KAAK,IAAI,GAAI,CAAE,CAAC;AAAA,IAC9B,OAAO,KAAK,MAAM,KAAK,IAAI,IAAK,CAAE,CAAC;AAAA,IACnC,QAAQ,KAAK,MAAM,KAAK,IAAI,IAAK,CAAE,CAAC;AAAA,EACtC;AACF;AAOA,SAAgB,GACd,GACA,GACA,GACA,GACA,IAAO,IACgB;AACvB,QAAM,IAAQ,KAAK,MAAM,IAAK,GAAI,IAAK,CAAE;AACzC,SAAO;AAAA,IACL;AAAA,MAAE,GAAG;AAAA,MAAI,GAAG;AAAA,IAAG;AAAA,IACf;AAAA,MACE,GAAG,IAAK,IAAO,KAAK,IAAI,IAAQ,KAAK,KAAK,CAAC;AAAA,MAC3C,GAAG,IAAK,IAAO,KAAK,IAAI,IAAQ,KAAK,KAAK,CAAC;AAAA,IAC7C;AAAA,IACA;AAAA,MACE,GAAG,IAAK,IAAO,KAAK,IAAI,IAAQ,KAAK,KAAK,CAAC;AAAA,MAC3C,GAAG,IAAK,IAAO,KAAK,IAAI,IAAQ,KAAK,KAAK,CAAC;AAAA,IAC7C;AAAA,EACF;AACF;AASA,SAAgB,GAAmB,GAIR;AACzB,QAAM,IAIF,CAAC;AACL,SAAI,EAAM,YAAS,EAAW,UAAU,EAAM,UAC1C,EAAM,WAAQ,EAAW,SAAS,EAAM,SACxC,EAAM,SAAS,EAAM,MAAM,SAAS,MAAG,EAAW,QAAQ,EAAM,MAAM,MAAM,IACzE,OAAO,KAAK,CAAU,EAAE,SAAS,IAAI,IAAa;AAC3D;ACvDA,SAAgB,GACd,GACA,GACoB;;AACpB,QAAM,IAKF,CAAC,GAEC,IAAQ,EAAI;AAClB,EAAI,OAAO,KAAU,YAAY,IAAQ,MAAG,EAAI,gBAAgB,KAAK,MAAM,CAAK;AAChF,QAAM,IAAS,EAAI;AACnB,EAAI,OAAO,KAAW,YAAY,IAAS,MACzC,EAAI,iBAAiB,KAAK,MAAM,CAAM;AAGxC,QAAM,KAAA,IAAK,EAAI,eAAA,QAAA,MAAA,SAAA,SAAA,EAAW;AAC1B,SAAI,OAAO,KAAO,YAAY,EAAG,SAAS,MAAG,EAAI,UAAU,IAEvD,EAAW,SAAS,MAAG,EAAI,aAAa,IAErC;AACT;AAQA,SAAgB,GAAgB,GAAmC;;AACjE,QAAM,IAOF,CAAC;AAEL,EAAI,EAAM,SAAM,EAAQ,OAAO,EAAM;AAErC,QAAM,KAAA,IAAU,EAAM,aAAA,QAAA,MAAA,SAAA,SAAA,EAAS,KAAK;AACpC,EAAI,MAAS,EAAQ,UAAU,IAE3B,EAAM,QAAK,EAAQ,MAAM,EAAM,MAE/B,EAAM,eAAe,OAAO,KAAK,EAAM,WAAW,EAAE,SAAS,MAC/D,EAAQ,cAAc,EAAM;AAG9B,QAAM,IAAa,GAAmB;AAAA,IACpC,SAAS,EAAM;AAAA,IACf,QAAQ,EAAM;AAAA,IACd,OAAO,EAAM;AAAA,EACf,CAAC;AACD,SAAI,MAAY,EAAQ,aAAa,IAGjC,EAAM,SAAS,EAAM,MAAM,SAAS,MAAG,EAAQ,QAAQ,EAAM,QAE1D;AACT;AChJA,IAAM,KAAkB,IAGlB,KAAuB,oBAAI,IAAI;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,SAAS,GAAW,GAAwB;AAC1C,QAAM,IAAI,KAAK,IAAI,KAAK,IAAI,GAAQ,CAAC,GAAG,EAAe;AACvD,SAAA,IAAiB,OAAO,CAAC;AAC3B;AAQA,SAAgB,GAAW,GAAwB;AACjD,aAAW,KAAS,MAAM,KAAK,EAAK,iBAAiB,OAAO,CAAC,GAAG;;AAC9D,UAAM,MAAA,IAAQ,EAAM,aAAa,MAAM,OAAA,QAAA,MAAA,SAAA,IAAK,QAAQ,YAAY;AAChE,QAAI,GAAqB,IAAI,CAAI,EAAG;AACpC,UAAM,IAAQ,EAAM;AACpB,QAAI,CAAC,EAAO;AACZ,UAAM,IAAS,GAAW,EAAM,MAAM;AACtC,IAAA,EAAM,QAAQ,GACd,EAAM,aAAa,SAAS,CAAM;AAAA,EACpC;AAEA,aAAW,KAAQ,MAAM,KAAK,EAAK,iBAAiB,UAAU,CAAC,GAAG;AAChE,UAAM,IAAQ,EAAK,SAAS,EAAK,eAAe;AAChD,QAAI,CAAC,EAAO;AACZ,UAAM,IAAS,GAAW,EAAM,MAAM;AACtC,IAAA,EAAK,QAAQ,GACb,EAAK,cAAc;AAAA,EACrB;AAEA,aAAW,KAAY,MAAM,KAC3B,EAAK,iBAA8B,mBAAmB,CACxD,GAAG;;AACD,QAAI,EAAS,aAAa,iBAAiB,MAAM,QAAS;AAC1D,UAAM,KAAA,IAAO,EAAS,iBAAA,QAAA,MAAA,SAAA,IAAe;AACrC,IAAK,EAAK,KAAK,MACf,EAAS,cAAc,GAAW,EAAK,MAAM;AAAA,EAC/C;AACF;AAWA,IAAM,KAA0D;AAAA,EAC9D,aAAa;AAAA,EACb,cAAc;AAAA,EACd,cAAc;AAAA,EACd,aAAa;AACf;AAGA,SAAgB,GAAa,GAAsB;;AACjD,UAAA,IAAO,GAAsB,CAAA,OAAA,QAAA,MAAA,SAAA,IAAS;AACxC;AAoBA,SAAS,GACP,GACA,GACA,GACQ;AACR,QAAM,IAAA,KAAA,OAAA,SAAQ,EAAM,QAAQ,CAAA,EAAA,GACtB,IAAS,EAAI,gBAAgB,SAAS,CAAA,EAAA;AAE5C,SAAO,KAAK,IAAI,GAAG,KAAK,OADV,OAAO,KAAU,YAAY,IAAQ,IAAI,IAAQ,MACxB,CAAC,KAAK,CAAC;AAChD;AAEA,SAAS,GAAe,GAAe,GAAe,GAAwB;AAE5E,SACE,kDAAkD,CAAA,aAAkB,CAAA,qGAFnD,IAAI,cAAc,EAAE,kBAAkB,CAIV,CAAA;AAGjD;AAGA,IAAM,KAAoB,KASpB,KAAA,CAAiC,GAAK,EAAE,OAAA,GAAO,QAAA,GAAQ,MAAA,EAAA,MAC3D,IAAI,QAAA,CAAS,MAAY;AACvB,MAAI,IAAU;AACd,QAAM,IAAA,CAAQ,MAA4B;AACxC,IAAI,MACJ,IAAU,IACV,EAAQ,CAAI;AAAA,EACd;AACA,MAAI;AACF,UAAM,IAAQ,IAAI,MAAM,GAClB,IAAQ,WAAA,MAAiB,EAAK,IAAI,GAAG,EAAiB;AAC5D,IAAA,EAAM,SAAA,MAAe;AACnB,UAAI;AACF,cAAM,IAAS,SAAS,cAAc,QAAQ;AAC9C,QAAA,EAAO,QAAQ,GACf,EAAO,SAAS;AAChB,cAAM,IAAM,EAAO,WAAW,IAAI;AAClC,YAAI,CAAC,GAAK;AACR,uBAAa,CAAK,GAClB,EAAK,IAAI;AACT;AAAA,QACF;AACA,QAAA,EAAI,UAAU,GAAO,GAAG,CAAC,GACzB,EAAO,OAAA,CAAQ,MAAS;AACtB,uBAAa,CAAK,GAClB,EAAK,CAAI;AAAA,QACX,GAAG,CAAI;AAAA,MACT,QAAQ;AACN,qBAAa,CAAK,GAClB,EAAK,IAAI;AAAA,MACX;AAAA,IACF,GACA,EAAM,UAAA,MAAgB;AACpB,mBAAa,CAAK,GAClB,EAAK,IAAI;AAAA,IACX,GACA,EAAM,MAAM,oCAAoC,mBAAmB,CAAG,CAAA;AAAA,EACxE,QAAQ;AACN,IAAA,EAAK,IAAI;AAAA,EACX;AACF,CAAC;AAOH,eAAsB,GACpB,IAA0B,CAAC,GACF;AACzB,MAAI;;AACF,UAAM,KAAA,IAAM,EAAQ,SAAA,QAAA,MAAA,SAAA,IAAO,UACrB,KAAA,KAAA,IAAM,EAAQ,SAAA,QAAA,MAAA,SAAA,IAAO,EAAI,iBAAA,QAAA,MAAA,SAAA,IAAe,QACxC,KAAA,IAAS,EAAQ,YAAA,QAAA,MAAA,SAAA,IAAU,EAAI,iBAC/B,KAAA,IAAO,EAAQ,UAAA,QAAA,MAAA,SAAA,IAAQ,aACvB,IAAQ,GAAe,GAAK,GAAK,OAAO,GACxC,IAAS,GAAe,GAAK,GAAK,QAAQ,GAE1C,IAAQ,EAAO,UAAU,EAAI;AACnC,IAAA,GAAkB,CAAK,GACvB,GAAW,CAAK;AAEhB,UAAM,IAAM,GAAe,GAAO,GAAO,CAAM,GAEzC,IAAO,QAAA,IADK,EAAQ,eAAA,QAAA,MAAA,SAAA,IAAa,IACV,GAAK;AAAA,MAAE,OAAA;AAAA,MAAO,QAAA;AAAA,MAAQ,MAAA;AAAA,IAAK,CAAC;AACzD,WAAK,IACE;AAAA,MAAE,MAAA;AAAA,MAAM,OAAA;AAAA,MAAO,QAAA;AAAA,MAAQ,MAAA;AAAA,IAAK,IADjB;AAAA,EAEpB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC7LA,SAAgB,GACd,GACA,GACQ;AACR,MAAI,KAAU,KAAM,QAAA;AACpB,QAAM,IAAQ,EAAO,KAAK;AAC1B,MAAI,MAAU,GAAI,QAAA;AAElB,MAAI,QAAQ,KAAK,CAAK,EACpB,QAAO,OAAO,CAAK;AAGrB,QAAM,IAAO,KAAK,MAAM,CAAK;AAC7B,SAAK,OAAO,MAAM,CAAI,IAItB,KAHS,KAAK,IAAI,GAAG,KAAK,MAAM,IAAO,KAAO,GAAI,CAAC;AAIrD;AAQA,IAAa,KAAb,MAA+B;AAAA,EAI7B,YAAY,IAA8B,KAAK,KAAK;AAAvB,IAAA,EAAA,MAAA,OAAA,MAAA,GAF7B,EAAA,MAAA,eAAsB,CAAA,GAEO,KAAA,MAAA;AAAA,EAAwB;AAAA,EAGrD,WAAoB;AAClB,WAAO,KAAK,IAAI,IAAI,KAAK;AAAA,EAC3B;AAAA,EAGA,oBAA4B;AAC1B,WAAO,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,cAAc,KAAK,IAAI,KAAK,GAAI,CAAC;AAAA,EACtE;AAAA,EAOA,KAAK,GAAqD;AACxD,UAAM,IAAM,KAAK,IAAI,GACf,IAAU,GAAgB,GAAkB,CAAG,GAC/C,IAAQ,IAAM,IAAU;AAC9B,WAAI,IAAQ,KAAK,gBAAa,KAAK,cAAc,IAC1C;AAAA,EACT;AACF,GCzDM,KAAmB,cAiDnB,KAAiB,IAAI,GAAkB;AAG7C,SAAgB,GAAiB,GAAwB;AACvD,SAAO,GAAG,EAAO,QAAQ,QAAQ,EAAE,CAAA;AACrC;AAGA,SAAS,GAAgB,GAAsD;AAC7E,MAAI,CAAC,EAAU,QAAO,CAAC;AACvB,QAAM,IAA8E,CAAC;AACrF,SAAI,EAAS,mBAAgB,EAAI,iBAAiB,EAAS,iBACvD,EAAS,eAAY,EAAI,aAAa,EAAS,aAC/C,EAAS,gBAAa,EAAI,cAAc,EAAS,cAC9C;AACT;AAeA,eAAsB,GACpB,GACA,GACA,IAA0B,OAC1B,IAA6B,IACN;;AACvB,QAAM,KAAA,IAAyB,EAAM,QAAQ,YAAA,QAAA,MAAA,SAAA,IAAU,YACjD,IAAS,MAAW;AAI1B,MAAI,KAAU,EAAQ,SAAS,EAC7B,QAAO;AAAA,IACL,IAAI;AAAA,IACJ,QAAQ;AAAA,IACR,mBAAmB,EAAQ,kBAAkB;AAAA,EAC/C;AAGF,QAAM,IAAU;AAAA,IACd,KAAK,EAAM;AAAA,IACX,GAAG,GAAgB,EAAM,QAAQ;AAAA,IACjC,GAAG,EAAM;AAAA,IACT,QAAA;AAAA,EACF,GAEM,IAAO,IAAI,SAAS;AAE1B,MADA,EAAK,OAAO,WAAW,KAAK,UAAU,CAAO,CAAC,GAC9C,GAAA,IAAI,EAAM,gBAAA,QAAA,MAAA,WAAA,EAAY,MAAM;;AAC1B,IAAA,EAAK,OACH,IACA,EAAM,WAAW,OAAA,IACjB,EAAM,WAAW,cAAA,QAAA,MAAA,SAAA,IAAY,gBAC/B;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AACF,IAAA,IAAW,MAAM,EAAU,GAAiB,CAAM,GAAG;AAAA,MACnD,QAAQ;AAAA,MACR,MAAM;AAAA,IACR,CAAC;AAAA,EACH,QAAQ;AACN,WAAO;AAAA,MAAE,IAAI;AAAA,MAAO,QAAQ;AAAA,IAAc;AAAA,EAC5C;AAEA,MAAI,CAAC,EAAS;AAIZ,WAAI,KAAU,EAAS,WAAW,MAEzB;AAAA,MAAE,IAAI;AAAA,MAAO,QAAQ;AAAA,MAAgB,mBADlB,EAAQ,KAAK,EAAS,QAAQ,IAAI,aAAa,CAC7B;AAAA,IAAkB,IAEzD;AAAA,MAAE,IAAI;AAAA,MAAO,QAAQ;AAAA,MAAW,QAAQ,EAAS;AAAA,IAAO;AAGjE,MAAI;AAEF,WAAO;AAAA,MAAE,IAAI;AAAA,MAAM,UAAA,MADK,EAAS,KAAK;AAAA,IACV;AAAA,EAC9B,QAAQ;AAEN,WAAO;AAAA,MAAE,IAAI;AAAA,MAAM,UAAU;AAAA,IAAK;AAAA,EACpC;AACF;ACjKA,IAAa,KAAc;ACkC3B,IAAa,KAA0B;AAKvC,IAAM,KAA0B,GAI1B,KACJ,kEACI,KAAS,4BACT,KAAY,qBACZ,KAAa,sBACb,KAAe;AASrB,SAAgB,GAAU,GAAuB;AAC/C,SAAI,OAAO,KAAU,YAAY,EAAM,WAAW,IAAU,KACrD,EACJ,QAAQ,IAAS,QAAQ,EACzB,QAAQ,IAAQ,OAAO,EACvB,QAAQ,IAAW,OAAO,EAC1B,QAAQ,IAAY,OAAO,EAC3B,QAAQ,IAAc,KAAK,EAC3B,KAAK;AACV;AAOA,SAAgB,GAAW,GAAuB;AAChD,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,EAAM,QAAQ;AAChC,IAAA,KAAK,EAAM,WAAW,CAAC,GACvB,IAAI,KAAK,KAAK,GAAG,QAAU;AAE7B,UAAQ,MAAM,GAAG,SAAS,EAAE;AAC9B;AAGA,SAAS,GAAgB,GAA0B;AACjD,QAAM,IAAU,EAAS,QAAQ,WAAW,EAAE,GACxC,IAAY,EAAQ,YAAY,GAAG;AACzC,SAAO,KAAa,IAAI,EAAQ,MAAM,IAAY,CAAC,IAAI;AACzD;AAGA,SAAS,GAAW,GAA6B;;AAE/C,QAAM,IAAU,EAAK,MAAM,yBAAyB;AACpD,MAAI,EAAS,QAAO,IAAA,IAAG,EAAQ,CAAA,OAAA,QAAA,MAAA,SAAA,IAAM,EAAA,IAAM,IAAA,IAAgB,EAAQ,CAAA,OAAA,QAAA,MAAA,SAAA,IAAM,EAAE,CAAA;AAC3E,QAAM,IAAS,EAAK,MAAM,aAAa;AACvC,MAAI,EAAQ,QAAO,IAAI,IAAA,IAAgB,EAAO,CAAA,OAAA,QAAA,MAAA,SAAA,IAAM,EAAE,CAAA;AAEtD,QAAM,IAAK,EAAK,QAAQ,GAAG;AAC3B,SAAI,KAAM,IAED,GADI,EAAK,MAAM,GAAG,CACf,CAAA,IAAM,GAAgB,EAAK,MAAM,IAAK,CAAC,CAAC,CAAA,KAE7C;AACT;AAQA,SAAgB,GACd,GACA,IAAQ,IACA;AACR,MAAI,OAAO,KAAU,YAAY,EAAM,WAAW,EAAG,QAAO;AAC5D,QAAM,IAAmB,CAAC;AAC1B,aAAW,KAAO,EAAM,MAAM;AAAA,CAAI,GAAG;AACnC,UAAM,IAAQ,GAAW,EAAI,KAAK,CAAC;AACnC,QAAI,MACF,EAAO,KAAK,CAAK,GACb,EAAO,UAAU;AAAO;AAAA,EAEhC;AACA,SAAO,EAAO,KAAK,KAAK;AAC1B;AAOA,SAAgB,GACd,GACA,GACA,GACQ;AACR,SAAO,GAAW,GAAG,CAAA,IAAQ,GAAU,CAAK,CAAA,IAAK,GAAiB,CAAK,CAAA,EAAG;AAC5E;AAkBA,IAAa,KAAb,MAAyB;AAAA,EAKvB,YAAY,GAA8C;;AAA7B,IAAA,EAAA,MAAA,WAAA,MAAA,GAJ7B,EAAA,MAAA,UAAA,MAAA,GACA,EAAA,MAAA,QAAA,MAAA,GACA,EAAA,MAAA,OAAA,MAAA,GAE6B,KAAA,UAAA,GAC3B,KAAK,OAAA,IAAM,EAAQ,SAAA,QAAA,MAAA,SAAA,IAAO,KAAK,KAC/B,KAAK,SAAS,KAAK,IAAI,GAAG,EAAQ,QAAQ,GAC1C,KAAK,OAAO,KAAK,IAAI;AAAA,EACvB;AAAA,EAGA,OAAgB;AACd,UAAM,IAAM,KAAK,IAAI,GACf,EAAE,UAAA,GAAU,kBAAA,EAAA,IAAqB,KAAK;AAC5C,QAAI,IAAmB,KAAK,IAAM,KAAK,MAAM;AAC3C,YAAM,IAAS,KAAK,OAAO,IAAM,KAAK,QAAQ,CAAgB;AAC9D,MAAI,IAAS,MACX,KAAK,SAAS,KAAK,IAAI,GAAU,KAAK,SAAS,CAAM,GACrD,KAAK,QAAQ,IAAS;AAAA,IAE1B;AACA,WAAI,KAAK,UAAU,KACjB,KAAK,UAAU,GACR,MAEF;AAAA,EACT;AACF;AAmBA,SAAS,EAAS,GAAwB;AACxC,MAAI,OAAO,KAAU,SAAU,QAAO;AACtC,MAAI,KAAS,KAAM,QAAO;AAC1B,MAAI;AACF,WAAO,OAAO,CAAK;AAAA,EACrB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,SAAS,GAAsB,GAAqC;AAClE,QAAM,IAAI,GACJ,IAAQ,EAAE;AAChB,MAAI,KAAS,OAAO,KAAU,UAAU;AACtC,UAAM,IAAM;AACZ,WAAO;AAAA,MACL,MAAM,EAAS,EAAI,IAAI,KAAK;AAAA,MAC5B,OAAO,EAAS,EAAI,OAAO,KAAK,EAAS,EAAE,OAAO;AAAA,MAClD,OAAO,OAAO,EAAI,SAAU,WAAW,EAAI,QAAQ;AAAA,MACnD,UAAU;AAAA,IACZ;AAAA,EACF;AAEA,QAAM,IAAU,EAAS,EAAE,OAAO;AAClC,SAAI,IAAgB;AAAA,IAAE,MAAM;AAAA,IAAS,OAAO;AAAA,IAAS,UAAU;AAAA,EAAQ,IAEhE;AACT;AAGA,SAAS,GAA0B,GAAqC;AACtE,QAAM,IAAU,EAAuC;AACvD,MAAI,KAAU,OAAO,KAAW,UAAU;AACxC,UAAM,IAAM;AAGZ,WAAO;AAAA,MACL,MAHW,EAAS,EAAI,IAAI,KAAK;AAAA,MAIjC,OAHY,EAAS,EAAI,OAAO,KAAK,EAAS,CAAM;AAAA,MAIpD,OAAO,OAAO,EAAI,SAAU,WAAW,EAAI,QAAQ;AAAA,MACnD,UAAU;AAAA,IACZ;AAAA,EACF;AACA,QAAM,IAAQ,EAAS,CAAM;AAC7B,SAAO;AAAA,IAAE,MAAM;AAAA,IAAsB,OAAA;AAAA,IAAO,UAAU,KAAA,OAAA,IAAU;AAAA,EAAM;AACxE;AAmEA,IAAM,KAAiC;AAAA,EAAE,SAAA,MAAe;AAAA,EAAC;AAAA,EAAG,cAAA,MAAoB;AAAE;AAQlF,SAAgB,GAAoB,GAA8C;;AAChF,QAAM,KAAA,IACJ,EAAO,SAAA,QAAA,MAAA,SAAA,IAAQ,OAAO,UAAW,cAAc,SAAS;AAC1D,MAAI,CAAC,EAAU,QAAO;AACtB,QAAM,IAAc,GACd,KAAA,KAAA,IACJ,EAAO,SAAA,QAAA,MAAA,SAAA,IACP,EAAI,cAAA,QAAA,MAAA,SAAA,IACH,OAAO,YAAa,cAAc,WAAW,QAE1C,KAAA,IAAM,EAAO,SAAA,QAAA,MAAA,SAAA,IAAO,KAAK,KACzB,KAAA,IAAa,EAAO,gBAAA,QAAA,MAAA,SAAA,IAAc,IAClC,KAAA,IAAc,EAAO,iBAAA,QAAA,MAAA,SAAA,IAAA,IACrB,KAAA,KAAA,IAAgB,EAAO,UAAA,QAAA,MAAA,SAAA,SAAA,EAAM,iBAAA,QAAA,MAAA,SAAA,IAAe,IAC5C,KAAA,KAAA,IAAiB,EAAO,UAAA,QAAA,MAAA,SAAA,SAAA,EAAM,kBAAA,QAAA,MAAA,SAAA,IAAgB,IAE9C,IAAS,IAAI,GAAY;AAAA,IAC7B,WAAA,IAAU,EAAO,mBAAA,QAAA,MAAA,SAAA,IAAA;AAAA,IACjB,mBAAA,IAAkB,EAAO,mBAAA,QAAA,MAAA,SAAA,IAAiB;AAAA,IAC1C,KAAA;AAAA,EACF,CAAC,GAEK,IAAO,oBAAI,IAAuB;AAExC,MAAI,IAAU;AAGd,WAAS,EAAa,GAAY,GAAqB,GAAkC;;AAOvF,WAAO;AAAA,MAAE,GANI,GAAgB;AAAA,QAC3B,MAAM;AAAA,QACN,MAAA,IAAK,EAAI,cAAA,QAAA,MAAA,SAAA,SAAA,EAAU;AAAA,QACnB,aAAa,GAAmB,GAAK,CAAU;AAAA,QAC/C,OAAO,EAAK,SAAA,IAAQ,EAAO,YAAA,QAAA,MAAA,SAAA,SAAA,EAAQ,SAAS,IAAI;AAAA,MAClD,CACY;AAAA,MAAM,QAAQ;AAAA,MAAQ,gBAAgB;AAAA,MAAI,aAAA;AAAA,IAAY;AAAA,EACpE;AAQA,iBAAe,EAAK,GAAY,GAAqB,GAAkC;AACrF,UAAM,IAAQ,EAAK,IAAI,CAAE;AACzB,QAAI,CAAC,EAAO;AACZ,UAAM,IAAiB,EAAM;AAC7B,IAAA,EAAM,YAAY;AAClB,QAAI;AACF,YAAM,KAAQ,EAAa,GAAI,GAAa,CAAI,GAC1C,IAAU,GAAc,IAAO;AAAA,QACnC,MAAM,EAAO;AAAA,QACb,OAAO,EAAO;AAAA,MAChB,CAAC;AACD,UAAI,CAAC,EAAS;AAEd,UAAI,IAAwC;AAC5C,UAAI,EAAK,YAAY;AACnB,cAAM,IAAU,MAAM,EAAc;AAAA,UAAE,KAAA;AAAA,UAAK,KAAA;AAAA,QAAI,CAAC;AAChD,QAAA,IAAa,IACT;AAAA,UAAE,MAAM,EAAQ;AAAA,UAAM,UAAU,cAAc,GAAa,EAAQ,IAAI,CAAA;AAAA,QAAI,IAC3E;AAAA,MACN;AAQA,MAAI,EAAC,MANgB,EAAe,EAAO,QAAQ;AAAA,QACjD,KAAK,EAAO;AAAA,QACZ,UAAU,EAAO;AAAA,QACjB,SAAA;AAAA,QACA,YAAA;AAAA,MACF,CAAC,GACW,MAAM,EAAM,cAAc,MAEpC,EAAM,YAAY;AAAA,IAEtB,QAAQ;AACN,MAAI,EAAM,cAAc,MAAa,EAAM,YAAY;AAAA,IAEzD;AAAA,EACF;AAGA,WAAS,EAAO,GAAiC;AAC/C,UAAM,IAAK,GAAmB,EAAU,MAAM,EAAU,OAAO,EAAU,KAAK,GACxE,IAAW,EAAK,IAAI,CAAE;AAC5B,QAAI,GAAU;AAEZ,MAAA,EAAS,SAAS,GAClB,EAAS,SAAS,EAAI;AACtB;AAAA,IACF;AACA,QAAI,CAAC,EAAO,KAAK,GAAG;AAClB,MAAA,KAAW;AACX;AAAA,IACF;AACA,QAAI,EAAK,QAAQ,GAAa;AAC5B,MAAA,KAAW;AACX;AAAA,IACF;AACA,UAAM,IAAK,EAAI;AACf,IAAA,EAAK,IAAI,GAAI;AAAA,MAAE,OAAO;AAAA,MAAG,SAAS;AAAA,MAAI,QAAQ;AAAA,MAAI,WAAW;AAAA,IAAE,CAAC;AAEhE,QAAI;;AACF,OAAA,IAAA,EAAO,YAAA,QAAA,MAAA,UAAA,EAAQ,IAAI,GAAW,EAAU,UAAU,CAAE,CAAC;AAAA,IACvD,QAAQ;AAAA,IAER;AACA,IAAA,EAAU,GAAI,GAAG;AAAA,MAAE,YAAY;AAAA,MAAM,OAAO;AAAA,IAAK,CAAC;AAAA,EACpD;AAEA,QAAM,IAAA,CAAW,MAAuB;AACtC,QAAI;AACF,YAAM,IAAY,GAAsB,CAAK;AAC7C,MAAI,KAAW,EAAO,CAAS;AAAA,IACjC,QAAQ;AAAA,IAER;AAAA,EACF,GAEM,IAAA,CAAe,MAAuB;AAC1C,QAAI;AACF,YAAM,IAAY,GAA0B,CAAK;AACjD,MAAI,KAAW,EAAO,CAAS;AAAA,IACjC,QAAQ;AAAA,IAER;AAAA,EACF,GAGM,IAAA,MAAoB;AACxB,QAAI;AACF,iBAAW,CAAC,GAAI,CAAA,KAAU,EACxB,CAAI,EAAM,QAAQ,EAAM,aACtB,EAAU,GAAI,EAAM,OAAO;AAAA,QAAE,YAAY;AAAA,QAAO,OAAO;AAAA,MAAM,CAAC;AAAA,IAGpE,QAAQ;AAAA,IAER;AAAA,EACF,GAEM,IAAA,MAA2B;AAC/B,KAAA,KAAA,OAAA,SAAI,EAAK,qBAAoB,YAAU,EAAM;AAAA,EAC/C;AAEA,SAAA,EAAI,iBAAiB,SAAS,GAAS,EAAI,GAC3C,EAAI,iBAAiB,sBAAsB,GAAa,EAAI,GAC5D,EAAI,iBAAiB,YAAY,CAAK,GACtC,KAAA,QAAA,EAAK,iBAAiB,oBAAoB,CAAY,GAE/C;AAAA,IACL,SAAA,MAAe;AACb,MAAA,EAAI,oBAAoB,SAAS,GAAS,EAAI,GAC9C,EAAI,oBAAoB,sBAAsB,GAAa,EAAI,GAC/D,EAAI,oBAAoB,YAAY,CAAK,GACzC,KAAA,QAAA,EAAK,oBAAoB,oBAAoB,CAAY;AAAA,IAC3D;AAAA,IACA,cAAA,MAAoB;AAAA,EACtB;AACF;AC7dA,IAAM,KAAc;AAGpB,SAAS,KAAqB;AAC5B,QAAM,IAAwB,WAAW;AACzC,SAAI,KAAK,OAAO,EAAE,cAAe,aAAmB,EAAE,WAAW,IAC1D,MAAM,KAAK,IAAI,EAAE,SAAS,EAAE,CAAA,IAAK,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,EAAE,CAAA;AAChF;AAGA,SAAgB,KAA4B;AAC1C,MAAI;AACF,UAAM,IAAQ,WAAW,cACnB,IAAW,EAAM,QAAQ,EAAW;AAC1C,QAAI,EAAU,QAAO;AACrB,UAAM,IAAK,GAAW;AACtB,WAAA,EAAM,QAAQ,IAAa,CAAE,GACtB;AAAA,EACT,QAAQ;AAEN,WAAO,GAAW;AAAA,EACpB;AACF;ACsCA,IAAa,KAAqB,kBAG5B,KAA0B;AAUhC,SAAgB,GAAgB,GAA6B;AAC3D,MAAI;AACF,UAAM,IAAQ,IAAI,IAAI,CAAI,EAAE,aAAa,IAAI,EAAkB;AAC/D,WAAO,KAAS,EAAM,SAAS,IAAI,IAAQ;AAAA,EAC7C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQA,SAAgB,GAAiB,GAAmB;AAClD,MAAI;AACF,UAAM,IAAM,IAAI,IAAI,EAAI,SAAS,IAAI;AACrC,QAAI,CAAC,EAAI,aAAa,IAAA,gBAAsB,EAAG;AAC/C,IAAA,EAAI,aAAa,OAAO,EAAkB;AAC1C,UAAM,IAAQ,EAAI,aAAa,SAAS,GAClC,IAAO,GAAG,EAAI,QAAA,GAAW,IAAQ,IAAI,CAAA,KAAU,EAAA,GAAK,EAAI,IAAA;AAC9D,IAAA,EAAI,QAAQ,aAAa,EAAI,QAAQ,OAAO,IAAI,CAAI;AAAA,EACtD,QAAQ;AAAA,EAER;AACF;AAMA,SAAS,GAAmB,GAAqB;AAC/C,SAAO,GAAG,EAAA,GAA0B,CAAA;AACtC;AAGA,SAAS,KAA8B;AACrC,MAAI;;AACF,YAAA,IAAO,WAAW,kBAAA,QAAA,MAAA,SAAA,IAAgB;AAAA,EACpC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,SAAS,GAAW,GAA+C;AACjE,MAAI,OAAO,KAAU,SAAU;AAC/B,QAAM,IAAU,EAAM,KAAK;AAC3B,SAAO,EAAQ,SAAS,IAAI,IAAU;AACxC;AAOA,SAAgB,GACd,GACA,GACA,IAAwB,GAAY,GAC9B;AACN,MAAI,CAAC,EAAO;AACZ,QAAM,IAAO,GAAW,EAAS,IAAI,GAC/B,IAAQ,GAAW,EAAS,KAAK,GACjC,IAA0B;AAAA,IAC9B,YAAY,EAAS;AAAA,IACrB,GAAI,IAAO,EAAE,MAAA,EAAK,IAAI,CAAC;AAAA,IACvB,GAAI,IAAQ,EAAE,OAAA,EAAM,IAAI,CAAC;AAAA,EAC3B;AACA,MAAI;AACF,IAAA,EAAM,QAAQ,GAAmB,CAAG,GAAG,KAAK,UAAU,CAAO,CAAC;AAAA,EAChE,QAAQ;AAAA,EAER;AACF;AAMA,SAAgB,GACd,GACA,IAAwB,GAAY,GACb;AACvB,MAAI,CAAC,EAAO,QAAO;AACnB,MAAI;AACF,UAAM,IAAM,EAAM,QAAQ,GAAmB,CAAG,CAAC;AACjD,QAAI,CAAC,EAAK,QAAO;AACjB,UAAM,IAAS,KAAK,MAAM,CAAG;AAC7B,QAAI,QAAA,KAAA,OAAA,SAAO,EAAQ,eAAe,YAAY,EAAO,WAAW,WAAW,EACzE,QAAO;AAET,UAAM,IAAO,GAAW,EAAO,IAAI,GAC7B,IAAQ,GAAW,EAAO,KAAK;AACrC,WAAO;AAAA,MACL,YAAY,EAAO;AAAA,MACnB,GAAI,IAAO,EAAE,MAAA,EAAK,IAAI,CAAC;AAAA,MACvB,GAAI,IAAQ,EAAE,OAAA,EAAM,IAAI,CAAC;AAAA,IAC3B;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOA,SAAgB,GAAqB,GAAgB,GAAuB;AAC1E,SAAO,GAAG,EAAO,QAAQ,QAAQ,EAAE,CAAA,gBAAiB,mBAAmB,CAAK,CAAA;AAC9E;AAGA,SAAgB,GAAe,GAAgB,GAAuB;AACpE,SAAO,GAAG,GAAqB,GAAQ,CAAK,CAAA;AAC9C;AAEA,IAAM,KAAgB,oBAAI,IAAsB;AAAA,EAC9C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,SAAS,GAAO,GAAuC;AACrD,SAAO,MAAU,YAAY,MAAU,aAAa,MAAU;AAChE;AAGA,SAAS,GAAqB,GAA6C;AACzE,MAAI,OAAO,KAAU,YAAY,MAAU,KAAM,QAAO;AACxD,QAAM,IAAI;AACV,SAAI,EAAE,WAAW,YAEb,OAAO,EAAE,aAAc,aACtB,EAAE,SAAS,cAAc,EAAE,SAAS,aACrC,GAAO,EAAE,IAAI,IAGV,OAAO,EAAE,UAAW,YAAY,GAAc,IAAI,EAAE,MAA0B;AACvF;AAGA,SAAS,GAAe,GAAuC;AAC7D,MAAI,OAAO,KAAU,YAAY,MAAU,KAAM,QAAO;AACxD,QAAM,IAAI;AACV,SAAI,EAAE,WAAW,aAEb,OAAO,EAAE,cAAe,YACxB,OAAO,EAAE,aAAc,YACvB,GAAO,EAAE,IAAI,IAGV,OAAO,EAAE,UAAW,YAAY,GAAc,IAAI,EAAE,MAA0B;AACvF;AAEA,eAAe,GAAS,GAAsC;AAC5D,MAAI;AACF,WAAO,MAAM,EAAS,KAAK;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AASA,eAAsB,GACpB,GACA,GACA,IAA0B,OACU;AACpC,MAAI;AACJ,MAAI;AACF,IAAA,IAAW,MAAM,EAAU,GAAqB,GAAQ,CAAK,GAAG;AAAA,MAC9D,QAAQ;AAAA,MACR,SAAS,EAAE,QAAQ,mBAAmB;AAAA,IACxC,CAAC;AAAA,EACH,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,IAAO,MAAM,GAAS,CAAQ;AACpC,SAAO,GAAqB,CAAI,IAAI,IAAO;AAC7C;AAMA,eAAsB,GACpB,GACA,GACA,IAA0B,OACI;AAC9B,MAAI;AACJ,MAAI;AACF,IAAA,IAAW,MAAM,EAAU,GAAe,GAAQ,CAAK,GAAG;AAAA,MACxD,QAAQ;AAAA,MACR,SAAS,EAAE,QAAQ,mBAAmB;AAAA,IACxC,CAAC;AAAA,EACH,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,IAAO,MAAM,GAAS,CAAQ;AACpC,SAAO,GAAe,CAAI,IAAI,IAAO;AACvC;ACpRA,IAAa,KAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GChBlB,KAAiB,qBAOjB,KAAe,kBAGtB,KAAyB,qBAGzB,KAAa,KAEb,KAAU,MAEV,KAAc,KAEd,KACJ,iSAII,KACJ;AA8BF,SAAS,GAAuB,GAAoB,GAAkC;AACpF,QAAM,IAAO,MAA0B,KAAA,OAAA,IAAO;AAC9C,MAAI;AACF,UAAM,IAAA,KAAA,OAAA,SAAU,EAAK;AACrB,WAAK,IACD,EAAQ,QAAQ,CAAI,MAAM,MAAY,MAC1C,EAAQ,QAAQ,GAAM,GAAG,GAClB,MAHc;AAAA,EAIvB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAeA,SAAgB,GACd,GACA,IAA2B,CAAC,GAClB;AACV,QAAM,IAAM,EAAO,eACb,IAAM,EAAI,aAEV,IAAW,EAAI,cAAc,IAAI,EAAA,GAAiB;AACxD,EAAI,KAAU,EAAS,OAAO;AAE9B,QAAM,IAAO,EAAI,cAAc,KAAK;AACpC,EAAA,EAAK,aAAa,IAAgB,EAAE,GAIpC,EAAK,MAAM,UACT,yFACE,EAAQ,gBAAc,EAAK,aAAa,yBAAyB,MAAM;AAE3E,QAAM,IAAS,EAAK,aAAa,EAAE,MAAM,OAAO,CAAC,GAE3C,IAAQ,EAAI,cAAc,OAAO;AACvC,EAAA,EAAM,cAAc,IACpB,EAAO,YAAY,CAAK;AAGxB,QAAM,IAAO,EAAI,cAAc,KAAK;AACpC,EAAA,EAAK,YAAY;AAEjB,QAAM,IAAS,EAAI,cAAc,QAAQ;AACzC,EAAA,EAAO,OAAO,UACd,EAAO,YAAY,uBACnB,EAAO,aAAa,iBAAiB,QAAQ,GAC7C,EAAO,aAAa,cAAc,eAAe,GACjD,EAAO,YAAY,GAAG,EAAA,oDACtB,EAAO,iBAAiB,SAAA,MAAe;AACrC,IAAA,EAAK,cACH,IAAI,YAAY,IAAc;AAAA,MAAE,SAAS;AAAA,MAAM,UAAU;AAAA,IAAK,CAAC,CACjE;AAAA,EACF,CAAC;AAED,QAAM,IAAU,EAAI,cAAc,MAAM;AACxC,EAAA,EAAQ,YAAY,wBACpB,EAAQ,aAAa,eAAe,MAAM;AAE1C,QAAM,IAAO,EAAI,cAAc,QAAQ;AACvC,EAAA,EAAK,OAAO,UACZ,EAAK,YAAY,qBACjB,EAAK,aAAa,gBAAgB,EAAE,GACpC,EAAK,aAAa,cAAc,sBAAsB,GACtD,EAAK,QAAQ,iDACb,EAAK,YAAY,IAEjB,EAAK,OAAO,GAAQ,GAAS,CAAI;AAGjC,QAAM,IAAW,EAAI,cAAc,KAAK;AACxC,EAAA,EAAS,YAAY,yBACrB,EAAS,aAAa,eAAe,MAAM;AAE3C,QAAM,IAAM,EAAI,cAAc,KAAK;AACnC,EAAA,EAAI,YAAY,oBAChB,EAAI,aAAa,QAAQ,QAAQ,GACjC,EAAI,aAAa,YAAY,GAAG,GAChC,EAAI,aAAa,cAAc,sBAAsB,GAErD,EAAO,OAAO,GAAM,GAAU,CAAG;AAGjC,MAAI,IAAS,IACT,IAAU,IACV,GACA,GACA,GACA,GACA;AAEJ,QAAM,IAAA,MAAwB;AAC5B,IAAI,IAAQ,EAAK,aAAa,kBAAkB,MAAM,IACjD,EAAK,gBAAgB,gBAAgB,GACtC,KAAU,IAAS,EAAK,aAAa,mBAAmB,MAAM,IAC7D,EAAK,gBAAgB,iBAAiB;AAAA,EAC7C,GAEM,IAAA,MAA6B;AACjC,IAAI,MAAiB,WACnB,aAAa,CAAY,GACzB,IAAe,SAEjB,EAAK,UAAU,OAAO,oBAAoB,GAC1C,KAAA,QAAA,EAAS,OAAO,GAChB,IAAU;AAAA,EACZ,GAEM,IAAA,MAA0B;AAC9B,IAAI,MAAc,WAChB,aAAa,CAAS,GACtB,IAAY,SAEd,KAAA,QAAA,EAAM,OAAO,GACb,IAAO;AAAA,EACT,GAEM,IAAA,MAAqB;AACzB,IAAI,MAAc,WAChB,aAAa,CAAS,GACtB,IAAY,SAET,MACH,IAAU,IACV,EAAU;AAAA,EAEd,GAEM,IAAA,MAAsB;AAC1B,IAAI,MAAc,UAAW,aAAa,CAAS,GACnD,IAAY,WAAA,MAAiB;AAC3B,MAAA,IAAY,QACZ,IAAU,IACV,EAAU;AAAA,IACZ,GAAG,EAAW;AAAA,EAChB;AAGA,aAAW,KAAM;AAAA,IAAC;AAAA,IAAM;AAAA,IAAU;AAAA,EAAG;AACnC,IAAA,EAAG,iBAAiB,cAAc,CAAM,GACxC,EAAG,iBAAiB,cAAc,CAAO;AAG3C,QAAM,IAAA,MAA2B;AAC/B,IAAI,MAAc,WAChB,aAAa,CAAS,GACtB,IAAY,SAEd,IAAS,IACT,IAAU,IACV,EAAU,GACV,EAAY;AAAA,EACd,GAEM,IAAA,MAA2B;AAC/B,IAAA,EAAe,GACf,IAAS,IACT,IAAU,IACV,EAAU,GAEV,EAAY,GACZ,IAAO,EAAI,cAAc,KAAK,GAC9B,EAAK,YAAY,qBACjB,EAAK,aAAa,QAAQ,QAAQ,GAClC,EAAK,cACH,gFACF,EAAO,YAAY,CAAI,GACvB,IAAY,WAAW,GAAa,EAAO;AAAA,EAC7C;AAEA,SAAA,EAAK,iBAAiB,SAAS,CAAY,GAI3C,EAAI,iBAAiB,SAAS,CAAY,GAC1C,EAAI,iBAAiB,WAAA,CAAY,MAAU;AACzC,KAAI,EAAM,QAAQ,WAAW,EAAM,QAAQ,OAAO,EAAM,QAAQ,gBAC9D,EAAM,eAAe,GACrB,EAAa;AAAA,EAEjB,CAAC,GAGG,GAAuB,GAAK,EAAQ,GAAG,MACzC,IAAU,EAAI,cAAc,KAAK,GACjC,EAAQ,YAAY,wBACpB,EAAQ,aAAa,QAAQ,QAAQ,GACrC,EAAQ,YACN,gFACF,EAAO,YAAY,CAAO,GACrB,EAAQ,gBAAc,EAAK,UAAU,IAAI,oBAAoB,GAClE,IAAe,WAAW,GAAgB,EAAU,IAGtD,EAAO,YAAY,CAAI,GAQhB;AAAA,IAAE,MAAA;AAAA,IAAM,SANT,MAAsB;AAC1B,MAAI,MAAc,UAAW,aAAa,CAAS,GAC/C,MAAiB,UAAW,aAAa,CAAY,GACrD,MAAc,UAAW,aAAa,CAAS;AAAA,IACrD;AAAA,EAEuB;AACzB;AAGA,SAAgB,GAAgB,GAA0B;AACxD,EAAA,EAAS,QAAQ,GACjB,EAAS,KAAK,OAAO;AACvB;AChRA,IAAa,KAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GCYpB,KAAuB,wBAG9B,KAA2C;AAAA,EAC/C,SAAS;AAAA,EACT,UAAU;AAAA,EACV,QAAQ;AACV,GAGM,KAA0C;AAAA,EAC9C,SAAS;AAAA,EACT,UAAU;AAAA,EACV,QAAQ;AACV,GAEM,KACJ;AA2BF,SAAS,EACP,GACA,GACA,IAAyC,CAAC,GAC1C,IAAyC,CAAC,GAChB;AAC1B,QAAM,IAAO,EAAI,cAAc,CAAG;AAClC,aAAW,CAAC,GAAM,CAAA,KAAU,OAAO,QAAQ,CAAK,EAC9C,CAAI,MAAU,UAAW,EAAK,aAAa,GAAM,CAAK;AAExD,aAAW,KAAS,EAClB,CAAA,EAAK,YAAY,OAAO,KAAU,WAAW,EAAI,eAAe,CAAK,IAAI,CAAK;AAEhF,SAAO;AACT;AAGA,SAAS,GAAM,GAAmC;AAChD,QAAM,IAAU,EAAM,KAAK;AAC3B,SAAO,EAAQ,SAAS,IAAI,IAAU;AACxC;AAQA,SAAgB,GAAsB,GAA2C;;AAC/E,QAAM,KAAA,IAAS,EAAO,YAAA,QAAA,MAAA,SAAA,IAAU,SAAS,MACnC,KAAA,IAAM,EAAO,SAAA,QAAA,MAAA,SAAA,IAAO,EAAO;AAIjC,GAAA,IAAA,EAAI,cAAc,wBAA2B,OAAA,QAAA,MAAA,UAAA,EAAG,OAAO;AAEvD,QAAM,IAAO,EAAE,GAAK,OAAO,EAAA,CAAG,EAAA,GAAuB,GAAG,CAAC,GACnD,IAAS,EAAK,aAAa,EAAE,MAAM,OAAO,CAAC,GAE3C,IAAQ,EAAI,cAAc,OAAO;AACvC,EAAA,EAAM,cAAc,IACpB,EAAO,YAAY,CAAK;AAGxB,QAAM,IAAO,EAAE,GAAK,OAAO,EAAE,OAAO,aAAa,GAAG;AAAA,IAClD,EAAE,GAAK,QAAQ,EAAE,OAAO,aAAa,CAAC;AAAA,IACtC,EAAE,GAAK,QAAQ,EAAE,OAAO,cAAc,GAAG,CAAC,SAAS,CAAC;AAAA,IACpD,EAAE,GAAK,QAAQ,EAAE,OAAO,aAAa,GAAG,CAAC,aAAa,CAAC;AAAA,EACzD,CAAC,GAGK,IAAO,EAAE,GAAK,KAAK,EAAE,OAAO,aAAa,GAAG;AAAA,IAChD;AAAA,IACA,EAAE,GAAK,UAAU,CAAC,GAAG,CAAC,EAAO,MAAM,CAAC;AAAA,IACpC;AAAA,EACF,CAAC,GAYK,IAAO,EAAE,GAAK,OAAO,EAAE,OAAO,aAAa,GAAG,CATpC,EAAE,GAAK,OAAO,EAAE,OAAO,YAAY,GAAG,CACpD,EAAE,GAAK,QAAQ,EAAE,OAAO,iBAAiB,GAAG,CAAC,MAAM,CAAC,GACpD,EAAE,GAAK,QAAQ,EAAE,OAAO,aAAa,GAAG,CAAC,EAAO,MAAM,CAAC,CACzD,CAMqD,GALnC,EAAE,GAAK,OAAO,EAAE,OAAO,YAAY,GAAG;AAAA,IACtD,EAAE,GAAK,QAAQ,EAAE,OAAO,iBAAiB,GAAG,CAAC,QAAQ,CAAC;AAAA,IACtD,EAAE,GAAK,QAAQ,EAAE,OAAO,aAAa,GAAG,CAAC,GAAW,EAAO,IAAA,CAAK,CAAC;AAAA,IACjE,EAAE,GAAK,QAAQ,EAAE,OAAO,iBAAiB,GAAG,CAAC,GAAU,EAAO,IAAA,CAAK,CAAC;AAAA,EACtE,CAC8D,CAAS,CAAC,GAGlE,IAAY,EAAE,GAAK,SAAS;AAAA,IAChC,OAAO;AAAA,IACP,MAAM;AAAA,IACN,cAAc;AAAA,IACd,aAAa;AAAA,EACf,CAAC,GACK,IAAa,EAAE,GAAK,SAAS;AAAA,IACjC,OAAO;AAAA,IACP,MAAM;AAAA,IACN,cAAc;AAAA,IACd,aAAa;AAAA,EACf,CAAC;AACD,EAAA,GAAA,IAAI,EAAO,cAAA,QAAA,MAAA,WAAA,EAAU,SAAM,EAAU,QAAQ,EAAO,SAAS,OAC7D,GAAA,IAAI,EAAO,cAAA,QAAA,MAAA,WAAA,EAAU,UAAO,EAAW,QAAQ,EAAO,SAAS;AAE/D,QAAM,IAAU,EAAE,GAAK,OAAO,EAAE,OAAO,gBAAgB,CAAC,GAClD,IAAO,EAAE,GAAK,MAAM;AAC1B,EAAA,EAAK,YAAY,IACjB,EAAQ,aAAA,IAAY,EAAK,gBAAA,QAAA,MAAA,SAAA,IAAc,EAAI,eAAe,EAAE,CAAC,GAC7D,EAAQ,YACN,EAAE,GAAK,QAAQ,CAAC,GAAG,CACjB,EAAE,GAAK,UAAU,CAAC,GAAG,CAAC,qBAAqB,CAAC,GAC5C,6DACF,CAAC,CACH;AAEA,QAAM,IAAO,EAAE,GAAK,OAAO,EAAE,OAAO,aAAa,GAAG;AAAA,IAClD,EAAE,GAAK,OAAO,EAAE,OAAO,cAAc,GAAG,CAAC,iCAAiC,CAAC;AAAA,IAC3E;AAAA,IACA;AAAA,IACA,EAAE,GAAK,SAAS;AAAA,MAAE,OAAO;AAAA,MAAoB,KAAK;AAAA,IAAmB,GAAG,CAAC,aAAa,CAAC;AAAA,IACvF,EAAE,GAAK,OAAO,EAAE,OAAO,eAAe,GAAG,CAAC,GAAW,CAAU,CAAC;AAAA,IAChE;AAAA,EACF,CAAC;AACD,EAAA,EAAU,KAAK;AAEf,QAAM,IAAU,EAAE,GAAK,UAAU;AAAA,IAAE,MAAM;AAAA,IAAU,OAAO;AAAA,EAAgB,GAAG,CAC3E,uBACF,CAAC,GASK,IAAW,EAAE,GAAK,OAAO,EAAE,OAAO,iBAAiB,GAAG,CAN9C,EACZ,GACA,OACA;AAAA,IAAE,OAAO;AAAA,IAAe,MAAM;AAAA,IAAU,cAAc;AAAA,IAAQ,cAAc;AAAA,EAAiB,GAC7F;AAAA,IAAC;AAAA,IAAM;AAAA,IANI,EAAE,GAAK,OAAO,EAAE,OAAO,aAAa,GAAG,CAAC,CAAO,CAM7C;AAAA,EAAI,CAE0C,CAAK,CAAC;AACnE,EAAA,EAAO,YAAY,CAAQ;AAE3B,MAAI,IAAY;AAChB,SAAA,EAAQ,iBAAiB,SAAA,MAAe;AACtC,QAAI,EAAW;AACf,IAAA,IAAY,IACZ,EAAQ,WAAW;AACnB,UAAM,IAA2B;AAAA,MAC/B,GAAI,GAAM,EAAU,KAAK,IAAI,EAAE,MAAM,GAAM,EAAU,KAAK,EAAE,IAAI,CAAC;AAAA,MACjE,GAAI,GAAM,EAAW,KAAK,IAAI,EAAE,OAAO,GAAM,EAAW,KAAK,EAAE,IAAI,CAAC;AAAA,IACtE;AACA,IAAA,EAAO,UAAU,CAAO;AAAA,EAC1B,CAAC,GAED,EAAO,YAAY,CAAI,GAChB;AAAA,IACL,MAAA;AAAA,IACA,SAAA,MAAe,EAAK,OAAO;AAAA,EAC7B;AACF;AC1KA,IAAM,KAAS,8BAGT,KAAe,KAGf,KAAW,GAGX,KACJ;AAsCF,SAAgB,GAAiB,GAA0C;;AACzE,QAAM,KAAA,IAAM,EAAQ,SAAA,QAAA,MAAA,SAAA,IAAO,UACrB,EAAE,KAAA,GAAK,WAAA,EAAA,IAAc,GACrB,KAAA,IAAQ,EAAQ,WAAA,QAAA,MAAA,SAAA,IAAS;AAE/B,MAAI,IAAiB,SACjB,IAAoB,EAAQ,eAAe,EAAQ,aAAa,MAAM,IAAI,CAAC,GAC3E,IAAgC,MAChC,IAAU,IACV,IAA4B,MAC5B,IAAU;AAEd,WAAS,IAAmB;;AAC1B,KAAA,IAAA,EAAQ,cAAA,QAAA,MAAA,UAAA,EAAA,KAAA,GAAW,EAAU,MAAM,CAAC;AAAA,EACtC;AAEA,WAAS,EAAM,GAA0B;AACvC,WAAO;AAAA,MAAE,GAAG,KAAK,MAAM,EAAM,OAAO;AAAA,MAAG,GAAG,KAAK,MAAM,EAAM,OAAO;AAAA,IAAE;AAAA,EACtE;AAGA,WAAS,EAAW,GAAuB;AACzC,UAAM,IACJ,OAAO,EAAM,gBAAiB,aAAa,EAAM,aAAa,IAAI,CAAC,EAAM,MAAM;AACjF,eAAW,KAAQ,GAAM;AACvB,UAAI,MAAS,EAAK;AAClB,UAAI,aAAgB,SAAS;AAC3B,cAAM,IAAM,EAAK;AACjB,YAAI,MAAQ,YAAY,MAAQ,WAAW,MAAQ,WAAY,QAAO;AAAA,MACxE;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,WAAS,EAAW,GAAqB;AACvC,WAAI,EAAK,SAAS,QAAc,EAAK,OAAO,SAAS,IACjD,EAAK,SAAS,SAAe,EAAK,KAAK,KAAK,EAAE,SAAS,IACpD,KAAK,IAAI,EAAK,KAAK,EAAK,EAAE,IAAI,MAAY,KAAK,IAAI,EAAK,KAAK,EAAK,EAAE,IAAI;AAAA,EACjF;AAEA,WAAS,EAAO,GAAyB;AACvC,QAAI,KAAW,KAAW,EAAW,CAAK,EAAG;AAC7C,UAAM,IAAI,EAAM,CAAK;AACrB,QAAI,MAAS,QAAQ;AACnB,MAAA,EAAS,CAAC;AACV;AAAA,IACF;AACA,IAAI,EAAM,cAAY,EAAM,eAAe,GAC3C,IAAU,IACV,IACE,MAAS,QACL;AAAA,MAAE,MAAM;AAAA,MAAO,QAAQ,CAAC,CAAC;AAAA,MAAG,OAAA;AAAA,IAAM,IAClC;AAAA,MAAE,MAAM;AAAA,MAAM,IAAI,EAAE;AAAA,MAAG,IAAI,EAAE;AAAA,MAAG,IAAI,EAAE;AAAA,MAAG,IAAI,EAAE;AAAA,MAAG,OAAA;AAAA,IAAM,GAC9D,EAAO;AAAA,EACT;AAEA,WAAS,EAAO,GAAyB;AACvC,QAAI,KAAW,CAAC,KAAW,CAAC,EAAS;AACrC,UAAM,IAAI,EAAM,CAAK;AACrB,IAAI,EAAQ,SAAS,QACnB,IAAU;AAAA,MAAE,MAAM;AAAA,MAAO,QAAQ,CAAC,GAAG,EAAQ,QAAQ,CAAC;AAAA,MAAG,OAAO,EAAQ;AAAA,IAAM,IAE9E,IAAU;AAAA,MAAE,GAAG;AAAA,MAAS,IAAI,EAAE;AAAA,MAAG,IAAI,EAAE;AAAA,IAAE,GAE3C,EAAO;AAAA,EACT;AAEA,WAAS,IAAa;AACpB,QAAI,KAAW,CAAC,EAAS;AACzB,IAAA,IAAU;AACV,UAAM,IAAK;AACX,IAAA,IAAU,MACN,KAAM,EAAW,CAAE,MACrB,IAAY,CAAC,GAAG,GAAW,CAAE,GAC7B,EAAW,IAEb,EAAO;AAAA,EACT;AAEA,WAAS,EAAS,GAAgB;AAChC,IAAA,IAAc,GACd,EAAU,QAAQ,IAClB,EAAU,MAAM,UAAU,SAC1B,EAAU,MAAM,OAAO,GAAG,EAAE,CAAA,MAC5B,EAAU,MAAM,MAAM,GAAG,EAAE,CAAA,MAC3B,EAAU,YAAY,GACtB,EAAU,SAAS,GAEnB,WAAA,MAAiB;AACf,UAAI;AACF,QAAA,EAAU,MAAM;AAAA,MAClB,QAAQ;AAAA,MAER;AAAA,IACF,GAAG,CAAC;AAAA,EACN;AAEA,WAAS,EAAU,GAA4B;AAC7C,IAAI,EAAM,QAAQ,WAChB,EAAM,eAAe,GACrB,EAAM,gBAAgB,GACtB,EAAW,KACF,EAAM,QAAQ,aACvB,EAAM,eAAe,GACrB,EAAM,gBAAgB,GACtB,EAAY;AAAA,EAEhB;AAEA,WAAS,IAAmB;AAC1B,QAAI,CAAC,EAAa;AAClB,UAAM,IAAO,EAAU,MAAM,KAAK,GAC5B,IAAK;AACX,IAAA,EAAY,GACR,EAAK,SAAS,MAChB,IAAY,CAAC,GAAG,GAAW;AAAA,MAAE,MAAM;AAAA,MAAQ,GAAG,EAAG;AAAA,MAAG,GAAG,EAAG;AAAA,MAAG,MAAA;AAAA,MAAM,OAAA;AAAA,IAAM,CAAC,GAC1E,EAAW,GACX,EAAO;AAAA,EAEX;AAEA,WAAS,IAAoB;AAC3B,IAAA,IAAc,MACd,EAAU,YAAY,MACtB,EAAU,SAAS,MACnB,EAAU,QAAQ,IAClB,EAAU,MAAM,UAAU;AAAA,EAC5B;AAEA,WAAS,IAAe;AACtB,WAAO,EAAI,aAAY,CAAA,EAAI,YAAY,EAAI,UAAU;AACrD,eAAW,KAAQ,EAAW,CAAA,EAAI,YAAY,GAAW,GAAK,CAAI,CAAC;AACnE,IAAI,KAAS,EAAI,YAAY,GAAW,GAAK,CAAO,CAAC;AAAA,EACvD;AAEA,WAAS,IAAa;AACpB,IAAI,MACJ,IAAU,IACV,EAAI,oBAAoB,aAAa,GAAQ,EAAI,GACjD,EAAI,oBAAoB,aAAa,GAAQ,EAAI,GACjD,EAAI,oBAAoB,WAAW,GAAM,EAAI,GAC7C,EAAY;AAAA,EACd;AAEA,SAAA,EAAI,iBAAiB,aAAa,GAAQ,EAAI,GAC9C,EAAI,iBAAiB,aAAa,GAAQ,EAAI,GAC9C,EAAI,iBAAiB,WAAW,GAAM,EAAI,GAC1C,EAAO,GAEA;AAAA,IACL,SAAA,CAAU,MAAS;AACjB,MAAA,IAAO;AAAA,IACT;AAAA,IACA,SAAA,MAAe;AAAA,IACf,MAAA,MAAY;AACV,MAAI,EAAU,WAAW,MACzB,IAAY,EAAU,MAAM,GAAG,EAAE,GACjC,EAAW,GACX,EAAO;AAAA,IACT;AAAA,IACA,OAAA,MAAa,EAAU,MAAM;AAAA,IAC7B,MAAA;AAAA,EACF;AACF;AAGA,SAAS,GAAW,GAAe,GAAwB;AACzD,UAAQ,EAAK,MAAb;AAAA,IACE,KAAK;AAWH,aAVa,EAAM,GAAK,QAAQ;AAAA,QAC9B,GAAG,OAAO,KAAK,IAAI,EAAK,IAAI,EAAK,EAAE,CAAC;AAAA,QACpC,GAAG,OAAO,KAAK,IAAI,EAAK,IAAI,EAAK,EAAE,CAAC;AAAA,QACpC,OAAO,OAAO,KAAK,IAAI,EAAK,KAAK,EAAK,EAAE,CAAC;AAAA,QACzC,QAAQ,OAAO,KAAK,IAAI,EAAK,KAAK,EAAK,EAAE,CAAC;AAAA,QAC1C,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,QAAQ,EAAK;AAAA,QACb,gBAAgB;AAAA,MAClB,CACO;AAAA,IAET,KAAK;AACH,aAAO,EAAM,GAAK,YAAY;AAAA,QAC5B,QAAQ,EAAK,OAAO,IAAA,CAAK,MAAM,GAAG,EAAE,CAAA,IAAK,EAAE,CAAA,EAAG,EAAE,KAAK,GAAG;AAAA,QACxD,MAAM;AAAA,QACN,QAAQ,EAAK;AAAA,QACb,gBAAgB;AAAA,QAChB,kBAAkB;AAAA,QAClB,mBAAmB;AAAA,MACrB,CAAC;AAAA,IAEH,KAAK,QAAQ;AACX,YAAM,IAAO,EAAM,GAAK,QAAQ;AAAA,QAC9B,GAAG,OAAO,EAAK,CAAC;AAAA,QAChB,GAAG,OAAO,EAAK,CAAC;AAAA,QAChB,MAAM,EAAK;AAAA,QACX,OAAO,QAAQ,EAAA;AAAA,MACjB,CAAC;AACD,aAAA,EAAK,cAAc,EAAK,MACjB;AAAA,IACT;AAAA,IACA,KAAK,SAAS;AACZ,YAAM,IAAQ,EAAM,GAAK,KAAK,CAAC,CAAC;AAChC,MAAA,EAAM,YACJ,EAAM,GAAK,QAAQ;AAAA,QACjB,IAAI,OAAO,EAAK,EAAE;AAAA,QAClB,IAAI,OAAO,EAAK,EAAE;AAAA,QAClB,IAAI,OAAO,EAAK,EAAE;AAAA,QAClB,IAAI,OAAO,EAAK,EAAE;AAAA,QAClB,QAAQ,EAAK;AAAA,QACb,gBAAgB;AAAA,QAChB,kBAAkB;AAAA,MACpB,CAAC,CACH;AACA,YAAM,IAAO,GAAgB,EAAK,IAAI,EAAK,IAAI,EAAK,IAAI,EAAK,EAAE;AAC/D,aAAA,EAAM,YACJ,EAAM,GAAK,WAAW;AAAA,QACpB,QAAQ,EAAK,IAAA,CAAK,MAAM,GAAG,EAAE,CAAA,IAAK,EAAE,CAAA,EAAG,EAAE,KAAK,GAAG;AAAA,QACjD,MAAM,EAAK;AAAA,MACb,CAAC,CACH,GACO;AAAA,IACT;AAAA,EACF;AACF;AAGA,SAAS,EACP,GACA,GACA,GACY;AACZ,QAAM,IAAO,EAAI,gBAAgB,IAAQ,CAAG;AAC5C,aAAW,CAAC,GAAM,CAAA,KAAU,OAAO,QAAQ,CAAK,EAAG,CAAA,EAAK,aAAa,GAAM,CAAK;AAChF,SAAO;AACT;ACpRA,IAAM,KAAmB;AASzB,SAAgB,GAAmB,GAA8C;;AAC/E,QAAM,KAAA,IAAM,EAAQ,SAAA,QAAA,MAAA,SAAA,IAAO,UACrB,KAAA,IAAU,EAAQ,aAAA,QAAA,MAAA,SAAA,IAAW;AACnC,MAAI,IAAyC,MACzC,IAAU;AAEd,WAAS,EAAO,GAAyB;AACvC,IAAI,KAAW,MACf,IAAQ;AAAA,MAAE,GAAG,EAAM;AAAA,MAAS,GAAG,EAAM;AAAA,IAAQ,GACzC,EAAM,cAAY,EAAM,eAAe;AAAA,EAC7C;AAEA,WAAS,EAAO,GAAyB;;AACvC,IAAI,KAAW,CAAC,MAChB,IAAA,EAAQ,gBAAA,QAAA,MAAA,UAAA,EAAA,KAAA,GAAa,GAAc,EAAM,GAAG,EAAM,GAAG,EAAM,SAAS,EAAM,OAAO,CAAC;AAAA,EACpF;AAEA,WAAS,EAAK,GAAyB;AACrC,QAAI,KAAW,CAAC,EAAO;AACvB,UAAM,IAAO,GAAc,EAAM,GAAG,EAAM,GAAG,EAAM,SAAS,EAAM,OAAO;AAGzE,QAFA,IAAQ,MACR,EAAK,GACD,EAAK,QAAQ,KAAW,EAAK,SAAS,GAAS;;AACjD,OAAA,IAAA,EAAQ,cAAA,QAAA,MAAA,UAAA,EAAA,KAAA,CAAW;AACnB;AAAA,IACF;AACA,IAAA,EAAQ,WAAW,CAAI;AAAA,EACzB;AAEA,WAAS,EAAM,GAA4B;AACzC,QAAI,CAAA,KACA,EAAM,QAAQ,UAAU;;AAC1B,MAAA,EAAM,eAAe,GACrB,EAAK,IACL,IAAA,EAAQ,cAAA,QAAA,MAAA,UAAA,EAAA,KAAA,CAAW;AAAA,IACrB;AAAA,EACF;AAEA,WAAS,IAAa;AACpB,IAAI,MACJ,IAAU,IACV,EAAI,oBAAoB,aAAa,GAAQ,EAAI,GACjD,EAAI,oBAAoB,aAAa,GAAQ,EAAI,GACjD,EAAI,oBAAoB,WAAW,GAAM,EAAI,GAC7C,EAAI,oBAAoB,WAAW,GAAO,EAAI,GAC9C,IAAQ;AAAA,EACV;AAEA,SAAA,EAAI,iBAAiB,aAAa,GAAQ,EAAI,GAC9C,EAAI,iBAAiB,aAAa,GAAQ,EAAI,GAC9C,EAAI,iBAAiB,WAAW,GAAM,EAAI,GAC1C,EAAI,iBAAiB,WAAW,GAAO,EAAI,GAEpC,EAAE,MAAA,EAAK;AAChB;AC5FA,IAAa,KAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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"}
|