@unhingged/vizu-core 0.1.1 → 0.1.2

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cursor-colors.ts","../src/presence-stack.ts","../src/selection-overlay.ts","../src/live-collab.ts"],"sourcesContent":["/**\n * Live-collab cursor palette + a11y helpers.\n *\n * 8 distinct colors covering the hue wheel, all darkened just enough\n * that white text rendered on top of them meets WCAG-AA contrast\n * (≥4.5:1 for normal-size text). The earlier draft used the brighter\n * \"500\" shades (amber, sky-cyan, plain red) which fail AA for white\n * labels — see audit table in the comment block below.\n *\n * Coral (`#FF6647`) is deliberately excluded — it's Vizu's brand accent\n * and reusing it for one user's cursor would confuse cursor identity\n * with Vizu UI affordances.\n *\n * The same color drives:\n * - cursor SVG fill (live-collab.ts CursorEntry)\n * - avatar ring + initials background (presence-stack.ts)\n * - selection-sync ghost-outline border + name pill (selection-overlay.ts)\n *\n * Color assignment is deterministic: `hash(userId) % 8`, so the same\n * Clerk user always gets the same color across sessions and devices.\n */\n\n/**\n * | Color | Hex | White-text contrast | AA-normal ≥4.5:1 |\n * |---------|----------|---------------------|------------------|\n * | indigo | #4F46E5 | 6.96 | ✓ |\n * | forest | #15803D | 5.84 | ✓ |\n * | orange | #C2410C | 5.85 | ✓ |\n * | rose | #BE185D | 7.32 | ✓ |\n * | cyan | #0E7490 | 6.43 | ✓ |\n * | plum | #7E22CE | 7.41 | ✓ |\n * | red | #DC2626 | 4.83 | ✓ |\n * | teal | #0F766E | 5.95 | ✓ |\n */\nexport const CURSOR_COLORS = [\n '#4F46E5', // indigo\n '#15803D', // forest\n '#C2410C', // orange\n '#BE185D', // rose\n '#0E7490', // cyan\n '#7E22CE', // plum\n '#DC2626', // red\n '#0F766E', // teal\n] as const;\n\n/**\n * Stable color for a Clerk user id. Same input → same color across\n * sessions, devices, and dashboard sidebar contexts.\n */\nexport function colorForUser(userId: string): string {\n let h = 0;\n for (let i = 0; i < userId.length; i++) {\n h = (h * 31 + userId.charCodeAt(i)) | 0;\n }\n return CURSOR_COLORS[Math.abs(h) % CURSOR_COLORS.length];\n}\n\n/* ──────────────────────── motion preference ────────────────── */\n\n/**\n * Respects the user's OS-level reduced-motion preference. When true,\n * call sites collapse animated CSS transitions to `none` so motion-\n * sensitive users don't have to track smooth-easing cursors, sliding\n * selection outlines, or lifting avatars.\n *\n * SSR-safe: returns false when window isn't available so initial\n * render matches the most common case (motion enabled).\n */\nexport function prefersReducedMotion(): boolean {\n if (typeof window === 'undefined' || !window.matchMedia) return false;\n return window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n","import { prefersReducedMotion } from './cursor-colors';\n\n/**\n * Top-right presence stack — avatars of other users currently in the\n * same Liveblocks room. Up to 5 visible; the rest collapse into a \"+N\"\n * chip. Hover an avatar to see the user's name in a tooltip.\n *\n * Vanilla DOM, no React. Driven by LiveCollab's `others` subscription\n * via {@link setOthers}.\n *\n * Local user is intentionally not shown — Liveblocks' `others` array\n * already excludes them. The stack rendering empty when you're alone\n * is the right signal (\"nobody else here\").\n *\n * See plans/live-collab.md → Slice 3. Follow action on hover lands in\n * Slice 4.\n */\n\nconst Z_INDEX = 2147483640; // just below the live-cursor layer\nconst MAX_VISIBLE = 5;\nconst AVATAR_SIZE = 28;\nconst OVERLAP_PX = 6;\n\nexport type PresenceUser = {\n /** Stable per-Clerk-user identifier. Drives avatar diffing. */\n id: string;\n /** Display name shown in the tooltip and used for the initials fallback. */\n name: string;\n /** Cursor color (also drives the avatar ring). Hex string. */\n color: string;\n /** Optional avatar image URL — shown when present, initials otherwise. */\n avatar?: string;\n};\n\nexport class PresenceStack {\n private container: HTMLDivElement | null = null;\n private avatars = new Map<string, HTMLDivElement>();\n private overflowEl: HTMLDivElement | null = null;\n private followingUserId: string | null = null;\n private onAvatarClick: ((userId: string) => void) | null = null;\n\n constructor(opts?: { onAvatarClick?: (userId: string) => void }) {\n this.onAvatarClick = opts?.onAvatarClick ?? null;\n }\n\n mount(): void {\n if (typeof document === 'undefined') return;\n if (this.container) return;\n this.container = document.createElement('div');\n this.container.setAttribute('data-vizu-presence-stack', '');\n this.container.style.cssText = `\n position: fixed;\n top: 16px;\n right: 16px;\n z-index: ${Z_INDEX};\n display: flex;\n align-items: center;\n pointer-events: auto;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n `;\n document.body.appendChild(this.container);\n }\n\n destroy(): void {\n for (const el of this.avatars.values()) el.remove();\n this.avatars.clear();\n this.overflowEl?.remove();\n this.overflowEl = null;\n this.container?.remove();\n this.container = null;\n }\n\n setOthers(others: PresenceUser[]): void {\n if (!this.container) return;\n\n const visible = others.slice(0, MAX_VISIBLE);\n const overflow = Math.max(0, others.length - visible.length);\n const nextIds = new Set(visible.map((u) => u.id));\n\n // Drop avatars for users that left or fell past the visible cutoff.\n for (const [id, el] of Array.from(this.avatars)) {\n if (!nextIds.has(id)) {\n el.remove();\n this.avatars.delete(id);\n }\n }\n\n // Insert / update visible avatars. Re-appending in order keeps\n // them in stable left-to-right order even when users come and go.\n for (const user of visible) {\n let el = this.avatars.get(user.id);\n if (!el) {\n el = createAvatar(user, () => this.onAvatarClick?.(user.id));\n this.avatars.set(user.id, el);\n } else {\n updateAvatar(el, user);\n }\n paintAvatarFollowState(el, user, this.followingUserId === user.id);\n this.container.appendChild(el);\n }\n\n this.renderOverflow(overflow);\n }\n\n /**\n * Mark a user as the active followee so its avatar can render the\n * coral ring highlight. Pass null to clear.\n */\n setFollowing(userId: string | null): void {\n this.followingUserId = userId;\n for (const [id, el] of this.avatars) {\n const user = avatarMeta(el);\n if (user) paintAvatarFollowState(el, user, id === userId);\n }\n }\n\n /* ────────────────── private ────────────────── */\n\n private renderOverflow(count: number): void {\n if (!this.container) return;\n if (count <= 0) {\n this.overflowEl?.remove();\n this.overflowEl = null;\n return;\n }\n if (!this.overflowEl) {\n this.overflowEl = createOverflowChip();\n }\n this.overflowEl.textContent = `+${count}`;\n this.container.appendChild(this.overflowEl);\n }\n}\n\n/* ──────────────────────── avatar DOM ───────────────────────── */\n\nfunction createAvatar(user: PresenceUser, onClick: () => void): HTMLDivElement {\n const wrap = document.createElement('div');\n wrap.setAttribute('data-vizu-presence-avatar', user.id);\n wrap.setAttribute('data-vizu-presence-name', user.name);\n wrap.setAttribute('data-vizu-presence-color', user.color);\n if (user.avatar) wrap.setAttribute('data-vizu-presence-img', user.avatar);\n // Reduced motion drops the hover lift+scale so cursor-tracking users\n // don't see avatars springing when they accidentally pass over them.\n const reduced = prefersReducedMotion();\n const hoverTransition = reduced\n ? 'none'\n : 'transform 130ms cubic-bezier(0.16, 1, 0.3, 1), box-shadow 130ms ease-out';\n wrap.style.cssText = `\n position: relative;\n width: ${AVATAR_SIZE}px;\n height: ${AVATAR_SIZE}px;\n margin-left: -${OVERLAP_PX}px;\n border-radius: 50%;\n background: ${user.color};\n padding: 1.5px;\n box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.95), 0 2px 6px rgba(0, 0, 0, 0.15);\n cursor: pointer;\n flex-shrink: 0;\n transition: ${hoverTransition};\n `;\n wrap.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n onClick();\n });\n if (!reduced) {\n wrap.addEventListener(\n 'mouseenter',\n () => (wrap.style.transform = 'translateY(-1px) scale(1.06)'),\n );\n wrap.addEventListener('mouseleave', () => (wrap.style.transform = ''));\n }\n\n const inner = document.createElement('div');\n inner.setAttribute('data-vizu-presence-inner', '');\n inner.style.cssText = `\n width: 100%;\n height: 100%;\n border-radius: 50%;\n overflow: hidden;\n background: ${user.color};\n color: #ffffff;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 10.5px;\n font-weight: 600;\n letter-spacing: -0.01em;\n line-height: 1;\n user-select: none;\n `;\n paintAvatarContent(inner, user);\n wrap.appendChild(inner);\n\n // Tooltip\n const tip = createTooltip(user.name);\n wrap.appendChild(tip);\n\n wrap.addEventListener('mouseenter', () => {\n tip.style.opacity = '1';\n tip.style.transform = 'translateY(0)';\n });\n wrap.addEventListener('mouseleave', () => {\n tip.style.opacity = '0';\n tip.style.transform = 'translateY(-2px)';\n });\n\n return wrap;\n}\n\nfunction updateAvatar(wrap: HTMLDivElement, user: PresenceUser): void {\n // Color is per-userId and stable, so we don't refresh the ring on each\n // update — but the name / avatar URL can change (e.g., Clerk profile\n // edit). Re-paint the inner + tooltip text + dataset only.\n const inner = wrap.querySelector<HTMLDivElement>('[data-vizu-presence-inner]');\n if (inner) paintAvatarContent(inner, user);\n const tip = wrap.querySelector<HTMLDivElement>('[data-vizu-presence-tip]');\n if (tip) tip.textContent = user.name;\n wrap.setAttribute('data-vizu-presence-name', user.name);\n if (user.avatar) wrap.setAttribute('data-vizu-presence-img', user.avatar);\n else wrap.removeAttribute('data-vizu-presence-img');\n}\n\n/**\n * Read back the metadata we stashed on the wrap so setFollowing() can\n * repaint without needing the full PresenceUser list.\n */\nfunction avatarMeta(wrap: HTMLDivElement): PresenceUser | null {\n const id = wrap.getAttribute('data-vizu-presence-avatar');\n const name = wrap.getAttribute('data-vizu-presence-name');\n const color = wrap.getAttribute('data-vizu-presence-color');\n if (!id || !name || !color) return null;\n const avatar = wrap.getAttribute('data-vizu-presence-img') ?? undefined;\n return { id, name, color, avatar };\n}\n\n/**\n * Swap the avatar's shadow rings to mark it as the followee. Coral\n * outer ring is the visual signature. Stays in sync with the\n * FollowPill across the top-right cluster.\n */\nfunction paintAvatarFollowState(\n wrap: HTMLDivElement,\n user: PresenceUser,\n isFollowing: boolean,\n): void {\n if (isFollowing) {\n wrap.style.boxShadow = `0 0 0 2px rgba(255, 255, 255, 0.95), 0 0 0 4px var(--vizu-coral, #FF6647), 0 4px 12px rgba(255, 102, 71, 0.4)`;\n } else {\n wrap.style.boxShadow = `0 0 0 2px rgba(255, 255, 255, 0.95), 0 2px 6px rgba(0, 0, 0, 0.15)`;\n }\n // Silence the unused-arg warning when user isn't read; kept for symmetry.\n void user;\n}\n\nfunction paintAvatarContent(inner: HTMLDivElement, user: PresenceUser): void {\n inner.textContent = '';\n if (user.avatar) {\n const img = document.createElement('img');\n img.src = user.avatar;\n img.alt = user.name;\n img.referrerPolicy = 'no-referrer';\n img.style.cssText = 'width: 100%; height: 100%; object-fit: cover; display: block;';\n // If the image fails (CSP, expired Clerk url, broken link), fall back\n // to initials. Without this the avatar shows the broken-image glyph.\n img.addEventListener(\n 'error',\n () => {\n img.remove();\n inner.textContent = initialsOf(user.name);\n },\n { once: true },\n );\n inner.appendChild(img);\n } else {\n inner.textContent = initialsOf(user.name);\n }\n}\n\nfunction createTooltip(text: string): HTMLDivElement {\n const tip = document.createElement('div');\n tip.setAttribute('data-vizu-presence-tip', '');\n tip.textContent = text;\n tip.style.cssText = `\n position: absolute;\n top: calc(100% + 6px);\n right: 0;\n background: rgba(10, 10, 10, 0.92);\n color: #ffffff;\n font-size: 11px;\n font-weight: 500;\n padding: 4px 8px;\n border-radius: 4px;\n white-space: nowrap;\n pointer-events: none;\n opacity: 0;\n transform: translateY(-2px);\n transition: opacity 130ms ease-out, transform 130ms ease-out;\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.28);\n max-width: 220px;\n overflow: hidden;\n text-overflow: ellipsis;\n `;\n return tip;\n}\n\nfunction createOverflowChip(): HTMLDivElement {\n const chip = document.createElement('div');\n chip.setAttribute('data-vizu-presence-overflow', '');\n chip.style.cssText = `\n width: ${AVATAR_SIZE}px;\n height: ${AVATAR_SIZE}px;\n margin-left: -${OVERLAP_PX}px;\n border-radius: 50%;\n background: rgba(82, 82, 82, 0.95);\n color: #ffffff;\n box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.95), 0 2px 6px rgba(0, 0, 0, 0.15);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 10.5px;\n font-weight: 600;\n letter-spacing: -0.02em;\n line-height: 1;\n user-select: none;\n flex-shrink: 0;\n `;\n return chip;\n}\n\nfunction initialsOf(name: string): string {\n const trimmed = name.trim();\n if (!trimmed) return '?';\n const parts = trimmed.split(/\\s+/);\n if (parts.length >= 2 && parts[0] && parts[parts.length - 1]) {\n return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();\n }\n return trimmed.slice(0, 2).toUpperCase();\n}\n","import { findByFingerprint } from './fingerprint';\nimport { prefersReducedMotion } from './cursor-colors';\nimport type { ElementFingerprint } from './types';\n\n/**\n * Ghost outlines for other users' in-flight element selections.\n *\n * When another reviewer hovers an element in highlighter mode, their\n * cursor's fingerprint comes through LivePresence. This overlay resolves\n * that fingerprint against the local DOM via Vizu's 6-rung matcher and\n * draws a colored dashed outline on the matched element, with the\n * user's name in a small pill at top-left.\n *\n * Coords here are DOM-anchored (not viewport-%) — the right call for\n * \"which element are they about to comment on\" because viewport-% would\n * land on different elements at different viewport widths. See\n * plans/live-collab.md → \"selection sync coord override\".\n *\n * Repositioning happens on scroll/resize via a single RAF — the\n * outline tracks its element as the page shifts under it.\n */\n\nconst Z_INDEX = 2147483639; // below cursors (~641), above the rest\n\nexport type SelectionUser = {\n /** Stable Clerk user id, used to diff overlay entries. */\n id: string;\n /** Display name shown in the small color pill. */\n name: string;\n /** Cursor color (matches their cursor + avatar). */\n color: string;\n /** The fingerprint to match against the local DOM. */\n fingerprint: ElementFingerprint;\n};\n\nexport class SelectionOverlay {\n private container: HTMLDivElement | null = null;\n private overlays = new Map<string, OverlayEntry>();\n private scrollListener: ((e: Event) => void) | null = null;\n private resizeListener: (() => void) | null = null;\n private rafScheduled = false;\n\n mount(): void {\n if (typeof document === 'undefined') return;\n if (this.container) return;\n this.container = document.createElement('div');\n this.container.setAttribute('data-vizu-selections', '');\n this.container.style.cssText = `\n position: fixed; inset: 0;\n pointer-events: none;\n z-index: ${Z_INDEX};\n contain: strict;\n `;\n document.body.appendChild(this.container);\n\n this.scrollListener = () => this.scheduleReposition();\n this.resizeListener = () => this.scheduleReposition();\n // Capture-phase scroll so we catch scrolls in nested scrollable\n // containers, not just window scroll. Matches Vizu's existing\n // anchor outline behavior.\n window.addEventListener('scroll', this.scrollListener, { passive: true, capture: true });\n window.addEventListener('resize', this.resizeListener);\n }\n\n destroy(): void {\n if (this.scrollListener) {\n window.removeEventListener('scroll', this.scrollListener, true);\n this.scrollListener = null;\n }\n if (this.resizeListener) {\n window.removeEventListener('resize', this.resizeListener);\n this.resizeListener = null;\n }\n for (const e of this.overlays.values()) e.el.remove();\n this.overlays.clear();\n this.container?.remove();\n this.container = null;\n }\n\n setSelections(selections: SelectionUser[]): void {\n if (!this.container) return;\n\n const nextIds = new Set(selections.map((s) => s.id));\n for (const [id, entry] of Array.from(this.overlays)) {\n if (!nextIds.has(id)) {\n entry.el.remove();\n this.overlays.delete(id);\n }\n }\n\n for (const sel of selections) {\n let entry = this.overlays.get(sel.id);\n if (!entry) {\n const el = createOverlay(sel.color, sel.name);\n this.container.appendChild(el);\n entry = { el, fingerprint: sel.fingerprint, color: sel.color, name: sel.name };\n this.overlays.set(sel.id, entry);\n } else {\n // Update if user moved to a different element or changed color/name.\n entry.fingerprint = sel.fingerprint;\n if (entry.color !== sel.color) {\n entry.color = sel.color;\n entry.el.style.borderColor = sel.color;\n }\n if (entry.name !== sel.name) {\n entry.name = sel.name;\n const label = entry.el.querySelector<HTMLDivElement>('[data-selection-label]');\n if (label) {\n label.textContent = sel.name;\n label.style.background = sel.color;\n }\n }\n }\n this.positionOverlay(entry);\n }\n }\n\n /* ────────────────── private ────────────────── */\n\n private positionOverlay(entry: OverlayEntry): void {\n const match = findByFingerprint(entry.fingerprint);\n const target = (match as { element?: Element | null } | null)?.element ?? null;\n if (!target) {\n entry.el.style.display = 'none';\n return;\n }\n const rect = target.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) {\n entry.el.style.display = 'none';\n return;\n }\n entry.el.style.display = 'block';\n entry.el.style.left = `${rect.left}px`;\n entry.el.style.top = `${rect.top}px`;\n entry.el.style.width = `${rect.width}px`;\n entry.el.style.height = `${rect.height}px`;\n }\n\n private scheduleReposition(): void {\n if (this.rafScheduled) return;\n this.rafScheduled = true;\n requestAnimationFrame(() => {\n this.rafScheduled = false;\n for (const entry of this.overlays.values()) this.positionOverlay(entry);\n });\n }\n}\n\ntype OverlayEntry = {\n el: HTMLDivElement;\n fingerprint: ElementFingerprint;\n color: string;\n name: string;\n};\n\nfunction createOverlay(color: string, name: string): HTMLDivElement {\n const el = document.createElement('div');\n el.setAttribute('data-vizu-selection-overlay', '');\n // Reduced motion snaps the outline to its new position instead of\n // sliding — the outline tracking another user's hover would otherwise\n // be one of the more vestibular-triggering motions in the UI.\n const positionTransition = prefersReducedMotion()\n ? 'none'\n : 'left 90ms ease-out, top 90ms ease-out, width 90ms ease-out, height 90ms ease-out';\n el.style.cssText = `\n position: fixed;\n pointer-events: none;\n display: none;\n border: 2px dashed ${color};\n border-radius: 4px;\n background: ${color}10;\n box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.85);\n transition: ${positionTransition};\n will-change: left, top, width, height;\n `;\n\n const label = document.createElement('div');\n label.setAttribute('data-selection-label', '');\n label.textContent = name;\n label.style.cssText = `\n position: absolute;\n top: -22px;\n left: -2px;\n padding: 2px 7px;\n background: ${color};\n color: #ffffff;\n font: 500 10.5px/1.4 -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n border-radius: 3px 3px 3px 0;\n white-space: nowrap;\n max-width: 180px;\n overflow: hidden;\n text-overflow: ellipsis;\n box-shadow: 0 2px 4px rgba(0,0,0,0.15);\n `;\n el.appendChild(label);\n return el;\n}\n","import { PresenceStack, type PresenceUser } from './presence-stack';\nimport { SelectionOverlay, type SelectionUser } from './selection-overlay';\nimport { colorForUser, prefersReducedMotion } from './cursor-colors';\nimport type { ElementFingerprint } from './types';\n\n/**\n * Snapshot of one other connection in the room. Mirrors the server's\n * StoredPresence shape minus the timestamp (clients don't need it).\n */\ntype OtherEntry = {\n connId: string;\n presence: LivePresence;\n user: { userId: string; name?: string; avatar?: string; role: string };\n};\n\ntype ServerOthersResponse = {\n others: Array<{ connId: string; payload: { presence: LivePresence; user: OtherEntry['user']; ts: number } }>;\n};\n\n/**\n * Random connection id — equivalent of Liveblocks' per-connection number,\n * but generated client-side since we don't have a WebSocket assigning one.\n * 16 hex chars = 64 bits of entropy; collision risk is negligible per room.\n */\nfunction generateConnId(): string {\n const a = Math.floor(Math.random() * 0xffffffff).toString(16).padStart(8, '0');\n const b = Math.floor(Math.random() * 0xffffffff).toString(16).padStart(8, '0');\n return `${a}${b}`;\n}\n\n/**\n * Live cursors + presence module.\n *\n * Vanilla DOM, no React. Renders other users' cursors as plain `<div>`s\n * with CSS transforms (GPU-accelerated). Smoothing via a single CSS\n * transition on `transform` — no RAF loop needed.\n *\n * Coords are viewport-relative percents — at different viewport widths,\n * a 50%×30% cursor lands at different physical positions, which we\n * accept as the trade for not having to anchor cursors to fingerprinted\n * DOM elements (see plans/live-collab.md → \"viewport-relative %\" pick).\n * Selection-sync in Slice 5 uses DOM-anchored coords for accuracy.\n *\n * Lifecycle:\n * start() → create client, enter room, attach mouse listener\n * destroy() → leave room, remove cursors, drop listeners\n *\n * Plan-gate (Pro+ for `live_collab` feature) happens server-side at the\n * auth endpoint. If that returns 402, `enterRoom` throws and we leave\n * the cursor surface empty — silently. No 402 → no rendering, but no\n * crash either; live collab is opt-in upgrade UX, not a gate.\n *\n * See plans/live-collab.md → Slice 2.\n */\n\n/* ─────────────────────── presence types ────────────────────── */\n\nexport type LivePresence = {\n cursor: { xPct: number; yPct: number } | null;\n /**\n * Absolute scroll position of the user's window. Broadcast at ~10Hz\n * while they scroll. Consumed by follow mode (Slice 4) to keep a\n * follower's viewport locked to the followee's.\n */\n scrollY: number;\n /**\n * The element the user is currently hovering in highlighter mode —\n * fingerprinted so the receiving client resolves it against its own\n * DOM regardless of viewport differences. Null when not in\n * highlighter mode or hovering nothing. Slice 5 of live-collab.md.\n *\n * Wire format is JSON string because Liveblocks presence values must\n * satisfy JsonObject (strict index signature), and ElementFingerprint\n * doesn't expose one. JSON-encoding sidesteps the type wrangling.\n */\n selecting: { fingerprintJson: string } | null;\n};\n\nexport type LiveUserMeta = {\n id: string;\n info: {\n name: string;\n avatar?: string;\n role?: string;\n };\n};\n\n/* ──────────────────────── tuning knobs ─────────────────────── */\n\n/**\n * Push every 100ms. Mouse + scroll events update `currentPresence` in\n * place; the next tick sends whatever's there. This pattern naturally\n * throttles broadcasts to 10 Hz without needing per-event timers.\n */\nconst PUSH_INTERVAL_MS = 100;\n/**\n * Pull every 200ms. Lower than push because cursor lag tolerates more\n * latency than our own broadcast cadence, and pulls are bigger payloads.\n */\nconst PULL_INTERVAL_MS = 200;\nconst INACTIVITY_FADE_MS = 5000;\nconst CURSOR_TRANSITION_MS = 220; // matches pull interval so cursors lerp smoothly between samples\nconst Z_INDEX = 2147483641; // 1 above @vizu/core's other UI\n/** Minimum delta in px before a followee's scrollY triggers our scroll. */\nconst FOLLOW_SCROLL_THRESHOLD_PX = 8;\n/** Errors before we flag the connection as reconnecting / disconnected. */\nconst RECONNECT_AFTER_ERRORS = 2;\nconst DISCONNECT_AFTER_ERRORS = 5;\n\n/* ──────────────────────── room id ──────────────────────────── */\n\n/**\n * 32-bit FNV-1a → 8-char lowercase hex. Must match the server's\n * `fnv1a32Hex` in apps/landing/lib/liveblocks.ts so client and server\n * compute the same room id from the same URL.\n */\nfunction fnv1a32Hex(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(16).padStart(8, '0');\n}\n\nexport function roomIdFor(workspaceSlug: string, pageUrl: string): string {\n return `ws_${workspaceSlug}__pg_${fnv1a32Hex(pageUrl)}`;\n}\n\n/* ──────────────────────── module ───────────────────────────── */\n\ntype CursorEntry = {\n el: HTMLDivElement;\n fadeTimer: number | null;\n};\n\nexport class LiveCollab {\n private container: HTMLDivElement | null = null;\n private cursors = new Map<string, CursorEntry>(); // keyed by connId now (was numeric connectionId)\n private presenceStack: PresenceStack | null = null;\n private followPill: FollowPill | null = null;\n private selectionOverlay: SelectionOverlay | null = null;\n private mouseListener: ((e: MouseEvent) => void) | null = null;\n private scrollListener: (() => void) | null = null;\n private visibilityListener: (() => void) | null = null;\n private keydownListener: ((e: KeyboardEvent) => void) | null = null;\n private statusIndicator: ConnectionIndicator | null = null;\n private pushTimer: number | null = null;\n private pullTimer: number | null = null;\n private destroyed = false;\n\n /** Server-assigned-equivalent — generated client-side on construction, included in every push. */\n private readonly connId: string;\n /** Full room id, built once in start() from workspace + pageUrl. */\n private roomId: string | null = null;\n /** Latest snapshot of other connections from /api/live/[room]/others. */\n private latestOthers: OtherEntry[] = [];\n /** Local presence — mouse + scroll + selection update this in place; pushTimer ships it. */\n private currentPresence: LivePresence = { cursor: null, scrollY: 0, selecting: null };\n /** True when something changed since the last push — skip the POST otherwise. */\n private presenceDirty = false;\n /** Connection-health tracker for the indicator. */\n private status: LiveStatus = 'initial';\n private consecutiveErrors = 0;\n\n // Follow mode state\n /** Clerk user id we're currently following, or null. */\n private followingUserId: string | null = null;\n /** Last scrollY we received from the followee — for delta gating. */\n private lastFolloweeScrollY: number | null = null;\n\n constructor(\n private opts: {\n /** Workspace slug — combined with page URL to derive the room id. */\n workspaceSlug: string;\n /** Vizu backend base URL, e.g. https://vizu.unhingged.com. */\n apiUrl: string;\n /**\n * Optional async resolver for cross-origin auth. Returns a header\n * map to merge into fetch calls — typically `{ Authorization:\n * 'Bearer <host-token>' }`. Same-origin callers can return null\n * and rely on credentials: 'include'.\n */\n getAuthHeader?: () => Promise<HeadersInit | null>;\n /** Page URL the room scopes to. Defaults to `location.origin + location.pathname`. */\n pageUrl?: string;\n },\n ) {\n this.connId = generateConnId();\n if (typeof window !== 'undefined') {\n this.currentPresence.scrollY = window.scrollY;\n }\n }\n\n async start(): Promise<void> {\n if (typeof window === 'undefined' || typeof document === 'undefined') return;\n if (this.destroyed) return;\n\n try {\n const pageUrl = this.opts.pageUrl ?? window.location.origin + window.location.pathname;\n this.roomId = roomIdFor(this.opts.workspaceSlug, pageUrl);\n\n this.mountContainer();\n this.presenceStack = new PresenceStack({\n onAvatarClick: (userId) => this.setFollowing(userId),\n });\n this.presenceStack.mount();\n this.followPill = new FollowPill({\n onStop: () => this.setFollowing(null),\n });\n this.selectionOverlay = new SelectionOverlay();\n this.selectionOverlay.mount();\n this.statusIndicator = new ConnectionIndicator();\n this.statusIndicator.mount();\n\n this.setStatus('connecting');\n this.attachMouseListener();\n this.attachScrollListener();\n this.attachVisibilityListener();\n this.attachKeyboardListener();\n\n // Push + pull tickers. First push runs immediately so we appear in\n // the room before the first 100ms tick; first pull also immediate\n // so other people's cursors render fast on join.\n this.pushTimer = window.setInterval(() => void this.push(), PUSH_INTERVAL_MS);\n this.pullTimer = window.setInterval(() => void this.pull(), PULL_INTERVAL_MS);\n this.presenceDirty = true; // force the first push even if no movement yet\n void this.push();\n void this.pull();\n } catch (err) {\n if (typeof console !== 'undefined') {\n console.warn('[vizu/live-collab] start failed; live cursors disabled:', err);\n }\n this.cleanup();\n }\n }\n\n destroy(): void {\n this.destroyed = true;\n this.cleanup();\n }\n\n /* ────────────────── private ────────────────── */\n\n private cleanup(): void {\n // Best-effort: tell the server we're gone so other clients see us\n // disappear before the 5-second TTL would auto-clean us.\n void this.clearRemotePresence();\n if (this.mouseListener) {\n window.removeEventListener('mousemove', this.mouseListener);\n this.mouseListener = null;\n }\n if (this.scrollListener) {\n window.removeEventListener('scroll', this.scrollListener);\n this.scrollListener = null;\n }\n if (this.visibilityListener) {\n document.removeEventListener('visibilitychange', this.visibilityListener);\n this.visibilityListener = null;\n }\n if (this.keydownListener) {\n document.removeEventListener('keydown', this.keydownListener);\n this.keydownListener = null;\n }\n if (this.pushTimer != null) {\n window.clearInterval(this.pushTimer);\n this.pushTimer = null;\n }\n if (this.pullTimer != null) {\n window.clearInterval(this.pullTimer);\n this.pullTimer = null;\n }\n for (const c of this.cursors.values()) {\n if (c.fadeTimer != null) window.clearTimeout(c.fadeTimer);\n c.el.remove();\n }\n this.cursors.clear();\n this.presenceStack?.destroy();\n this.presenceStack = null;\n this.followPill?.destroy();\n this.followPill = null;\n this.selectionOverlay?.destroy();\n this.selectionOverlay = null;\n this.statusIndicator?.destroy();\n this.statusIndicator = null;\n this.followingUserId = null;\n this.lastFolloweeScrollY = null;\n this.container?.remove();\n this.container = null;\n this.roomId = null;\n this.latestOthers = [];\n }\n\n /**\n * Public API: broadcast the local user's currently-hovered element\n * (highlighter mode) so other clients can render a ghost outline on\n * it. Pass null to clear (highlighter stopped, popover opened, etc.).\n * Idempotent on no-op transitions. Slice 5 of live-collab.md.\n */\n setLocalSelection(fingerprint: ElementFingerprint | null): void {\n this.currentPresence.selecting = fingerprint\n ? { fingerprintJson: JSON.stringify(fingerprint) }\n : null;\n this.presenceDirty = true;\n }\n\n /**\n * Public API: start/stop following a user by Clerk id. Null clears.\n * Called by the avatar click handler and by the FollowPill stop button.\n * Idempotent — same id again is a no-op.\n */\n setFollowing(userId: string | null): void {\n if (this.followingUserId === userId) return;\n this.followingUserId = userId;\n this.lastFolloweeScrollY = null;\n this.presenceStack?.setFollowing(userId);\n this.followPill?.setFollowing(userId ? this.lookupUserForPill(userId) : null);\n // If we just started following someone whose scroll position we\n // already know from the latest others snapshot, jump to it now.\n if (userId) this.scrollToFolloweeIfKnown();\n }\n\n private lookupUserForPill(userId: string): PresenceUser | null {\n for (const o of this.latestOthers) {\n if (o.user.userId === userId) {\n return {\n id: userId,\n name: o.user.name ?? 'Anonymous',\n color: colorForUser(userId),\n avatar: o.user.avatar,\n };\n }\n }\n return null;\n }\n\n private scrollToFolloweeIfKnown(): void {\n if (!this.followingUserId) return;\n for (const o of this.latestOthers) {\n if (o.user.userId === this.followingUserId) {\n if (typeof o.presence.scrollY === 'number') {\n this.followScrollTo(o.presence.scrollY);\n }\n return;\n }\n }\n }\n\n private followScrollTo(scrollY: number): void {\n if (this.lastFolloweeScrollY !== null && Math.abs(this.lastFolloweeScrollY - scrollY) < FOLLOW_SCROLL_THRESHOLD_PX) {\n return;\n }\n this.lastFolloweeScrollY = scrollY;\n window.scrollTo({ top: scrollY, behavior: 'smooth' });\n }\n\n private mountContainer(): void {\n this.container = document.createElement('div');\n this.container.setAttribute('data-vizu-live-cursors', '');\n this.container.style.cssText = `\n position: fixed; inset: 0;\n pointer-events: none;\n z-index: ${Z_INDEX};\n contain: strict;\n `;\n document.body.appendChild(this.container);\n }\n\n private attachMouseListener(): void {\n // Update in place; the pushTimer ships it on the next 100ms tick.\n // No per-event timer needed because the interval IS the throttle.\n this.mouseListener = (e: MouseEvent) => {\n this.currentPresence.cursor = {\n xPct: (e.clientX / window.innerWidth) * 100,\n yPct: (e.clientY / window.innerHeight) * 100,\n };\n this.presenceDirty = true;\n };\n window.addEventListener('mousemove', this.mouseListener, { passive: true });\n }\n\n private attachScrollListener(): void {\n // Same pattern as the mouse listener — update in place, let the\n // push interval batch.\n this.scrollListener = () => {\n this.currentPresence.scrollY = window.scrollY;\n this.presenceDirty = true;\n };\n window.addEventListener('scroll', this.scrollListener, { passive: true });\n }\n\n private attachVisibilityListener(): void {\n this.visibilityListener = () => {\n if (document.hidden) {\n // Stop broadcasting so other tabs don't see a stale cursor stuck\n // in place after we alt-tabbed away. clearOnly path on /me\n // deletes our Redis entry; others see us disappear on their\n // next pull within ~200ms.\n void this.clearRemotePresence();\n } else {\n // Tab back in focus — resume by marking presence dirty so the\n // next tick re-establishes our entry.\n this.presenceDirty = true;\n }\n };\n document.addEventListener('visibilitychange', this.visibilityListener);\n }\n\n private attachKeyboardListener(): void {\n // Esc cancels follow mode — a single, well-known keybind for \"let me out\".\n // We deliberately don't preventDefault / stopPropagation so other Esc\n // handlers on the page still fire; they just see the key after we react.\n this.keydownListener = (e: KeyboardEvent) => {\n if (e.key !== 'Escape') return;\n if (!this.followingUserId) return;\n this.setFollowing(null);\n };\n document.addEventListener('keydown', this.keydownListener);\n }\n\n private setStatus(next: LiveStatus): void {\n if (this.status === next) return;\n this.status = next;\n this.statusIndicator?.setStatus(next);\n }\n\n /**\n * Push tick — POST current presence to /api/live/[room]/me. Skips\n * the request when nothing changed since last push, so an idle tab\n * generates near-zero traffic.\n */\n private async push(): Promise<void> {\n if (this.destroyed || !this.roomId) return;\n if (!this.presenceDirty) return;\n if (typeof document !== 'undefined' && document.hidden) return; // hidden tabs skip pushes\n this.presenceDirty = false;\n try {\n const res = await this.fetchLive(`/api/live/${encodeURIComponent(this.roomId)}/me`, {\n method: 'POST',\n body: JSON.stringify({\n connId: this.connId,\n presence: this.currentPresence,\n }),\n });\n if (!res.ok) {\n // 402 plan_limit / 401 auth / 503 store-down — any of these are\n // fatal for the live layer. Stop pushing and surface offline.\n if (res.status === 402 || res.status === 401 || res.status === 503) {\n this.setStatus('disconnected');\n if (this.pushTimer != null) {\n window.clearInterval(this.pushTimer);\n this.pushTimer = null;\n }\n if (this.pullTimer != null) {\n window.clearInterval(this.pullTimer);\n this.pullTimer = null;\n }\n return;\n }\n throw new Error(`push HTTP ${res.status}`);\n }\n this.noteSuccess();\n } catch (err) {\n this.noteError(err);\n // Re-mark dirty so the next tick retries the same payload.\n this.presenceDirty = true;\n }\n }\n\n /**\n * Pull tick — GET /api/live/[room]/others and drive cursor + stack +\n * selection + follow updates. Same handler shape as the Liveblocks-era\n * subscribeOthers callback; only the data source changed.\n */\n private async pull(): Promise<void> {\n if (this.destroyed || !this.roomId) return;\n try {\n const res = await this.fetchLive(\n `/api/live/${encodeURIComponent(this.roomId)}/others?me=${encodeURIComponent(this.connId)}`,\n { method: 'GET' },\n );\n if (!res.ok) {\n if (res.status === 503 || res.status === 401) {\n this.setStatus('disconnected');\n if (this.pullTimer != null) {\n window.clearInterval(this.pullTimer);\n this.pullTimer = null;\n }\n return;\n }\n throw new Error(`pull HTTP ${res.status}`);\n }\n const body = (await res.json()) as ServerOthersResponse;\n const others: OtherEntry[] = (body.others ?? []).map((o) => ({\n connId: o.connId,\n presence: o.payload.presence,\n user: o.payload.user,\n }));\n this.latestOthers = others;\n this.renderOthers(others);\n this.noteSuccess();\n } catch (err) {\n this.noteError(err);\n }\n }\n\n private noteSuccess(): void {\n this.consecutiveErrors = 0;\n if (this.status !== 'connected') this.setStatus('connected');\n }\n\n private noteError(err: unknown): void {\n this.consecutiveErrors++;\n if (this.consecutiveErrors >= DISCONNECT_AFTER_ERRORS) {\n this.setStatus('disconnected');\n } else if (this.consecutiveErrors >= RECONNECT_AFTER_ERRORS) {\n this.setStatus('reconnecting');\n }\n if (typeof console !== 'undefined' && this.consecutiveErrors === 1) {\n console.warn('[vizu/live-collab] transport error', err);\n }\n }\n\n /**\n * Drive the cursor / stack / selection / follow layers from a fresh\n * `others` snapshot. Mirrors the body of the Liveblocks-era\n * subscribeOthers callback so DOM behavior is unchanged.\n */\n private renderOthers(others: OtherEntry[]): void {\n const seen = new Set<string>();\n // De-dupe presence-stack entries by user id (one user, multiple\n // tabs → multiple connId entries → one avatar). Cursors stay\n // per-connection because cursors are per-mouse; the stack is\n // per-person.\n const stackUsers = new Map<string, PresenceUser>();\n const selections: SelectionUser[] = [];\n let followeeScrollY: number | null = null;\n let followeeStillPresent = false;\n\n for (const other of others) {\n seen.add(other.connId);\n const userId = other.user.userId;\n const name = other.user.name ?? 'Anonymous';\n const color = colorForUser(userId);\n\n if (!stackUsers.has(userId)) {\n stackUsers.set(userId, { id: userId, name, color, avatar: other.user.avatar });\n }\n\n if (this.followingUserId === userId && followeeScrollY === null) {\n followeeStillPresent = true;\n if (typeof other.presence.scrollY === 'number') {\n followeeScrollY = other.presence.scrollY;\n }\n }\n\n if (other.presence.selecting && !selections.find((s) => s.id === userId)) {\n try {\n const fp = JSON.parse(other.presence.selecting.fingerprintJson) as ElementFingerprint;\n selections.push({ id: userId, name, color, fingerprint: fp });\n } catch {\n /* malformed payload from a future client; skip */\n }\n }\n\n if (!other.presence.cursor) {\n this.removeCursor(other.connId);\n continue;\n }\n this.upsertCursor(other.connId, userId, name, other.presence.cursor);\n }\n\n for (const id of Array.from(this.cursors.keys())) {\n if (!seen.has(id)) this.removeCursor(id);\n }\n\n this.presenceStack?.setOthers(Array.from(stackUsers.values()));\n this.selectionOverlay?.setSelections(selections);\n\n if (this.followingUserId) {\n if (!followeeStillPresent) {\n this.setFollowing(null);\n } else if (followeeScrollY !== null) {\n this.followScrollTo(followeeScrollY);\n }\n }\n }\n\n /**\n * Async wrapper around fetch that injects the optional auth header\n * resolved from opts.getAuthHeader. Same-origin requests rely on\n * cookies via `credentials: 'include'`; cross-origin gets the\n * bearer token returned by the host.\n */\n private async fetchLive(path: string, init: RequestInit): Promise<Response> {\n const headers = new Headers(init.headers);\n headers.set('content-type', 'application/json');\n if (this.opts.getAuthHeader) {\n const extra = await this.opts.getAuthHeader();\n if (extra) {\n new Headers(extra).forEach((v, k) => headers.set(k, v));\n }\n }\n return fetch(this.opts.apiUrl + path, {\n ...init,\n headers,\n credentials: 'include',\n });\n }\n\n /**\n * Fire-and-forget clearOnly POST so other clients see us drop within\n * one pull cycle (~200ms) instead of waiting for the 5s presence TTL.\n * Errors swallowed — the TTL is the safety net.\n */\n private async clearRemotePresence(): Promise<void> {\n if (!this.roomId) return;\n try {\n await this.fetchLive(`/api/live/${encodeURIComponent(this.roomId)}/me`, {\n method: 'POST',\n body: JSON.stringify({ connId: this.connId, clearOnly: true }),\n });\n } catch {\n /* silent — Redis TTL evicts within 5s anyway */\n }\n }\n\n private upsertCursor(\n connectionId: string,\n userId: string,\n name: string,\n cursor: { xPct: number; yPct: number },\n ): void {\n if (!this.container) return;\n let entry = this.cursors.get(connectionId);\n if (!entry) {\n const color = colorForUser(userId);\n const el = createCursorElement(name, color);\n this.container.appendChild(el);\n entry = { el, fadeTimer: null };\n this.cursors.set(connectionId, entry);\n }\n // Clip cursors that wander outside the viewport — visible at the edge\n // looks worse than just hidden.\n const inViewport = cursor.xPct >= 0 && cursor.xPct <= 100 && cursor.yPct >= 0 && cursor.yPct <= 100;\n if (!inViewport) {\n entry.el.style.opacity = '0';\n return;\n }\n const x = (window.innerWidth * cursor.xPct) / 100;\n const y = (window.innerHeight * cursor.yPct) / 100;\n entry.el.style.transform = `translate(${x}px, ${y}px)`;\n entry.el.style.opacity = '1';\n if (entry.fadeTimer != null) window.clearTimeout(entry.fadeTimer);\n entry.fadeTimer = window.setTimeout(() => {\n if (entry) entry.el.style.opacity = '0';\n }, INACTIVITY_FADE_MS);\n }\n\n private removeCursor(connectionId: string): void {\n const entry = this.cursors.get(connectionId);\n if (!entry) return;\n if (entry.fadeTimer != null) window.clearTimeout(entry.fadeTimer);\n entry.el.style.opacity = '0';\n // Defer DOM removal until the fade transition completes so it doesn't\n // pop. 220 = transition (110) + a little slack.\n window.setTimeout(() => entry.el.remove(), 220);\n this.cursors.delete(connectionId);\n }\n}\n\n/* ──────────────────────── cursor DOM ───────────────────────── */\n\n/**\n * Build the cursor element — SVG pointer + colored name label.\n * Returns a positioned wrapper; caller updates `transform` to move it.\n */\nfunction createCursorElement(name: string, color: string): HTMLDivElement {\n const wrap = document.createElement('div');\n wrap.setAttribute('data-vizu-cursor', '');\n // Reduced-motion drops the cursor smoothing so the pointer snaps instead\n // of easing — opacity transitions stay because they're not vestibular.\n const transformTransition = prefersReducedMotion()\n ? 'none'\n : `transform ${CURSOR_TRANSITION_MS}ms cubic-bezier(0.16, 1, 0.3, 1)`;\n wrap.style.cssText = `\n position: absolute; top: 0; left: 0;\n pointer-events: none;\n opacity: 0;\n transform: translate(0px, 0px);\n transition: ${transformTransition}, opacity 220ms ease-out;\n will-change: transform;\n `;\n\n // SVG cursor pointer — classic arrow shape with a white stroke for contrast.\n const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n svg.setAttribute('width', '18');\n svg.setAttribute('height', '20');\n svg.setAttribute('viewBox', '0 0 16 18');\n svg.style.cssText = 'display: block; filter: drop-shadow(0 1px 1.5px rgba(0,0,0,0.3));';\n const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');\n path.setAttribute('d', 'M0 0 L0 13 L4 9 L7 16 L9 15 L6 8 L11 8 Z');\n path.setAttribute('fill', color);\n path.setAttribute('stroke', '#ffffff');\n path.setAttribute('stroke-width', '1.2');\n path.setAttribute('stroke-linejoin', 'round');\n svg.appendChild(path);\n wrap.appendChild(svg);\n\n const label = document.createElement('div');\n label.textContent = name;\n label.style.cssText = `\n margin-top: 2px;\n margin-left: 10px;\n padding: 2px 7px;\n background: ${color};\n color: #ffffff;\n font: 500 11px/1.4 -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n border-radius: 4px;\n white-space: nowrap;\n box-shadow: 0 2px 6px rgba(0,0,0,0.18);\n max-width: 160px;\n overflow: hidden;\n text-overflow: ellipsis;\n `;\n wrap.appendChild(label);\n return wrap;\n}\n\n/* ──────────────────────── connection indicator ────────────── */\n\ntype LiveStatus = 'initial' | 'connecting' | 'connected' | 'reconnecting' | 'disconnected';\n\n/**\n * Tiny inline chip that surfaces non-healthy Liveblocks connection\n * states. Hidden when connected so it doesn't clutter; appears just\n * to the left of the presence stack during connect / reconnect /\n * disconnect.\n *\n * Auto-reconnection is handled by the Liveblocks SDK — this is\n * purely an \"I see you noticed\" surface for the human while the\n * SDK does its thing.\n */\nclass ConnectionIndicator {\n private el: HTMLDivElement | null = null;\n private label: HTMLSpanElement | null = null;\n\n mount(): void {\n if (typeof document === 'undefined') return;\n if (this.el) return;\n this.el = document.createElement('div');\n this.el.setAttribute('data-vizu-connection-status', '');\n this.el.style.cssText = `\n position: fixed;\n top: 18px;\n right: 200px;\n z-index: ${Z_INDEX};\n display: none;\n align-items: center;\n gap: 7px;\n padding: 4px 10px 4px 8px;\n background: rgba(10, 10, 10, 0.88);\n color: #fafafa;\n font: 500 11px/1.4 -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n border-radius: 999px;\n box-shadow: 0 6px 16px rgba(0, 0, 0, 0.22);\n pointer-events: none;\n `;\n const dot = document.createElement('span');\n dot.style.cssText = `\n display: inline-block;\n width: 6px; height: 6px;\n border-radius: 50%;\n background: #F59E0B;\n box-shadow: 0 0 6px #F59E0B;\n `;\n const label = document.createElement('span');\n this.label = label;\n this.el.appendChild(dot);\n this.el.appendChild(label);\n document.body.appendChild(this.el);\n }\n\n setStatus(status: LiveStatus): void {\n if (!this.el || !this.label) return;\n if (status === 'connected') {\n this.el.style.display = 'none';\n return;\n }\n this.label.textContent = labelForStatus(status);\n this.el.style.display = 'inline-flex';\n }\n\n destroy(): void {\n this.el?.remove();\n this.el = null;\n this.label = null;\n }\n}\n\nfunction labelForStatus(status: LiveStatus): string {\n switch (status) {\n case 'connecting':\n return 'Connecting…';\n case 'reconnecting':\n return 'Reconnecting…';\n case 'disconnected':\n return 'Offline';\n case 'initial':\n return 'Connecting…';\n case 'connected':\n return ''; // hidden anyway\n }\n}\n\n/* ──────────────────────── follow pill ──────────────────────── */\n\n/**\n * Top-right floating pill that shows the active followee + a stop\n * button. Sibling to the PresenceStack — appears below it when\n * follow mode is active, hides otherwise.\n *\n * Pure vanilla DOM; takes a stop callback from LiveCollab.\n */\nclass FollowPill {\n private el: HTMLDivElement | null = null;\n private onStop: () => void;\n\n constructor(opts: { onStop: () => void }) {\n this.onStop = opts.onStop;\n }\n\n setFollowing(user: PresenceUser | null): void {\n if (typeof document === 'undefined') return;\n if (!user) {\n this.el?.remove();\n this.el = null;\n return;\n }\n if (!this.el) this.el = this.mount();\n this.paint(user);\n }\n\n destroy(): void {\n this.el?.remove();\n this.el = null;\n }\n\n private mount(): HTMLDivElement {\n const pill = document.createElement('div');\n pill.setAttribute('data-vizu-follow-pill', '');\n pill.style.cssText = `\n position: fixed;\n top: 56px;\n right: 16px;\n z-index: ${Z_INDEX};\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 6px 6px 6px 12px;\n background: rgba(10, 10, 10, 0.92);\n color: #ffffff;\n border-radius: 999px;\n font: 500 12px/1.4 -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n box-shadow: 0 10px 24px rgba(0, 0, 0, 0.28);\n pointer-events: auto;\n `;\n document.body.appendChild(pill);\n return pill;\n }\n\n private paint(user: PresenceUser): void {\n if (!this.el) return;\n this.el.textContent = '';\n\n const dot = document.createElement('span');\n dot.style.cssText = `\n display: inline-block;\n width: 8px; height: 8px;\n border-radius: 50%;\n background: ${user.color};\n box-shadow: 0 0 8px ${user.color};\n `;\n this.el.appendChild(dot);\n\n const label = document.createElement('span');\n label.textContent = `Following ${user.name}`;\n label.style.cssText = 'max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;';\n this.el.appendChild(label);\n\n const stop = document.createElement('button');\n stop.type = 'button';\n stop.setAttribute('aria-label', `Stop following ${user.name}`);\n stop.innerHTML =\n '<svg width=\"11\" height=\"11\" viewBox=\"0 0 12 12\" aria-hidden=\"true\"><path d=\"M1 1 L11 11 M11 1 L1 11\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" /></svg>';\n stop.style.cssText = `\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px; height: 22px;\n border: none;\n background: rgba(255, 255, 255, 0.12);\n color: #ffffff;\n border-radius: 50%;\n cursor: pointer;\n transition: background 120ms ease-out;\n `;\n stop.addEventListener('mouseenter', () => (stop.style.background = 'rgba(255, 255, 255, 0.22)'));\n stop.addEventListener('mouseleave', () => (stop.style.background = 'rgba(255, 255, 255, 0.12)'));\n stop.addEventListener('click', (e) => {\n e.preventDefault();\n this.onStop();\n });\n this.el.appendChild(stop);\n }\n}\n"],"mappings":"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","names":["Z_INDEX","Z_INDEX"]}