@satanwagen/reviewkit 0.1.2 → 0.1.4

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.
Files changed (56) hide show
  1. package/CHANGELOG.md +158 -0
  2. package/README.md +158 -64
  3. package/dist/activate.cjs +150 -0
  4. package/dist/activate.cjs.map +1 -0
  5. package/dist/activate.d.cts +59 -0
  6. package/dist/activate.d.ts +59 -0
  7. package/dist/activate.js +21 -0
  8. package/dist/activate.js.map +1 -0
  9. package/dist/{chunk-YWBFAV57.js → chunk-5MAFDRUI.js} +1161 -1555
  10. package/dist/chunk-5MAFDRUI.js.map +1 -0
  11. package/dist/{chunk-4YNLMSCK.js → chunk-DG6O5XIC.js} +36 -10
  12. package/dist/chunk-DG6O5XIC.js.map +1 -0
  13. package/dist/chunk-EOX3UJNP.js +36 -0
  14. package/dist/chunk-EOX3UJNP.js.map +1 -0
  15. package/dist/chunk-ETAGGIDN.js +119 -0
  16. package/dist/chunk-ETAGGIDN.js.map +1 -0
  17. package/dist/client/index.cjs +4834 -4244
  18. package/dist/client/index.cjs.map +1 -1
  19. package/dist/client/index.d.cts +1 -1
  20. package/dist/client/index.d.ts +1 -1
  21. package/dist/client/index.js +49 -3
  22. package/dist/client/index.js.map +1 -1
  23. package/dist/effects-EWKHKUDP.js +851 -0
  24. package/dist/effects-EWKHKUDP.js.map +1 -0
  25. package/dist/index-CWGkg1-E.d.cts +86 -0
  26. package/dist/index-DPsjkvaY.d.ts +86 -0
  27. package/dist/index.cjs +4852 -4267
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +1 -1
  30. package/dist/index.d.ts +1 -1
  31. package/dist/index.js +35 -3
  32. package/dist/index.js.map +1 -1
  33. package/dist/lazy.cjs +9308 -0
  34. package/dist/lazy.cjs.map +1 -0
  35. package/dist/lazy.d.cts +7 -0
  36. package/dist/lazy.d.ts +7 -0
  37. package/dist/lazy.js +8 -0
  38. package/dist/lazy.js.map +1 -0
  39. package/dist/next.cjs +9309 -0
  40. package/dist/next.cjs.map +1 -0
  41. package/dist/next.d.cts +4 -0
  42. package/dist/next.d.ts +4 -0
  43. package/dist/next.js +9 -0
  44. package/dist/next.js.map +1 -0
  45. package/dist/schema.cjs +53 -16
  46. package/dist/schema.cjs.map +1 -1
  47. package/dist/schema.d.cts +20 -5
  48. package/dist/schema.d.ts +20 -5
  49. package/dist/schema.js +30 -2
  50. package/dist/schema.js.map +1 -1
  51. package/package.json +23 -8
  52. package/cli/emblema-sync.mjs +0 -675
  53. package/dist/chunk-4YNLMSCK.js.map +0 -1
  54. package/dist/chunk-YWBFAV57.js.map +0 -1
  55. package/dist/index-BbucFgZi.d.ts +0 -49
  56. package/dist/index-CBlJrKkm.d.cts +0 -49
@@ -1,7 +1,30 @@
1
- import { parseSessionJson, isCopyItem, isImageItem, sendToEmblema, parseSession, SCHEMA_VERSION, pingEmblema, isKnownItemType, fetchSyncKey, revokeSyncKey, isCommentItem, isDeleteItem, isMoveItem, isStyleItem, fetchSyncState, generateSyncKey, ITEM_STATUSES } from './chunk-4YNLMSCK.js';
2
- import { createContext, useState, useEffect, useReducer, useMemo, useRef, useContext, useLayoutEffect } from 'react';
3
- import { createPortal } from 'react-dom';
4
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
1
+ import {
2
+ ITEM_STATUSES,
3
+ SCHEMA_VERSION,
4
+ fetchSyncKey,
5
+ fetchSyncState,
6
+ generateSyncKey,
7
+ isCommentItem,
8
+ isCopyItem,
9
+ isDeleteItem,
10
+ isImageItem,
11
+ isKnownItemType,
12
+ isMoveItem,
13
+ isStyleItem,
14
+ parseSession,
15
+ parseSessionJson,
16
+ pingEmblema,
17
+ revokeSyncKey,
18
+ sendToEmblema
19
+ } from "./chunk-DG6O5XIC.js";
20
+ import {
21
+ clearSticky,
22
+ isLocalHost,
23
+ isStickyEnabled,
24
+ reviewParam,
25
+ setSticky,
26
+ shouldActivate
27
+ } from "./chunk-ETAGGIDN.js";
5
28
 
6
29
  // src/client/target.ts
7
30
  var HOST_ATTR = "data-review-kit-host";
@@ -316,19 +339,14 @@ function importSessionFile(file) {
316
339
  });
317
340
  }
318
341
 
342
+ // src/client/ReviewKit.tsx
343
+ import { useEffect as useEffect18, useState as useState14 } from "react";
344
+ import { createPortal } from "react-dom";
345
+
319
346
  // src/client/env.ts
320
- var STICKY_KEY = "review-kit:sticky";
321
347
  var LEGACY_SESSION_KEY = "review-kit:active";
322
348
  var LEGACY_LOCAL_FLAG = "reviewkit";
323
- var STICKY_TTL_MS = 4 * 60 * 60 * 1e3;
324
349
  var DEACTIVATE_EVENT = "review-kit:deactivate";
325
- function isDevBuild() {
326
- try {
327
- return process.env.NODE_ENV === "development";
328
- } catch {
329
- return false;
330
- }
331
- }
332
350
  function safeRemove(storage, key) {
333
351
  try {
334
352
  storage().removeItem(key);
@@ -339,40 +357,8 @@ function clearLegacyKeys() {
339
357
  safeRemove(() => window.sessionStorage, LEGACY_SESSION_KEY);
340
358
  safeRemove(() => window.localStorage, LEGACY_LOCAL_FLAG);
341
359
  }
342
- function readSticky() {
343
- try {
344
- const raw = window.sessionStorage.getItem(STICKY_KEY);
345
- if (!raw) return null;
346
- const parsed = JSON.parse(raw);
347
- if (typeof parsed === "object" && parsed !== null && typeof parsed.token === "string" && typeof parsed.until === "number") {
348
- return parsed;
349
- }
350
- } catch {
351
- }
352
- return null;
353
- }
354
- function isStickyEnabled(token) {
355
- const sticky = readSticky();
356
- if (!sticky) return false;
357
- if (sticky.until < Date.now() || sticky.token !== (token ?? "")) {
358
- safeRemove(() => window.sessionStorage, STICKY_KEY);
359
- return false;
360
- }
361
- return true;
362
- }
363
- function setSticky(token, on) {
364
- try {
365
- if (on) {
366
- const value = { token: token ?? "", until: Date.now() + STICKY_TTL_MS };
367
- window.sessionStorage.setItem(STICKY_KEY, JSON.stringify(value));
368
- } else {
369
- window.sessionStorage.removeItem(STICKY_KEY);
370
- }
371
- } catch {
372
- }
373
- }
374
360
  function deactivateReview() {
375
- safeRemove(() => window.sessionStorage, STICKY_KEY);
361
+ clearSticky();
376
362
  clearLegacyKeys();
377
363
  try {
378
364
  const url = new URL(window.location.href);
@@ -387,67 +373,24 @@ function deactivateReview() {
387
373
  } catch {
388
374
  }
389
375
  }
390
- function hostAllowed(allowedHosts) {
391
- if (!allowedHosts || allowedHosts.length === 0) return true;
392
- let host = "";
393
- try {
394
- host = window.location.hostname.toLowerCase();
395
- } catch {
396
- return false;
397
- }
398
- return allowedHosts.some((pattern) => {
399
- const regex = new RegExp(
400
- "^" + pattern.toLowerCase().split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*") + "$"
401
- );
402
- return regex.test(host);
403
- });
404
- }
405
- function resolveActivation(token) {
376
+ var warnedNoAllowlist = false;
377
+ function resolveActivation(options) {
406
378
  if (typeof window === "undefined") return false;
407
- let param = null;
408
- try {
409
- param = new URLSearchParams(window.location.search).get("review");
410
- } catch {
411
- param = null;
412
- }
413
- if (param === "off") {
414
- safeRemove(() => window.sessionStorage, STICKY_KEY);
415
- clearLegacyKeys();
416
- return false;
417
- }
418
- const tokenConfigured = token !== void 0 && token !== "";
419
- if (tokenConfigured) {
420
- if (param !== null && param !== "") {
421
- if (param === token) {
422
- setSticky(token, true);
423
- return true;
424
- }
425
- console.warn("[review-kit] `?review` token does not match the configured token; staying inactive.");
426
- return false;
427
- }
428
- if (isStickyEnabled(token)) {
429
- setSticky(token, true);
430
- return true;
379
+ clearLegacyKeys();
380
+ const active = shouldActivate(options);
381
+ if (!active) return false;
382
+ const token = options.token;
383
+ const param = reviewParam();
384
+ if (token !== void 0 && token !== "" && (param === token || isStickyEnabled(token))) {
385
+ setSticky(token, true);
386
+ if (param === token && !options.allowedHosts?.length && !isLocalHost() && !warnedNoAllowlist) {
387
+ warnedNoAllowlist = true;
388
+ console.warn(
389
+ "[review-kit] Review mode activated by URL token on a public hostname with no `allowedHosts`. For production pass `allowedHosts` next to `token` so the layer cannot be opened on hosts you did not intend."
390
+ );
431
391
  }
432
- clearLegacyKeys();
433
- return false;
434
- }
435
- if (param !== null && param !== "") {
436
- console.warn(
437
- "[review-kit] Activated via `?review` with no token configured. Pass a `token` prop to <ReviewKit /> to restrict who can open review mode."
438
- );
439
- setSticky(void 0, true);
440
- return true;
441
- }
442
- if (isStickyEnabled(void 0)) {
443
- setSticky(void 0, true);
444
- return true;
445
- }
446
- try {
447
- if (window.localStorage.getItem(LEGACY_LOCAL_FLAG) !== null) return true;
448
- } catch {
449
392
  }
450
- return isDevBuild();
393
+ return true;
451
394
  }
452
395
  var FOCUS_KEY = "review-kit:focus";
453
396
  function rememberFocusItem(id) {
@@ -1291,10 +1234,13 @@ input, textarea, select {
1291
1234
  .rk-sync-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--rk-fg-faint); }
1292
1235
  .rk-sync-online .rk-sync-dot { background: var(--rk-success); }
1293
1236
  .rk-sync-connecting .rk-sync-dot { background: var(--rk-warn); }
1237
+ .rk-sync-reason-token-short .rk-sync-dot, .rk-sync-reason-bad-token .rk-sync-dot { background: var(--rk-danger); }
1238
+ .rk-sync[title] { cursor: help; }
1294
1239
  /* Automatic Emblema push hint (same quiet language) */
1295
1240
  .rk-epush-ok .rk-sync-dot { background: var(--rk-success); }
1296
1241
  .rk-epush-sending .rk-sync-dot { background: var(--rk-warn); }
1297
1242
  .rk-epush-offline .rk-sync-dot { background: var(--rk-fg-faint); }
1243
+ .rk-epush-rejected .rk-sync-dot { background: var(--rk-danger, #d33); }
1298
1244
 
1299
1245
  /* ---------------------------------------------------------------- */
1300
1246
  /* Floating editor cards */
@@ -2280,6 +2226,9 @@ input, textarea, select {
2280
2226
  `;
2281
2227
  }
2282
2228
 
2229
+ // src/client/store.tsx
2230
+ import { createContext, useContext, useMemo, useReducer } from "react";
2231
+
2283
2232
  // src/client/id.ts
2284
2233
  function rkId() {
2285
2234
  const time = Date.now().toString(36);
@@ -2295,6 +2244,9 @@ function rkId() {
2295
2244
  }
2296
2245
  return `rk-${time}-${rand}`;
2297
2246
  }
2247
+
2248
+ // src/client/store.tsx
2249
+ import { jsx } from "react/jsx-runtime";
2298
2250
  function itemMatchesFilter(item, filter) {
2299
2251
  if (!filter) return true;
2300
2252
  if (filter.types.length > 0 && !filter.types.includes(item.type)) return false;
@@ -2356,7 +2308,8 @@ function reducer(state, action) {
2356
2308
  panelOpen: false,
2357
2309
  confirm: null,
2358
2310
  cheatsheetOpen: false,
2359
- emblemaConnectOpen: false
2311
+ emblemaConnectOpen: false,
2312
+ terminalConnectOpen: false
2360
2313
  } : { ...state, hidden: false };
2361
2314
  case "SELECT":
2362
2315
  return { ...state, selection: action.selection };
@@ -2466,12 +2419,16 @@ function reducer(state, action) {
2466
2419
  return { ...state, cheatsheetOpen: action.open };
2467
2420
  case "SET_EMBLEMA_CONNECT":
2468
2421
  return { ...state, emblemaConnectOpen: action.open };
2422
+ case "SET_TERMINAL_CONNECT":
2423
+ return { ...state, terminalConnectOpen: action.open };
2469
2424
  case "SET_COACH":
2470
2425
  return { ...state, coachOpen: action.open };
2471
2426
  case "SET_CORNER":
2472
2427
  return { ...state, badgeCorner: action.corner };
2473
- case "SYNC_STATUS":
2474
- return state.syncStatus === action.status ? state : { ...state, syncStatus: action.status };
2428
+ case "SYNC_STATUS": {
2429
+ const reason = action.reason === void 0 ? action.status === "online" ? null : state.syncReason : action.reason;
2430
+ return state.syncStatus === action.status && state.syncReason === reason ? state : { ...state, syncStatus: action.status, syncReason: reason };
2431
+ }
2475
2432
  case "EMBLEMA_PUSH_STATUS":
2476
2433
  return state.emblemaPush === action.status ? state : { ...state, emblemaPush: action.status };
2477
2434
  /*
@@ -2600,9 +2557,11 @@ function initialState() {
2600
2557
  confirm: null,
2601
2558
  cheatsheetOpen: false,
2602
2559
  emblemaConnectOpen: false,
2560
+ terminalConnectOpen: false,
2603
2561
  coachOpen: false,
2604
2562
  undoStack: [],
2605
2563
  syncStatus: "disabled",
2564
+ syncReason: null,
2606
2565
  emblemaPush: "idle",
2607
2566
  peers: [],
2608
2567
  quick: null,
@@ -2617,7 +2576,7 @@ function StoreProvider({ config, children }) {
2617
2576
  const configValue = useMemo(
2618
2577
  () => config,
2619
2578
  // eslint-disable-next-line react-hooks/exhaustive-deps -- config object identity is per-render; depend on its fields
2620
- [config.token, config.syncToken, config.syncUrl, config.defaultAuthor, config.position, config.storageKey, config.onSessionChange, config.emblema]
2579
+ [config.enabled, config.token, config.devAutoOn, config.sync, config.syncToken, config.syncUrl, config.defaultAuthor, config.fxAssetsUrl, config.position, config.storageKey, config.onSessionChange, config.emblema]
2621
2580
  );
2622
2581
  return /* @__PURE__ */ jsx(ConfigContext.Provider, { value: configValue, children: /* @__PURE__ */ jsx(StateContext.Provider, { value: state, children: /* @__PURE__ */ jsx(DispatchContext.Provider, { value: dispatch, children }) }) });
2623
2582
  }
@@ -2645,6 +2604,9 @@ function toast(dispatch, message, tone = "info") {
2645
2604
  dispatch({ type: "PUSH_TOAST", toast: { id: rkId(), message, tone } });
2646
2605
  }
2647
2606
 
2607
+ // src/client/App.tsx
2608
+ import { useEffect as useEffect17, useRef as useRef16 } from "react";
2609
+
2648
2610
  // src/client/persist.ts
2649
2611
  var DB_NAME = "review-kit";
2650
2612
  var STORE = "sessions";
@@ -2771,6 +2733,9 @@ async function revealTarget(target) {
2771
2733
  const el = resolveTarget(target)?.element ?? null;
2772
2734
  return el && isElementVisible(el) ? "revealed" : "failed";
2773
2735
  }
2736
+
2737
+ // src/client/preview.ts
2738
+ import { useEffect, useRef } from "react";
2774
2739
  var EDITED_ATTR = "data-rk-edited";
2775
2740
  var STYLE_ID = "rk-preview-style";
2776
2741
  var RESCHEDULE_MS = 180;
@@ -2989,836 +2954,91 @@ function useAppliedPreview(state, dispatch) {
2989
2954
  }, []);
2990
2955
  }
2991
2956
 
2992
- // src/client/effects.ts
2993
- var FX_SOUND_BASE = "https://agropolio.fucking.style/rk-assets";
2994
- var ONDRO_MS = 12e4;
2995
- var BIG_MS = 45e3;
2996
- var NICE_MS = 6900;
2997
- var CHAOS_SEL = "body > :not([data-review-kit-host]):not(script):not(style):not(svg[data-rk-fx])";
2998
- var broadcast = null;
2999
- function setEffectBroadcaster(fn) {
3000
- broadcast = fn;
3001
- }
3002
- var active = /* @__PURE__ */ new Set();
3003
- function cancelEffects() {
3004
- if (active.size === 0) return false;
3005
- for (const cancel of [...active]) cancel();
3006
- return true;
3007
- }
3008
- function reducedMotion() {
3009
- try {
3010
- return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
3011
- } catch {
3012
- return false;
3013
- }
3014
- }
3015
- function spawnParticles(layer, spec, reduced, duration) {
3016
- const count = reduced ? Math.min(10, spec.count) : spec.count;
3017
- const vw = window.innerWidth;
3018
- const vh = window.innerHeight;
3019
- for (let i = 0; i < count; i++) {
3020
- const particle = document.createElement("span");
3021
- particle.className = spec.className ? `rk-fx-particle ${spec.className}` : "rk-fx-particle";
3022
- particle.textContent = spec.glyphs[i % spec.glyphs.length];
3023
- const delay = i / count * duration * 0.4;
3024
- const life = duration * (0.45 + 0.35 * (i * 7 % 10) / 10);
3025
- particle.style.fontSize = `${14 + i * 13 % 18}px`;
3026
- if (spec.kind === "rain") {
3027
- const x = i * 61 % 100 / 100 * vw;
3028
- const sway = i * 31 % 60 - 30 | 0;
3029
- particle.style.setProperty("--fx-x0", `${x}px`);
3030
- particle.style.setProperty("--fx-x1", `${x + sway}px`);
3031
- particle.style.setProperty("--fx-y0", `-40px`);
3032
- particle.style.setProperty("--fx-y1", `${vh + 40}px`);
3033
- } else {
3034
- const angle = i / count * Math.PI * 2;
3035
- const radius = 120 + i * 47 % 240;
3036
- particle.style.setProperty("--fx-x0", `${vw / 2}px`);
3037
- particle.style.setProperty("--fx-y0", `${vh / 2}px`);
3038
- particle.style.setProperty("--fx-x1", `${vw / 2 + Math.cos(angle) * radius}px`);
3039
- particle.style.setProperty("--fx-y1", `${vh / 2 + Math.sin(angle) * radius + vh * 0.4}px`);
3040
- }
3041
- particle.style.animation = `rk-fx-fall ${reduced ? life * 0.8 : life}ms linear ${delay}ms both`;
3042
- layer.appendChild(particle);
3043
- }
3044
- }
3045
- function runShake() {
3046
- const prev = document.body.style.animation;
3047
- let styleTag = document.getElementById("rk-fx-shake-style");
3048
- if (!styleTag) {
3049
- styleTag = document.createElement("style");
3050
- styleTag.id = "rk-fx-shake-style";
3051
- styleTag.textContent = "@keyframes rk-fx-shake { 0%,100% { transform: translate(0,0); } 25% { transform: translate(-5px,3px); } 50% { transform: translate(4px,-4px); } 75% { transform: translate(-3px,-2px); } }";
3052
- document.head.appendChild(styleTag);
3053
- }
3054
- document.body.style.animation = "rk-fx-shake 260ms ease-in-out 6";
3055
- let done = false;
3056
- const restore2 = () => {
3057
- if (done) return;
3058
- done = true;
3059
- document.body.style.animation = prev;
3060
- if (document.body.getAttribute("style") === "") document.body.removeAttribute("style");
3061
- };
3062
- window.setTimeout(restore2, 1700);
3063
- return restore2;
3064
- }
3065
- function startOndro({ layer, reduced }) {
3066
- const cleanups = [];
3067
- const timers = [];
3068
- const intervals = [];
3069
- const later = (fn, ms) => timers.push(window.setTimeout(fn, ms));
3070
- const every = (fn, ms) => intervals.push(window.setInterval(fn, ms));
3071
- cleanups.push(() => {
3072
- timers.forEach((t) => window.clearTimeout(t));
3073
- intervals.forEach((t) => window.clearInterval(t));
3074
- });
3075
- const addStyle = (css) => {
3076
- const style = document.createElement("style");
3077
- style.setAttribute("data-rk-fx", "ondro");
3078
- style.textContent = css;
3079
- document.head.appendChild(style);
3080
- cleanups.push(() => style.remove());
3081
- return style;
3082
- };
3083
- const wave = (spec) => {
3084
- const sub = document.createElement("div");
3085
- sub.className = "rk-fx-layer";
3086
- layer.appendChild(sub);
3087
- spawnParticles(sub, spec, reduced, spec.durationMs);
3088
- const t = window.setTimeout(() => sub.remove(), spec.durationMs + 1600);
3089
- timers.push(t);
3090
- cleanups.push(() => sub.remove());
3091
- };
3092
- if (reduced) {
3093
- addStyle(`@keyframes rk-ondro { 0% { filter: none; } 50% { filter: hue-rotate(40deg) saturate(1.15); } 100% { filter: none; } }
3094
- ${CHAOS_SEL} { animation: rk-ondro 8s ease-in-out infinite; }`);
3095
- wave({ kind: "rain", glyphs: ["\u{1F92A}", "\u{1F4A5}", "\u{1F300}"], count: 8, durationMs: 8e3 });
3096
- every(() => wave({ kind: "rain", glyphs: ["\u{1F92A}", "\u{1F300}"], count: 6, durationMs: 8e3 }), 15e3);
3097
- } else {
3098
- const scrollX = window.scrollX;
3099
- const scrollY = window.scrollY;
3100
- const htmlOverflow = document.documentElement.style.overflow;
3101
- document.documentElement.style.overflow = "hidden";
3102
- cleanups.push(() => {
3103
- document.documentElement.style.overflow = htmlOverflow;
3104
- if (document.documentElement.getAttribute("style") === "") document.documentElement.removeAttribute("style");
3105
- window.scrollTo(scrollX, scrollY);
3106
- });
3107
- const originY = scrollY + window.innerHeight / 2;
3108
- addStyle(`${CHAOS_SEL} { transform-origin: 50vw ${originY}px; }`);
3109
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
3110
- svg.setAttribute("data-rk-fx", "ondro");
3111
- svg.setAttribute("width", "0");
3112
- svg.setAttribute("height", "0");
3113
- svg.style.position = "absolute";
3114
- svg.innerHTML = `<defs>
3115
- <filter id="rk-ondro-warp" x="-10%" y="-10%" width="120%" height="120%">
3116
- <feTurbulence type="turbulence" baseFrequency="0.004 0.017" numOctaves="2" result="n" seed="7">
3117
- <animate attributeName="baseFrequency" dur="5.2s" values="0.004 0.017;0.013 0.005;0.004 0.017" repeatCount="indefinite"/>
3118
- </feTurbulence>
3119
- <feDisplacementMap in="SourceGraphic" in2="n" scale="14" xChannelSelector="R" yChannelSelector="G">
3120
- <animate attributeName="scale" dur="4.4s" values="6;26;6" repeatCount="indefinite"/>
3121
- </feDisplacementMap>
3122
- </filter>
3123
- <filter id="rk-ondro-ca" x="-5%" y="-5%" width="110%" height="110%">
3124
- <feColorMatrix in="SourceGraphic" type="matrix" values="1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" result="r"/>
3125
- <feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0" result="g"/>
3126
- <feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0" result="b"/>
3127
- <feOffset in="r" dx="4" dy="0" result="ro"><animate attributeName="dx" dur="2.9s" values="1;6;1" repeatCount="indefinite"/></feOffset>
3128
- <feOffset in="b" dx="-4" dy="1" result="bo"><animate attributeName="dx" dur="3.3s" values="-1;-6;-1" repeatCount="indefinite"/></feOffset>
3129
- <feBlend in="ro" in2="g" mode="screen" result="rg"/>
3130
- <feBlend in="rg" in2="bo" mode="screen"/>
3131
- </filter>
3132
- </defs>`;
3133
- document.body.appendChild(svg);
3134
- cleanups.push(() => svg.remove());
3135
- addStyle(`@keyframes rk-ondro1 {
3136
- 0% { filter: none; transform: scale(1.22); }
3137
- 25% { filter: hue-rotate(120deg) saturate(1.9) blur(1.6px); transform: scale(1.238) rotate(1.4deg) translateX(6px); }
3138
- 50% { filter: hue-rotate(230deg) saturate(0.6) contrast(1.12) blur(0.6px); transform: scale(1.202) rotate(-1.2deg) translateY(5px); }
3139
- 75% { filter: hue-rotate(310deg) saturate(2.1) blur(2.6px); transform: scale(1.22) skewX(1.1deg) translateX(-7px); }
3140
- 100% { filter: hue-rotate(360deg); transform: scale(1.22); }
3141
- }
3142
- ${CHAOS_SEL} { animation: rk-ondro1 2.6s ease-in-out infinite; }`);
3143
- later(() => {
3144
- addStyle(`@keyframes rk-ondro2 {
3145
- 0% { filter: url(#rk-ondro-warp) hue-rotate(0deg) saturate(1.3); transform: scale(1.22); }
3146
- 20% { filter: url(#rk-ondro-ca) hue-rotate(100deg) saturate(2.3) blur(3.4px); transform: scale(1.256) rotate(2.4deg) translate(12px, -6px); }
3147
- 45% { filter: url(#rk-ondro-warp) hue-rotate(190deg) saturate(0.45) contrast(1.2) blur(1.4px); transform: scale(1.19) rotate(-2.2deg) translate(-13px, 8px) skewY(1.2deg); }
3148
- 70% { filter: url(#rk-ondro-ca) hue-rotate(280deg) saturate(2.4) blur(4.6px); transform: scale(1.244) skewX(-1.8deg) translateY(-10px); }
3149
- 100% { filter: url(#rk-ondro-warp) hue-rotate(360deg) saturate(1.3); transform: scale(1.22); }
3150
- }
3151
- ${CHAOS_SEL} { animation: rk-ondro2 2.2s ease-in-out infinite; }
3152
- @keyframes rk-ondro-shake { 0%,100% { transform: translate(0,0); } 20% { transform: translate(-6px,4px); } 40% { transform: translate(5px,-5px); } 60% { transform: translate(-4px,-3px); } 80% { transform: translate(6px,3px); } }
3153
- @keyframes rk-ondro-text {
3154
- 0%,100% { letter-spacing: normal; word-spacing: normal; text-shadow: none; }
3155
- 30% { letter-spacing: 0.12em; word-spacing: 0.3em; text-shadow: 4px 0 rgba(255,0,90,0.6), -4px 1px rgba(0,190,255,0.6); }
3156
- 60% { letter-spacing: -0.05em; word-spacing: -0.1em; text-shadow: -6px 0 rgba(255,0,90,0.55), 6px 2px rgba(0,190,255,0.55), 0 3px rgba(120,255,0,0.4); }
3157
- 80% { letter-spacing: 0.2em; text-shadow: 2px -2px rgba(255,0,90,0.65), -2px 2px rgba(0,190,255,0.65); }
3158
- }
3159
- h1, h2, h3, h4, p, a, li, button, span { animation: rk-ondro-text 3.1s ease-in-out infinite; }`);
3160
- startTextDecay(cleanups, every);
3161
- spawnGlitchBands(layer, cleanups);
3162
- wave({ kind: "burst", glyphs: ["\u{1F4A5}", "\u{1F9E8}", "\u{1F525}", "\u{1F300}"], count: 34, durationMs: 5e3 });
3163
- }, 6e3);
3164
- later(() => {
3165
- addStyle(`@keyframes rk-ondro3 {
3166
- 0% { filter: url(#rk-ondro-warp) hue-rotate(0deg) saturate(1.6) invert(0); transform: scale(1.22); }
3167
- 18% { filter: url(#rk-ondro-ca) hue-rotate(140deg) saturate(2.5) blur(6px) invert(0.35); transform: scale(1.27) rotate(3.2deg) translate(20px, -12px); }
3168
- 38% { filter: url(#rk-ondro-warp) hue-rotate(220deg) saturate(0.4) blur(2px) invert(0.75); transform: scale(1.18) rotate(-3deg) translate(-22px, 12px) skewY(2deg); }
3169
- 58% { filter: url(#rk-ondro-ca) hue-rotate(300deg) saturate(2.2) blur(7px) invert(0.3); transform: scale(1.25) skewX(2.4deg) translateY(-16px); }
3170
- 80% { filter: url(#rk-ondro-warp) hue-rotate(340deg) saturate(1.8) blur(3px) invert(0.05); transform: scale(1.23) rotate(1.6deg) translate(14px, 6px); }
3171
- 100% { filter: url(#rk-ondro-warp) hue-rotate(360deg) saturate(1.6) invert(0); transform: scale(1.22); }
3172
- }
3173
- ${CHAOS_SEL} { animation: rk-ondro3 4s ease-in-out infinite; }
3174
- @keyframes rk-ondro-shake3 { 0%,100% { transform: translate(0,0) rotate(0); } 25% { transform: translate(-9px,6px) rotate(-0.4deg); } 50% { transform: translate(8px,-8px) rotate(0.5deg); } 75% { transform: translate(-7px,-5px) rotate(-0.3deg); } }
3175
- body { animation: rk-ondro-shake3 620ms ease-in-out infinite; }`);
3176
- wave({ kind: "rain", glyphs: ["\u{1F92F}", "\u{1F480}", "\u{1F9E0}", "\u26A1\uFE0F", "\u{1FAE0}", "\u{1F529}"], count: 60, durationMs: 7e3 });
3177
- }, 13e3);
3178
- wave({ kind: "rain", glyphs: ["\u{1F92A}", "\u{1F4A5}", "\u{1F300}", "\u{1F9E0}", "\u26A1\uFE0F"], count: 40, durationMs: 12e3 });
3179
- every(() => wave({ kind: "rain", glyphs: ["\u{1F92A}", "\u{1F4A5}", "\u{1F300}", "\u{1F9E0}", "\u26A1\uFE0F", "\u{1F355}", "\u{1F4C0}"], count: 30, durationMs: 9e3 }), 9500);
3180
- every(() => spawnFallingLetters(layer), 12e3);
3181
- later(() => spawnFallingLetters(layer), 7e3);
3182
- }
3183
- const audio = new Audio(`${FX_SOUND_BASE}/rick.mp3`);
3184
- audio.volume = 0.8;
3185
- audio.loop = true;
3186
- cleanups.push(() => {
3187
- audio.pause();
3188
- audio.src = "";
3189
- });
3190
- audio.play().catch(() => {
3191
- const chip = document.createElement("button");
3192
- chip.className = "rk-fx-sound";
3193
- chip.type = "button";
3194
- chip.textContent = "\u{1F50A} klikni pre zvuk";
3195
- chip.addEventListener("click", () => {
3196
- audio.play().catch(() => void 0);
3197
- chip.remove();
3198
- });
3199
- layer.appendChild(chip);
3200
- cleanups.push(() => chip.remove());
3201
- });
3202
- installKillWord(cleanups);
3203
- return () => {
3204
- for (const fn of cleanups) fn();
3205
- };
2957
+ // src/client/effectCatalog.ts
2958
+ var EFFECT_NAMES = [
2959
+ "ondro",
2960
+ "lava",
2961
+ "sklo",
2962
+ "vir",
2963
+ "crt",
2964
+ "hovno",
2965
+ "konfety",
2966
+ "srdiecka",
2967
+ "sneh",
2968
+ "zemetrasenie",
2969
+ "69",
2970
+ "kokot",
2971
+ "vrtule",
2972
+ "muchy",
2973
+ "pes",
2974
+ "pivo",
2975
+ "nikotin",
2976
+ "matrix",
2977
+ "disko",
2978
+ "zrkadlo",
2979
+ "pohreb",
2980
+ "laser",
2981
+ "kura",
2982
+ "spam"
2983
+ ];
2984
+ var NAME_SET = new Set(EFFECT_NAMES);
2985
+ function isEffectCommand(name) {
2986
+ return name === "stop" || NAME_SET.has(name);
3206
2987
  }
3207
- function installKillWord(cleanups) {
3208
- let buffer = "";
3209
- const inTextField = (event) => event.composedPath().some(
3210
- (node) => node instanceof HTMLElement && (node.tagName === "INPUT" || node.tagName === "TEXTAREA" || node.isContentEditable)
3211
- );
3212
- const KILL_LETTERS = /* @__PURE__ */ new Set(["h", "o", "v", "n"]);
3213
- const onKey = (event) => {
3214
- if (event.key.length !== 1 || event.metaKey || event.ctrlKey || event.altKey) return;
3215
- const key = event.key.toLowerCase();
3216
- buffer = (buffer + key).slice(-8);
3217
- if (KILL_LETTERS.has(key) && !inTextField(event)) event.stopPropagation();
3218
- if (buffer.includes("hovno")) {
3219
- buffer = "";
3220
- broadcast?.("stop");
3221
- cancelEffects();
3222
- }
3223
- };
3224
- document.addEventListener("keydown", onKey, true);
3225
- cleanups.push(() => document.removeEventListener("keydown", onKey, true));
3226
- }
3227
- function bigEffect(build) {
3228
- return function start({ layer, reduced }) {
3229
- const cleanups = [];
3230
- const timers = [];
3231
- const intervals = [];
3232
- const later = (fn, ms) => timers.push(window.setTimeout(fn, ms));
3233
- const every = (fn, ms) => intervals.push(window.setInterval(fn, ms));
3234
- cleanups.push(() => {
3235
- timers.forEach((t) => window.clearTimeout(t));
3236
- intervals.forEach((t) => window.clearInterval(t));
3237
- });
3238
- const addStyle = (css) => {
3239
- const style = document.createElement("style");
3240
- style.setAttribute("data-rk-fx", "");
3241
- style.textContent = css;
3242
- document.head.appendChild(style);
3243
- cleanups.push(() => style.remove());
3244
- };
3245
- const wave = (spec) => {
3246
- const sub = document.createElement("div");
3247
- sub.className = "rk-fx-layer";
3248
- layer.appendChild(sub);
3249
- spawnParticles(sub, spec, reduced, spec.durationMs);
3250
- timers.push(window.setTimeout(() => sub.remove(), spec.durationMs + 1600));
3251
- cleanups.push(() => sub.remove());
3252
- };
3253
- if (!reduced) {
3254
- const scrollX = window.scrollX;
3255
- const scrollY = window.scrollY;
3256
- const htmlOverflow = document.documentElement.style.overflow;
3257
- document.documentElement.style.overflow = "hidden";
3258
- cleanups.push(() => {
3259
- document.documentElement.style.overflow = htmlOverflow;
3260
- if (document.documentElement.getAttribute("style") === "") document.documentElement.removeAttribute("style");
3261
- window.scrollTo(scrollX, scrollY);
3262
- });
3263
- const originY = scrollY + window.innerHeight / 2;
3264
- addStyle(`${CHAOS_SEL} { transform-origin: 50vw ${originY}px; }`);
3265
- }
3266
- build({
3267
- layer,
3268
- reduced,
3269
- addStyle,
3270
- later,
3271
- every,
3272
- wave,
3273
- decayText: () => startTextDecay(cleanups, every),
3274
- cleanups
2988
+
2989
+ // src/client/effectsLazy.ts
2990
+ var loaded = null;
2991
+ var loading = null;
2992
+ var broadcaster = null;
2993
+ var assetsUrl = null;
2994
+ var queued = 0;
2995
+ function load() {
2996
+ if (loaded) return Promise.resolve(loaded);
2997
+ if (!loading) {
2998
+ loading = import("./effects-EWKHKUDP.js").then((mod) => {
2999
+ loaded = mod;
3000
+ mod.setEffectBroadcaster(broadcaster);
3001
+ mod.setFxAssetsUrl(assetsUrl);
3002
+ return mod;
3003
+ }).catch((error) => {
3004
+ loading = null;
3005
+ queued = 0;
3006
+ throw error;
3275
3007
  });
3276
- installKillWord(cleanups);
3277
- return () => {
3278
- for (const fn of cleanups) fn();
3279
- };
3280
- };
3281
- }
3282
- function startTextDecay(cleanups, every) {
3283
- const GLYPHS = "\u2593\u2592\u2591#@%&$?!\xD7\xD8\u2206\xA7";
3284
- const originals = /* @__PURE__ */ new Map();
3285
- const nodes = [];
3286
- const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
3287
- let node;
3288
- while ((node = walker.nextNode()) && nodes.length < 120) {
3289
- const text = node;
3290
- const value = text.nodeValue ?? "";
3291
- if (value.trim().length < 4) continue;
3292
- const parent = text.parentElement;
3293
- if (!parent || parent.closest("[data-review-kit-host], script, style, [data-rk-edited]")) continue;
3294
- nodes.push(text);
3295
- }
3296
- every(() => {
3297
- for (let i = 0; i < 22; i++) {
3298
- const text = nodes[Math.random() * nodes.length | 0];
3299
- if (!text || !text.isConnected) continue;
3300
- if (!originals.has(text)) originals.set(text, text.nodeValue ?? "");
3301
- const original = originals.get(text) ?? "";
3302
- if (Math.random() < 0.3) {
3303
- text.nodeValue = original;
3304
- continue;
3305
- }
3306
- const chars = [...original];
3307
- const swaps = 1 + (Math.random() * 3 | 0);
3308
- for (let k = 0; k < swaps; k++) {
3309
- const idx = Math.random() * chars.length | 0;
3310
- if (chars[idx] !== " ") chars[idx] = GLYPHS[Math.random() * GLYPHS.length | 0];
3311
- }
3312
- text.nodeValue = chars.join("");
3313
- }
3314
- }, 260);
3315
- cleanups.push(() => {
3316
- for (const [text, value] of originals) {
3317
- if (text.isConnected && text.nodeValue !== value) text.nodeValue = value;
3318
- }
3319
- originals.clear();
3320
- });
3321
- }
3322
- function spawnFallingLetters(layer) {
3323
- const sub = document.createElement("div");
3324
- sub.className = "rk-fx-layer";
3325
- layer.appendChild(sub);
3326
- const headings = [...document.querySelectorAll("h1, h2, h3")].filter((el) => {
3327
- const r = el.getBoundingClientRect();
3328
- return r.width > 0 && r.top < window.innerHeight && r.bottom > 0 && !el.closest("[data-review-kit-host]");
3329
- });
3330
- let spawned = 0;
3331
- for (const heading of headings.slice(0, 3)) {
3332
- const textNode = [...heading.childNodes].find((n) => n.nodeType === 3 && (n.nodeValue ?? "").trim().length > 3);
3333
- const source = textNode ?? heading.firstChild;
3334
- if (!source || source.nodeType !== 3) continue;
3335
- const value = source.nodeValue ?? "";
3336
- const range = document.createRange();
3337
- for (let i = 0; i < value.length && spawned < 26; i++) {
3338
- if (value[i] === " " || Math.random() < 0.55) continue;
3339
- range.setStart(source, i);
3340
- range.setEnd(source, i + 1);
3341
- const rect = range.getBoundingClientRect();
3342
- if (rect.width === 0) continue;
3343
- const span = document.createElement("span");
3344
- span.className = "rk-fx-letter";
3345
- span.textContent = value[i];
3346
- span.style.left = `${rect.left}px`;
3347
- span.style.top = `${rect.top}px`;
3348
- span.style.fontSize = `${rect.height}px`;
3349
- span.style.animationDelay = `${Math.random() * 900 | 0}ms`;
3350
- span.style.setProperty("--fx-drift", `${(Math.random() * 120 | 0) - 60}px`);
3351
- sub.appendChild(span);
3352
- spawned++;
3353
- }
3354
- }
3355
- window.setTimeout(() => sub.remove(), 4600);
3356
- }
3357
- function spawnGlitchBands(layer, cleanups) {
3358
- for (let i = 0; i < 3; i++) {
3359
- const band = document.createElement("div");
3360
- band.className = "rk-fx-band";
3361
- band.style.height = `${34 + i * 16}px`;
3362
- band.style.animationDuration = `${2.8 + i * 0.7}s`;
3363
- band.style.animationDelay = `${i * 0.9}s`;
3364
- layer.appendChild(band);
3365
- cleanups.push(() => band.remove());
3366
3008
  }
3009
+ return loading;
3367
3010
  }
3368
- var startLava = bigEffect(({ layer, reduced, addStyle, later, every, wave, decayText, cleanups }) => {
3369
- if (reduced) {
3370
- addStyle(`@keyframes rk-lava-soft { 0%,100% { filter: none; } 50% { filter: sepia(0.35) hue-rotate(-20deg) saturate(1.3); } }
3371
- ${CHAOS_SEL} { animation: rk-lava-soft 9s ease-in-out infinite; }`);
3372
- wave({ kind: "rain", glyphs: ["\u{1FAE0}", "\u{1F525}"], count: 8, durationMs: 9e3 });
3373
- every(() => wave({ kind: "rain", glyphs: ["\u{1FAE0}", "\u{1F4A7}"], count: 6, durationMs: 9e3 }), 14e3);
3374
- return;
3375
- }
3376
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
3377
- svg.setAttribute("data-rk-fx", "");
3378
- svg.setAttribute("width", "0");
3379
- svg.setAttribute("height", "0");
3380
- svg.style.position = "absolute";
3381
- svg.innerHTML = `<defs><filter id="rk-lava-warp" x="-10%" y="-10%" width="120%" height="130%">
3382
- <feTurbulence type="fractalNoise" baseFrequency="0.008 0.003" numOctaves="2" result="n" seed="4">
3383
- <animate attributeName="baseFrequency" dur="7s" values="0.008 0.003;0.014 0.006;0.008 0.003" repeatCount="indefinite"/>
3384
- </feTurbulence>
3385
- <feDisplacementMap in="SourceGraphic" in2="n" scale="10" xChannelSelector="R" yChannelSelector="G">
3386
- <animate attributeName="scale" dur="6s" values="4;24;4" repeatCount="indefinite"/>
3387
- </feDisplacementMap>
3388
- </filter></defs>`;
3389
- document.body.appendChild(svg);
3390
- cleanups.push(() => svg.remove());
3391
- addStyle(`@keyframes rk-lava1 {
3392
- 0% { filter: none; transform: scale(1.22); }
3393
- 35% { filter: sepia(0.4) hue-rotate(-24deg) saturate(1.8) blur(0.8px); transform: scale(1.235) skewY(0.5deg) translateY(6px); }
3394
- 70% { filter: sepia(0.6) hue-rotate(-38deg) saturate(2.2) contrast(1.1) blur(1.6px); transform: scale(1.22) scaleY(1.03) translateY(14px) skewX(-0.6deg); }
3395
- 100% { filter: none; transform: scale(1.22); }
3396
- }
3397
- ${CHAOS_SEL} { animation: rk-lava1 7s ease-in-out infinite; }`);
3398
- later(() => {
3399
- addStyle(`@keyframes rk-lava2 {
3400
- 0% { filter: url(#rk-lava-warp) sepia(0.5) hue-rotate(-30deg) saturate(2); transform: scale(1.22) translateY(10px); }
3401
- 40% { filter: url(#rk-lava-warp) sepia(0.8) hue-rotate(-48deg) saturate(2.8) contrast(1.18) blur(2.4px); transform: scale(1.2) scaleY(1.1) translateY(46px) skewX(1deg); }
3402
- 70% { filter: url(#rk-lava-warp) sepia(0.7) hue-rotate(-40deg) saturate(2.4) blur(1.2px); transform: scale(1.23) scaleY(1.06) translateY(28px) skewY(-0.8deg); }
3403
- 100% { filter: url(#rk-lava-warp) sepia(0.5) hue-rotate(-30deg) saturate(2); transform: scale(1.22) translateY(10px); }
3404
- }
3405
- ${CHAOS_SEL} { animation: rk-lava2 6.5s ease-in-out infinite; }
3406
- @keyframes rk-lava-text { 0%,100% { letter-spacing: normal; } 50% { letter-spacing: 0.1em; word-spacing: 0.25em; text-shadow: 0 6px 6px rgba(255,80,0,0.5); } }
3407
- h1, h2, h3, p, a, li, button { animation: rk-lava-text 5s ease-in-out infinite; }`);
3408
- decayText();
3409
- }, 1e4);
3410
- const drips = document.createElement("div");
3411
- drips.className = "rk-fx-layer";
3412
- layer.appendChild(drips);
3413
- cleanups.push(() => drips.remove());
3414
- every(() => {
3415
- const d = document.createElement("div");
3416
- d.className = "rk-fx-drip";
3417
- d.style.left = `${(Math.random() * 96 + 2).toFixed(1)}vw`;
3418
- d.style.animationDuration = `${(3.5 + Math.random() * 3).toFixed(2)}s`;
3419
- d.style.background = `linear-gradient(180deg, transparent, hsl(${Math.random() * 30 + 8 | 0}, 95%, 55%))`;
3420
- drips.appendChild(d);
3421
- window.setTimeout(() => d.remove(), 7e3);
3422
- }, 700);
3423
- wave({ kind: "rain", glyphs: ["\u{1F525}", "\u{1FAE0}", "\u{1F321}\uFE0F", "\u{1F4A7}"], count: 30, durationMs: 1e4 });
3424
- every(() => wave({ kind: "rain", glyphs: ["\u{1F525}", "\u{1FAE0}", "\u{1F4A7}"], count: 22, durationMs: 9e3 }), 9500);
3425
- });
3426
- var startSklo = bigEffect(({ layer, reduced, addStyle, every, wave, decayText, cleanups }) => {
3427
- if (reduced) {
3428
- addStyle(`@keyframes rk-sklo-soft { 0%,100% { filter: none; } 50% { filter: saturate(0.7) brightness(1.06); } }
3429
- ${CHAOS_SEL} { animation: rk-sklo-soft 8s ease-in-out infinite; }`);
3430
- wave({ kind: "burst", glyphs: ["\u2727", "\u2756", "\u{1F4A0}"], count: 10, durationMs: 8e3 });
3431
- return;
3432
- }
3433
- addStyle(`@keyframes rk-sklo-base {
3434
- 0%,10% { transform: scale(1.12); filter: none; }
3435
- 16% { transform: scale(1.125) rotate(0.4deg); filter: brightness(1.12) contrast(1.06); }
3436
- 50% { transform: scale(1.1) rotate(-0.5deg); filter: saturate(0.65) brightness(1.05) blur(0.5px); }
3437
- 72% { transform: scale(1.12); filter: none; }
3438
- 100% { transform: scale(1.12); filter: none; }
3439
- }
3440
- ${CHAOS_SEL} { animation: rk-sklo-base 11s ease-in-out infinite; }
3441
- @keyframes rk-sklo-fly {
3442
- 0%, 12% { transform: translate(0, 0) rotate(0); opacity: 1; }
3443
- 30% { transform: translate(var(--rk-gx), var(--rk-gy)) rotate(var(--rk-gr)); opacity: 0.92; }
3444
- 52% { transform: translate(calc(var(--rk-gx) * 1.18), calc(var(--rk-gy) * 1.18)) rotate(calc(var(--rk-gr) * 1.25)); opacity: 0.85; }
3445
- 74% { transform: translate(0, 0) rotate(0); opacity: 1; }
3446
- 100% { transform: translate(0, 0) rotate(0); opacity: 1; }
3447
- }
3448
- [data-rk-shard] { animation: rk-sklo-fly 11s cubic-bezier(0.55, 0, 0.3, 1) infinite; }`);
3449
- const shards = [...document.querySelectorAll("h1, h2, h3, h4, p, li, img, button, a")].filter((el) => {
3450
- if (el.closest("[data-review-kit-host]")) return false;
3451
- const r = el.getBoundingClientRect();
3452
- return r.width > 8 && r.height > 8 && r.bottom > -200 && r.top < window.innerHeight + 200;
3453
- }).slice(0, 90);
3454
- const rules = [];
3455
- shards.forEach((el, i) => {
3456
- const angle = i * 137.5 * Math.PI / 180;
3457
- const dist = 18 + i * 29 % 46;
3458
- rules.push(
3459
- `[data-rk-shard="${i}"] { --rk-gx: ${(Math.cos(angle) * dist).toFixed(1)}vw; --rk-gy: ${(Math.sin(angle) * dist).toFixed(1)}vh; --rk-gr: ${i * 97 % 640 - 320}deg; }`
3460
- );
3461
- el.setAttribute("data-rk-shard", String(i));
3462
- });
3463
- addStyle(rules.join("\n"));
3464
- cleanups.push(() => {
3465
- for (const el of shards) el.removeAttribute("data-rk-shard");
3466
- });
3467
- const cracks = document.createElement("div");
3468
- cracks.className = "rk-fx-cracks";
3469
- cracks.innerHTML = Array.from({ length: 9 }, (_, i) => {
3470
- const angle = i * 40 + i % 3 * 7;
3471
- return `<span style="transform: rotate(${angle}deg); animation-delay: ${(i * 0.05).toFixed(2)}s"></span>`;
3472
- }).join("");
3473
- layer.appendChild(cracks);
3474
- cleanups.push(() => cracks.remove());
3475
- decayText();
3476
- every(() => wave({ kind: "burst", glyphs: ["\u2727", "\u2756", "\u{1F4A0}", "\u{1F9CA}"], count: 26, durationMs: 5e3 }), 11e3);
3477
- wave({ kind: "burst", glyphs: ["\u2727", "\u2756", "\u{1F4A0}"], count: 26, durationMs: 5e3 });
3478
- });
3479
- var startVir = bigEffect(({ layer, reduced, addStyle, later, wave, decayText, cleanups }) => {
3480
- if (reduced) {
3481
- addStyle(`@keyframes rk-vir-soft { 0%,100% { filter: none; } 50% { filter: hue-rotate(50deg); } }
3482
- ${CHAOS_SEL} { animation: rk-vir-soft 9s ease-in-out infinite; }`);
3483
- wave({ kind: "burst", glyphs: ["\u{1F300}"], count: 8, durationMs: 8e3 });
3484
- return;
3485
- }
3486
- addStyle(`@keyframes rk-vir1 {
3487
- 0% { transform: scale(1.25) rotate(0deg); filter: none; }
3488
- 30% { transform: scale(1.31) rotate(4deg); filter: hue-rotate(50deg) saturate(1.3) blur(0.5px); }
3489
- 60% { transform: scale(1.28) rotate(-3.4deg); filter: hue-rotate(140deg) saturate(0.8) blur(1px); }
3490
- 100% { transform: scale(1.25) rotate(0deg); filter: none; }
3491
- }
3492
- ${CHAOS_SEL} { animation: rk-vir1 6.5s ease-in-out infinite; }`);
3493
- later(() => {
3494
- addStyle(`@keyframes rk-vir2 {
3495
- 0% { transform: scale(1.25) rotate(0deg); filter: hue-rotate(0deg) saturate(1.4); }
3496
- 25% { transform: scale(1.42) rotate(9deg); filter: hue-rotate(90deg) saturate(1.9) blur(1.6px); }
3497
- 50% { transform: scale(1.55) rotate(-7deg); filter: hue-rotate(200deg) saturate(0.6) contrast(1.15) blur(2.6px); }
3498
- 75% { transform: scale(1.38) rotate(10deg); filter: hue-rotate(300deg) saturate(1.8) blur(1.2px); }
3499
- 100% { transform: scale(1.25) rotate(0deg); filter: hue-rotate(360deg) saturate(1.4); }
3500
- }
3501
- ${CHAOS_SEL} { animation: rk-vir2 5.5s ease-in-out infinite; }
3502
- @keyframes rk-vir-text { 0%,100% { letter-spacing: normal; } 50% { letter-spacing: 0.16em; text-shadow: 3px 0 rgba(0,180,255,0.5), -3px 0 rgba(255,60,160,0.5); } }
3503
- h1, h2, h3, p, a, li, button { animation: rk-vir-text 4.5s ease-in-out infinite; }`);
3504
- decayText();
3505
- }, 12e3);
3506
- const GLYPHS = ["\u{1F300}", "\u2728", "\u{1F343}", "\u{1F4C4}", "\u2604\uFE0F"];
3507
- const bits = [];
3508
- const cx = () => window.innerWidth / 2;
3509
- const cy = () => window.innerHeight / 2;
3510
- const maxR = () => Math.hypot(window.innerWidth, window.innerHeight) / 2 + 60;
3511
- for (let i = 0; i < 36; i++) {
3512
- const el = document.createElement("span");
3513
- el.className = "rk-fx-particle";
3514
- el.textContent = GLYPHS[i % GLYPHS.length];
3515
- el.style.fontSize = `${13 + i * 11 % 16}px`;
3516
- layer.appendChild(el);
3517
- bits.push({ el, r: maxR() * ((i + 1) / 36), a: i * 0.7, v: 0.6 + i % 5 * 0.16 });
3518
- }
3519
- let raf = 0;
3520
- const tick = () => {
3521
- raf = requestAnimationFrame(tick);
3522
- for (const bit of bits) {
3523
- bit.a += 0.014 * bit.v * (1 + 140 / Math.max(bit.r, 40));
3524
- bit.r -= 0.55 * bit.v;
3525
- if (bit.r < 16) bit.r = maxR();
3526
- bit.el.style.transform = `translate3d(${cx() + Math.cos(bit.a) * bit.r}px, ${cy() + Math.sin(bit.a) * bit.r}px, 0) rotate(${bit.a}rad)`;
3527
- bit.el.style.opacity = `${Math.min(1, bit.r / 120)}`;
3528
- }
3529
- };
3530
- raf = requestAnimationFrame(tick);
3531
- cleanups.push(() => {
3532
- cancelAnimationFrame(raf);
3533
- bits.forEach((bit) => bit.el.remove());
3534
- });
3535
- });
3536
- var startCrt = bigEffect(({ layer, reduced, addStyle, every, wave, decayText, cleanups }) => {
3537
- if (reduced) {
3538
- addStyle(`${CHAOS_SEL} { filter: sepia(0.7) hue-rotate(55deg) saturate(1.6) contrast(1.05); transition: filter 2s ease; }`);
3539
- return;
3011
+ function cancelWhenReady() {
3012
+ if (loaded) return loaded.cancelEffects();
3013
+ if (loading && queued > 0) {
3014
+ loading.then((mod) => mod.cancelEffects()).catch(() => void 0);
3015
+ return true;
3540
3016
  }
3541
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
3542
- svg.setAttribute("data-rk-fx", "");
3543
- svg.setAttribute("width", "0");
3544
- svg.setAttribute("height", "0");
3545
- svg.style.position = "absolute";
3546
- svg.innerHTML = `<defs><filter id="rk-crt-tear" x="-10%" y="-10%" width="120%" height="120%">
3547
- <feTurbulence type="turbulence" baseFrequency="0.002 0.09" numOctaves="1" result="n" seed="11">
3548
- <animate attributeName="seed" dur="3s" values="11;37;11" repeatCount="indefinite"/>
3549
- </feTurbulence>
3550
- <feDisplacementMap in="SourceGraphic" in2="n" scale="0" xChannelSelector="R" yChannelSelector="G">
3551
- <animate attributeName="scale" dur="8s" values="0;0;26;4;0;0" keyTimes="0;0.55;0.62;0.7;0.78;1" repeatCount="indefinite"/>
3552
- </feDisplacementMap>
3553
- </filter></defs>`;
3554
- document.body.appendChild(svg);
3555
- cleanups.push(() => svg.remove());
3556
- addStyle(`@keyframes rk-crt-hold {
3557
- 0%, 60% { transform: scale(1.16) translate(0, 0); }
3558
- 64% { transform: scale(1.16) translate(-3px, 14px) skewX(-0.8deg); }
3559
- 68% { transform: scale(1.16) translate(2px, -8px); }
3560
- 74%, 100% { transform: scale(1.16) translate(0, 0); }
3561
- }
3562
- ${CHAOS_SEL} {
3563
- filter: url(#rk-crt-tear) sepia(1) hue-rotate(55deg) saturate(2.6) contrast(1.22) brightness(0.92);
3564
- animation: rk-crt-hold 8s linear infinite;
3565
- }
3566
- @keyframes rk-crt-text { 0%,100% { text-shadow: 1px 0 rgba(0,255,120,0.5); } 50% { text-shadow: -2px 0 rgba(0,255,120,0.7), 2px 0 rgba(0,120,60,0.5); letter-spacing: 0.06em; } }
3567
- h1, h2, h3, p, a, li, button { animation: rk-crt-text 5s ease-in-out infinite; }`);
3568
- const overlay = document.createElement("div");
3569
- overlay.className = "rk-fx-crt";
3570
- overlay.innerHTML = '<div class="rk-fx-crt-scan"></div><div class="rk-fx-crt-roll"></div><div class="rk-fx-crt-vignette"></div><div class="rk-fx-crt-osd">KAN\xC1L 69 \xB7 SLAB\xDD SIGN\xC1L</div>';
3571
- layer.appendChild(overlay);
3572
- cleanups.push(() => overlay.remove());
3573
- decayText();
3574
- every(() => wave({ kind: "rain", glyphs: ["\u{1F4FA}", "\u{1F4E1}", "\u{1D34D}"], count: 10, durationMs: 7e3 }), 16e3);
3575
- });
3576
- function startBanner({ layer, reduced }) {
3577
- const banner = document.createElement("div");
3578
- banner.className = "rk-fx-banner";
3579
- banner.textContent = "\u{1F6A8} KOKOT ALERT \u{1F6A8} KOKOT ALERT \u{1F6A8} KOKOT ALERT \u{1F6A8}";
3580
- if (reduced) banner.classList.add("rk-fx-banner-still");
3581
- layer.appendChild(banner);
3582
- return () => banner.remove();
3583
- }
3584
- function startVrtule({ reduced }) {
3585
- const style = document.createElement("style");
3586
- style.setAttribute("data-rk-fx", "vrtule");
3587
- style.textContent = reduced ? `@keyframes rk-vrtula { 0%,100% { transform: rotate(0); } 50% { transform: rotate(3deg); } }
3588
- img { animation: rk-vrtula 4s ease-in-out infinite; }` : `@keyframes rk-vrtula { from { transform: rotate(0) scale(1); } 50% { transform: rotate(180deg) scale(0.9); } to { transform: rotate(360deg) scale(1); } }
3589
- img { animation: rk-vrtula 3.6s ease-in-out infinite; }`;
3590
- document.head.appendChild(style);
3591
- return () => style.remove();
3592
- }
3593
- function makeChase(options) {
3594
- return function startChase({ layer, reduced }) {
3595
- const critters = [];
3596
- const count = reduced ? Math.min(4, options.count) : options.count;
3597
- for (let i = 0; i < count; i++) {
3598
- const el = document.createElement("span");
3599
- el.className = "rk-fx-particle";
3600
- el.textContent = options.glyphs[i % options.glyphs.length];
3601
- el.style.fontSize = `${options.sizePx(i)}px`;
3602
- layer.appendChild(el);
3603
- critters.push({ el, x: window.innerWidth / 2, y: window.innerHeight / 2, seed: i * 137 });
3604
- }
3605
- const wobble = options.wobbleT ?? 400;
3606
- let raf = 0;
3607
- const tick = (t) => {
3608
- raf = requestAnimationFrame(tick);
3609
- const root = layer.getRootNode();
3610
- const chips = [...root.querySelectorAll(".rk-peer-cursor")].filter((c) => c.style.opacity !== "0").map((c) => {
3611
- const m = /translate3d\(([-\d.]+)px, ([-\d.]+)px/.exec(c.style.transform);
3612
- return m ? { x: parseFloat(m[1]), y: parseFloat(m[2]) } : null;
3613
- }).filter((p) => p !== null);
3614
- chips.push({ x: lastPointerFallback.x, y: lastPointerFallback.y });
3615
- for (const critter of critters) {
3616
- const target = chips[critter.seed % chips.length];
3617
- const orbit = reduced ? 14 : options.orbit(critter.seed);
3618
- const wx = target.x + Math.cos(t / wobble + critter.seed) * orbit;
3619
- const wy = target.y + Math.sin(t / (wobble * 0.82) + critter.seed) * orbit;
3620
- const prevX = critter.x;
3621
- critter.x += (wx - critter.x) * options.lag;
3622
- critter.y += (wy - critter.y) * options.lag;
3623
- const face = options.flip && critter.x < prevX ? " scaleX(-1)" : "";
3624
- critter.el.style.transform = `translate3d(${critter.x}px, ${critter.y}px, 0)${face}`;
3625
- }
3626
- };
3627
- raf = requestAnimationFrame(tick);
3628
- const onMove = (event) => {
3629
- lastPointerFallback.x = event.clientX;
3630
- lastPointerFallback.y = event.clientY;
3631
- };
3632
- document.addEventListener("pointermove", onMove, { passive: true });
3633
- return () => {
3634
- cancelAnimationFrame(raf);
3635
- document.removeEventListener("pointermove", onMove);
3636
- for (const critter of critters) critter.el.remove();
3637
- };
3638
- };
3017
+ return false;
3639
3018
  }
3640
- var lastPointerFallback = { x: 300, y: 300 };
3641
- function styleEffect(css) {
3642
- const style = document.createElement("style");
3643
- style.setAttribute("data-rk-fx", "");
3644
- style.textContent = css;
3645
- document.head.appendChild(style);
3646
- return () => style.remove();
3647
- }
3648
- var PAGE_SEL = "body > :not([data-review-kit-host]):not(script):not(style)";
3649
- function startDisko({ layer, reduced }) {
3650
- const remove = styleEffect(
3651
- reduced ? `@keyframes rk-disko { 0%,100% { filter: none; } 50% { filter: hue-rotate(60deg) saturate(1.2); } }
3652
- ${PAGE_SEL} { animation: rk-disko 8s ease-in-out infinite; }` : `@keyframes rk-disko { from { filter: hue-rotate(0deg) saturate(1.35); } to { filter: hue-rotate(360deg) saturate(1.35); } }
3653
- ${PAGE_SEL} { animation: rk-disko 4s linear infinite; }`
3654
- );
3655
- spawnParticles(layer, { kind: "rain", glyphs: ["\u2728", "\u{1FAA9}", "\u{1F483}", "\u{1F57A}"], count: 26}, reduced, 15e3);
3656
- return remove;
3019
+ function setEffectBroadcaster(fn) {
3020
+ broadcaster = fn;
3021
+ loaded?.setEffectBroadcaster(fn);
3657
3022
  }
3658
- function startZrkadlo({ reduced }) {
3659
- if (reduced) {
3660
- return styleEffect(`${PAGE_SEL} { filter: saturate(0.8); transition: filter 2s ease; }`);
3661
- }
3662
- return styleEffect(`@keyframes rk-zrkadlo {
3663
- 0%, 100% { transform: perspective(1400px) rotateY(0); }
3664
- 35%, 65% { transform: perspective(1400px) rotateY(180deg); }
3665
- }
3666
- ${PAGE_SEL} { animation: rk-zrkadlo 12s ease-in-out both; transform-origin: 50% 50%; }`);
3667
- }
3668
- function startPohreb({ layer, reduced }) {
3669
- const remove = styleEffect(`@keyframes rk-pohreb { from { filter: none; } to { filter: grayscale(1) brightness(0.82) sepia(0.15); } }
3670
- ${PAGE_SEL} { animation: rk-pohreb 5s ease-out both; }`);
3671
- spawnParticles(layer, { kind: "rain", glyphs: ["\u{1F940}", "\u26B0\uFE0F", "\u{1F56F}\uFE0F", "\u{1F5A4}"], count: reduced ? 8 : 22}, reduced, 14e3);
3672
- return remove;
3673
- }
3674
- function startLaser({ layer, reduced }) {
3675
- const beams = [];
3676
- const count = reduced ? 2 : 5;
3677
- const hues = [330, 190, 120, 45, 265];
3678
- for (let i = 0; i < count; i++) {
3679
- const beam = document.createElement("div");
3680
- beam.className = "rk-fx-laser";
3681
- beam.style.background = `linear-gradient(90deg, transparent, hsla(${hues[i % hues.length]}, 100%, 60%, 0.55), transparent)`;
3682
- beam.style.left = `${i % 2 * 100}%`;
3683
- beam.style.animationDuration = `${3.4 + i * 0.6}s`;
3684
- beam.style.animationDelay = `${i * 0.45}s`;
3685
- if (i % 2) beam.style.animationDirection = "reverse";
3686
- layer.appendChild(beam);
3687
- beams.push(beam);
3688
- }
3689
- return () => beams.forEach((b) => b.remove());
3690
- }
3691
- function startKura({ layer, reduced }) {
3692
- const hen = document.createElement("span");
3693
- hen.className = "rk-fx-kura";
3694
- hen.textContent = "\u{1F414}";
3695
- layer.appendChild(hen);
3696
- const props = [hen];
3697
- const timers = [];
3698
- const walkMs = reduced ? 16e3 : 11e3;
3699
- hen.style.animationDuration = `${walkMs}ms`;
3700
- if (!reduced) {
3701
- for (let i = 1; i <= 5; i++) {
3702
- timers.push(
3703
- window.setTimeout(() => {
3704
- const egg = document.createElement("span");
3705
- egg.className = "rk-fx-egg";
3706
- egg.textContent = "\u{1F95A}";
3707
- egg.style.left = `${i / 6 * 100}%`;
3708
- layer.appendChild(egg);
3709
- props.push(egg);
3710
- timers.push(
3711
- window.setTimeout(() => {
3712
- egg.textContent = "\u{1F423}";
3713
- }, 2600 + i * 400)
3714
- );
3715
- }, walkMs * i / 6)
3716
- );
3717
- }
3718
- }
3719
- return () => {
3720
- timers.forEach((t) => window.clearTimeout(t));
3721
- props.forEach((p) => p.remove());
3722
- };
3023
+ function setEffectAssetsUrl(url) {
3024
+ assetsUrl = url ?? null;
3025
+ loaded?.setFxAssetsUrl(assetsUrl);
3723
3026
  }
3724
- function startSpam({ layer, reduced }) {
3725
- const MESSAGES = [
3726
- "\u26A0\uFE0F Kritick\xE1 chyba: str\xE1nka je pr\xEDli\u0161 pekn\xE1",
3727
- "\u{1F525} 69 \u013Eud\xED pr\xE1ve pozer\xE1 tento button",
3728
- "\u{1F4BE} Uklad\xE1m tvoje hriechy\u2026",
3729
- "\u{1F4C8} Konverzia st\xFApla o 420 %",
3730
- "\u{1F9E0} AI navrhuje: prerob to cel\xE9",
3731
- "\u{1F36A} T\xE1to str\xE1nka \u017Eerie kol\xE1\u010Diky",
3732
- "\u{1F4DE} Vol\xE1 ti klient. Zdvihni.",
3733
- "\u{1F4B8} Fakt\xFAra po splatnosti (odjak\u017Eiva)",
3734
- "\u{1F6F8} Zisten\xE1 mimozemsk\xE1 aktivita v p\xE4ti\u010Dke",
3735
- "\u2705 Ni\u010D sa nepodarilo \xFAspe\u0161ne"
3736
- ];
3737
- const toasts = [];
3738
- const timers = [];
3739
- const total = reduced ? 4 : MESSAGES.length;
3740
- for (let i = 0; i < total; i++) {
3741
- timers.push(
3742
- window.setTimeout(() => {
3743
- const toast2 = document.createElement("div");
3744
- toast2.className = "rk-fx-toast";
3745
- toast2.textContent = MESSAGES[i % MESSAGES.length];
3746
- toast2.style.top = `${14 + i % 5 * 58}px`;
3747
- layer.appendChild(toast2);
3748
- toasts.push(toast2);
3749
- timers.push(window.setTimeout(() => toast2.remove(), 5200));
3750
- }, i * (reduced ? 1800 : 950))
3751
- );
3752
- }
3753
- return () => {
3754
- timers.forEach((t) => window.clearTimeout(t));
3755
- toasts.forEach((t) => t.remove());
3756
- };
3027
+ function cancelEffects() {
3028
+ return cancelWhenReady();
3757
3029
  }
3758
- var EFFECTS = {
3759
- ondro: { kind: "custom", durationMs: ONDRO_MS, start: startOndro },
3760
- lava: { kind: "custom", durationMs: BIG_MS, start: startLava },
3761
- sklo: { kind: "custom", durationMs: BIG_MS, start: startSklo },
3762
- vir: { kind: "custom", durationMs: BIG_MS, start: startVir },
3763
- crt: { kind: "custom", durationMs: BIG_MS, start: startCrt },
3764
- hovno: { kind: "rain", glyphs: ["\u{1F4A9}"], count: 44, durationMs: 4200 },
3765
- konfety: { kind: "burst", glyphs: ["\u{1F389}", "\u{1F38A}", "\u2728"], count: 56, durationMs: 3600 },
3766
- srdiecka: { kind: "rain", glyphs: ["\u2764\uFE0F", "\u{1F49A}", "\u{1F49B}"], count: 40, durationMs: 4200 },
3767
- sneh: { kind: "rain", glyphs: ["\u2744\uFE0F", "\u{1F328}\uFE0F"], count: 48, durationMs: 5200 },
3768
- zemetrasenie: { kind: "rain", glyphs: ["\u{1FAA8}", "\u{1F9F1}"], count: 18, durationMs: 2400, shake: true },
3769
- "69": { kind: "burst", glyphs: ["\u{1F346}", "\u{1F351}", "\u{1F4A6}"], count: 69, durationMs: NICE_MS },
3770
- kokot: { kind: "custom", durationMs: 14500, start: startBanner },
3771
- vrtule: { kind: "custom", durationMs: NICE_MS, start: startVrtule },
3772
- muchy: {
3773
- kind: "custom",
3774
- durationMs: NICE_MS,
3775
- start: makeChase({ glyphs: ["\u{1FAB0}"], count: 14, sizePx: (i) => 12 + i % 3 * 4, orbit: (s) => 26 + s % 20, lag: 0.12, wobbleT: 400 })
3776
- },
3777
- pes: {
3778
- kind: "custom",
3779
- durationMs: 12e3,
3780
- start: makeChase({ glyphs: ["\u{1F415}", "\u{1F429}", "\u{1F9AE}"], count: 3, sizePx: (i) => 26 + i % 2 * 6, orbit: (s) => 46 + s % 30, lag: 0.055, wobbleT: 900, flip: true })
3781
- },
3782
- pivo: { kind: "rain", glyphs: ["\u{1F37A}", "\u{1F37B}", "\u{1F37E}"], count: 42, durationMs: 8e3 },
3783
- nikotin: { kind: "rain", glyphs: ["\u{1F6AC}", "\u{1F4A8}", "\u{1F62E}\u200D\u{1F4A8}"], count: 20, durationMs: 13e3 },
3784
- matrix: { kind: "rain", glyphs: [..."\u30A2\u30AB\u30B5\u30BF\u30CA0169\uFF8A\uFF8F\uFF94\uFF97\uFF66"], count: 64, durationMs: 12e3, className: "rk-fx-matrix" },
3785
- disko: { kind: "custom", durationMs: 15e3, start: startDisko },
3786
- zrkadlo: { kind: "custom", durationMs: 12e3, start: startZrkadlo },
3787
- pohreb: { kind: "custom", durationMs: 14e3, start: startPohreb },
3788
- laser: { kind: "custom", durationMs: 12e3, start: startLaser },
3789
- kura: { kind: "custom", durationMs: 11500, start: startKura },
3790
- spam: { kind: "custom", durationMs: 11e3, start: startSpam }
3791
- };
3792
3030
  function runEffect(name, layerHost) {
3031
+ if (!isEffectCommand(name)) return false;
3793
3032
  if (name === "stop") {
3794
- cancelEffects();
3033
+ cancelWhenReady();
3795
3034
  return true;
3796
3035
  }
3797
- const spec = EFFECTS[name];
3798
- if (!spec || !layerHost) return spec !== void 0;
3799
- const reduced = reducedMotion();
3800
- const duration = reduced && spec.kind !== "custom" ? Math.min(2e3, spec.durationMs) : spec.durationMs;
3801
- const layer = document.createElement("div");
3802
- layer.className = "rk-fx-layer";
3803
- layerHost.appendChild(layer);
3804
- let customCleanup = null;
3805
- let shakeCleanup = null;
3806
- if (spec.kind === "custom") {
3807
- customCleanup = spec.start({ layer, reduced });
3808
- } else {
3809
- spawnParticles(layer, spec, reduced, duration);
3810
- if (spec.shake && !reduced) shakeCleanup = runShake();
3811
- }
3812
- let timer = 0;
3813
- const cancel = () => {
3814
- window.clearTimeout(timer);
3815
- customCleanup?.();
3816
- shakeCleanup?.();
3817
- layer.remove();
3818
- active.delete(cancel);
3819
- };
3820
- timer = window.setTimeout(cancel, duration + 1200);
3821
- active.add(cancel);
3036
+ queued += 1;
3037
+ void load().then((mod) => {
3038
+ queued = Math.max(0, queued - 1);
3039
+ mod.runEffect(name, layerHost);
3040
+ }).catch(() => {
3041
+ });
3822
3042
  return true;
3823
3043
  }
3824
3044
 
@@ -4118,21 +3338,24 @@ var SHORTCUTS = [
4118
3338
  }
4119
3339
  }
4120
3340
  ];
3341
+
3342
+ // src/client/emblemaPush.ts
3343
+ import { useEffect as useEffect2, useRef as useRef2 } from "react";
4121
3344
  var AUTO_PUSH_DEBOUNCE_MS = 500;
4122
3345
  function useEmblemaAutoPush(state, dispatch, config) {
4123
3346
  const emblema = config.emblema;
4124
- const sessionRef = useRef(state.session);
3347
+ const sessionRef = useRef2(state.session);
4125
3348
  sessionRef.current = state.session;
4126
- const lastSent = useRef(/* @__PURE__ */ new Map());
4127
- const inFlight = useRef(false);
4128
- const alive = useRef(true);
4129
- useEffect(() => {
3349
+ const lastSent = useRef2(/* @__PURE__ */ new Map());
3350
+ const inFlight = useRef2(false);
3351
+ const alive = useRef2(true);
3352
+ useEffect2(() => {
4130
3353
  alive.current = true;
4131
3354
  return () => {
4132
3355
  alive.current = false;
4133
3356
  };
4134
3357
  }, []);
4135
- const flushRef = useRef(() => {
3358
+ const flushRef = useRef2(() => {
4136
3359
  });
4137
3360
  flushRef.current = () => {
4138
3361
  if (!alive.current || !emblema) return;
@@ -4154,12 +3377,12 @@ function useEmblemaAutoPush(state, dispatch, config) {
4154
3377
  for (const item of changed) lastSent.current.set(item.id, item);
4155
3378
  if (alive.current) dispatch({ type: "EMBLEMA_PUSH_STATUS", status: "ok" });
4156
3379
  } else if (alive.current) {
4157
- dispatch({ type: "EMBLEMA_PUSH_STATUS", status: "offline" });
3380
+ dispatch({ type: "EMBLEMA_PUSH_STATUS", status: result.reason === "rejected" ? "rejected" : "offline" });
4158
3381
  }
4159
3382
  });
4160
3383
  };
4161
3384
  const items = state.session?.items;
4162
- useEffect(() => {
3385
+ useEffect2(() => {
4163
3386
  if (!emblema || !items) return;
4164
3387
  for (const id of [...lastSent.current.keys()]) {
4165
3388
  if (!items.some((item) => item.id === id)) lastSent.current.delete(id);
@@ -4173,6 +3396,9 @@ function useEmblemaAutoPush(state, dispatch, config) {
4173
3396
  }, [items, emblema]);
4174
3397
  }
4175
3398
 
3399
+ // src/client/sync.ts
3400
+ import { useEffect as useEffect3, useMemo as useMemo2, useRef as useRef3 } from "react";
3401
+
4176
3402
  // realtime/client/rkSync.ts
4177
3403
  var RK_SYNC_PROTOCOL_VERSION = 1;
4178
3404
  var BACKOFF_BASE_MS = 1e3;
@@ -4303,8 +3529,8 @@ function createRkSync(options) {
4303
3529
  log(`welcome: ${items.length} items, ${peers.length} peers, ${deleted.length} tombstones, flushing ${queue.length}`);
4304
3530
  safely(() => handlers.onSnapshot?.(items, peers, deleted));
4305
3531
  while (queue.length > 0) {
4306
- const queued = queue[0];
4307
- if (!rawSend(queued)) break;
3532
+ const queued2 = queue[0];
3533
+ if (!rawSend(queued2)) break;
4308
3534
  queue.shift();
4309
3535
  }
4310
3536
  break;
@@ -4315,9 +3541,13 @@ function createRkSync(options) {
4315
3541
  pongTimer = null;
4316
3542
  }
4317
3543
  break;
4318
- case "error":
3544
+ case "error": {
4319
3545
  log("server error:", msg.code, msg.message);
3546
+ const code = typeof msg.code === "string" ? msg.code : "error";
3547
+ const message = typeof msg.message === "string" ? msg.message : "";
3548
+ safely(() => handlers.onError?.(code, message));
4320
3549
  break;
3550
+ }
4321
3551
  case "presence:join": {
4322
3552
  const peer = msg.peer;
4323
3553
  if (peer && typeof peer.id === "string") safely(() => handlers.onPeerJoin?.(peer));
@@ -4356,8 +3586,8 @@ function createRkSync(options) {
4356
3586
  if (typeof msg.peerId === "string") {
4357
3587
  const peerId = msg.peerId;
4358
3588
  const itemId = typeof msg.itemId === "string" ? msg.itemId : null;
4359
- const active2 = msg.active === true;
4360
- safely(() => handlers.onPeerTyping?.(peerId, itemId, active2));
3589
+ const active = msg.active === true;
3590
+ safely(() => handlers.onPeerTyping?.(peerId, itemId, active));
4361
3591
  }
4362
3592
  break;
4363
3593
  case "item:add": {
@@ -4388,6 +3618,8 @@ function createRkSync(options) {
4388
3618
  safely(() => handlers.onItemDelete?.(id, authorId));
4389
3619
  }
4390
3620
  break;
3621
+ default:
3622
+ break;
4391
3623
  }
4392
3624
  };
4393
3625
  const connect = () => {
@@ -4471,8 +3703,8 @@ function createRkSync(options) {
4471
3703
  sendSelecting(selector) {
4472
3704
  sendEphemeral({ t: "selecting", selector });
4473
3705
  },
4474
- sendTyping(itemId, active2) {
4475
- sendEphemeral({ t: "typing", itemId, active: active2 });
3706
+ sendTyping(itemId, active) {
3707
+ sendEphemeral({ t: "typing", itemId, active });
4476
3708
  },
4477
3709
  sendEffect(name) {
4478
3710
  sendEphemeral({ t: "fx", name });
@@ -4517,7 +3749,18 @@ function currentAuthor(name) {
4517
3749
  }
4518
3750
 
4519
3751
  // src/client/sync.ts
4520
- var DEFAULT_SYNC_URL = "wss://agropolio.fucking.style/rk-sync";
3752
+ var DEFAULT_SYNC_URL = "wss://2-59-219-26.sslip.io/rk-sync";
3753
+ function resolveSyncUrl(config) {
3754
+ return typeof config.syncUrl === "string" && config.syncUrl !== "" ? config.syncUrl : DEFAULT_SYNC_URL;
3755
+ }
3756
+ var MIN_SYNC_TOKEN_LENGTH = 8;
3757
+ var warnedShortToken = false;
3758
+ function resolveSyncToken(config) {
3759
+ if (config.sync === false) return null;
3760
+ const explicit = typeof config.syncToken === "string" && config.syncToken !== "" ? config.syncToken : null;
3761
+ const fallback = typeof config.token === "string" && config.token !== "" ? config.token : null;
3762
+ return explicit ?? fallback;
3763
+ }
4521
3764
  function toPeerState(peer) {
4522
3765
  return {
4523
3766
  id: peer.id,
@@ -4541,24 +3784,47 @@ function sanitizeRemoteItem(raw) {
4541
3784
  return { ...raw, target };
4542
3785
  }
4543
3786
  function useRealtimeSync(state, rawDispatch, config, reducer2) {
4544
- const syncRef = useRef(null);
4545
- const stateRef = useRef(state);
3787
+ const syncRef = useRef3(null);
3788
+ const stateRef = useRef3(state);
4546
3789
  stateRef.current = state;
4547
- const myIdRef = useRef("");
4548
- const enabled = typeof config.syncToken === "string" && config.syncToken !== "";
3790
+ const myIdRef = useRef3("");
3791
+ const syncToken = resolveSyncToken(config);
3792
+ const syncUrl = resolveSyncUrl(config);
3793
+ const enabled = syncToken !== null;
4549
3794
  const author = state.session?.author ?? "";
4550
3795
  const hasSession = state.session !== null;
4551
- useEffect(() => {
3796
+ useEffect3(() => {
4552
3797
  if (!enabled || !hasSession) return;
3798
+ const dispatchGuard = rawDispatch;
3799
+ if (syncToken.length < MIN_SYNC_TOKEN_LENGTH) {
3800
+ dispatchGuard({ type: "SYNC_STATUS", status: "offline", reason: "token-short" });
3801
+ if (!warnedShortToken) {
3802
+ warnedShortToken = true;
3803
+ console.warn(
3804
+ `[review-kit] live sync is off: the token has ${syncToken.length} characters, the sync server needs at least ${MIN_SYNC_TOKEN_LENGTH}. Use a longer \`token\` (or \`syncToken\`) \u2014 it is the secret of your private rooms.`
3805
+ );
3806
+ }
3807
+ return () => dispatchGuard({ type: "SYNC_STATUS", status: "disabled", reason: null });
3808
+ }
4553
3809
  const id = reviewerId();
4554
3810
  myIdRef.current = id;
4555
3811
  const dispatch = rawDispatch;
4556
3812
  const sync = createRkSync({
4557
- token: config.syncToken,
4558
- url: config.syncUrl ?? DEFAULT_SYNC_URL,
3813
+ token: syncToken,
3814
+ url: syncUrl,
4559
3815
  user: { id, name: author || "Reviewer", color: colorForReviewer(id) },
4560
3816
  handlers: {
4561
- onStatus: (status) => dispatch({ type: "SYNC_STATUS", status }),
3817
+ onStatus: (status) => dispatch({
3818
+ type: "SYNC_STATUS",
3819
+ status,
3820
+ // Offline with no server verdict = the socket never got through
3821
+ // (server down, CSP connect-src, network); a verdict keeps its reason.
3822
+ ...status === "offline" ? { reason: stateRef.current.syncReason ?? "unreachable" } : {}
3823
+ }),
3824
+ onError: (code) => {
3825
+ if (code === "bad-token") dispatch({ type: "SYNC_STATUS", status: "offline", reason: "bad-token" });
3826
+ else if (code === "room-full") dispatch({ type: "SYNC_STATUS", status: "offline", reason: "room-full" });
3827
+ },
4562
3828
  onSnapshot: (items, peers, deletedIds = []) => {
4563
3829
  const safeItems = items.map(sanitizeRemoteItem).filter((item) => item !== null);
4564
3830
  dispatch({ type: "SYNC_SNAPSHOT", items: safeItems });
@@ -4580,7 +3846,7 @@ function useRealtimeSync(state, rawDispatch, config, reducer2) {
4580
3846
  runEffect(name, layerRoot);
4581
3847
  },
4582
3848
  onPeerSelecting: (peerId, selector) => dispatch({ type: "PEER_SELECTING", peerId, selector }),
4583
- onPeerTyping: (peerId, itemId, active2) => dispatch({ type: "PEER_TYPING", peerId, itemId, active: active2 }),
3849
+ onPeerTyping: (peerId, itemId, active) => dispatch({ type: "PEER_TYPING", peerId, itemId, active }),
4584
3850
  onItemAdd: (item, authorId) => {
4585
3851
  const safeItem = sanitizeRemoteItem(item);
4586
3852
  if (!safeItem) return;
@@ -4615,11 +3881,11 @@ function useRealtimeSync(state, rawDispatch, config, reducer2) {
4615
3881
  if (activeSyncRef === sync) activeSyncRef = null;
4616
3882
  setEffectBroadcaster(null);
4617
3883
  sync.stop();
4618
- dispatch({ type: "SYNC_STATUS", status: "disabled" });
3884
+ dispatch({ type: "SYNC_STATUS", status: "disabled", reason: null });
4619
3885
  dispatch({ type: "PEERS_SET", peers: [] });
4620
3886
  };
4621
- }, [enabled, hasSession, config.syncToken, config.syncUrl, author, rawDispatch]);
4622
- const wrapped = useMemo(() => {
3887
+ }, [enabled, hasSession, syncToken, syncUrl, author, rawDispatch]);
3888
+ const wrapped = useMemo2(() => {
4623
3889
  return (action) => {
4624
3890
  const prev = stateRef.current;
4625
3891
  rawDispatch(action);
@@ -4641,7 +3907,7 @@ function useRealtimeSync(state, rawDispatch, config, reducer2) {
4641
3907
  }
4642
3908
  };
4643
3909
  }, [rawDispatch, reducer2]);
4644
- useEffect(() => {
3910
+ useEffect3(() => {
4645
3911
  if (!enabled) return;
4646
3912
  let lastEl = null;
4647
3913
  let lastSel = null;
@@ -4670,18 +3936,21 @@ function useRealtimeSync(state, rawDispatch, config, reducer2) {
4670
3936
  return () => document.removeEventListener("pointermove", onPointerMove);
4671
3937
  }, [enabled]);
4672
3938
  const selectingSelector = state.editor?.target.selector ?? state.selection?.target.selector ?? null;
4673
- useEffect(() => {
3939
+ useEffect3(() => {
4674
3940
  if (!enabled) return;
4675
3941
  syncRef.current?.sendSelecting(selectingSelector);
4676
3942
  }, [enabled, selectingSelector]);
4677
3943
  const editorOpen = state.editor !== null;
4678
- useEffect(() => {
3944
+ useEffect3(() => {
4679
3945
  if (!enabled) return;
4680
3946
  syncRef.current?.sendTyping(null, editorOpen);
4681
3947
  }, [enabled, editorOpen]);
4682
3948
  return enabled ? wrapped : rawDispatch;
4683
3949
  }
4684
3950
 
3951
+ // src/client/components/Badge.tsx
3952
+ import { useLayoutEffect, useRef as useRef4, useState } from "react";
3953
+
4685
3954
  // src/client/dragPosition.ts
4686
3955
  var MARGIN = 8;
4687
3956
  var SNAP = 14;
@@ -4723,6 +3992,9 @@ function snapClampPos(x, y, width, height) {
4723
3992
  if (y > vh - height - 16 - SNAP) y = vh - height - 16;
4724
3993
  return clampPos(x, y, width, height);
4725
3994
  }
3995
+
3996
+ // src/client/components/Badge.tsx
3997
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
4726
3998
  var POS_KEY = "review-kit:badge-pos";
4727
3999
  var DRAG_THRESHOLD = 6;
4728
4000
  function defaultPos(corner, width, height) {
@@ -4745,16 +4017,16 @@ function Badge() {
4745
4017
  const { session, mode, panelOpen } = useStore();
4746
4018
  const dispatch = useDispatch();
4747
4019
  const config = useConfig();
4748
- const ref = useRef(null);
4749
- const clickTimer = useRef(null);
4750
- const lastClickAt = useRef(0);
4020
+ const ref = useRef4(null);
4021
+ const clickTimer = useRef4(null);
4022
+ const lastClickAt = useRef4(0);
4751
4023
  const [pos, setPos] = useState(() => readStoredPos(POS_KEY));
4752
4024
  const [dragging, setDragging] = useState(false);
4753
- const drag = useRef(null);
4025
+ const drag = useRef4(null);
4754
4026
  const openCount = session?.items.filter((item) => item.status === "open").length ?? 0;
4755
4027
  const applyPos = (next, persist) => {
4756
4028
  setPos(next);
4757
- storePos(POS_KEY, next);
4029
+ if (persist) storePos(POS_KEY, next);
4758
4030
  const el = ref.current;
4759
4031
  if (el) {
4760
4032
  const effective = next ?? defaultPos(config.position ?? "bottom-right", el.offsetWidth, el.offsetHeight);
@@ -4828,7 +4100,7 @@ function Badge() {
4828
4100
  if (clickTimer.current !== null) window.clearTimeout(clickTimer.current);
4829
4101
  clickTimer.current = null;
4830
4102
  lastClickAt.current = 0;
4831
- applyPos(null);
4103
+ applyPos(null, true);
4832
4104
  return;
4833
4105
  }
4834
4106
  lastClickAt.current = now;
@@ -4846,7 +4118,7 @@ function Badge() {
4846
4118
  node.offsetHeight
4847
4119
  );
4848
4120
  node.style.translate = `${snapped.x}px ${snapped.y}px`;
4849
- applyPos(snapped);
4121
+ applyPos(snapped, true);
4850
4122
  };
4851
4123
  window.addEventListener("pointermove", onMove, { passive: false });
4852
4124
  window.addEventListener("pointerup", onUp);
@@ -4865,15 +4137,18 @@ function Badge() {
4865
4137
  "aria-expanded": panelOpen,
4866
4138
  onPointerDown,
4867
4139
  children: [
4868
- /* @__PURE__ */ jsx("span", { className: "rk-badge-dot" }),
4140
+ /* @__PURE__ */ jsx2("span", { className: "rk-badge-dot" }),
4869
4141
  "Review",
4870
- openCount > 0 && /* @__PURE__ */ jsx("span", { className: "rk-badge-count", children: openCount })
4142
+ openCount > 0 && /* @__PURE__ */ jsx2("span", { className: "rk-badge-count", children: openCount })
4871
4143
  ]
4872
4144
  }
4873
4145
  );
4874
4146
  }
4147
+
4148
+ // src/client/components/icons.tsx
4149
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
4875
4150
  function Base({ size = 16, children, ...rest }) {
4876
- return /* @__PURE__ */ jsx(
4151
+ return /* @__PURE__ */ jsx3(
4877
4152
  "svg",
4878
4153
  {
4879
4154
  width: size,
@@ -4892,170 +4167,177 @@ function Base({ size = 16, children, ...rest }) {
4892
4167
  );
4893
4168
  }
4894
4169
  function IconText(props) {
4895
- return /* @__PURE__ */ jsxs(Base, { ...props, children: [
4896
- /* @__PURE__ */ jsx("path", { d: "M3.5 4.5V3h9v1.5" }),
4897
- /* @__PURE__ */ jsx("path", { d: "M8 3v10" }),
4898
- /* @__PURE__ */ jsx("path", { d: "M6 13h4" })
4170
+ return /* @__PURE__ */ jsxs2(Base, { ...props, children: [
4171
+ /* @__PURE__ */ jsx3("path", { d: "M3.5 4.5V3h9v1.5" }),
4172
+ /* @__PURE__ */ jsx3("path", { d: "M8 3v10" }),
4173
+ /* @__PURE__ */ jsx3("path", { d: "M6 13h4" })
4899
4174
  ] });
4900
4175
  }
4901
4176
  function IconImage(props) {
4902
- return /* @__PURE__ */ jsxs(Base, { ...props, children: [
4903
- /* @__PURE__ */ jsx("rect", { x: "2.25", y: "3.25", width: "11.5", height: "9.5", rx: "1.5" }),
4904
- /* @__PURE__ */ jsx("circle", { cx: "5.75", cy: "6.5", r: "1", fill: "currentColor", stroke: "none" }),
4905
- /* @__PURE__ */ jsx("path", { d: "M2.5 11l3.2-3.2a1 1 0 0 1 1.4 0L10 10.7l1.4-1.4a1 1 0 0 1 1.4 0l.95.95" })
4177
+ return /* @__PURE__ */ jsxs2(Base, { ...props, children: [
4178
+ /* @__PURE__ */ jsx3("rect", { x: "2.25", y: "3.25", width: "11.5", height: "9.5", rx: "1.5" }),
4179
+ /* @__PURE__ */ jsx3("circle", { cx: "5.75", cy: "6.5", r: "1", fill: "currentColor", stroke: "none" }),
4180
+ /* @__PURE__ */ jsx3("path", { d: "M2.5 11l3.2-3.2a1 1 0 0 1 1.4 0L10 10.7l1.4-1.4a1 1 0 0 1 1.4 0l.95.95" })
4906
4181
  ] });
4907
4182
  }
4908
4183
  function IconComment(props) {
4909
- return /* @__PURE__ */ jsx(Base, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M13.5 7.6c0 2.7-2.46 4.6-5.5 4.6-.6 0-1.18-.07-1.72-.2L3.5 13.5l.55-2.2C3.06 10.45 2.5 9.1 2.5 7.6 2.5 4.9 4.96 3 8 3s5.5 1.9 5.5 4.6Z" }) });
4184
+ return /* @__PURE__ */ jsx3(Base, { ...props, children: /* @__PURE__ */ jsx3("path", { d: "M13.5 7.6c0 2.7-2.46 4.6-5.5 4.6-.6 0-1.18-.07-1.72-.2L3.5 13.5l.55-2.2C3.06 10.45 2.5 9.1 2.5 7.6 2.5 4.9 4.96 3 8 3s5.5 1.9 5.5 4.6Z" }) });
4910
4185
  }
4911
4186
  function IconTrash(props) {
4912
- return /* @__PURE__ */ jsxs(Base, { ...props, children: [
4913
- /* @__PURE__ */ jsx("path", { d: "M2.75 4.25h10.5" }),
4914
- /* @__PURE__ */ jsx("path", { d: "M5.5 4.25V3.4c0-.63.51-1.15 1.15-1.15h2.7c.64 0 1.15.52 1.15 1.15v.85" }),
4915
- /* @__PURE__ */ jsx("path", { d: "M4.25 4.5l.55 8.05c.04.62.56 1.1 1.18 1.1h4.04c.62 0 1.14-.48 1.18-1.1l.55-8.05" }),
4916
- /* @__PURE__ */ jsx("path", { d: "M6.6 7v3.8M9.4 7v3.8" })
4187
+ return /* @__PURE__ */ jsxs2(Base, { ...props, children: [
4188
+ /* @__PURE__ */ jsx3("path", { d: "M2.75 4.25h10.5" }),
4189
+ /* @__PURE__ */ jsx3("path", { d: "M5.5 4.25V3.4c0-.63.51-1.15 1.15-1.15h2.7c.64 0 1.15.52 1.15 1.15v.85" }),
4190
+ /* @__PURE__ */ jsx3("path", { d: "M4.25 4.5l.55 8.05c.04.62.56 1.1 1.18 1.1h4.04c.62 0 1.14-.48 1.18-1.1l.55-8.05" }),
4191
+ /* @__PURE__ */ jsx3("path", { d: "M6.6 7v3.8M9.4 7v3.8" })
4917
4192
  ] });
4918
4193
  }
4919
4194
  function IconMove(props) {
4920
- return /* @__PURE__ */ jsxs(Base, { ...props, children: [
4921
- /* @__PURE__ */ jsx("path", { d: "M8 2.5v11" }),
4922
- /* @__PURE__ */ jsx("path", { d: "M5.25 5.25 8 2.5l2.75 2.75" }),
4923
- /* @__PURE__ */ jsx("path", { d: "M5.25 10.75 8 13.5l2.75-2.75" })
4195
+ return /* @__PURE__ */ jsxs2(Base, { ...props, children: [
4196
+ /* @__PURE__ */ jsx3("path", { d: "M8 2.5v11" }),
4197
+ /* @__PURE__ */ jsx3("path", { d: "M5.25 5.25 8 2.5l2.75 2.75" }),
4198
+ /* @__PURE__ */ jsx3("path", { d: "M5.25 10.75 8 13.5l2.75-2.75" })
4924
4199
  ] });
4925
4200
  }
4926
4201
  function IconStyle(props) {
4927
- return /* @__PURE__ */ jsxs(Base, { ...props, children: [
4928
- /* @__PURE__ */ jsx("path", { d: "M11.2 2.6a1.6 1.6 0 0 1 2.26 2.26L7.1 11.2l-3.1.85.84-3.1 6.36-6.35Z" }),
4929
- /* @__PURE__ */ jsx("path", { d: "M9.6 4.2l2.2 2.2" })
4202
+ return /* @__PURE__ */ jsxs2(Base, { ...props, children: [
4203
+ /* @__PURE__ */ jsx3("path", { d: "M11.2 2.6a1.6 1.6 0 0 1 2.26 2.26L7.1 11.2l-3.1.85.84-3.1 6.36-6.35Z" }),
4204
+ /* @__PURE__ */ jsx3("path", { d: "M9.6 4.2l2.2 2.2" })
4930
4205
  ] });
4931
4206
  }
4932
4207
  function IconClose(props) {
4933
- return /* @__PURE__ */ jsx(Base, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M4 4l8 8M12 4l-8 8" }) });
4208
+ return /* @__PURE__ */ jsx3(Base, { ...props, children: /* @__PURE__ */ jsx3("path", { d: "M4 4l8 8M12 4l-8 8" }) });
4934
4209
  }
4935
4210
  function IconSearch(props) {
4936
- return /* @__PURE__ */ jsxs(Base, { ...props, children: [
4937
- /* @__PURE__ */ jsx("circle", { cx: "7", cy: "7", r: "4.25" }),
4938
- /* @__PURE__ */ jsx("path", { d: "M10.3 10.3 13.5 13.5" })
4211
+ return /* @__PURE__ */ jsxs2(Base, { ...props, children: [
4212
+ /* @__PURE__ */ jsx3("circle", { cx: "7", cy: "7", r: "4.25" }),
4213
+ /* @__PURE__ */ jsx3("path", { d: "M10.3 10.3 13.5 13.5" })
4939
4214
  ] });
4940
4215
  }
4941
4216
  function IconDownload(props) {
4942
- return /* @__PURE__ */ jsxs(Base, { ...props, children: [
4943
- /* @__PURE__ */ jsx("path", { d: "M8 2.5v7.5" }),
4944
- /* @__PURE__ */ jsx("path", { d: "M5.25 7.5 8 10.25 10.75 7.5" }),
4945
- /* @__PURE__ */ jsx("path", { d: "M3 11.5v1a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1" })
4217
+ return /* @__PURE__ */ jsxs2(Base, { ...props, children: [
4218
+ /* @__PURE__ */ jsx3("path", { d: "M8 2.5v7.5" }),
4219
+ /* @__PURE__ */ jsx3("path", { d: "M5.25 7.5 8 10.25 10.75 7.5" }),
4220
+ /* @__PURE__ */ jsx3("path", { d: "M3 11.5v1a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1" })
4946
4221
  ] });
4947
4222
  }
4948
4223
  function IconUpload(props) {
4949
- return /* @__PURE__ */ jsxs(Base, { ...props, children: [
4950
- /* @__PURE__ */ jsx("path", { d: "M8 10V2.75" }),
4951
- /* @__PURE__ */ jsx("path", { d: "M5.25 5.25 8 2.5l2.75 2.75" }),
4952
- /* @__PURE__ */ jsx("path", { d: "M3 11.5v1a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1" })
4224
+ return /* @__PURE__ */ jsxs2(Base, { ...props, children: [
4225
+ /* @__PURE__ */ jsx3("path", { d: "M8 10V2.75" }),
4226
+ /* @__PURE__ */ jsx3("path", { d: "M5.25 5.25 8 2.5l2.75 2.75" }),
4227
+ /* @__PURE__ */ jsx3("path", { d: "M3 11.5v1a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1" })
4953
4228
  ] });
4954
4229
  }
4955
4230
  function IconCopyClipboard(props) {
4956
- return /* @__PURE__ */ jsxs(Base, { ...props, children: [
4957
- /* @__PURE__ */ jsx("rect", { x: "5.5", y: "5.5", width: "8", height: "8", rx: "1.5" }),
4958
- /* @__PURE__ */ jsx("path", { d: "M10.5 5.5v-2a1 1 0 0 0-1-1h-6a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2" })
4231
+ return /* @__PURE__ */ jsxs2(Base, { ...props, children: [
4232
+ /* @__PURE__ */ jsx3("rect", { x: "5.5", y: "5.5", width: "8", height: "8", rx: "1.5" }),
4233
+ /* @__PURE__ */ jsx3("path", { d: "M10.5 5.5v-2a1 1 0 0 0-1-1h-6a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2" })
4959
4234
  ] });
4960
4235
  }
4961
4236
  function IconUndo(props) {
4962
- return /* @__PURE__ */ jsxs(Base, { ...props, children: [
4963
- /* @__PURE__ */ jsx("path", { d: "M3 6.5h6.5a3.5 3.5 0 0 1 0 7H6" }),
4964
- /* @__PURE__ */ jsx("path", { d: "M5.5 4 3 6.5 5.5 9" })
4237
+ return /* @__PURE__ */ jsxs2(Base, { ...props, children: [
4238
+ /* @__PURE__ */ jsx3("path", { d: "M3 6.5h6.5a3.5 3.5 0 0 1 0 7H6" }),
4239
+ /* @__PURE__ */ jsx3("path", { d: "M5.5 4 3 6.5 5.5 9" })
4965
4240
  ] });
4966
4241
  }
4967
4242
  function IconCheck(props) {
4968
- return /* @__PURE__ */ jsx(Base, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M3 8.5 6.5 12 13 4.5" }) });
4243
+ return /* @__PURE__ */ jsx3(Base, { ...props, children: /* @__PURE__ */ jsx3("path", { d: "M3 8.5 6.5 12 13 4.5" }) });
4969
4244
  }
4970
4245
  function IconWarning(props) {
4971
- return /* @__PURE__ */ jsxs(Base, { ...props, children: [
4972
- /* @__PURE__ */ jsx("path", { d: "M8 2.75 14.5 13.5h-13L8 2.75Z" }),
4973
- /* @__PURE__ */ jsx("path", { d: "M8 7v3" }),
4974
- /* @__PURE__ */ jsx("circle", { cx: "8", cy: "11.9", r: "0.55", fill: "currentColor", stroke: "none" })
4246
+ return /* @__PURE__ */ jsxs2(Base, { ...props, children: [
4247
+ /* @__PURE__ */ jsx3("path", { d: "M8 2.75 14.5 13.5h-13L8 2.75Z" }),
4248
+ /* @__PURE__ */ jsx3("path", { d: "M8 7v3" }),
4249
+ /* @__PURE__ */ jsx3("circle", { cx: "8", cy: "11.9", r: "0.55", fill: "currentColor", stroke: "none" })
4975
4250
  ] });
4976
4251
  }
4977
4252
  function IconTarget(props) {
4978
- return /* @__PURE__ */ jsxs(Base, { ...props, children: [
4979
- /* @__PURE__ */ jsx("circle", { cx: "8", cy: "8", r: "5.5" }),
4980
- /* @__PURE__ */ jsx("circle", { cx: "8", cy: "8", r: "1", fill: "currentColor", stroke: "none" }),
4981
- /* @__PURE__ */ jsx("path", { d: "M8 1.5v2M8 12.5v2M1.5 8h2M12.5 8h2" })
4253
+ return /* @__PURE__ */ jsxs2(Base, { ...props, children: [
4254
+ /* @__PURE__ */ jsx3("circle", { cx: "8", cy: "8", r: "5.5" }),
4255
+ /* @__PURE__ */ jsx3("circle", { cx: "8", cy: "8", r: "1", fill: "currentColor", stroke: "none" }),
4256
+ /* @__PURE__ */ jsx3("path", { d: "M8 1.5v2M8 12.5v2M1.5 8h2M12.5 8h2" })
4982
4257
  ] });
4983
4258
  }
4984
4259
  function IconKeyboard(props) {
4985
- return /* @__PURE__ */ jsxs(Base, { ...props, children: [
4986
- /* @__PURE__ */ jsx("rect", { x: "2", y: "4.5", width: "12", height: "7.5", rx: "1.5" }),
4987
- /* @__PURE__ */ jsx("path", { d: "M4.5 7h.01M7 7h.01M9.5 7h.01M12 7h.01M5 9.5h6" })
4260
+ return /* @__PURE__ */ jsxs2(Base, { ...props, children: [
4261
+ /* @__PURE__ */ jsx3("rect", { x: "2", y: "4.5", width: "12", height: "7.5", rx: "1.5" }),
4262
+ /* @__PURE__ */ jsx3("path", { d: "M4.5 7h.01M7 7h.01M9.5 7h.01M12 7h.01M5 9.5h6" })
4988
4263
  ] });
4989
4264
  }
4990
4265
  function IconGear(props) {
4991
- return /* @__PURE__ */ jsxs(Base, { ...props, children: [
4992
- /* @__PURE__ */ jsx("circle", { cx: "8", cy: "8", r: "2.1" }),
4993
- /* @__PURE__ */ jsx("path", { d: "M8 1.8v2M8 12.2v2M1.8 8h2M12.2 8h2M3.6 3.6l1.4 1.4M11 11l1.4 1.4M12.4 3.6 11 5M5 11l-1.4 1.4" })
4266
+ return /* @__PURE__ */ jsxs2(Base, { ...props, children: [
4267
+ /* @__PURE__ */ jsx3("circle", { cx: "8", cy: "8", r: "2.1" }),
4268
+ /* @__PURE__ */ jsx3("path", { d: "M8 1.8v2M8 12.2v2M1.8 8h2M12.2 8h2M3.6 3.6l1.4 1.4M11 11l1.4 1.4M12.4 3.6 11 5M5 11l-1.4 1.4" })
4994
4269
  ] });
4995
4270
  }
4996
4271
  function IconFilter(props) {
4997
- return /* @__PURE__ */ jsx(Base, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M2.5 4h11M4.5 8h7M6.5 12h3" }) });
4272
+ return /* @__PURE__ */ jsx3(Base, { ...props, children: /* @__PURE__ */ jsx3("path", { d: "M2.5 4h11M4.5 8h7M6.5 12h3" }) });
4998
4273
  }
4999
4274
  function IconLink(props) {
5000
- return /* @__PURE__ */ jsxs(Base, { ...props, children: [
5001
- /* @__PURE__ */ jsx("path", { d: "M6.5 9.5 9.5 6.5" }),
5002
- /* @__PURE__ */ jsx("path", { d: "M7.25 4.9 8.4 3.75a2.4 2.4 0 0 1 3.4 0 2.4 2.4 0 0 1 0 3.4L10.65 8.3" }),
5003
- /* @__PURE__ */ jsx("path", { d: "M8.75 11.1 7.6 12.25a2.4 2.4 0 0 1-3.4 0 2.4 2.4 0 0 1 0-3.4L5.35 7.7" })
4275
+ return /* @__PURE__ */ jsxs2(Base, { ...props, children: [
4276
+ /* @__PURE__ */ jsx3("path", { d: "M6.5 9.5 9.5 6.5" }),
4277
+ /* @__PURE__ */ jsx3("path", { d: "M7.25 4.9 8.4 3.75a2.4 2.4 0 0 1 3.4 0 2.4 2.4 0 0 1 0 3.4L10.65 8.3" }),
4278
+ /* @__PURE__ */ jsx3("path", { d: "M8.75 11.1 7.6 12.25a2.4 2.4 0 0 1-3.4 0 2.4 2.4 0 0 1 0-3.4L5.35 7.7" })
5004
4279
  ] });
5005
4280
  }
5006
4281
  function IconUnlink(props) {
5007
- return /* @__PURE__ */ jsxs(Base, { ...props, children: [
5008
- /* @__PURE__ */ jsx("path", { d: "M6.5 4.5v-2M9.5 13.5v-2M4.5 6.5h-2M13.5 9.5h-2" }),
5009
- /* @__PURE__ */ jsx("path", { d: "M6 10 3.5 12.5M10 6l2.5-2.5" })
4282
+ return /* @__PURE__ */ jsxs2(Base, { ...props, children: [
4283
+ /* @__PURE__ */ jsx3("path", { d: "M6.5 4.5v-2M9.5 13.5v-2M4.5 6.5h-2M13.5 9.5h-2" }),
4284
+ /* @__PURE__ */ jsx3("path", { d: "M6 10 3.5 12.5M10 6l2.5-2.5" })
5010
4285
  ] });
5011
4286
  }
5012
4287
  function iconForType(type, size = 14) {
5013
4288
  switch (type) {
5014
4289
  case "copy":
5015
- return /* @__PURE__ */ jsx(IconText, { size });
4290
+ return /* @__PURE__ */ jsx3(IconText, { size });
5016
4291
  case "image":
5017
- return /* @__PURE__ */ jsx(IconImage, { size });
4292
+ return /* @__PURE__ */ jsx3(IconImage, { size });
5018
4293
  case "comment":
5019
- return /* @__PURE__ */ jsx(IconComment, { size });
4294
+ return /* @__PURE__ */ jsx3(IconComment, { size });
5020
4295
  case "delete":
5021
- return /* @__PURE__ */ jsx(IconTrash, { size });
4296
+ return /* @__PURE__ */ jsx3(IconTrash, { size });
5022
4297
  case "move":
5023
- return /* @__PURE__ */ jsx(IconMove, { size });
4298
+ return /* @__PURE__ */ jsx3(IconMove, { size });
5024
4299
  case "style":
5025
- return /* @__PURE__ */ jsx(IconStyle, { size });
4300
+ return /* @__PURE__ */ jsx3(IconStyle, { size });
5026
4301
  default:
5027
- return /* @__PURE__ */ jsx(IconTarget, { size });
4302
+ return /* @__PURE__ */ jsx3(IconTarget, { size });
5028
4303
  }
5029
4304
  }
4305
+
4306
+ // src/client/components/CheatSheet.tsx
4307
+ import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
5030
4308
  var GROUPS = ["Tools", "View", "Items", "Session"];
5031
4309
  function CheatSheet() {
5032
4310
  const { cheatsheetOpen } = useStore();
5033
4311
  const dispatch = useDispatch();
5034
4312
  if (!cheatsheetOpen) return null;
5035
4313
  const close = () => dispatch({ type: "SET_CHEATSHEET", open: false });
5036
- return /* @__PURE__ */ jsxs(Fragment, { children: [
5037
- /* @__PURE__ */ jsx("div", { className: "rk-backdrop", onClick: close }),
5038
- /* @__PURE__ */ jsxs("div", { className: "rk-modal rk-cheatsheet", role: "dialog", "aria-modal": "true", "aria-label": "Keyboard shortcuts", children: [
5039
- /* @__PURE__ */ jsxs("div", { className: "rk-cheat-head", children: [
5040
- /* @__PURE__ */ jsx("span", { className: "rk-cheat-head-icon", children: /* @__PURE__ */ jsx(IconKeyboard, { size: 15 }) }),
5041
- /* @__PURE__ */ jsx("span", { className: "rk-cheat-title", children: "Keyboard shortcuts" }),
5042
- /* @__PURE__ */ jsx("span", { style: { flex: 1 } }),
5043
- /* @__PURE__ */ jsx("button", { type: "button", className: "rk-icon-btn", "aria-label": "Close", onClick: close, autoFocus: true, children: /* @__PURE__ */ jsx(IconClose, { size: 13 }) })
4314
+ return /* @__PURE__ */ jsxs3(Fragment, { children: [
4315
+ /* @__PURE__ */ jsx4("div", { className: "rk-backdrop", onClick: close }),
4316
+ /* @__PURE__ */ jsxs3("div", { className: "rk-modal rk-cheatsheet", role: "dialog", "aria-modal": "true", "aria-label": "Keyboard shortcuts", children: [
4317
+ /* @__PURE__ */ jsxs3("div", { className: "rk-cheat-head", children: [
4318
+ /* @__PURE__ */ jsx4("span", { className: "rk-cheat-head-icon", children: /* @__PURE__ */ jsx4(IconKeyboard, { size: 15 }) }),
4319
+ /* @__PURE__ */ jsx4("span", { className: "rk-cheat-title", children: "Keyboard shortcuts" }),
4320
+ /* @__PURE__ */ jsx4("span", { style: { flex: 1 } }),
4321
+ /* @__PURE__ */ jsx4("button", { type: "button", className: "rk-icon-btn", "aria-label": "Close", onClick: close, autoFocus: true, children: /* @__PURE__ */ jsx4(IconClose, { size: 13 }) })
5044
4322
  ] }),
5045
- /* @__PURE__ */ jsx("div", { className: "rk-cheat-grid", children: GROUPS.map((group) => /* @__PURE__ */ jsxs("section", { className: "rk-cheat-group", children: [
5046
- /* @__PURE__ */ jsx("h3", { className: "rk-cheat-group-title", children: group }),
5047
- SHORTCUTS.filter((def) => def.group === group).map((def) => /* @__PURE__ */ jsxs("div", { className: "rk-shortcut-row", children: [
5048
- /* @__PURE__ */ jsxs("span", { className: "rk-shortcut-text", children: [
5049
- /* @__PURE__ */ jsx("span", { className: "rk-shortcut-title", children: def.title }),
5050
- /* @__PURE__ */ jsx("span", { className: "rk-shortcut-desc", children: def.description })
4323
+ /* @__PURE__ */ jsx4("div", { className: "rk-cheat-grid", children: GROUPS.map((group) => /* @__PURE__ */ jsxs3("section", { className: "rk-cheat-group", children: [
4324
+ /* @__PURE__ */ jsx4("h3", { className: "rk-cheat-group-title", children: group }),
4325
+ SHORTCUTS.filter((def) => def.group === group).map((def) => /* @__PURE__ */ jsxs3("div", { className: "rk-shortcut-row", children: [
4326
+ /* @__PURE__ */ jsxs3("span", { className: "rk-shortcut-text", children: [
4327
+ /* @__PURE__ */ jsx4("span", { className: "rk-shortcut-title", children: def.title }),
4328
+ /* @__PURE__ */ jsx4("span", { className: "rk-shortcut-desc", children: def.description })
5051
4329
  ] }),
5052
- /* @__PURE__ */ jsx("span", { className: "rk-keys", children: displayKeys(def, IS_MAC).map((key) => /* @__PURE__ */ jsx("span", { className: "rk-kbd", children: key }, key)) })
4330
+ /* @__PURE__ */ jsx4("span", { className: "rk-keys", children: displayKeys(def, IS_MAC).map((key) => /* @__PURE__ */ jsx4("span", { className: "rk-kbd", children: key }, key)) })
5053
4331
  ] }, def.id))
5054
4332
  ] }, group)) }),
5055
- /* @__PURE__ */ jsx("div", { className: "rk-egg", children: "Psst \u2014 the \u201C/\u201D box knows a few tricks it won\u2019t tell you about." })
4333
+ /* @__PURE__ */ jsx4("div", { className: "rk-egg", children: "Psst \u2014 the \u201C/\u201D box knows a few tricks it won\u2019t tell you about." })
5056
4334
  ] })
5057
4335
  ] });
5058
4336
  }
4337
+
4338
+ // src/client/components/EmblemaConnect.tsx
4339
+ import { useEffect as useEffect4, useState as useState2 } from "react";
4340
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
5059
4341
  function shortKey(key) {
5060
4342
  return `${key.slice(0, 4)}\u2026${key.slice(-4)}`;
5061
4343
  }
@@ -5070,14 +4352,14 @@ function ago(iso) {
5070
4352
  function EmblemaConnect() {
5071
4353
  const { emblemaConnectOpen } = useStore();
5072
4354
  const dispatch = useDispatch();
5073
- const [state, setState] = useState("loading");
5074
- const [key, setKey] = useState(null);
5075
- const [busy, setBusy] = useState(false);
4355
+ const [state, setState] = useState2("loading");
4356
+ const [key, setKey] = useState2(null);
4357
+ const [busy, setBusy] = useState2(false);
5076
4358
  const refresh = () => {
5077
4359
  setState("loading");
5078
4360
  void fetchSyncState().then(setState);
5079
4361
  };
5080
- useEffect(() => {
4362
+ useEffect4(() => {
5081
4363
  if (emblemaConnectOpen) {
5082
4364
  setKey(null);
5083
4365
  refresh();
@@ -5106,48 +4388,48 @@ function EmblemaConnect() {
5106
4388
  refresh();
5107
4389
  }).finally(() => setBusy(false));
5108
4390
  };
5109
- return /* @__PURE__ */ jsxs(Fragment, { children: [
5110
- /* @__PURE__ */ jsx("div", { className: "rk-backdrop", onClick: close }),
5111
- /* @__PURE__ */ jsx("div", { className: "rk-modal", role: "dialog", "aria-modal": "true", "aria-label": "Connect to Emblema", children: /* @__PURE__ */ jsxs("div", { className: "rk-modal-body", children: [
5112
- /* @__PURE__ */ jsxs("div", { className: "rk-modal-title", style: { display: "flex", alignItems: "center", gap: 8 }, children: [
4391
+ return /* @__PURE__ */ jsxs4(Fragment2, { children: [
4392
+ /* @__PURE__ */ jsx5("div", { className: "rk-backdrop", onClick: close }),
4393
+ /* @__PURE__ */ jsx5("div", { className: "rk-modal", role: "dialog", "aria-modal": "true", "aria-label": "Connect to Emblema", children: /* @__PURE__ */ jsxs4("div", { className: "rk-modal-body", children: [
4394
+ /* @__PURE__ */ jsxs4("div", { className: "rk-modal-title", style: { display: "flex", alignItems: "center", gap: 8 }, children: [
5113
4395
  "Connect to Emblema",
5114
- /* @__PURE__ */ jsx("span", { style: { flex: 1 } }),
5115
- /* @__PURE__ */ jsx("button", { type: "button", className: "rk-icon-btn", "aria-label": "Close", onClick: close, autoFocus: true, children: /* @__PURE__ */ jsx(IconClose, { size: 13 }) })
4396
+ /* @__PURE__ */ jsx5("span", { style: { flex: 1 } }),
4397
+ /* @__PURE__ */ jsx5("button", { type: "button", className: "rk-icon-btn", "aria-label": "Close", onClick: close, autoFocus: true, children: /* @__PURE__ */ jsx5(IconClose, { size: 13 }) })
5116
4398
  ] }),
5117
- state === "loading" && /* @__PURE__ */ jsx("div", { className: "rk-modal-text", children: "Looking for the local sync agent\u2026" }),
5118
- state === null && /* @__PURE__ */ jsxs("div", { className: "rk-modal-text", children: [
4399
+ state === "loading" && /* @__PURE__ */ jsx5("div", { className: "rk-modal-text", children: "Looking for the local sync agent\u2026" }),
4400
+ state === null && /* @__PURE__ */ jsxs4("div", { className: "rk-modal-text", children: [
5119
4401
  "The local sync agent isn't running. In the project repo run:",
5120
- /* @__PURE__ */ jsxs("code", { className: "rk-connect-cmd", children: [
5121
- "npx reviewkit-emblema init --origin ",
4402
+ /* @__PURE__ */ jsxs4("code", { className: "rk-connect-cmd", children: [
4403
+ "npx -p @satanwagen/reviewkit-cli reviewkit-emblema init --origin ",
5122
4404
  window.location.origin
5123
4405
  ] }),
5124
- /* @__PURE__ */ jsx("code", { className: "rk-connect-cmd", children: "npx reviewkit-emblema serve" }),
4406
+ /* @__PURE__ */ jsx5("code", { className: "rk-connect-cmd", children: "npx -p @satanwagen/reviewkit-cli reviewkit-emblema serve" }),
5125
4407
  "then reopen this dialog."
5126
4408
  ] }),
5127
- state !== null && state !== "loading" && /* @__PURE__ */ jsxs(Fragment, { children: [
5128
- /* @__PURE__ */ jsxs("div", { className: "rk-modal-text", children: [
4409
+ state !== null && state !== "loading" && /* @__PURE__ */ jsxs4(Fragment2, { children: [
4410
+ /* @__PURE__ */ jsxs4("div", { className: "rk-modal-text", children: [
5129
4411
  "Project ",
5130
- /* @__PURE__ */ jsx("strong", { children: state.name }),
4412
+ /* @__PURE__ */ jsx5("strong", { children: state.name }),
5131
4413
  " ",
5132
- /* @__PURE__ */ jsxs("span", { className: "rk-connect-id", children: [
4414
+ /* @__PURE__ */ jsxs4("span", { className: "rk-connect-id", children: [
5133
4415
  "(",
5134
4416
  state.id,
5135
4417
  ")"
5136
4418
  ] }),
5137
- /* @__PURE__ */ jsx("br", {}),
4419
+ /* @__PURE__ */ jsx5("br", {}),
5138
4420
  "Status:",
5139
4421
  " ",
5140
- state.revoked ? /* @__PURE__ */ jsx("span", { className: "rk-connect-bad", children: "revoked \u2014 generate a new key to reconnect" }) : /* @__PURE__ */ jsx(Fragment, { children: "paired key active" }),
5141
- /* @__PURE__ */ jsx("br", {}),
4422
+ state.revoked ? /* @__PURE__ */ jsx5("span", { className: "rk-connect-bad", children: "revoked \u2014 generate a new key to reconnect" }) : /* @__PURE__ */ jsx5(Fragment2, { children: "paired key active" }),
4423
+ /* @__PURE__ */ jsx5("br", {}),
5142
4424
  "Last confirmed by Emblema: ",
5143
- /* @__PURE__ */ jsx("strong", { children: ago(state.lastAck) }),
4425
+ /* @__PURE__ */ jsx5("strong", { children: ago(state.lastAck) }),
5144
4426
  " \xB7 ",
5145
4427
  state.findings,
5146
4428
  " finding",
5147
4429
  state.findings === 1 ? "" : "s",
5148
4430
  " tracked"
5149
4431
  ] }),
5150
- key && /* @__PURE__ */ jsx(
4432
+ key && /* @__PURE__ */ jsx5(
5151
4433
  "code",
5152
4434
  {
5153
4435
  className: "rk-connect-key",
@@ -5155,8 +4437,8 @@ function EmblemaConnect() {
5155
4437
  children: shortKey(key)
5156
4438
  }
5157
4439
  ),
5158
- /* @__PURE__ */ jsxs("div", { className: "rk-connect-actions", children: [
5159
- /* @__PURE__ */ jsx(
4440
+ /* @__PURE__ */ jsxs4("div", { className: "rk-connect-actions", children: [
4441
+ /* @__PURE__ */ jsx5(
5160
4442
  "button",
5161
4443
  {
5162
4444
  type: "button",
@@ -5167,7 +4449,7 @@ function EmblemaConnect() {
5167
4449
  children: state.revoked ? "Generate new key" : "New key (rotate)"
5168
4450
  }
5169
4451
  ),
5170
- /* @__PURE__ */ jsx(
4452
+ /* @__PURE__ */ jsx5(
5171
4453
  "button",
5172
4454
  {
5173
4455
  type: "button",
@@ -5184,8 +4466,8 @@ function EmblemaConnect() {
5184
4466
  children: "Copy key"
5185
4467
  }
5186
4468
  ),
5187
- /* @__PURE__ */ jsx("span", { style: { flex: 1 } }),
5188
- /* @__PURE__ */ jsx(
4469
+ /* @__PURE__ */ jsx5("span", { style: { flex: 1 } }),
4470
+ /* @__PURE__ */ jsx5(
5189
4471
  "button",
5190
4472
  {
5191
4473
  type: "button",
@@ -5207,12 +4489,230 @@ function EmblemaConnect() {
5207
4489
  ] }) })
5208
4490
  ] });
5209
4491
  }
4492
+
4493
+ // src/client/components/TerminalConnect.tsx
4494
+ import { useEffect as useEffect5, useState as useState3 } from "react";
4495
+
4496
+ // src/integrations/terminal.ts
4497
+ var TERMINAL_PAIR_PORTS = [48780, 48781, 48782, 48783];
4498
+ async function fetchJson(url, init, timeoutMs) {
4499
+ const abort = new AbortController();
4500
+ const timer = window.setTimeout(() => abort.abort(), timeoutMs);
4501
+ try {
4502
+ return await fetch(url, { ...init, signal: abort.signal });
4503
+ } finally {
4504
+ window.clearTimeout(timer);
4505
+ }
4506
+ }
4507
+ async function probeTerminal() {
4508
+ for (const port of TERMINAL_PAIR_PORTS) {
4509
+ try {
4510
+ const res = await fetchJson(`http://127.0.0.1:${port}/pair`, { method: "GET" }, 900);
4511
+ if (!res.ok) continue;
4512
+ const body = await res.json();
4513
+ if (typeof body === "object" && body !== null && body.app === "reviewkit-cli") {
4514
+ const b = body;
4515
+ return {
4516
+ port,
4517
+ cwd: typeof b.cwd === "string" ? b.cwd : void 0,
4518
+ projects: Array.isArray(b.projects) ? b.projects.filter((p) => typeof p === "string") : []
4519
+ };
4520
+ }
4521
+ } catch {
4522
+ }
4523
+ }
4524
+ return null;
4525
+ }
4526
+ async function pairTerminal(port, key) {
4527
+ try {
4528
+ const res = await fetchJson(
4529
+ `http://127.0.0.1:${port}/pair`,
4530
+ { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ key }) },
4531
+ 5e3
4532
+ );
4533
+ const body = await res.json().catch(() => null);
4534
+ if (res.ok && body?.ok) return { ok: true, name: body.name ?? "", repo: body.repo ?? null };
4535
+ return { ok: false, error: body?.error ?? `HTTP ${res.status}` };
4536
+ } catch {
4537
+ return { ok: false, error: "the terminal stopped answering" };
4538
+ }
4539
+ }
4540
+
4541
+ // src/client/terminalKey.ts
4542
+ var TERMINAL_KEY_PREFIX = "rkc_";
4543
+ var TERMINAL_KEY_VERSION = 1;
4544
+ function base64url(text) {
4545
+ const bytes = new TextEncoder().encode(text);
4546
+ let binary = "";
4547
+ for (const byte of bytes) binary += String.fromCharCode(byte);
4548
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
4549
+ }
4550
+ function encodeTerminalKey(fields) {
4551
+ const payload = { v: TERMINAL_KEY_VERSION, ...fields };
4552
+ return TERMINAL_KEY_PREFIX + base64url(JSON.stringify(payload));
4553
+ }
4554
+ function currentTerminalKey(syncUrl, syncToken) {
4555
+ const origin = window.location.origin;
4556
+ let route = "/";
4557
+ try {
4558
+ route = window.location.pathname || "/";
4559
+ } catch {
4560
+ }
4561
+ return encodeTerminalKey({ name: window.location.hostname, origin, syncUrl, syncToken, route });
4562
+ }
4563
+
4564
+ // src/client/components/TerminalConnect.tsx
4565
+ import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
4566
+ function shortKey2(key) {
4567
+ return `${key.slice(0, 8)}\u2026${key.slice(-6)}`;
4568
+ }
4569
+ function shortPath(path) {
4570
+ if (!path) return "";
4571
+ const parts = path.split("/").filter(Boolean);
4572
+ return parts.length > 2 ? `\u2026/${parts.slice(-2).join("/")}` : path;
4573
+ }
4574
+ function TerminalConnect() {
4575
+ const { terminalConnectOpen } = useStore();
4576
+ const config = useConfig();
4577
+ const dispatch = useDispatch();
4578
+ const [phase, setPhase] = useState3("probing");
4579
+ const [probe, setProbe] = useState3(null);
4580
+ const [paired, setPaired] = useState3(null);
4581
+ const [showManual, setShowManual] = useState3(false);
4582
+ const syncToken = resolveSyncToken(config);
4583
+ const key = terminalConnectOpen && syncToken ? currentTerminalKey(resolveSyncUrl(config), syncToken) : null;
4584
+ useEffect5(() => {
4585
+ if (!terminalConnectOpen) return;
4586
+ let cancelled = false;
4587
+ setPhase("probing");
4588
+ setProbe(null);
4589
+ setPaired(null);
4590
+ setShowManual(false);
4591
+ const look = () => void probeTerminal().then((found) => {
4592
+ if (cancelled) return;
4593
+ setProbe(found);
4594
+ setPhase((current) => current === "pairing" || current === "paired" ? current : found ? "found" : "missing");
4595
+ });
4596
+ look();
4597
+ const timer = window.setInterval(look, 2e3);
4598
+ return () => {
4599
+ cancelled = true;
4600
+ window.clearInterval(timer);
4601
+ };
4602
+ }, [terminalConnectOpen]);
4603
+ if (!terminalConnectOpen) return null;
4604
+ const close = () => dispatch({ type: "SET_TERMINAL_CONNECT", open: false });
4605
+ const copy = async (value, what) => {
4606
+ try {
4607
+ await navigator.clipboard.writeText(value);
4608
+ toast(dispatch, `${what} copied`, "success");
4609
+ } catch {
4610
+ toast(dispatch, "Copy failed \u2014 select the text manually", "error");
4611
+ }
4612
+ };
4613
+ const connect = () => {
4614
+ if (!probe || !key) return;
4615
+ setPhase("pairing");
4616
+ void pairTerminal(probe.port, key).then((result) => {
4617
+ if (result.ok) {
4618
+ setPaired({ name: result.name, repo: result.repo });
4619
+ setPhase("paired");
4620
+ toast(dispatch, `Terminal paired as \u201C${result.name}\u201D`, "success");
4621
+ } else {
4622
+ setPhase("found");
4623
+ toast(dispatch, `Pairing failed: ${result.error}`, "error");
4624
+ }
4625
+ });
4626
+ };
4627
+ const connectCmd = key ? `npx -y @satanwagen/reviewkit-cli connect ${key}` : "";
4628
+ return /* @__PURE__ */ jsxs5(Fragment3, { children: [
4629
+ /* @__PURE__ */ jsx6("div", { className: "rk-backdrop", onClick: close }),
4630
+ /* @__PURE__ */ jsx6("div", { className: "rk-modal", role: "dialog", "aria-modal": "true", "aria-label": "Connect terminal or agent", children: /* @__PURE__ */ jsxs5("div", { className: "rk-modal-body", children: [
4631
+ /* @__PURE__ */ jsxs5("div", { className: "rk-modal-title", style: { display: "flex", alignItems: "center", gap: 8 }, children: [
4632
+ "Connect terminal / agent",
4633
+ /* @__PURE__ */ jsx6("span", { style: { flex: 1 } }),
4634
+ /* @__PURE__ */ jsx6("button", { type: "button", className: "rk-icon-btn", "aria-label": "Close", onClick: close, autoFocus: true, children: /* @__PURE__ */ jsx6(IconClose, { size: 13 }) })
4635
+ ] }),
4636
+ !key && /* @__PURE__ */ jsxs5("div", { className: "rk-modal-text", children: [
4637
+ "Live sync is off on this page (",
4638
+ /* @__PURE__ */ jsx6("code", { children: "sync=false" }),
4639
+ " or no token), so there is no shared room a terminal could read."
4640
+ ] }),
4641
+ key && phase === "paired" && paired && /* @__PURE__ */ jsxs5("div", { className: "rk-modal-text", children: [
4642
+ /* @__PURE__ */ jsx6("span", { className: "rk-emblema-dot", "data-state": "live" }),
4643
+ " Paired as ",
4644
+ /* @__PURE__ */ jsx6("strong", { children: paired.name }),
4645
+ paired.repo ? /* @__PURE__ */ jsxs5(Fragment3, { children: [
4646
+ " in ",
4647
+ /* @__PURE__ */ jsx6("code", { children: shortPath(paired.repo) })
4648
+ ] }) : null,
4649
+ ". The terminal shows this page's comments live, and Claude Code / Cursor can read and resolve them through the ",
4650
+ /* @__PURE__ */ jsx6("code", { children: "reviewkit" }),
4651
+ " MCP tools."
4652
+ ] }),
4653
+ key && phase !== "paired" && /* @__PURE__ */ jsxs5(Fragment3, { children: [
4654
+ /* @__PURE__ */ jsxs5("div", { className: "rk-modal-text", children: [
4655
+ phase === "probing" && /* @__PURE__ */ jsxs5(Fragment3, { children: [
4656
+ "Looking for a ",
4657
+ /* @__PURE__ */ jsx6("code", { children: "reviewkit" }),
4658
+ " terminal on this machine\u2026"
4659
+ ] }),
4660
+ phase === "missing" && /* @__PURE__ */ jsxs5(Fragment3, { children: [
4661
+ "No terminal is listening yet. In the site's repo run",
4662
+ /* @__PURE__ */ jsx6("code", { className: "rk-connect-cmd", children: "npx -y @satanwagen/reviewkit-cli setup" }),
4663
+ "and this dialog will pick it up by itself."
4664
+ ] }),
4665
+ (phase === "found" || phase === "pairing") && probe && /* @__PURE__ */ jsxs5(Fragment3, { children: [
4666
+ /* @__PURE__ */ jsx6("span", { className: "rk-emblema-dot", "data-state": "live" }),
4667
+ " Terminal found",
4668
+ probe.cwd ? /* @__PURE__ */ jsxs5(Fragment3, { children: [
4669
+ " in ",
4670
+ /* @__PURE__ */ jsx6("code", { children: shortPath(probe.cwd) })
4671
+ ] }) : null,
4672
+ ".",
4673
+ probe.projects.length > 0 ? /* @__PURE__ */ jsxs5(Fragment3, { children: [
4674
+ " Already paired: ",
4675
+ probe.projects.join(", "),
4676
+ "."
4677
+ ] }) : null
4678
+ ] })
4679
+ ] }),
4680
+ /* @__PURE__ */ jsxs5("div", { className: "rk-connect-actions", children: [
4681
+ /* @__PURE__ */ jsx6(
4682
+ "button",
4683
+ {
4684
+ type: "button",
4685
+ className: "rk-btn rk-btn-primary",
4686
+ disabled: phase !== "found",
4687
+ onClick: connect,
4688
+ title: "Send this site's connection to the terminal \u2014 nothing to copy",
4689
+ children: phase === "pairing" ? "Connecting\u2026" : "Connect this terminal"
4690
+ }
4691
+ ),
4692
+ /* @__PURE__ */ jsx6("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => setShowManual((v) => !v), children: showManual ? "Hide manual option" : "Another machine\u2026" })
4693
+ ] }),
4694
+ showManual && /* @__PURE__ */ jsxs5(Fragment3, { children: [
4695
+ /* @__PURE__ */ jsx6("div", { className: "rk-modal-text", style: { marginTop: 10 }, children: "On a machine without a listening terminal, run this once (the key carries the sync server and its token \u2014 treat it like a review link):" }),
4696
+ /* @__PURE__ */ jsxs5("code", { className: "rk-connect-cmd", title: connectCmd, children: [
4697
+ "npx -y @satanwagen/reviewkit-cli connect ",
4698
+ shortKey2(key)
4699
+ ] }),
4700
+ /* @__PURE__ */ jsx6("div", { className: "rk-connect-actions", children: /* @__PURE__ */ jsx6("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => void copy(connectCmd, "Command"), children: "Copy command" }) })
4701
+ ] })
4702
+ ] })
4703
+ ] }) })
4704
+ ] });
4705
+ }
4706
+
4707
+ // src/client/components/CoachMark.tsx
4708
+ import { useState as useState4 } from "react";
4709
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
5210
4710
  var COACH_KEY = "review-kit:coach-dismissed";
5211
4711
  function CoachMark() {
5212
4712
  const { coachOpen, session } = useStore();
5213
4713
  const dispatch = useDispatch();
5214
4714
  const config = useConfig();
5215
- const [name, setName] = useState(session?.author ?? config.defaultAuthor ?? "");
4715
+ const [name, setName] = useState4(session?.author ?? config.defaultAuthor ?? "");
5216
4716
  if (!coachOpen) return null;
5217
4717
  const done = (startPicking) => {
5218
4718
  if (name.trim() !== "") dispatch({ type: "SET_AUTHOR", author: name.trim() });
@@ -5223,39 +4723,39 @@ function CoachMark() {
5223
4723
  }
5224
4724
  if (startPicking) dispatch({ type: "SET_MODE", mode: "pick" });
5225
4725
  };
5226
- return /* @__PURE__ */ jsxs("div", { className: "rk-coach", role: "dialog", "aria-label": "Welcome to review mode", children: [
5227
- /* @__PURE__ */ jsx("div", { className: "rk-coach-accent" }),
5228
- /* @__PURE__ */ jsxs("div", { className: "rk-coach-body", children: [
5229
- /* @__PURE__ */ jsxs("div", { className: "rk-modal-title", style: { display: "flex", alignItems: "center", gap: 8 }, children: [
5230
- /* @__PURE__ */ jsx(IconTarget, { size: 16 }),
4726
+ return /* @__PURE__ */ jsxs6("div", { className: "rk-coach", role: "dialog", "aria-label": "Welcome to review mode", children: [
4727
+ /* @__PURE__ */ jsx7("div", { className: "rk-coach-accent" }),
4728
+ /* @__PURE__ */ jsxs6("div", { className: "rk-coach-body", children: [
4729
+ /* @__PURE__ */ jsxs6("div", { className: "rk-modal-title", style: { display: "flex", alignItems: "center", gap: 8 }, children: [
4730
+ /* @__PURE__ */ jsx7(IconTarget, { size: 16 }),
5231
4731
  " Review mode"
5232
4732
  ] }),
5233
- /* @__PURE__ */ jsx("div", { className: "rk-modal-text", children: "Click anything on the page to suggest a change \u2014 rewrite the copy, swap an image, or leave a note. The live site stays untouched; everything you add is collected for the team." }),
5234
- /* @__PURE__ */ jsxs("div", { className: "rk-coach-keys", "aria-label": "Key shortcuts", children: [
5235
- /* @__PURE__ */ jsxs("span", { children: [
5236
- /* @__PURE__ */ jsx("span", { className: "rk-kbd", children: "T" }),
4733
+ /* @__PURE__ */ jsx7("div", { className: "rk-modal-text", children: "Click anything on the page to suggest a change \u2014 rewrite the copy, swap an image, or leave a note. The live site stays untouched; everything you add is collected for the team." }),
4734
+ /* @__PURE__ */ jsxs6("div", { className: "rk-coach-keys", "aria-label": "Key shortcuts", children: [
4735
+ /* @__PURE__ */ jsxs6("span", { children: [
4736
+ /* @__PURE__ */ jsx7("span", { className: "rk-kbd", children: "T" }),
5237
4737
  " rewrite"
5238
4738
  ] }),
5239
- /* @__PURE__ */ jsxs("span", { children: [
5240
- /* @__PURE__ */ jsx("span", { className: "rk-kbd", children: "C" }),
4739
+ /* @__PURE__ */ jsxs6("span", { children: [
4740
+ /* @__PURE__ */ jsx7("span", { className: "rk-kbd", children: "C" }),
5241
4741
  " comment"
5242
4742
  ] }),
5243
- /* @__PURE__ */ jsxs("span", { children: [
5244
- /* @__PURE__ */ jsx("span", { className: "rk-kbd", children: "I" }),
4743
+ /* @__PURE__ */ jsxs6("span", { children: [
4744
+ /* @__PURE__ */ jsx7("span", { className: "rk-kbd", children: "I" }),
5245
4745
  " image"
5246
4746
  ] }),
5247
- /* @__PURE__ */ jsxs("span", { children: [
5248
- /* @__PURE__ */ jsx("span", { className: "rk-kbd", children: "V" }),
4747
+ /* @__PURE__ */ jsxs6("span", { children: [
4748
+ /* @__PURE__ */ jsx7("span", { className: "rk-kbd", children: "V" }),
5249
4749
  " select"
5250
4750
  ] }),
5251
- /* @__PURE__ */ jsxs("span", { children: [
5252
- /* @__PURE__ */ jsx("span", { className: "rk-kbd", children: "?" }),
4751
+ /* @__PURE__ */ jsxs6("span", { children: [
4752
+ /* @__PURE__ */ jsx7("span", { className: "rk-kbd", children: "?" }),
5253
4753
  " all shortcuts"
5254
4754
  ] })
5255
4755
  ] }),
5256
- /* @__PURE__ */ jsxs("div", { children: [
5257
- /* @__PURE__ */ jsx("label", { className: "rk-label", htmlFor: "rk-author", children: "Your name" }),
5258
- /* @__PURE__ */ jsx(
4756
+ /* @__PURE__ */ jsxs6("div", { children: [
4757
+ /* @__PURE__ */ jsx7("label", { className: "rk-label", htmlFor: "rk-author", children: "Your name" }),
4758
+ /* @__PURE__ */ jsx7(
5259
4759
  "input",
5260
4760
  {
5261
4761
  id: "rk-author",
@@ -5269,32 +4769,36 @@ function CoachMark() {
5269
4769
  }
5270
4770
  )
5271
4771
  ] }),
5272
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "flex-end", gap: 8, marginTop: 4 }, children: [
5273
- /* @__PURE__ */ jsx("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => done(false), children: "Not now" }),
5274
- /* @__PURE__ */ jsx("button", { type: "button", className: "rk-btn rk-btn-primary", onClick: () => done(true), children: "Start reviewing" })
4772
+ /* @__PURE__ */ jsxs6("div", { style: { display: "flex", justifyContent: "flex-end", gap: 8, marginTop: 4 }, children: [
4773
+ /* @__PURE__ */ jsx7("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => done(false), children: "Not now" }),
4774
+ /* @__PURE__ */ jsx7("button", { type: "button", className: "rk-btn rk-btn-primary", onClick: () => done(true), children: "Start reviewing" })
5275
4775
  ] })
5276
4776
  ] })
5277
4777
  ] });
5278
4778
  }
4779
+
4780
+ // src/client/components/Confirm.tsx
4781
+ import { useEffect as useEffect6, useRef as useRef5 } from "react";
4782
+ import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
5279
4783
  function ConfirmHost() {
5280
4784
  const { confirm } = useStore();
5281
4785
  const dispatch = useDispatch();
5282
- const cancelRef = useRef(null);
5283
- useEffect(() => {
4786
+ const cancelRef = useRef5(null);
4787
+ useEffect6(() => {
5284
4788
  if (confirm) cancelRef.current?.focus();
5285
4789
  }, [confirm]);
5286
4790
  if (!confirm) return null;
5287
4791
  const close = () => dispatch({ type: "SET_CONFIRM", confirm: null });
5288
- return /* @__PURE__ */ jsxs(Fragment, { children: [
5289
- /* @__PURE__ */ jsx("div", { className: "rk-backdrop", onClick: close }),
5290
- /* @__PURE__ */ jsxs("div", { className: "rk-modal", role: "alertdialog", "aria-modal": "true", "aria-label": confirm.title, children: [
5291
- /* @__PURE__ */ jsxs("div", { className: "rk-modal-body", children: [
5292
- /* @__PURE__ */ jsx("div", { className: "rk-modal-title", children: confirm.title }),
5293
- /* @__PURE__ */ jsx("div", { className: "rk-modal-text", children: confirm.message })
4792
+ return /* @__PURE__ */ jsxs7(Fragment4, { children: [
4793
+ /* @__PURE__ */ jsx8("div", { className: "rk-backdrop", onClick: close }),
4794
+ /* @__PURE__ */ jsxs7("div", { className: "rk-modal", role: "alertdialog", "aria-modal": "true", "aria-label": confirm.title, children: [
4795
+ /* @__PURE__ */ jsxs7("div", { className: "rk-modal-body", children: [
4796
+ /* @__PURE__ */ jsx8("div", { className: "rk-modal-title", children: confirm.title }),
4797
+ /* @__PURE__ */ jsx8("div", { className: "rk-modal-text", children: confirm.message })
5294
4798
  ] }),
5295
- /* @__PURE__ */ jsxs("div", { className: "rk-modal-actions", children: [
5296
- /* @__PURE__ */ jsx("button", { ref: cancelRef, type: "button", className: "rk-btn rk-btn-ghost", onClick: close, children: "Cancel" }),
5297
- /* @__PURE__ */ jsx(
4799
+ /* @__PURE__ */ jsxs7("div", { className: "rk-modal-actions", children: [
4800
+ /* @__PURE__ */ jsx8("button", { ref: cancelRef, type: "button", className: "rk-btn rk-btn-ghost", onClick: close, children: "Cancel" }),
4801
+ /* @__PURE__ */ jsx8(
5298
4802
  "button",
5299
4803
  {
5300
4804
  type: "button",
@@ -5312,6 +4816,12 @@ function ConfirmHost() {
5312
4816
  ] });
5313
4817
  }
5314
4818
 
4819
+ // src/client/components/ImageEditor.tsx
4820
+ import { useEffect as useEffect7, useRef as useRef7, useState as useState6 } from "react";
4821
+
4822
+ // src/client/useAnchored.ts
4823
+ import { useLayoutEffect as useLayoutEffect2, useRef as useRef6, useState as useState5 } from "react";
4824
+
5315
4825
  // src/client/position.ts
5316
4826
  function place(anchor, size, side, offset) {
5317
4827
  switch (side) {
@@ -5355,9 +4865,9 @@ function computePosition(anchor, size, options = {}) {
5355
4865
 
5356
4866
  // src/client/useAnchored.ts
5357
4867
  function useAnchored(anchorEl, side = "bottom", offset = 8) {
5358
- const ref = useRef(null);
5359
- const [pos, setPos] = useState(null);
5360
- useLayoutEffect(() => {
4868
+ const ref = useRef6(null);
4869
+ const [pos, setPos] = useState5(null);
4870
+ useLayoutEffect2(() => {
5361
4871
  if (!anchorEl) {
5362
4872
  setPos(null);
5363
4873
  return;
@@ -5386,6 +4896,9 @@ function anchoredStyle(pos) {
5386
4896
  if (!pos) return { visibility: "hidden", transform: "translate3d(-9999px, -9999px, 0)" };
5387
4897
  return { transform: `translate3d(${pos.x}px, ${pos.y}px, 0)`, top: 0, left: 0 };
5388
4898
  }
4899
+
4900
+ // src/client/components/ImageEditor.tsx
4901
+ import { Fragment as Fragment5, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
5389
4902
  var MAX_BYTES = 2 * 1024 * 1024;
5390
4903
  var FETCH_TIMEOUT_MS = 12e3;
5391
4904
  var PROBE_TIMEOUT_MS = 8e3;
@@ -5443,15 +4956,15 @@ function urlFromDataTransfer(dt) {
5443
4956
  function ImageEditor({ editor }) {
5444
4957
  const dispatch = useDispatch();
5445
4958
  const { route, session } = useStore();
5446
- const [replacement, setReplacement] = useState(null);
5447
- const [urlValue, setUrlValue] = useState("");
5448
- const [loading, setLoading] = useState(false);
5449
- const [description, setDescription] = useState("");
5450
- const [priority, setPriority] = useState("nice");
5451
- const [over, setOver] = useState(false);
5452
- const fileInput = useRef(null);
5453
- const abortRef = useRef(null);
5454
- const debounceRef = useRef(null);
4959
+ const [replacement, setReplacement] = useState6(null);
4960
+ const [urlValue, setUrlValue] = useState6("");
4961
+ const [loading2, setLoading] = useState6(false);
4962
+ const [description, setDescription] = useState6("");
4963
+ const [priority, setPriority] = useState6("nice");
4964
+ const [over, setOver] = useState6(false);
4965
+ const fileInput = useRef7(null);
4966
+ const abortRef = useRef7(null);
4967
+ const debounceRef = useRef7(null);
5455
4968
  const { ref, pos } = useAnchored(editor.element, "bottom", 12);
5456
4969
  const image = findImage(editor.element);
5457
4970
  const beforeSrc = image?.currentSrc || image?.src || "";
@@ -5515,7 +5028,7 @@ function ImageEditor({ editor }) {
5515
5028
  }
5516
5029
  }
5517
5030
  };
5518
- useEffect(() => {
5031
+ useEffect7(() => {
5519
5032
  if (debounceRef.current !== null) window.clearTimeout(debounceRef.current);
5520
5033
  if (urlValue.trim() === "" || replacement !== null) return;
5521
5034
  debounceRef.current = window.setTimeout(() => {
@@ -5525,8 +5038,8 @@ function ImageEditor({ editor }) {
5525
5038
  if (debounceRef.current !== null) window.clearTimeout(debounceRef.current);
5526
5039
  };
5527
5040
  }, [urlValue, replacement]);
5528
- useEffect(() => () => abortRef.current?.abort(), []);
5529
- useEffect(() => {
5041
+ useEffect7(() => () => abortRef.current?.abort(), []);
5042
+ useEffect7(() => {
5530
5043
  const onPaste = (event) => {
5531
5044
  const data = event.clipboardData;
5532
5045
  if (!data) return;
@@ -5604,23 +5117,23 @@ function ImageEditor({ editor }) {
5604
5117
  toast(dispatch, "Image proposal saved", "success");
5605
5118
  };
5606
5119
  const previewSrc = replacement?.kind === "data" ? replacement.dataUrl : replacement?.url;
5607
- return /* @__PURE__ */ jsxs("div", { ref, className: "rk-card", style: anchoredStyle(pos), role: "dialog", "aria-label": "Replace image", children: [
5608
- /* @__PURE__ */ jsxs("div", { className: "rk-card-head", children: [
5609
- /* @__PURE__ */ jsx(IconImage, { size: 14 }),
5120
+ return /* @__PURE__ */ jsxs8("div", { ref, className: "rk-card", style: anchoredStyle(pos), role: "dialog", "aria-label": "Replace image", children: [
5121
+ /* @__PURE__ */ jsxs8("div", { className: "rk-card-head", children: [
5122
+ /* @__PURE__ */ jsx9(IconImage, { size: 14 }),
5610
5123
  "Replace image"
5611
5124
  ] }),
5612
- /* @__PURE__ */ jsxs("div", { className: "rk-card-body", children: [
5613
- replacement && previewSrc ? /* @__PURE__ */ jsx("div", { className: "rk-thumbs", children: /* @__PURE__ */ jsxs("div", { className: "rk-thumb-pair", title: "Hover to compare with the current image", children: [
5614
- /* @__PURE__ */ jsxs("figure", { className: "rk-thumb", children: [
5615
- /* @__PURE__ */ jsx("img", { src: beforeSrc, alt: "Current" }),
5616
- /* @__PURE__ */ jsx("figcaption", { children: "Before" })
5125
+ /* @__PURE__ */ jsxs8("div", { className: "rk-card-body", children: [
5126
+ replacement && previewSrc ? /* @__PURE__ */ jsx9("div", { className: "rk-thumbs", children: /* @__PURE__ */ jsxs8("div", { className: "rk-thumb-pair", title: "Hover to compare with the current image", children: [
5127
+ /* @__PURE__ */ jsxs8("figure", { className: "rk-thumb", children: [
5128
+ /* @__PURE__ */ jsx9("img", { src: beforeSrc, alt: "Current" }),
5129
+ /* @__PURE__ */ jsx9("figcaption", { children: "Before" })
5617
5130
  ] }),
5618
- /* @__PURE__ */ jsxs("figure", { className: "rk-thumb", children: [
5619
- /* @__PURE__ */ jsx("img", { src: previewSrc, alt: "Proposed replacement" }),
5620
- /* @__PURE__ */ jsx("figcaption", { children: replacement.kind === "url" ? "After \xB7 linked" : "After" })
5131
+ /* @__PURE__ */ jsxs8("figure", { className: "rk-thumb", children: [
5132
+ /* @__PURE__ */ jsx9("img", { src: previewSrc, alt: "Proposed replacement" }),
5133
+ /* @__PURE__ */ jsx9("figcaption", { children: replacement.kind === "url" ? "After \xB7 linked" : "After" })
5621
5134
  ] })
5622
- ] }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
5623
- /* @__PURE__ */ jsxs(
5135
+ ] }) }) : /* @__PURE__ */ jsxs8(Fragment5, { children: [
5136
+ /* @__PURE__ */ jsxs8(
5624
5137
  "button",
5625
5138
  {
5626
5139
  type: "button",
@@ -5633,18 +5146,18 @@ function ImageEditor({ editor }) {
5633
5146
  onDragLeave: () => setOver(false),
5634
5147
  onDrop,
5635
5148
  children: [
5636
- /* @__PURE__ */ jsx(IconUpload, { size: 18 }),
5637
- /* @__PURE__ */ jsxs("span", { children: [
5149
+ /* @__PURE__ */ jsx9(IconUpload, { size: 18 }),
5150
+ /* @__PURE__ */ jsxs8("span", { children: [
5638
5151
  "Drop an image \u2014 from your disk or another tab \u2014",
5639
- /* @__PURE__ */ jsx("br", {}),
5152
+ /* @__PURE__ */ jsx9("br", {}),
5640
5153
  "or click to browse (max 2 MB)"
5641
5154
  ] })
5642
5155
  ]
5643
5156
  }
5644
5157
  ),
5645
- /* @__PURE__ */ jsxs("div", { className: "rk-url", children: [
5646
- loading ? /* @__PURE__ */ jsx("span", { className: "rk-spinner", "aria-label": "Loading image" }) : /* @__PURE__ */ jsx(IconLink, { size: 13 }),
5647
- /* @__PURE__ */ jsx(
5158
+ /* @__PURE__ */ jsxs8("div", { className: "rk-url", children: [
5159
+ loading2 ? /* @__PURE__ */ jsx9("span", { className: "rk-spinner", "aria-label": "Loading image" }) : /* @__PURE__ */ jsx9(IconLink, { size: 13 }),
5160
+ /* @__PURE__ */ jsx9(
5648
5161
  "input",
5649
5162
  {
5650
5163
  className: "rk-input",
@@ -5659,9 +5172,9 @@ function ImageEditor({ editor }) {
5659
5172
  )
5660
5173
  ] })
5661
5174
  ] }),
5662
- replacement && /* @__PURE__ */ jsx("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: clearReplacement, children: "Remove replacement" }),
5663
- /* @__PURE__ */ jsx("input", { ref: fileInput, type: "file", accept: "image/*", hidden: true, onChange: onPick }),
5664
- /* @__PURE__ */ jsx(
5175
+ replacement && /* @__PURE__ */ jsx9("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: clearReplacement, children: "Remove replacement" }),
5176
+ /* @__PURE__ */ jsx9("input", { ref: fileInput, type: "file", accept: "image/*", hidden: true, onChange: onPick }),
5177
+ /* @__PURE__ */ jsx9(
5665
5178
  "textarea",
5666
5179
  {
5667
5180
  className: "rk-textarea",
@@ -5671,39 +5184,43 @@ function ImageEditor({ editor }) {
5671
5184
  rows: 2
5672
5185
  }
5673
5186
  ),
5674
- /* @__PURE__ */ jsxs("div", { className: "rk-seg", role: "group", "aria-label": "Priority", children: [
5675
- /* @__PURE__ */ jsx("button", { type: "button", "aria-pressed": priority === "must", onClick: () => setPriority("must"), children: "Must" }),
5676
- /* @__PURE__ */ jsx("button", { type: "button", "aria-pressed": priority === "nice", onClick: () => setPriority("nice"), children: "Nice to have" })
5187
+ /* @__PURE__ */ jsxs8("div", { className: "rk-seg", role: "group", "aria-label": "Priority", children: [
5188
+ /* @__PURE__ */ jsx9("button", { type: "button", "aria-pressed": priority === "must", onClick: () => setPriority("must"), children: "Must" }),
5189
+ /* @__PURE__ */ jsx9("button", { type: "button", "aria-pressed": priority === "nice", onClick: () => setPriority("nice"), children: "Nice to have" })
5677
5190
  ] })
5678
5191
  ] }),
5679
- /* @__PURE__ */ jsxs("div", { className: "rk-card-foot", children: [
5680
- /* @__PURE__ */ jsxs("span", { className: "rk-hint", children: [
5681
- /* @__PURE__ */ jsx("span", { className: "rk-kbd", children: "esc" }),
5192
+ /* @__PURE__ */ jsxs8("div", { className: "rk-card-foot", children: [
5193
+ /* @__PURE__ */ jsxs8("span", { className: "rk-hint", children: [
5194
+ /* @__PURE__ */ jsx9("span", { className: "rk-kbd", children: "esc" }),
5682
5195
  " cancel"
5683
5196
  ] }),
5684
- /* @__PURE__ */ jsxs("div", { className: "rk-actions", children: [
5685
- /* @__PURE__ */ jsx("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => dispatch({ type: "CLOSE_EDITOR" }), children: "Cancel" }),
5686
- /* @__PURE__ */ jsx("button", { type: "button", className: "rk-btn rk-btn-primary", onClick: save, children: "Save" })
5197
+ /* @__PURE__ */ jsxs8("div", { className: "rk-actions", children: [
5198
+ /* @__PURE__ */ jsx9("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => dispatch({ type: "CLOSE_EDITOR" }), children: "Cancel" }),
5199
+ /* @__PURE__ */ jsx9("button", { type: "button", className: "rk-btn rk-btn-primary", onClick: save, children: "Save" })
5687
5200
  ] })
5688
5201
  ] })
5689
5202
  ] });
5690
5203
  }
5204
+
5205
+ // src/client/components/InlineEditor.tsx
5206
+ import { useEffect as useEffect8, useRef as useRef8, useState as useState7 } from "react";
5207
+ import { Fragment as Fragment6, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
5691
5208
  function InlineEditor({ editor }) {
5692
5209
  const dispatch = useDispatch();
5693
5210
  const { route, session } = useStore();
5694
- const [after, setAfter] = useState(() => editor.element.innerText);
5695
- const [priority, setPriority] = useState("nice");
5696
- const [note, setNote] = useState("");
5697
- const original = useRef(null);
5698
- const baseline = useRef(null);
5699
- const [sizeDelta, setSizeDelta] = useState(0);
5700
- const [widthDelta, setWidthDelta] = useState(0);
5701
- const [clipped, setClipped] = useState(false);
5702
- const ringRef = useRef(null);
5703
- const afterRef = useRef(null);
5211
+ const [after, setAfter] = useState7(() => editor.element.innerText);
5212
+ const [priority, setPriority] = useState7("nice");
5213
+ const [note, setNote] = useState7("");
5214
+ const original = useRef8(null);
5215
+ const baseline = useRef8(null);
5216
+ const [sizeDelta, setSizeDelta] = useState7(0);
5217
+ const [widthDelta, setWidthDelta] = useState7(0);
5218
+ const [clipped, setClipped] = useState7(false);
5219
+ const ringRef = useRef8(null);
5220
+ const afterRef = useRef8(null);
5704
5221
  const { ref, pos } = useAnchored(editor.element, "bottom", 12);
5705
5222
  const element = editor.element;
5706
- useEffect(() => {
5223
+ useEffect8(() => {
5707
5224
  const el = element;
5708
5225
  original.current = { html: el.innerHTML, text: el.innerText, styleAttr: el.getAttribute("style") };
5709
5226
  const rect = el.getBoundingClientRect();
@@ -5733,8 +5250,8 @@ function InlineEditor({ editor }) {
5733
5250
  }
5734
5251
  };
5735
5252
  }, [element]);
5736
- const focusedOnce = useRef(false);
5737
- useEffect(() => {
5253
+ const focusedOnce = useRef8(false);
5254
+ useEffect8(() => {
5738
5255
  const field = afterRef.current;
5739
5256
  if (pos === null || focusedOnce.current || !field) return;
5740
5257
  focusedOnce.current = true;
@@ -5742,7 +5259,7 @@ function InlineEditor({ editor }) {
5742
5259
  const end = field.value.length;
5743
5260
  field.setSelectionRange(end, end);
5744
5261
  }, [pos]);
5745
- useEffect(() => {
5262
+ useEffect8(() => {
5746
5263
  const field = afterRef.current;
5747
5264
  if (!field) return;
5748
5265
  field.style.height = "auto";
@@ -5752,7 +5269,7 @@ function InlineEditor({ editor }) {
5752
5269
  setAfter(value);
5753
5270
  if (element.innerText !== value) setElementText(element, value);
5754
5271
  };
5755
- useEffect(() => {
5272
+ useEffect8(() => {
5756
5273
  let raf = 0;
5757
5274
  let lastDelta = 0;
5758
5275
  let lastWidth = 0;
@@ -5822,7 +5339,7 @@ function InlineEditor({ editor }) {
5822
5339
  cancel();
5823
5340
  }
5824
5341
  };
5825
- useEffect(() => {
5342
+ useEffect8(() => {
5826
5343
  const el = element;
5827
5344
  const onKeyDown = (event) => {
5828
5345
  if (event.key === "Enter" && !event.shiftKey) {
@@ -5840,19 +5357,19 @@ function InlineEditor({ editor }) {
5840
5357
  });
5841
5358
  const before = original.current?.text ?? "";
5842
5359
  const delta = after.length - before.length;
5843
- return /* @__PURE__ */ jsxs(Fragment, { children: [
5844
- /* @__PURE__ */ jsx("div", { ref: ringRef, className: "rk-edit-ring" }),
5845
- /* @__PURE__ */ jsxs("div", { ref, className: "rk-card", style: anchoredStyle(pos), role: "dialog", "aria-label": "Rewrite text", children: [
5846
- /* @__PURE__ */ jsxs("div", { className: "rk-card-head", children: [
5847
- /* @__PURE__ */ jsx(IconText, { size: 14 }),
5360
+ return /* @__PURE__ */ jsxs9(Fragment6, { children: [
5361
+ /* @__PURE__ */ jsx10("div", { ref: ringRef, className: "rk-edit-ring" }),
5362
+ /* @__PURE__ */ jsxs9("div", { ref, className: "rk-card", style: anchoredStyle(pos), role: "dialog", "aria-label": "Rewrite text", children: [
5363
+ /* @__PURE__ */ jsxs9("div", { className: "rk-card-head", children: [
5364
+ /* @__PURE__ */ jsx10(IconText, { size: 14 }),
5848
5365
  "Rewrite text",
5849
- /* @__PURE__ */ jsx("span", { className: "rk-spacer" }),
5850
- /* @__PURE__ */ jsxs("span", { className: "rk-delta", "aria-live": "polite", children: [
5366
+ /* @__PURE__ */ jsx10("span", { className: "rk-spacer" }),
5367
+ /* @__PURE__ */ jsxs9("span", { className: "rk-delta", "aria-live": "polite", children: [
5851
5368
  before.length,
5852
5369
  " \u2192 ",
5853
5370
  after.length,
5854
5371
  " ",
5855
- delta !== 0 && /* @__PURE__ */ jsxs("span", { className: delta > 0 ? "rk-plus" : "rk-minus", children: [
5372
+ delta !== 0 && /* @__PURE__ */ jsxs9("span", { className: delta > 0 ? "rk-plus" : "rk-minus", children: [
5856
5373
  "(",
5857
5374
  delta > 0 ? "+" : "",
5858
5375
  delta,
@@ -5860,15 +5377,15 @@ function InlineEditor({ editor }) {
5860
5377
  ] })
5861
5378
  ] })
5862
5379
  ] }),
5863
- /* @__PURE__ */ jsxs("div", { className: "rk-card-body", children: [
5864
- /* @__PURE__ */ jsxs("div", { className: "rk-diff", "aria-label": "Before and after", children: [
5865
- /* @__PURE__ */ jsxs("div", { className: "rk-diff-row rk-diff-before", children: [
5866
- /* @__PURE__ */ jsx("span", { className: "rk-diff-tag", children: "Before" }),
5867
- /* @__PURE__ */ jsx("span", { className: "rk-diff-text", children: before })
5380
+ /* @__PURE__ */ jsxs9("div", { className: "rk-card-body", children: [
5381
+ /* @__PURE__ */ jsxs9("div", { className: "rk-diff", "aria-label": "Before and after", children: [
5382
+ /* @__PURE__ */ jsxs9("div", { className: "rk-diff-row rk-diff-before", children: [
5383
+ /* @__PURE__ */ jsx10("span", { className: "rk-diff-tag", children: "Before" }),
5384
+ /* @__PURE__ */ jsx10("span", { className: "rk-diff-text", children: before })
5868
5385
  ] }),
5869
- /* @__PURE__ */ jsxs("div", { className: "rk-diff-row rk-diff-after", children: [
5870
- /* @__PURE__ */ jsx("span", { className: "rk-diff-tag", children: "After" }),
5871
- /* @__PURE__ */ jsx(
5386
+ /* @__PURE__ */ jsxs9("div", { className: "rk-diff-row rk-diff-after", children: [
5387
+ /* @__PURE__ */ jsx10("span", { className: "rk-diff-tag", children: "After" }),
5388
+ /* @__PURE__ */ jsx10(
5872
5389
  "textarea",
5873
5390
  {
5874
5391
  ref: afterRef,
@@ -5882,11 +5399,11 @@ function InlineEditor({ editor }) {
5882
5399
  )
5883
5400
  ] })
5884
5401
  ] }),
5885
- (Math.abs(sizeDelta) > 2 || Math.abs(widthDelta) > 4 || clipped) && /* @__PURE__ */ jsxs("div", { className: "rk-size-note", role: "status", children: [
5402
+ (Math.abs(sizeDelta) > 2 || Math.abs(widthDelta) > 4 || clipped) && /* @__PURE__ */ jsxs9("div", { className: "rk-size-note", role: "status", children: [
5886
5403
  Math.abs(sizeDelta) > 2 && (() => {
5887
5404
  const lines = baseline.current ? Math.round(Math.abs(sizeDelta) / baseline.current.lineHeight) : 0;
5888
5405
  const lineNote = lines >= 1 ? ` (~${lines} line${lines === 1 ? "" : "s"})` : "";
5889
- return /* @__PURE__ */ jsxs("span", { children: [
5406
+ return /* @__PURE__ */ jsxs9("span", { children: [
5890
5407
  "Block ",
5891
5408
  sizeDelta > 0 ? "grew" : "shrank",
5892
5409
  " by ",
@@ -5896,16 +5413,16 @@ function InlineEditor({ editor }) {
5896
5413
  " \u2014 as it would with this text live."
5897
5414
  ] });
5898
5415
  })(),
5899
- Math.abs(sizeDelta) <= 2 && Math.abs(widthDelta) > 4 && /* @__PURE__ */ jsxs("span", { children: [
5416
+ Math.abs(sizeDelta) <= 2 && Math.abs(widthDelta) > 4 && /* @__PURE__ */ jsxs9("span", { children: [
5900
5417
  "Block got ",
5901
5418
  widthDelta > 0 ? "wider" : "narrower",
5902
5419
  " by ",
5903
5420
  Math.abs(widthDelta),
5904
5421
  " px \u2014 as it would with this text live."
5905
5422
  ] }),
5906
- clipped && /* @__PURE__ */ jsx("span", { className: "rk-clip-note", children: "Text exceeds the visible crop here and will be cut off." })
5423
+ clipped && /* @__PURE__ */ jsx10("span", { className: "rk-clip-note", children: "Text exceeds the visible crop here and will be cut off." })
5907
5424
  ] }),
5908
- /* @__PURE__ */ jsx(
5425
+ /* @__PURE__ */ jsx10(
5909
5426
  "input",
5910
5427
  {
5911
5428
  className: "rk-input",
@@ -5914,29 +5431,33 @@ function InlineEditor({ editor }) {
5914
5431
  onChange: (event) => setNote(event.target.value)
5915
5432
  }
5916
5433
  ),
5917
- /* @__PURE__ */ jsxs("div", { className: "rk-seg", role: "group", "aria-label": "Priority", children: [
5918
- /* @__PURE__ */ jsx("button", { type: "button", "aria-pressed": priority === "must", onClick: () => setPriority("must"), children: "Must" }),
5919
- /* @__PURE__ */ jsx("button", { type: "button", "aria-pressed": priority === "nice", onClick: () => setPriority("nice"), children: "Nice to have" })
5434
+ /* @__PURE__ */ jsxs9("div", { className: "rk-seg", role: "group", "aria-label": "Priority", children: [
5435
+ /* @__PURE__ */ jsx10("button", { type: "button", "aria-pressed": priority === "must", onClick: () => setPriority("must"), children: "Must" }),
5436
+ /* @__PURE__ */ jsx10("button", { type: "button", "aria-pressed": priority === "nice", onClick: () => setPriority("nice"), children: "Nice to have" })
5920
5437
  ] })
5921
5438
  ] }),
5922
- /* @__PURE__ */ jsxs("div", { className: "rk-card-foot", children: [
5923
- /* @__PURE__ */ jsxs("span", { className: "rk-hint", children: [
5924
- /* @__PURE__ */ jsx("span", { className: "rk-kbd", children: "\u21B5" }),
5439
+ /* @__PURE__ */ jsxs9("div", { className: "rk-card-foot", children: [
5440
+ /* @__PURE__ */ jsxs9("span", { className: "rk-hint", children: [
5441
+ /* @__PURE__ */ jsx10("span", { className: "rk-kbd", children: "\u21B5" }),
5925
5442
  " save \xB7 ",
5926
- /* @__PURE__ */ jsx("span", { className: "rk-kbd", children: "\u21E7\u21B5" }),
5443
+ /* @__PURE__ */ jsx10("span", { className: "rk-kbd", children: "\u21E7\u21B5" }),
5927
5444
  " newline \xB7",
5928
5445
  " ",
5929
- /* @__PURE__ */ jsx("span", { className: "rk-kbd", children: "esc" }),
5446
+ /* @__PURE__ */ jsx10("span", { className: "rk-kbd", children: "esc" }),
5930
5447
  " cancel"
5931
5448
  ] }),
5932
- /* @__PURE__ */ jsxs("div", { className: "rk-actions", children: [
5933
- /* @__PURE__ */ jsx("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: cancel, children: "Cancel" }),
5934
- /* @__PURE__ */ jsx("button", { type: "button", className: "rk-btn rk-btn-primary", onClick: commit, children: "Save" })
5449
+ /* @__PURE__ */ jsxs9("div", { className: "rk-actions", children: [
5450
+ /* @__PURE__ */ jsx10("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: cancel, children: "Cancel" }),
5451
+ /* @__PURE__ */ jsx10("button", { type: "button", className: "rk-btn rk-btn-primary", onClick: commit, children: "Save" })
5935
5452
  ] })
5936
5453
  ] })
5937
5454
  ] })
5938
5455
  ] });
5939
5456
  }
5457
+
5458
+ // src/client/components/NoteComposer.tsx
5459
+ import { useEffect as useEffect9, useRef as useRef9, useState as useState8 } from "react";
5460
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
5940
5461
  var TITLES = {
5941
5462
  comment: "Comment",
5942
5463
  delete: "Mark for deletion",
@@ -5952,14 +5473,14 @@ var PLACEHOLDERS = {
5952
5473
  function NoteComposer({ editor }) {
5953
5474
  const dispatch = useDispatch();
5954
5475
  const { route, session } = useStore();
5955
- const [text, setText] = useState("");
5956
- const [direction, setDirection] = useState(void 0);
5957
- const [priority, setPriority] = useState("nice");
5958
- const textareaRef = useRef(null);
5476
+ const [text, setText] = useState8("");
5477
+ const [direction, setDirection] = useState8(void 0);
5478
+ const [priority, setPriority] = useState8("nice");
5479
+ const textareaRef = useRef9(null);
5959
5480
  const { ref, pos } = useAnchored(editor.element, "bottom", 12);
5960
5481
  const { kind } = editor;
5961
- const focusedOnce = useRef(false);
5962
- useEffect(() => {
5482
+ const focusedOnce = useRef9(false);
5483
+ useEffect9(() => {
5963
5484
  const field = textareaRef.current;
5964
5485
  if (pos === null || focusedOnce.current || !field) return;
5965
5486
  focusedOnce.current = true;
@@ -6018,13 +5539,13 @@ function NoteComposer({ editor }) {
6018
5539
  save();
6019
5540
  }
6020
5541
  };
6021
- return /* @__PURE__ */ jsxs("div", { ref, className: "rk-card", style: anchoredStyle(pos), role: "dialog", "aria-label": TITLES[kind], children: [
6022
- /* @__PURE__ */ jsxs("div", { className: "rk-card-head", children: [
5542
+ return /* @__PURE__ */ jsxs10("div", { ref, className: "rk-card", style: anchoredStyle(pos), role: "dialog", "aria-label": TITLES[kind], children: [
5543
+ /* @__PURE__ */ jsxs10("div", { className: "rk-card-head", children: [
6023
5544
  iconForType(kind),
6024
5545
  TITLES[kind]
6025
5546
  ] }),
6026
- /* @__PURE__ */ jsxs("div", { className: "rk-card-body", children: [
6027
- kind === "move" && /* @__PURE__ */ jsx("div", { className: "rk-seg", role: "group", "aria-label": "Direction", children: ["up", "down", "first", "last"].map((option) => /* @__PURE__ */ jsx(
5547
+ /* @__PURE__ */ jsxs10("div", { className: "rk-card-body", children: [
5548
+ kind === "move" && /* @__PURE__ */ jsx11("div", { className: "rk-seg", role: "group", "aria-label": "Direction", children: ["up", "down", "first", "last"].map((option) => /* @__PURE__ */ jsx11(
6028
5549
  "button",
6029
5550
  {
6030
5551
  type: "button",
@@ -6034,7 +5555,7 @@ function NoteComposer({ editor }) {
6034
5555
  },
6035
5556
  option
6036
5557
  )) }),
6037
- /* @__PURE__ */ jsx(
5558
+ /* @__PURE__ */ jsx11(
6038
5559
  "textarea",
6039
5560
  {
6040
5561
  ref: textareaRef,
@@ -6046,25 +5567,29 @@ function NoteComposer({ editor }) {
6046
5567
  rows: 3
6047
5568
  }
6048
5569
  ),
6049
- /* @__PURE__ */ jsxs("div", { className: "rk-seg", role: "group", "aria-label": "Priority", children: [
6050
- /* @__PURE__ */ jsx("button", { type: "button", "aria-pressed": priority === "must", onClick: () => setPriority("must"), children: "Must" }),
6051
- /* @__PURE__ */ jsx("button", { type: "button", "aria-pressed": priority === "nice", onClick: () => setPriority("nice"), children: "Nice to have" })
5570
+ /* @__PURE__ */ jsxs10("div", { className: "rk-seg", role: "group", "aria-label": "Priority", children: [
5571
+ /* @__PURE__ */ jsx11("button", { type: "button", "aria-pressed": priority === "must", onClick: () => setPriority("must"), children: "Must" }),
5572
+ /* @__PURE__ */ jsx11("button", { type: "button", "aria-pressed": priority === "nice", onClick: () => setPriority("nice"), children: "Nice to have" })
6052
5573
  ] })
6053
5574
  ] }),
6054
- /* @__PURE__ */ jsxs("div", { className: "rk-card-foot", children: [
6055
- /* @__PURE__ */ jsxs("span", { className: "rk-hint", children: [
6056
- /* @__PURE__ */ jsx("span", { className: "rk-kbd", children: "\u2318\u21B5" }),
5575
+ /* @__PURE__ */ jsxs10("div", { className: "rk-card-foot", children: [
5576
+ /* @__PURE__ */ jsxs10("span", { className: "rk-hint", children: [
5577
+ /* @__PURE__ */ jsx11("span", { className: "rk-kbd", children: "\u2318\u21B5" }),
6057
5578
  " save \xB7 ",
6058
- /* @__PURE__ */ jsx("span", { className: "rk-kbd", children: "esc" }),
5579
+ /* @__PURE__ */ jsx11("span", { className: "rk-kbd", children: "esc" }),
6059
5580
  " cancel"
6060
5581
  ] }),
6061
- /* @__PURE__ */ jsxs("div", { className: "rk-actions", children: [
6062
- /* @__PURE__ */ jsx("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => dispatch({ type: "CLOSE_EDITOR" }), children: "Cancel" }),
6063
- /* @__PURE__ */ jsx("button", { type: "button", className: "rk-btn rk-btn-primary", onClick: save, children: "Save" })
5582
+ /* @__PURE__ */ jsxs10("div", { className: "rk-actions", children: [
5583
+ /* @__PURE__ */ jsx11("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => dispatch({ type: "CLOSE_EDITOR" }), children: "Cancel" }),
5584
+ /* @__PURE__ */ jsx11("button", { type: "button", className: "rk-btn rk-btn-primary", onClick: save, children: "Save" })
6064
5585
  ] })
6065
5586
  ] })
6066
5587
  ] });
6067
5588
  }
5589
+
5590
+ // src/client/components/Panel.tsx
5591
+ import { useEffect as useEffect10, useMemo as useMemo3, useRef as useRef10, useState as useState9 } from "react";
5592
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
6068
5593
  function changeSummary(item) {
6069
5594
  if (isCopyItem(item)) return `\u201C${item.payload.after}\u201D`;
6070
5595
  if (isImageItem(item)) {
@@ -6146,24 +5671,24 @@ function formatBytes(bytes) {
6146
5671
  function CopyDiff({ before, after }) {
6147
5672
  const parts = diffParts(before, after);
6148
5673
  const delta = after.length - before.length;
6149
- return /* @__PURE__ */ jsxs("div", { className: "rk-diffview", children: [
6150
- /* @__PURE__ */ jsxs("div", { className: "rk-diff-row rk-diff-before", children: [
6151
- /* @__PURE__ */ jsx("span", { className: "rk-diff-tag", children: "Before" }),
6152
- /* @__PURE__ */ jsxs("span", { className: "rk-diff-text", children: [
5674
+ return /* @__PURE__ */ jsxs11("div", { className: "rk-diffview", children: [
5675
+ /* @__PURE__ */ jsxs11("div", { className: "rk-diff-row rk-diff-before", children: [
5676
+ /* @__PURE__ */ jsx12("span", { className: "rk-diff-tag", children: "Before" }),
5677
+ /* @__PURE__ */ jsxs11("span", { className: "rk-diff-text", children: [
6153
5678
  parts.prefix,
6154
- parts.delMid && /* @__PURE__ */ jsx("mark", { className: "rk-del", children: parts.delMid }),
5679
+ parts.delMid && /* @__PURE__ */ jsx12("mark", { className: "rk-del", children: parts.delMid }),
6155
5680
  parts.suffix
6156
5681
  ] })
6157
5682
  ] }),
6158
- /* @__PURE__ */ jsxs("div", { className: "rk-diff-row rk-diff-after", children: [
6159
- /* @__PURE__ */ jsx("span", { className: "rk-diff-tag", children: "After" }),
6160
- /* @__PURE__ */ jsxs("span", { className: "rk-diff-text", children: [
5683
+ /* @__PURE__ */ jsxs11("div", { className: "rk-diff-row rk-diff-after", children: [
5684
+ /* @__PURE__ */ jsx12("span", { className: "rk-diff-tag", children: "After" }),
5685
+ /* @__PURE__ */ jsxs11("span", { className: "rk-diff-text", children: [
6161
5686
  parts.prefix,
6162
- parts.insMid && /* @__PURE__ */ jsx("mark", { className: "rk-ins", children: parts.insMid }),
5687
+ parts.insMid && /* @__PURE__ */ jsx12("mark", { className: "rk-ins", children: parts.insMid }),
6163
5688
  parts.suffix
6164
5689
  ] })
6165
5690
  ] }),
6166
- /* @__PURE__ */ jsxs("div", { className: "rk-diff-len", children: [
5691
+ /* @__PURE__ */ jsxs11("div", { className: "rk-diff-len", children: [
6167
5692
  before.length,
6168
5693
  " \u2192 ",
6169
5694
  after.length,
@@ -6174,8 +5699,8 @@ function CopyDiff({ before, after }) {
6174
5699
  }
6175
5700
  function ImageDetail({ beforeSrc, afterSrc, afterDataUrl }) {
6176
5701
  const replacement = afterDataUrl ?? afterSrc;
6177
- const [dims, setDims] = useState("");
6178
- useEffect(() => {
5702
+ const [dims, setDims] = useState9("");
5703
+ useEffect10(() => {
6179
5704
  if (!replacement) return;
6180
5705
  const img = new Image();
6181
5706
  img.onload = () => setDims(`${img.naturalWidth}\xD7${img.naturalHeight}px`);
@@ -6200,55 +5725,67 @@ function ImageDetail({ beforeSrc, afterSrc, afterDataUrl }) {
6200
5725
  window.open(replacement, "_blank", "noopener");
6201
5726
  }
6202
5727
  };
6203
- return /* @__PURE__ */ jsxs("div", { className: "rk-imgdetail", children: [
6204
- /* @__PURE__ */ jsxs("div", { className: "rk-thumbs", children: [
6205
- /* @__PURE__ */ jsxs("figure", { className: "rk-thumb", children: [
6206
- beforeSrc ? /* @__PURE__ */ jsx("img", { src: beforeSrc, alt: "Current" }) : /* @__PURE__ */ jsx("div", { className: "rk-thumb-missing", children: "no image" }),
6207
- /* @__PURE__ */ jsx("figcaption", { children: "Before" })
5728
+ return /* @__PURE__ */ jsxs11("div", { className: "rk-imgdetail", children: [
5729
+ /* @__PURE__ */ jsxs11("div", { className: "rk-thumbs", children: [
5730
+ /* @__PURE__ */ jsxs11("figure", { className: "rk-thumb", children: [
5731
+ beforeSrc ? /* @__PURE__ */ jsx12("img", { src: beforeSrc, alt: "Current" }) : /* @__PURE__ */ jsx12("div", { className: "rk-thumb-missing", children: "no image" }),
5732
+ /* @__PURE__ */ jsx12("figcaption", { children: "Before" })
6208
5733
  ] }),
6209
- /* @__PURE__ */ jsxs("figure", { className: "rk-thumb", children: [
6210
- /* @__PURE__ */ jsx("img", { src: replacement, alt: "Proposed replacement" }),
6211
- /* @__PURE__ */ jsxs("figcaption", { children: [
5734
+ /* @__PURE__ */ jsxs11("figure", { className: "rk-thumb", children: [
5735
+ /* @__PURE__ */ jsx12("img", { src: replacement, alt: "Proposed replacement" }),
5736
+ /* @__PURE__ */ jsxs11("figcaption", { children: [
6212
5737
  "After",
6213
5738
  embedded ? "" : " \xB7 linked"
6214
5739
  ] })
6215
5740
  ] })
6216
5741
  ] }),
6217
- /* @__PURE__ */ jsxs("div", { className: "rk-imgdetail-meta", children: [
5742
+ /* @__PURE__ */ jsxs11("div", { className: "rk-imgdetail-meta", children: [
6218
5743
  embedded ? "Embedded image" : "Linked image",
6219
5744
  dims ? ` \xB7 ${dims}` : "",
6220
5745
  bytes !== null ? ` \xB7 ${formatBytes(bytes)}` : ""
6221
5746
  ] }),
6222
- !embedded && afterSrc && /* @__PURE__ */ jsx("a", { className: "rk-imgdetail-url", href: afterSrc, target: "_blank", rel: "noopener noreferrer", title: afterSrc, children: afterSrc }),
6223
- /* @__PURE__ */ jsxs("div", { className: "rk-imgdetail-actions", children: [
6224
- /* @__PURE__ */ jsxs("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => void download(), children: [
6225
- /* @__PURE__ */ jsx(IconDownload, { size: 12 }),
5747
+ !embedded && afterSrc && /* @__PURE__ */ jsx12("a", { className: "rk-imgdetail-url", href: afterSrc, target: "_blank", rel: "noopener noreferrer", title: afterSrc, children: afterSrc }),
5748
+ /* @__PURE__ */ jsxs11("div", { className: "rk-imgdetail-actions", children: [
5749
+ /* @__PURE__ */ jsxs11("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => void download(), children: [
5750
+ /* @__PURE__ */ jsx12(IconDownload, { size: 12 }),
6226
5751
  " Download"
6227
5752
  ] }),
6228
- /* @__PURE__ */ jsx("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => window.open(replacement, "_blank", "noopener"), children: "Open" })
5753
+ /* @__PURE__ */ jsx12("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => window.open(replacement, "_blank", "noopener"), children: "Open" })
6229
5754
  ] })
6230
5755
  ] });
6231
5756
  }
5757
+ var SYNC_REASON_LABEL = {
5758
+ "token-short": "token too short",
5759
+ "bad-token": "token rejected",
5760
+ "room-full": "room full",
5761
+ unreachable: "server unreachable"
5762
+ };
5763
+ var SYNC_REASON_HELP = {
5764
+ "token-short": "The sync server needs a token of at least 8 characters. Use a longer `token` (or `syncToken`) in <ReviewKit /> and everyone with the link shares items live.",
5765
+ "bad-token": "The sync server rejected the token. Check `syncToken` / `token` in <ReviewKit />.",
5766
+ "room-full": "This page already has the maximum number of reviewers online (16). Try again later.",
5767
+ unreachable: "Could not reach the sync server. Usually the page's Content-Security-Policy is missing the server in connect-src, or the server is down. Items stay on this device meanwhile."
5768
+ };
6232
5769
  function Panel() {
6233
- const { session, panelOpen, activeItemId, badgeCorner, undoStack, preview, unapplied, syncStatus, emblemaPush, peers } = useStore();
5770
+ const { session, panelOpen, activeItemId, badgeCorner, undoStack, preview, unapplied, syncStatus, syncReason, emblemaPush, peers } = useStore();
6234
5771
  const dispatch = useDispatch();
6235
5772
  const config = useConfig();
6236
- const [sticky, setStickyState] = useState(() => isStickyEnabled(config.token));
6237
- const [query, setQuery] = useState("");
6238
- const [typeFilter, setTypeFilter] = useState(/* @__PURE__ */ new Set());
6239
- const [prioFilter, setPrioFilter] = useState(/* @__PURE__ */ new Set());
6240
- const [statusFilter, setStatusFilter] = useState(/* @__PURE__ */ new Set());
6241
- const [filterOpen, setFilterOpen] = useState(false);
6242
- const [settingsOpen, setSettingsOpen] = useState(false);
6243
- const [emblemaAlive, setEmblemaAlive] = useState(null);
6244
- const [emblemaSending, setEmblemaSending] = useState(false);
6245
- const [emblemaSync, setEmblemaSync] = useState("unknown");
6246
- const panelRef = useRef(null);
6247
- const searchRef = useRef(null);
6248
- const importRef = useRef(null);
6249
- const sheetDrag = useRef(null);
6250
- const items = useMemo(() => session?.items ?? [], [session]);
6251
- const { orphanIds, hiddenIds } = useMemo(() => {
5773
+ const [sticky, setStickyState] = useState9(() => isStickyEnabled(config.token));
5774
+ const [query, setQuery] = useState9("");
5775
+ const [typeFilter, setTypeFilter] = useState9(/* @__PURE__ */ new Set());
5776
+ const [prioFilter, setPrioFilter] = useState9(/* @__PURE__ */ new Set());
5777
+ const [statusFilter, setStatusFilter] = useState9(/* @__PURE__ */ new Set());
5778
+ const [filterOpen, setFilterOpen] = useState9(false);
5779
+ const [settingsOpen, setSettingsOpen] = useState9(false);
5780
+ const [emblemaAlive, setEmblemaAlive] = useState9(null);
5781
+ const [emblemaSending, setEmblemaSending] = useState9(false);
5782
+ const [emblemaSync, setEmblemaSync] = useState9("unknown");
5783
+ const panelRef = useRef10(null);
5784
+ const searchRef = useRef10(null);
5785
+ const importRef = useRef10(null);
5786
+ const sheetDrag = useRef10(null);
5787
+ const items = useMemo3(() => session?.items ?? [], [session]);
5788
+ const { orphanIds, hiddenIds } = useMemo3(() => {
6252
5789
  const orphans = /* @__PURE__ */ new Set();
6253
5790
  const hiddens = /* @__PURE__ */ new Set();
6254
5791
  for (const item of items) {
@@ -6259,15 +5796,15 @@ function Panel() {
6259
5796
  }
6260
5797
  return { orphanIds: orphans, hiddenIds: hiddens };
6261
5798
  }, [items, panelOpen]);
6262
- const activeFilter = useMemo(
5799
+ const activeFilter = useMemo3(
6263
5800
  () => typeFilter.size + prioFilter.size + statusFilter.size > 0 ? { types: [...typeFilter], priorities: [...prioFilter], statuses: [...statusFilter] } : null,
6264
5801
  [typeFilter, prioFilter, statusFilter]
6265
5802
  );
6266
- useEffect(() => {
5803
+ useEffect10(() => {
6267
5804
  dispatch({ type: "SET_FILTER", filter: activeFilter });
6268
5805
  return () => dispatch({ type: "SET_FILTER", filter: null });
6269
5806
  }, [activeFilter, dispatch]);
6270
- const filtered = useMemo(() => {
5807
+ const filtered = useMemo3(() => {
6271
5808
  const q = query.trim().toLowerCase();
6272
5809
  return items.map((item, index) => ({ item, number: index + 1 })).filter(({ item }) => {
6273
5810
  if (!itemMatchesFilter(item, activeFilter)) return false;
@@ -6276,22 +5813,22 @@ function Panel() {
6276
5813
  return haystack.includes(q);
6277
5814
  });
6278
5815
  }, [items, query, activeFilter]);
6279
- const typeCounts = useMemo(() => {
5816
+ const typeCounts = useMemo3(() => {
6280
5817
  const counts = /* @__PURE__ */ new Map();
6281
5818
  for (const item of items) counts.set(item.type, (counts.get(item.type) ?? 0) + 1);
6282
5819
  return counts;
6283
5820
  }, [items]);
6284
- const prioCounts = useMemo(() => {
5821
+ const prioCounts = useMemo3(() => {
6285
5822
  const counts = /* @__PURE__ */ new Map();
6286
5823
  for (const item of items) counts.set(item.priority, (counts.get(item.priority) ?? 0) + 1);
6287
5824
  return counts;
6288
5825
  }, [items]);
6289
- const statusCounts = useMemo(() => {
5826
+ const statusCounts = useMemo3(() => {
6290
5827
  const counts = /* @__PURE__ */ new Map();
6291
5828
  for (const item of items) counts.set(item.status, (counts.get(item.status) ?? 0) + 1);
6292
5829
  return counts;
6293
5830
  }, [items]);
6294
- const groups = useMemo(() => {
5831
+ const groups = useMemo3(() => {
6295
5832
  const map = /* @__PURE__ */ new Map();
6296
5833
  for (const entry of filtered) {
6297
5834
  const list = map.get(entry.item.route) ?? [];
@@ -6300,7 +5837,7 @@ function Panel() {
6300
5837
  }
6301
5838
  return Array.from(map.entries());
6302
5839
  }, [filtered]);
6303
- useEffect(() => {
5840
+ useEffect10(() => {
6304
5841
  if (!filterOpen && !settingsOpen) return;
6305
5842
  const onDown = (event) => {
6306
5843
  const path = event.composedPath();
@@ -6311,7 +5848,7 @@ function Panel() {
6311
5848
  document.addEventListener("pointerdown", onDown, true);
6312
5849
  return () => document.removeEventListener("pointerdown", onDown, true);
6313
5850
  }, [filterOpen, settingsOpen]);
6314
- useEffect(() => {
5851
+ useEffect10(() => {
6315
5852
  if (!settingsOpen) return;
6316
5853
  let alive = true;
6317
5854
  const probe = () => {
@@ -6335,7 +5872,7 @@ function Panel() {
6335
5872
  live: "paired",
6336
5873
  off: "not paired"
6337
5874
  }[emblemaPairState];
6338
- useEffect(() => {
5875
+ useEffect10(() => {
6339
5876
  if (!panelOpen || activeItemId === null) return;
6340
5877
  const row = panelRef.current?.querySelector(`[data-item-id="${activeItemId}"]`);
6341
5878
  row?.scrollIntoView({ block: "nearest" });
@@ -6366,11 +5903,11 @@ function Panel() {
6366
5903
  if (focusable.length === 0) return;
6367
5904
  const first = focusable[0];
6368
5905
  const last = focusable[focusable.length - 1];
6369
- const active2 = panel.getRootNode() instanceof ShadowRoot ? panel.getRootNode().activeElement : document.activeElement;
6370
- if (event.shiftKey && active2 === first) {
5906
+ const active = panel.getRootNode() instanceof ShadowRoot ? panel.getRootNode().activeElement : document.activeElement;
5907
+ if (event.shiftKey && active === first) {
6371
5908
  event.preventDefault();
6372
5909
  last.focus();
6373
- } else if (!event.shiftKey && active2 === last) {
5910
+ } else if (!event.shiftKey && active === last) {
6374
5911
  event.preventDefault();
6375
5912
  first.focus();
6376
5913
  }
@@ -6451,7 +5988,7 @@ function Panel() {
6451
5988
  panel.style.transform = "";
6452
5989
  if (dy > 110) dispatch({ type: "SET_PANEL", open: false });
6453
5990
  };
6454
- return /* @__PURE__ */ jsxs(
5991
+ return /* @__PURE__ */ jsxs11(
6455
5992
  "div",
6456
5993
  {
6457
5994
  ref: panelRef,
@@ -6461,7 +5998,7 @@ function Panel() {
6461
5998
  "aria-label": "Review feedback panel",
6462
5999
  onKeyDown,
6463
6000
  children: [
6464
- /* @__PURE__ */ jsx(
6001
+ /* @__PURE__ */ jsx12(
6465
6002
  "div",
6466
6003
  {
6467
6004
  className: "rk-sheet-handle",
@@ -6472,34 +6009,41 @@ function Panel() {
6472
6009
  onPointerCancel: onSheetHandleUp
6473
6010
  }
6474
6011
  ),
6475
- /* @__PURE__ */ jsxs("div", { className: "rk-panel-head", children: [
6476
- /* @__PURE__ */ jsxs("div", { children: [
6477
- /* @__PURE__ */ jsx("div", { className: "rk-panel-title", children: "Feedback" }),
6478
- /* @__PURE__ */ jsxs("div", { className: "rk-panel-sub", children: [
6012
+ /* @__PURE__ */ jsxs11("div", { className: "rk-panel-head", children: [
6013
+ /* @__PURE__ */ jsxs11("div", { children: [
6014
+ /* @__PURE__ */ jsx12("div", { className: "rk-panel-title", children: "Feedback" }),
6015
+ /* @__PURE__ */ jsxs11("div", { className: "rk-panel-sub", children: [
6479
6016
  items.length,
6480
6017
  " item",
6481
6018
  items.length === 1 ? "" : "s",
6482
6019
  session.author ? ` \xB7 ${session.author}` : "",
6483
- syncStatus !== "disabled" && /* @__PURE__ */ jsxs("span", { className: `rk-sync rk-sync-${syncStatus}`, children: [
6484
- /* @__PURE__ */ jsx("span", { className: "rk-sync-dot" }),
6485
- syncStatus === "online" ? `live${peers.length > 0 ? ` \xB7 ${peers.length} other${peers.length === 1 ? "" : "s"}` : ""}` : syncStatus === "connecting" ? "connecting\u2026" : "local only"
6486
- ] }),
6487
- config.emblema && emblemaPush !== "idle" && /* @__PURE__ */ jsxs(
6020
+ syncStatus !== "disabled" && /* @__PURE__ */ jsxs11(
6021
+ "span",
6022
+ {
6023
+ className: `rk-sync rk-sync-${syncStatus}${syncReason ? ` rk-sync-reason-${syncReason}` : ""}`,
6024
+ title: syncStatus === "online" ? "Live: everyone with this review link shares these items in real time" : syncStatus === "connecting" ? "Connecting to the sync server\u2026" : SYNC_REASON_HELP[syncReason ?? "unreachable"],
6025
+ children: [
6026
+ /* @__PURE__ */ jsx12("span", { className: "rk-sync-dot" }),
6027
+ syncStatus === "online" ? `live${peers.length > 0 ? ` \xB7 ${peers.length} other${peers.length === 1 ? "" : "s"}` : ""}` : syncStatus === "connecting" ? "connecting\u2026" : `local only \xB7 ${SYNC_REASON_LABEL[syncReason ?? "unreachable"]}`
6028
+ ]
6029
+ }
6030
+ ),
6031
+ config.emblema && emblemaPush !== "idle" && /* @__PURE__ */ jsxs11(
6488
6032
  "span",
6489
6033
  {
6490
6034
  className: `rk-sync rk-epush-${emblemaPush}`,
6491
6035
  title: emblemaPush === "ok" ? "Review items stream to Emblema automatically" : emblemaPush === "sending" ? "Sending items to Emblema\u2026" : "Emblema isn't reachable \u2014 items resend on the next change, or use Resend all in the menu",
6492
6036
  children: [
6493
- /* @__PURE__ */ jsx("span", { className: "rk-sync-dot" }),
6494
- emblemaPush === "ok" ? "emblema" : emblemaPush === "sending" ? "emblema\u2026" : "emblema offline"
6037
+ /* @__PURE__ */ jsx12("span", { className: "rk-sync-dot" }),
6038
+ emblemaPush === "ok" ? "emblema" : emblemaPush === "sending" ? "emblema\u2026" : emblemaPush === "rejected" ? "emblema rejected" : "emblema offline"
6495
6039
  ]
6496
6040
  }
6497
6041
  )
6498
6042
  ] })
6499
6043
  ] }),
6500
- /* @__PURE__ */ jsx("span", { className: "rk-spacer" }),
6501
- /* @__PURE__ */ jsxs("div", { className: "rk-menu-scope rk-menu-anchor", children: [
6502
- /* @__PURE__ */ jsx(
6044
+ /* @__PURE__ */ jsx12("span", { className: "rk-spacer" }),
6045
+ /* @__PURE__ */ jsxs11("div", { className: "rk-menu-scope rk-menu-anchor", children: [
6046
+ /* @__PURE__ */ jsx12(
6503
6047
  "button",
6504
6048
  {
6505
6049
  type: "button",
@@ -6515,11 +6059,11 @@ function Panel() {
6515
6059
  void pingEmblema(config.emblema).then((port) => setEmblemaAlive(port !== null));
6516
6060
  }
6517
6061
  },
6518
- children: /* @__PURE__ */ jsx(IconGear, { size: 15 })
6062
+ children: /* @__PURE__ */ jsx12(IconGear, { size: 15 })
6519
6063
  }
6520
6064
  ),
6521
- settingsOpen && /* @__PURE__ */ jsxs("div", { className: "rk-menu", role: "menu", "aria-label": "Session settings", children: [
6522
- /* @__PURE__ */ jsxs(
6065
+ settingsOpen && /* @__PURE__ */ jsxs11("div", { className: "rk-menu", role: "menu", "aria-label": "Session settings", children: [
6066
+ config.token && /* @__PURE__ */ jsxs11(
6523
6067
  "button",
6524
6068
  {
6525
6069
  type: "button",
@@ -6534,12 +6078,12 @@ function Panel() {
6534
6078
  toast(dispatch, next ? "Review stays on in this tab" : "Review active only via the link", "info");
6535
6079
  },
6536
6080
  children: [
6537
- /* @__PURE__ */ jsx("span", { className: `rk-menu-check${sticky ? " rk-on" : ""}` }),
6081
+ /* @__PURE__ */ jsx12("span", { className: `rk-menu-check${sticky ? " rk-on" : ""}` }),
6538
6082
  "Keep review on in this tab"
6539
6083
  ]
6540
6084
  }
6541
6085
  ),
6542
- config.emblema && /* @__PURE__ */ jsxs(
6086
+ config.emblema && /* @__PURE__ */ jsxs11(
6543
6087
  "button",
6544
6088
  {
6545
6089
  type: "button",
@@ -6569,7 +6113,7 @@ function Panel() {
6569
6113
  }).finally(() => setEmblemaSending(false));
6570
6114
  },
6571
6115
  children: [
6572
- /* @__PURE__ */ jsx(
6116
+ /* @__PURE__ */ jsx12(
6573
6117
  "span",
6574
6118
  {
6575
6119
  className: "rk-emblema-dot",
@@ -6580,25 +6124,40 @@ function Panel() {
6580
6124
  ]
6581
6125
  }
6582
6126
  ),
6583
- /* @__PURE__ */ jsxs(
6127
+ /* @__PURE__ */ jsxs11(
6584
6128
  "button",
6585
6129
  {
6586
6130
  type: "button",
6587
6131
  role: "menuitem",
6588
6132
  className: "rk-menu-item",
6589
- title: emblemaPairState === "stale" ? "The local sync agent is not answering \u2014 run `npx reviewkit-emblema serve` in the project" : "Pair this project with the Emblema app via a sync key",
6133
+ title: emblemaPairState === "stale" ? "The local sync agent is not answering \u2014 run `npx -p @satanwagen/reviewkit-cli reviewkit-emblema serve` in the project" : "Pair this project with the Emblema app via a sync key",
6590
6134
  onClick: () => {
6591
6135
  setSettingsOpen(false);
6592
6136
  dispatch({ type: "SET_EMBLEMA_CONNECT", open: true });
6593
6137
  },
6594
6138
  children: [
6595
- /* @__PURE__ */ jsx("span", { className: "rk-emblema-dot", "data-state": emblemaPairState }),
6139
+ /* @__PURE__ */ jsx12("span", { className: "rk-emblema-dot", "data-state": emblemaPairState }),
6596
6140
  "Connect to Emblema\u2026",
6597
- /* @__PURE__ */ jsx("span", { className: "rk-menu-count", children: emblemaPairLabel })
6141
+ /* @__PURE__ */ jsx12("span", { className: "rk-menu-count", children: emblemaPairLabel })
6598
6142
  ]
6599
6143
  }
6600
6144
  ),
6601
- /* @__PURE__ */ jsx(
6145
+ /* @__PURE__ */ jsx12(
6146
+ "button",
6147
+ {
6148
+ type: "button",
6149
+ role: "menuitem",
6150
+ className: "rk-menu-item",
6151
+ title: resolveSyncToken(config) ? "Pair the reviewkit terminal CLI / Claude Code MCP bridge with this site" : "Needs live sync (sync is off on this page) \u2014 the terminal reads the shared room",
6152
+ disabled: !resolveSyncToken(config),
6153
+ onClick: () => {
6154
+ setSettingsOpen(false);
6155
+ dispatch({ type: "SET_TERMINAL_CONNECT", open: true });
6156
+ },
6157
+ children: "Connect terminal / agent\u2026"
6158
+ }
6159
+ ),
6160
+ /* @__PURE__ */ jsx12(
6602
6161
  "button",
6603
6162
  {
6604
6163
  type: "button",
@@ -6611,31 +6170,31 @@ function Panel() {
6611
6170
  )
6612
6171
  ] })
6613
6172
  ] }),
6614
- /* @__PURE__ */ jsx(
6173
+ /* @__PURE__ */ jsx12(
6615
6174
  "button",
6616
6175
  {
6617
6176
  type: "button",
6618
6177
  className: "rk-icon-btn",
6619
6178
  "aria-label": "Keyboard shortcuts",
6620
6179
  onClick: () => dispatch({ type: "SET_CHEATSHEET", open: true }),
6621
- children: /* @__PURE__ */ jsx(IconKeyboard, { size: 15 })
6180
+ children: /* @__PURE__ */ jsx12(IconKeyboard, { size: 15 })
6622
6181
  }
6623
6182
  ),
6624
- /* @__PURE__ */ jsx(
6183
+ /* @__PURE__ */ jsx12(
6625
6184
  "button",
6626
6185
  {
6627
6186
  type: "button",
6628
6187
  className: "rk-icon-btn",
6629
6188
  "aria-label": "Close panel",
6630
6189
  onClick: () => dispatch({ type: "SET_PANEL", open: false }),
6631
- children: /* @__PURE__ */ jsx(IconClose, { size: 14 })
6190
+ children: /* @__PURE__ */ jsx12(IconClose, { size: 14 })
6632
6191
  }
6633
6192
  )
6634
6193
  ] }),
6635
- /* @__PURE__ */ jsxs("div", { className: "rk-view-row", children: [
6636
- /* @__PURE__ */ jsx("div", { className: "rk-view-info", children: /* @__PURE__ */ jsx("div", { className: "rk-view-title", children: "Page view" }) }),
6637
- /* @__PURE__ */ jsxs("div", { className: "rk-seg rk-view-toggle", role: "group", "aria-label": "Page view (P to toggle)", children: [
6638
- /* @__PURE__ */ jsx(
6194
+ /* @__PURE__ */ jsxs11("div", { className: "rk-view-row", children: [
6195
+ /* @__PURE__ */ jsx12("div", { className: "rk-view-info", children: /* @__PURE__ */ jsx12("div", { className: "rk-view-title", children: "Page view" }) }),
6196
+ /* @__PURE__ */ jsxs11("div", { className: "rk-seg rk-view-toggle", role: "group", "aria-label": "Page view (P to toggle)", children: [
6197
+ /* @__PURE__ */ jsx12(
6639
6198
  "button",
6640
6199
  {
6641
6200
  type: "button",
@@ -6645,7 +6204,7 @@ function Panel() {
6645
6204
  children: "Original"
6646
6205
  }
6647
6206
  ),
6648
- /* @__PURE__ */ jsx(
6207
+ /* @__PURE__ */ jsx12(
6649
6208
  "button",
6650
6209
  {
6651
6210
  type: "button",
@@ -6657,8 +6216,8 @@ function Panel() {
6657
6216
  )
6658
6217
  ] })
6659
6218
  ] }),
6660
- /* @__PURE__ */ jsxs("div", { className: "rk-panel-tools", children: [
6661
- /* @__PURE__ */ jsxs(
6219
+ /* @__PURE__ */ jsxs11("div", { className: "rk-panel-tools", children: [
6220
+ /* @__PURE__ */ jsxs11(
6662
6221
  "button",
6663
6222
  {
6664
6223
  type: "button",
@@ -6669,15 +6228,15 @@ function Panel() {
6669
6228
  dispatch({ type: "SET_MODE", mode: "pick" });
6670
6229
  },
6671
6230
  children: [
6672
- /* @__PURE__ */ jsx(IconTarget, { size: 14 }),
6231
+ /* @__PURE__ */ jsx12(IconTarget, { size: 14 }),
6673
6232
  " Pick an element"
6674
6233
  ]
6675
6234
  }
6676
6235
  ),
6677
- /* @__PURE__ */ jsxs("div", { className: "rk-search-row", children: [
6678
- /* @__PURE__ */ jsxs("div", { className: "rk-search", children: [
6679
- /* @__PURE__ */ jsx(IconSearch, { size: 13 }),
6680
- /* @__PURE__ */ jsx(
6236
+ /* @__PURE__ */ jsxs11("div", { className: "rk-search-row", children: [
6237
+ /* @__PURE__ */ jsxs11("div", { className: "rk-search", children: [
6238
+ /* @__PURE__ */ jsx12(IconSearch, { size: 13 }),
6239
+ /* @__PURE__ */ jsx12(
6681
6240
  "input",
6682
6241
  {
6683
6242
  ref: searchRef,
@@ -6689,8 +6248,8 @@ function Panel() {
6689
6248
  }
6690
6249
  )
6691
6250
  ] }),
6692
- /* @__PURE__ */ jsxs("div", { className: "rk-menu-scope rk-menu-anchor", children: [
6693
- /* @__PURE__ */ jsxs(
6251
+ /* @__PURE__ */ jsxs11("div", { className: "rk-menu-scope rk-menu-anchor", children: [
6252
+ /* @__PURE__ */ jsxs11(
6694
6253
  "button",
6695
6254
  {
6696
6255
  type: "button",
@@ -6702,15 +6261,15 @@ function Panel() {
6702
6261
  setSettingsOpen(false);
6703
6262
  },
6704
6263
  children: [
6705
- /* @__PURE__ */ jsx(IconFilter, { size: 13 }),
6264
+ /* @__PURE__ */ jsx12(IconFilter, { size: 13 }),
6706
6265
  "Filter",
6707
- typeFilter.size + prioFilter.size + statusFilter.size > 0 && /* @__PURE__ */ jsx("span", { className: "rk-badge-count", children: typeFilter.size + prioFilter.size + statusFilter.size })
6266
+ typeFilter.size + prioFilter.size + statusFilter.size > 0 && /* @__PURE__ */ jsx12("span", { className: "rk-badge-count", children: typeFilter.size + prioFilter.size + statusFilter.size })
6708
6267
  ]
6709
6268
  }
6710
6269
  ),
6711
- filterOpen && /* @__PURE__ */ jsxs("div", { className: "rk-menu", role: "menu", "aria-label": "Filter items", children: [
6712
- /* @__PURE__ */ jsx("div", { className: "rk-menu-title", children: "Type" }),
6713
- TYPE_FILTERS.map((type) => /* @__PURE__ */ jsxs(
6270
+ filterOpen && /* @__PURE__ */ jsxs11("div", { className: "rk-menu", role: "menu", "aria-label": "Filter items", children: [
6271
+ /* @__PURE__ */ jsx12("div", { className: "rk-menu-title", children: "Type" }),
6272
+ TYPE_FILTERS.map((type) => /* @__PURE__ */ jsxs11(
6714
6273
  "button",
6715
6274
  {
6716
6275
  type: "button",
@@ -6724,16 +6283,16 @@ function Panel() {
6724
6283
  setTypeFilter(next);
6725
6284
  },
6726
6285
  children: [
6727
- /* @__PURE__ */ jsx("span", { className: `rk-menu-check${typeFilter.has(type) ? " rk-on" : ""}` }),
6286
+ /* @__PURE__ */ jsx12("span", { className: `rk-menu-check${typeFilter.has(type) ? " rk-on" : ""}` }),
6728
6287
  iconForType(type, 12),
6729
- /* @__PURE__ */ jsx("span", { className: "rk-menu-label", children: type }),
6730
- /* @__PURE__ */ jsx("span", { className: "rk-menu-count", children: typeCounts.get(type) ?? 0 })
6288
+ /* @__PURE__ */ jsx12("span", { className: "rk-menu-label", children: type }),
6289
+ /* @__PURE__ */ jsx12("span", { className: "rk-menu-count", children: typeCounts.get(type) ?? 0 })
6731
6290
  ]
6732
6291
  },
6733
6292
  type
6734
6293
  )),
6735
- /* @__PURE__ */ jsx("div", { className: "rk-menu-title", children: "Priority" }),
6736
- PRIORITY_FILTERS.map((priority) => /* @__PURE__ */ jsxs(
6294
+ /* @__PURE__ */ jsx12("div", { className: "rk-menu-title", children: "Priority" }),
6295
+ PRIORITY_FILTERS.map((priority) => /* @__PURE__ */ jsxs11(
6737
6296
  "button",
6738
6297
  {
6739
6298
  type: "button",
@@ -6747,15 +6306,15 @@ function Panel() {
6747
6306
  setPrioFilter(next);
6748
6307
  },
6749
6308
  children: [
6750
- /* @__PURE__ */ jsx("span", { className: `rk-menu-check${prioFilter.has(priority) ? " rk-on" : ""}` }),
6751
- /* @__PURE__ */ jsx("span", { className: "rk-menu-label", children: PRIORITY_LABELS[priority] }),
6752
- /* @__PURE__ */ jsx("span", { className: "rk-menu-count", children: prioCounts.get(priority) ?? 0 })
6309
+ /* @__PURE__ */ jsx12("span", { className: `rk-menu-check${prioFilter.has(priority) ? " rk-on" : ""}` }),
6310
+ /* @__PURE__ */ jsx12("span", { className: "rk-menu-label", children: PRIORITY_LABELS[priority] }),
6311
+ /* @__PURE__ */ jsx12("span", { className: "rk-menu-count", children: prioCounts.get(priority) ?? 0 })
6753
6312
  ]
6754
6313
  },
6755
6314
  priority
6756
6315
  )),
6757
- /* @__PURE__ */ jsx("div", { className: "rk-menu-title", children: "Status" }),
6758
- STATUSES.map((status) => /* @__PURE__ */ jsxs(
6316
+ /* @__PURE__ */ jsx12("div", { className: "rk-menu-title", children: "Status" }),
6317
+ STATUSES.map((status) => /* @__PURE__ */ jsxs11(
6759
6318
  "button",
6760
6319
  {
6761
6320
  type: "button",
@@ -6769,15 +6328,15 @@ function Panel() {
6769
6328
  setStatusFilter(next);
6770
6329
  },
6771
6330
  children: [
6772
- /* @__PURE__ */ jsx("span", { className: `rk-menu-check${statusFilter.has(status) ? " rk-on" : ""}` }),
6773
- /* @__PURE__ */ jsx("span", { className: `rk-status-dot rk-st-${status}` }),
6774
- /* @__PURE__ */ jsx("span", { className: "rk-menu-label", children: status }),
6775
- /* @__PURE__ */ jsx("span", { className: "rk-menu-count", children: statusCounts.get(status) ?? 0 })
6331
+ /* @__PURE__ */ jsx12("span", { className: `rk-menu-check${statusFilter.has(status) ? " rk-on" : ""}` }),
6332
+ /* @__PURE__ */ jsx12("span", { className: `rk-status-dot rk-st-${status}` }),
6333
+ /* @__PURE__ */ jsx12("span", { className: "rk-menu-label", children: status }),
6334
+ /* @__PURE__ */ jsx12("span", { className: "rk-menu-count", children: statusCounts.get(status) ?? 0 })
6776
6335
  ]
6777
6336
  },
6778
6337
  status
6779
6338
  )),
6780
- /* @__PURE__ */ jsx(
6339
+ /* @__PURE__ */ jsx12(
6781
6340
  "button",
6782
6341
  {
6783
6342
  type: "button",
@@ -6797,28 +6356,28 @@ function Panel() {
6797
6356
  ] })
6798
6357
  ] })
6799
6358
  ] }),
6800
- preview && unapplied > 0 && /* @__PURE__ */ jsxs("div", { className: "rk-unplaced", role: "status", children: [
6359
+ preview && unapplied > 0 && /* @__PURE__ */ jsxs11("div", { className: "rk-unplaced", role: "status", children: [
6801
6360
  unapplied,
6802
6361
  " saved change",
6803
6362
  unapplied === 1 ? "" : "s",
6804
6363
  " couldn\u2019t be placed on the current page \u2014 the page has changed since they were captured."
6805
6364
  ] }),
6806
- /* @__PURE__ */ jsx("div", { className: "rk-panel-list", children: filtered.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "rk-empty", children: [
6807
- /* @__PURE__ */ jsx("div", { className: "rk-empty-art", children: /* @__PURE__ */ jsx(IconTarget, { size: 22 }) }),
6808
- /* @__PURE__ */ jsx("div", { className: "rk-empty-title", children: items.length === 0 ? "Nothing here yet" : "No matches" }),
6809
- /* @__PURE__ */ jsx("div", { className: "rk-empty-text", children: items.length === 0 ? "Choose \u201CPick an element\u201D, then click anything on the page. Your suggestions collect here, ready to hand off." : "Try a different search, or clear the filters." })
6810
- ] }) : groups.map(([route, entries]) => /* @__PURE__ */ jsxs("div", { children: [
6811
- /* @__PURE__ */ jsx("div", { className: "rk-group-title", children: route }),
6365
+ /* @__PURE__ */ jsx12("div", { className: "rk-panel-list", children: filtered.length === 0 ? /* @__PURE__ */ jsxs11("div", { className: "rk-empty", children: [
6366
+ /* @__PURE__ */ jsx12("div", { className: "rk-empty-art", children: /* @__PURE__ */ jsx12(IconTarget, { size: 22 }) }),
6367
+ /* @__PURE__ */ jsx12("div", { className: "rk-empty-title", children: items.length === 0 ? "Nothing here yet" : "No matches" }),
6368
+ /* @__PURE__ */ jsx12("div", { className: "rk-empty-text", children: items.length === 0 ? "Choose \u201CPick an element\u201D, then click anything on the page. Your suggestions collect here, ready to hand off." : "Try a different search, or clear the filters." })
6369
+ ] }) : groups.map(([route, entries]) => /* @__PURE__ */ jsxs11("div", { children: [
6370
+ /* @__PURE__ */ jsx12("div", { className: "rk-group-title", children: route }),
6812
6371
  entries.map(({ item, number }) => {
6813
- const active2 = item.id === activeItemId;
6372
+ const active = item.id === activeItemId;
6814
6373
  const orphan = orphanIds.has(item.id);
6815
6374
  const hiddenEl = hiddenIds.has(item.id);
6816
- return /* @__PURE__ */ jsxs("div", { children: [
6817
- /* @__PURE__ */ jsxs(
6375
+ return /* @__PURE__ */ jsxs11("div", { children: [
6376
+ /* @__PURE__ */ jsxs11(
6818
6377
  "div",
6819
6378
  {
6820
6379
  "data-item-id": item.id,
6821
- className: `rk-item${active2 ? " rk-active" : ""}${orphan ? " rk-orphan" : ""}`,
6380
+ className: `rk-item${active ? " rk-active" : ""}${orphan ? " rk-orphan" : ""}`,
6822
6381
  role: "button",
6823
6382
  tabIndex: 0,
6824
6383
  onClick: () => jumpTo(item),
@@ -6829,18 +6388,18 @@ function Panel() {
6829
6388
  }
6830
6389
  },
6831
6390
  children: [
6832
- /* @__PURE__ */ jsx("span", { className: "rk-item-num", children: number }),
6833
- /* @__PURE__ */ jsx("span", { className: "rk-item-icon", children: iconForType(item.type) }),
6834
- /* @__PURE__ */ jsxs("span", { className: "rk-item-body", children: [
6835
- /* @__PURE__ */ jsxs("span", { className: "rk-item-snippet", children: [
6391
+ /* @__PURE__ */ jsx12("span", { className: "rk-item-num", children: number }),
6392
+ /* @__PURE__ */ jsx12("span", { className: "rk-item-icon", children: iconForType(item.type) }),
6393
+ /* @__PURE__ */ jsxs11("span", { className: "rk-item-body", children: [
6394
+ /* @__PURE__ */ jsxs11("span", { className: "rk-item-snippet", children: [
6836
6395
  item.target.tagName,
6837
6396
  " \xB7 ",
6838
6397
  item.target.textSnippet || item.target.selector
6839
6398
  ] }),
6840
- /* @__PURE__ */ jsx("span", { className: "rk-item-change", children: changeSummary(item) }),
6841
- /* @__PURE__ */ jsxs("span", { className: "rk-item-meta", children: [
6842
- /* @__PURE__ */ jsxs("span", { className: "rk-author", title: new Date(item.createdAt).toLocaleString(), children: [
6843
- /* @__PURE__ */ jsx(
6399
+ /* @__PURE__ */ jsx12("span", { className: "rk-item-change", children: changeSummary(item) }),
6400
+ /* @__PURE__ */ jsxs11("span", { className: "rk-item-meta", children: [
6401
+ /* @__PURE__ */ jsxs11("span", { className: "rk-author", title: new Date(item.createdAt).toLocaleString(), children: [
6402
+ /* @__PURE__ */ jsx12(
6844
6403
  "span",
6845
6404
  {
6846
6405
  className: "rk-author-dot",
@@ -6848,15 +6407,15 @@ function Panel() {
6848
6407
  }
6849
6408
  ),
6850
6409
  item.author?.name ?? "Unknown",
6851
- /* @__PURE__ */ jsxs("span", { className: "rk-time", children: [
6410
+ /* @__PURE__ */ jsxs11("span", { className: "rk-time", children: [
6852
6411
  " \xB7 ",
6853
6412
  timeAgo(item.createdAt)
6854
6413
  ] })
6855
6414
  ] }),
6856
- item.priority === "must" && /* @__PURE__ */ jsx("span", { className: "rk-badge-mini rk-must", children: "must" }),
6857
- item.status !== "open" && /* @__PURE__ */ jsx("span", { className: `rk-badge-mini rk-${item.status}`, children: item.status }),
6858
- orphan && /* @__PURE__ */ jsx("span", { className: "rk-badge-mini rk-orphaned", children: "unanchored" }),
6859
- hiddenEl && /* @__PURE__ */ jsx(
6415
+ item.priority === "must" && /* @__PURE__ */ jsx12("span", { className: "rk-badge-mini rk-must", children: "must" }),
6416
+ item.status !== "open" && /* @__PURE__ */ jsx12("span", { className: `rk-badge-mini rk-${item.status}`, children: item.status }),
6417
+ orphan && /* @__PURE__ */ jsx12("span", { className: "rk-badge-mini rk-orphaned", children: "unanchored" }),
6418
+ hiddenEl && /* @__PURE__ */ jsx12(
6860
6419
  "span",
6861
6420
  {
6862
6421
  className: "rk-badge-mini rk-hidden-el",
@@ -6864,14 +6423,14 @@ function Panel() {
6864
6423
  children: "hidden"
6865
6424
  }
6866
6425
  ),
6867
- item.note && /* @__PURE__ */ jsx("span", { className: "rk-badge-mini", children: "note" }),
6868
- peers.filter((peer) => peer.typingActive && peer.typingItemId === item.id).map((peer) => /* @__PURE__ */ jsxs("span", { className: "rk-badge-mini", style: { color: peer.color }, children: [
6426
+ item.note && /* @__PURE__ */ jsx12("span", { className: "rk-badge-mini", children: "note" }),
6427
+ peers.filter((peer) => peer.typingActive && peer.typingItemId === item.id).map((peer) => /* @__PURE__ */ jsxs11("span", { className: "rk-badge-mini", style: { color: peer.color }, children: [
6869
6428
  peer.name || "peer",
6870
6429
  " is writing\u2026"
6871
6430
  ] }, peer.id))
6872
6431
  ] })
6873
6432
  ] }),
6874
- /* @__PURE__ */ jsx("span", { className: "rk-item-actions", children: /* @__PURE__ */ jsx(
6433
+ /* @__PURE__ */ jsx12("span", { className: "rk-item-actions", children: /* @__PURE__ */ jsx12(
6875
6434
  "button",
6876
6435
  {
6877
6436
  type: "button",
@@ -6886,15 +6445,15 @@ function Panel() {
6886
6445
  "info"
6887
6446
  );
6888
6447
  },
6889
- children: /* @__PURE__ */ jsx(IconTrash, { size: 13 })
6448
+ children: /* @__PURE__ */ jsx12(IconTrash, { size: 13 })
6890
6449
  }
6891
6450
  ) })
6892
6451
  ]
6893
6452
  }
6894
6453
  ),
6895
- active2 && /* @__PURE__ */ jsxs("div", { className: "rk-item-edit", children: [
6896
- /* @__PURE__ */ jsxs("div", { className: "rk-detail-byline", title: new Date(item.createdAt).toLocaleString(), children: [
6897
- /* @__PURE__ */ jsx(
6454
+ active && /* @__PURE__ */ jsxs11("div", { className: "rk-item-edit", children: [
6455
+ /* @__PURE__ */ jsxs11("div", { className: "rk-detail-byline", title: new Date(item.createdAt).toLocaleString(), children: [
6456
+ /* @__PURE__ */ jsx12(
6898
6457
  "span",
6899
6458
  {
6900
6459
  className: "rk-author-dot",
@@ -6905,8 +6464,8 @@ function Panel() {
6905
6464
  " \xB7 ",
6906
6465
  timeAgo(item.createdAt)
6907
6466
  ] }),
6908
- isCopyItem(item) && /* @__PURE__ */ jsx(CopyDiff, { before: item.payload.before, after: item.payload.after }),
6909
- isImageItem(item) && /* @__PURE__ */ jsx(
6467
+ isCopyItem(item) && /* @__PURE__ */ jsx12(CopyDiff, { before: item.payload.before, after: item.payload.after }),
6468
+ isImageItem(item) && /* @__PURE__ */ jsx12(
6910
6469
  ImageDetail,
6911
6470
  {
6912
6471
  beforeSrc: item.payload.beforeSrc,
@@ -6914,7 +6473,7 @@ function Panel() {
6914
6473
  afterDataUrl: item.payload.afterDataUrl
6915
6474
  }
6916
6475
  ),
6917
- isKnownItemType(item.type) && item.type !== "move" && /* @__PURE__ */ jsx(
6476
+ isKnownItemType(item.type) && item.type !== "move" && /* @__PURE__ */ jsx12(
6918
6477
  "textarea",
6919
6478
  {
6920
6479
  className: "rk-textarea",
@@ -6924,7 +6483,7 @@ function Panel() {
6924
6483
  onChange: (event) => dispatch({ type: "UPDATE_ITEM", id: item.id, patch: withPayloadText(item, event.target.value) })
6925
6484
  }
6926
6485
  ),
6927
- /* @__PURE__ */ jsx(
6486
+ /* @__PURE__ */ jsx12(
6928
6487
  "input",
6929
6488
  {
6930
6489
  className: "rk-input",
@@ -6934,9 +6493,9 @@ function Panel() {
6934
6493
  onChange: (event) => dispatch({ type: "UPDATE_ITEM", id: item.id, patch: { note: event.target.value } })
6935
6494
  }
6936
6495
  ),
6937
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, flexWrap: "wrap" }, children: [
6938
- /* @__PURE__ */ jsxs("div", { className: "rk-seg", role: "group", "aria-label": "Priority", children: [
6939
- /* @__PURE__ */ jsx(
6496
+ /* @__PURE__ */ jsxs11("div", { style: { display: "flex", gap: 8, flexWrap: "wrap" }, children: [
6497
+ /* @__PURE__ */ jsxs11("div", { className: "rk-seg", role: "group", "aria-label": "Priority", children: [
6498
+ /* @__PURE__ */ jsx12(
6940
6499
  "button",
6941
6500
  {
6942
6501
  type: "button",
@@ -6945,7 +6504,7 @@ function Panel() {
6945
6504
  children: "Must"
6946
6505
  }
6947
6506
  ),
6948
- /* @__PURE__ */ jsx(
6507
+ /* @__PURE__ */ jsx12(
6949
6508
  "button",
6950
6509
  {
6951
6510
  type: "button",
@@ -6955,7 +6514,7 @@ function Panel() {
6955
6514
  }
6956
6515
  )
6957
6516
  ] }),
6958
- /* @__PURE__ */ jsx("div", { className: "rk-seg", role: "group", "aria-label": "Status", children: STATUSES.map((status) => /* @__PURE__ */ jsx(
6517
+ /* @__PURE__ */ jsx12("div", { className: "rk-seg", role: "group", "aria-label": "Status", children: STATUSES.map((status) => /* @__PURE__ */ jsx12(
6959
6518
  "button",
6960
6519
  {
6961
6520
  type: "button",
@@ -6970,22 +6529,22 @@ function Panel() {
6970
6529
  ] }, item.id);
6971
6530
  })
6972
6531
  ] }, route)) }),
6973
- /* @__PURE__ */ jsxs("div", { className: "rk-panel-foot", children: [
6974
- /* @__PURE__ */ jsxs("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: onExport, disabled: items.length === 0, children: [
6975
- /* @__PURE__ */ jsx(IconDownload, { size: 13 }),
6532
+ /* @__PURE__ */ jsxs11("div", { className: "rk-panel-foot", children: [
6533
+ /* @__PURE__ */ jsxs11("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: onExport, disabled: items.length === 0, children: [
6534
+ /* @__PURE__ */ jsx12(IconDownload, { size: 13 }),
6976
6535
  " Export"
6977
6536
  ] }),
6978
- /* @__PURE__ */ jsxs("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => void onCopy(), disabled: items.length === 0, children: [
6979
- /* @__PURE__ */ jsx(IconCopyClipboard, { size: 13 }),
6537
+ /* @__PURE__ */ jsxs11("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => void onCopy(), disabled: items.length === 0, children: [
6538
+ /* @__PURE__ */ jsx12(IconCopyClipboard, { size: 13 }),
6980
6539
  " Copy"
6981
6540
  ] }),
6982
- /* @__PURE__ */ jsxs("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => importRef.current?.click(), children: [
6983
- /* @__PURE__ */ jsx(IconUpload, { size: 13 }),
6541
+ /* @__PURE__ */ jsxs11("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => importRef.current?.click(), children: [
6542
+ /* @__PURE__ */ jsx12(IconUpload, { size: 13 }),
6984
6543
  " Import"
6985
6544
  ] }),
6986
- /* @__PURE__ */ jsx("span", { className: "rk-spacer" }),
6987
- undoStack.length > 0 && /* @__PURE__ */ jsx("button", { type: "button", className: "rk-icon-btn", "aria-label": "Undo last change (\u2318Z)", onClick: () => dispatch({ type: "UNDO" }), children: /* @__PURE__ */ jsx(IconUndo, { size: 14 }) }),
6988
- /* @__PURE__ */ jsx(
6545
+ /* @__PURE__ */ jsx12("span", { className: "rk-spacer" }),
6546
+ undoStack.length > 0 && /* @__PURE__ */ jsx12("button", { type: "button", className: "rk-icon-btn", "aria-label": "Undo last change (\u2318Z)", onClick: () => dispatch({ type: "UNDO" }), children: /* @__PURE__ */ jsx12(IconUndo, { size: 14 }) }),
6547
+ /* @__PURE__ */ jsx12(
6989
6548
  "button",
6990
6549
  {
6991
6550
  type: "button",
@@ -6996,7 +6555,7 @@ function Panel() {
6996
6555
  children: "Clear"
6997
6556
  }
6998
6557
  ),
6999
- /* @__PURE__ */ jsx(
6558
+ /* @__PURE__ */ jsx12(
7000
6559
  "button",
7001
6560
  {
7002
6561
  type: "button",
@@ -7006,7 +6565,7 @@ function Panel() {
7006
6565
  children: "Exit review"
7007
6566
  }
7008
6567
  ),
7009
- /* @__PURE__ */ jsx(
6568
+ /* @__PURE__ */ jsx12(
7010
6569
  "input",
7011
6570
  {
7012
6571
  ref: importRef,
@@ -7025,6 +6584,10 @@ function Panel() {
7025
6584
  }
7026
6585
  );
7027
6586
  }
6587
+
6588
+ // src/client/components/Presence.tsx
6589
+ import { useEffect as useEffect11, useRef as useRef11 } from "react";
6590
+ import { Fragment as Fragment7, jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
7028
6591
  function initials(name) {
7029
6592
  const parts = name.trim().split(/\s+/).filter(Boolean);
7030
6593
  if (parts.length === 0) return "\xB7";
@@ -7034,8 +6597,8 @@ function initials(name) {
7034
6597
  function PresenceStrip() {
7035
6598
  const { peers, syncStatus } = useStore();
7036
6599
  if (syncStatus === "disabled" || peers.length === 0) return null;
7037
- return /* @__PURE__ */ jsxs("div", { className: "rk-presence", "aria-label": `${peers.length} reviewer${peers.length === 1 ? "" : "s"} online`, children: [
7038
- peers.slice(0, 5).map((peer) => /* @__PURE__ */ jsx(
6600
+ return /* @__PURE__ */ jsxs12("div", { className: "rk-presence", "aria-label": `${peers.length} reviewer${peers.length === 1 ? "" : "s"} online`, children: [
6601
+ peers.slice(0, 5).map((peer) => /* @__PURE__ */ jsx13(
7039
6602
  "span",
7040
6603
  {
7041
6604
  className: "rk-avatar",
@@ -7045,7 +6608,7 @@ function PresenceStrip() {
7045
6608
  },
7046
6609
  peer.id
7047
6610
  )),
7048
- peers.length > 5 && /* @__PURE__ */ jsxs("span", { className: "rk-avatar rk-avatar-more", children: [
6611
+ peers.length > 5 && /* @__PURE__ */ jsxs12("span", { className: "rk-avatar rk-avatar-more", children: [
7049
6612
  "+",
7050
6613
  peers.length - 5
7051
6614
  ] })
@@ -7053,13 +6616,13 @@ function PresenceStrip() {
7053
6616
  }
7054
6617
  function PeerCursors() {
7055
6618
  const { peers } = useStore();
7056
- const ringRefs = useRef(/* @__PURE__ */ new Map());
7057
- const cursorRefs = useRef(/* @__PURE__ */ new Map());
7058
- const motion = useRef(
6619
+ const ringRefs = useRef11(/* @__PURE__ */ new Map());
6620
+ const cursorRefs = useRef11(/* @__PURE__ */ new Map());
6621
+ const motion = useRef11(
7059
6622
  /* @__PURE__ */ new Map()
7060
6623
  );
7061
- const peersRef = useRef(peers);
7062
- const seen = useRef(/* @__PURE__ */ new Map());
6624
+ const peersRef = useRef11(peers);
6625
+ const seen = useRef11(/* @__PURE__ */ new Map());
7063
6626
  for (const peer of peers) {
7064
6627
  const sig = peer.cursor ? `${peer.cursor.x},${peer.cursor.y},${peer.cursor.ox},${peer.cursor.oy}` : "";
7065
6628
  if (seen.current.get(peer.id) !== sig) {
@@ -7069,7 +6632,7 @@ function PeerCursors() {
7069
6632
  }
7070
6633
  }
7071
6634
  peersRef.current = peers;
7072
- useEffect(() => {
6635
+ useEffect11(() => {
7073
6636
  const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
7074
6637
  let raf = 0;
7075
6638
  let last = performance.now();
@@ -7174,8 +6737,8 @@ function PeerCursors() {
7174
6737
  return () => cancelAnimationFrame(raf);
7175
6738
  }, []);
7176
6739
  if (peers.length === 0) return null;
7177
- return /* @__PURE__ */ jsxs(Fragment, { children: [
7178
- peers.map((peer) => /* @__PURE__ */ jsx(
6740
+ return /* @__PURE__ */ jsxs12(Fragment7, { children: [
6741
+ peers.map((peer) => /* @__PURE__ */ jsx13(
7179
6742
  "div",
7180
6743
  {
7181
6744
  ref: (el) => {
@@ -7187,7 +6750,7 @@ function PeerCursors() {
7187
6750
  },
7188
6751
  `ring-${peer.id}`
7189
6752
  )),
7190
- peers.map((peer, index) => /* @__PURE__ */ jsxs(
6753
+ peers.map((peer, index) => /* @__PURE__ */ jsxs12(
7191
6754
  "div",
7192
6755
  {
7193
6756
  ref: (el) => {
@@ -7197,8 +6760,8 @@ function PeerCursors() {
7197
6760
  className: "rk-peer-cursor",
7198
6761
  style: { opacity: 0 },
7199
6762
  children: [
7200
- /* @__PURE__ */ jsx("svg", { width: "14", height: "18", viewBox: "0 0 14 18", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M1 1l12 7.5-5.2 1.2L5 16z", fill: peer.color, stroke: "rgba(0,0,0,0.25)", strokeWidth: "1" }) }),
7201
- /* @__PURE__ */ jsxs(
6763
+ /* @__PURE__ */ jsx13("svg", { width: "14", height: "18", viewBox: "0 0 14 18", "aria-hidden": "true", children: /* @__PURE__ */ jsx13("path", { d: "M1 1l12 7.5-5.2 1.2L5 16z", fill: peer.color, stroke: "rgba(0,0,0,0.25)", strokeWidth: "1" }) }),
6764
+ /* @__PURE__ */ jsxs12(
7202
6765
  "span",
7203
6766
  {
7204
6767
  className: "rk-peer-name",
@@ -7215,17 +6778,21 @@ function PeerCursors() {
7215
6778
  ))
7216
6779
  ] });
7217
6780
  }
6781
+
6782
+ // src/client/components/QuickComment.tsx
6783
+ import { useEffect as useEffect12, useRef as useRef12, useState as useState10 } from "react";
6784
+ import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
7218
6785
  function QuickComment() {
7219
6786
  const { quick } = useStore();
7220
6787
  if (!quick) return null;
7221
- return /* @__PURE__ */ jsx(QuickComposerCard, { quick }, quick.id);
6788
+ return /* @__PURE__ */ jsx14(QuickComposerCard, { quick }, quick.id);
7222
6789
  }
7223
6790
  function QuickComposerCard({ quick }) {
7224
6791
  const { route, session } = useStore();
7225
6792
  const dispatch = useDispatch();
7226
- const [text, setText] = useState("");
7227
- const fieldRef = useRef(null);
7228
- useEffect(() => {
6793
+ const [text, setText] = useState10("");
6794
+ const fieldRef = useRef12(null);
6795
+ useEffect12(() => {
7229
6796
  const field = fieldRef.current;
7230
6797
  if (!field) return;
7231
6798
  field.focus();
@@ -7242,7 +6809,7 @@ function QuickComposerCard({ quick }) {
7242
6809
  return;
7243
6810
  }
7244
6811
  const command = /^\/(\S+)$/.exec(trimmed);
7245
- if (command && EFFECTS[command[1]]) {
6812
+ if (command && isEffectCommand(command[1])) {
7246
6813
  try {
7247
6814
  runEffect(command[1], layerRoot);
7248
6815
  sendEffectToRoom(command[1]);
@@ -7278,18 +6845,18 @@ function QuickComposerCard({ quick }) {
7278
6845
  };
7279
6846
  const x = Math.min(Math.max(quick.x, 8), window.innerWidth - 288);
7280
6847
  const y = Math.min(Math.max(quick.y + 12, 8), window.innerHeight - 108);
7281
- return /* @__PURE__ */ jsxs("div", { className: "rk-quick", style: { transform: `translate3d(${x}px, ${y}px, 0)` }, role: "dialog", "aria-label": "Quick comment", children: [
7282
- /* @__PURE__ */ jsxs("div", { className: "rk-quick-head", children: [
7283
- /* @__PURE__ */ jsx(IconComment, { size: 12 }),
7284
- /* @__PURE__ */ jsx("span", { children: "Comment here" }),
7285
- /* @__PURE__ */ jsxs("span", { className: "rk-hint", children: [
7286
- /* @__PURE__ */ jsx("span", { className: "rk-kbd", children: "\u21B5" }),
6848
+ return /* @__PURE__ */ jsxs13("div", { className: "rk-quick", style: { transform: `translate3d(${x}px, ${y}px, 0)` }, role: "dialog", "aria-label": "Quick comment", children: [
6849
+ /* @__PURE__ */ jsxs13("div", { className: "rk-quick-head", children: [
6850
+ /* @__PURE__ */ jsx14(IconComment, { size: 12 }),
6851
+ /* @__PURE__ */ jsx14("span", { children: "Comment here" }),
6852
+ /* @__PURE__ */ jsxs13("span", { className: "rk-hint", children: [
6853
+ /* @__PURE__ */ jsx14("span", { className: "rk-kbd", children: "\u21B5" }),
7287
6854
  " save \xB7 ",
7288
- /* @__PURE__ */ jsx("span", { className: "rk-kbd", children: "esc" }),
6855
+ /* @__PURE__ */ jsx14("span", { className: "rk-kbd", children: "esc" }),
7289
6856
  " cancel"
7290
6857
  ] })
7291
6858
  ] }),
7292
- /* @__PURE__ */ jsx(
6859
+ /* @__PURE__ */ jsx14(
7293
6860
  "textarea",
7294
6861
  {
7295
6862
  ref: fieldRef,
@@ -7303,6 +6870,10 @@ function QuickComposerCard({ quick }) {
7303
6870
  )
7304
6871
  ] });
7305
6872
  }
6873
+
6874
+ // src/client/components/PickLayer.tsx
6875
+ import { useEffect as useEffect13, useRef as useRef13, useState as useState11 } from "react";
6876
+ import { Fragment as Fragment8, jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
7306
6877
  function applyRect(el, rect, pad2 = 3) {
7307
6878
  if (!el) return;
7308
6879
  if (!rect) {
@@ -7317,14 +6888,14 @@ function applyRect(el, rect, pad2 = 3) {
7317
6888
  function PickLayer() {
7318
6889
  const { mode, selection, tool } = useStore();
7319
6890
  const dispatch = useDispatch();
7320
- const [hovered, setHovered] = useState(null);
7321
- const outlineRef = useRef(null);
7322
- const labelRef = useRef(null);
7323
- const selectRef = useRef(null);
7324
- const hoveredRef = useRef(null);
6891
+ const [hovered, setHovered] = useState11(null);
6892
+ const outlineRef = useRef13(null);
6893
+ const labelRef = useRef13(null);
6894
+ const selectRef = useRef13(null);
6895
+ const hoveredRef = useRef13(null);
7325
6896
  hoveredRef.current = hovered;
7326
6897
  const picking = mode === "pick";
7327
- useEffect(() => {
6898
+ useEffect13(() => {
7328
6899
  if (!picking) {
7329
6900
  setHovered(null);
7330
6901
  return;
@@ -7368,7 +6939,7 @@ function PickLayer() {
7368
6939
  document.removeEventListener("mousedown", swallow, true);
7369
6940
  };
7370
6941
  }, [picking, tool, dispatch]);
7371
- useEffect(() => {
6942
+ useEffect13(() => {
7372
6943
  if (!picking) return;
7373
6944
  const body = document.body;
7374
6945
  const prev = body.style.cursor;
@@ -7378,7 +6949,7 @@ function PickLayer() {
7378
6949
  if (body.getAttribute("style") === "") body.removeAttribute("style");
7379
6950
  };
7380
6951
  }, [picking, tool]);
7381
- useEffect(() => {
6952
+ useEffect13(() => {
7382
6953
  let raf = 0;
7383
6954
  const tick = () => {
7384
6955
  const hoverEl = hoveredRef.current;
@@ -7404,17 +6975,21 @@ function PickLayer() {
7404
6975
  raf = requestAnimationFrame(tick);
7405
6976
  return () => cancelAnimationFrame(raf);
7406
6977
  }, [picking, selection]);
7407
- return /* @__PURE__ */ jsxs(Fragment, { children: [
7408
- picking && /* @__PURE__ */ jsxs(Fragment, { children: [
7409
- /* @__PURE__ */ jsx("div", { ref: outlineRef, className: "rk-outline", style: { opacity: 0 } }),
7410
- /* @__PURE__ */ jsx("div", { ref: labelRef, className: "rk-outline-label", style: { opacity: 0 }, children: hovered ? /* @__PURE__ */ jsxs(Fragment, { children: [
7411
- /* @__PURE__ */ jsx("span", { className: "rk-tag", children: hovered.tagName.toLowerCase() }),
7412
- /* @__PURE__ */ jsx("span", { children: describeElement(hovered).replace(/^[a-z0-9]+ · /, "") })
7413
- ] }) : /* @__PURE__ */ jsx("span", { children: "Pick an element" }) })
6978
+ return /* @__PURE__ */ jsxs14(Fragment8, { children: [
6979
+ picking && /* @__PURE__ */ jsxs14(Fragment8, { children: [
6980
+ /* @__PURE__ */ jsx15("div", { ref: outlineRef, className: "rk-outline", style: { opacity: 0 } }),
6981
+ /* @__PURE__ */ jsx15("div", { ref: labelRef, className: "rk-outline-label", style: { opacity: 0 }, children: hovered ? /* @__PURE__ */ jsxs14(Fragment8, { children: [
6982
+ /* @__PURE__ */ jsx15("span", { className: "rk-tag", children: hovered.tagName.toLowerCase() }),
6983
+ /* @__PURE__ */ jsx15("span", { children: describeElement(hovered).replace(/^[a-z0-9]+ · /, "") })
6984
+ ] }) : /* @__PURE__ */ jsx15("span", { children: "Pick an element" }) })
7414
6985
  ] }),
7415
- selection && /* @__PURE__ */ jsx("div", { ref: selectRef, className: "rk-select-ring", style: { opacity: 0 } })
6986
+ selection && /* @__PURE__ */ jsx15("div", { ref: selectRef, className: "rk-select-ring", style: { opacity: 0 } })
7416
6987
  ] });
7417
6988
  }
6989
+
6990
+ // src/client/components/Pins.tsx
6991
+ import { useEffect as useEffect14, useMemo as useMemo4, useRef as useRef14, useState as useState12 } from "react";
6992
+ import { Fragment as Fragment9, jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
7418
6993
  var STATUS_LABELS = {
7419
6994
  open: "open",
7420
6995
  accepted: "accepted",
@@ -7426,22 +7001,22 @@ var RERESOLVE_MS = 800;
7426
7001
  function Pins() {
7427
7002
  const { session, route, activeItemId, editor, mode, filter } = useStore();
7428
7003
  const dispatch = useDispatch();
7429
- const [clusters, setClusters] = useState([]);
7430
- const [orphanCount, setOrphanCount] = useState(0);
7431
- const wrapRefs = useRef(/* @__PURE__ */ new Map());
7432
- const visibleCache = useRef(/* @__PURE__ */ new Map());
7433
- const highlightRef = useRef(null);
7434
- const hoveredItems = useRef(null);
7435
- const elementCache = useRef(/* @__PURE__ */ new Map());
7436
- const lastResolve = useRef(0);
7437
- const [newestId, setNewestId] = useState(null);
7438
- const prevCount = useRef(0);
7439
- const items = useMemo(() => {
7004
+ const [clusters, setClusters] = useState12([]);
7005
+ const [orphanCount, setOrphanCount] = useState12(0);
7006
+ const wrapRefs = useRef14(/* @__PURE__ */ new Map());
7007
+ const visibleCache = useRef14(/* @__PURE__ */ new Map());
7008
+ const highlightRef = useRef14(null);
7009
+ const hoveredItems = useRef14(null);
7010
+ const elementCache = useRef14(/* @__PURE__ */ new Map());
7011
+ const lastResolve = useRef14(0);
7012
+ const [newestId, setNewestId] = useState12(null);
7013
+ const prevCount = useRef14(0);
7014
+ const items = useMemo4(() => {
7440
7015
  const all = session?.items ?? [];
7441
7016
  return all.map((item, index) => ({ item, number: index + 1 })).filter(({ item }) => item.route === route);
7442
7017
  }, [session, route]);
7443
7018
  const byId = new Map(items.map(({ item }) => [item.id, item]));
7444
- useEffect(() => {
7019
+ useEffect14(() => {
7445
7020
  const count = session?.items.length ?? 0;
7446
7021
  if (count > prevCount.current && session && count > 0) {
7447
7022
  const id = session.items[count - 1].id;
@@ -7452,7 +7027,7 @@ function Pins() {
7452
7027
  }
7453
7028
  prevCount.current = count;
7454
7029
  }, [session]);
7455
- useEffect(() => {
7030
+ useEffect14(() => {
7456
7031
  let raf = 0;
7457
7032
  let lastSignature = "";
7458
7033
  const tick = (time) => {
@@ -7536,8 +7111,8 @@ function Pins() {
7536
7111
  dispatch({ type: "SET_ACTIVE_ITEM", id });
7537
7112
  dispatch({ type: "SET_PANEL", open: true });
7538
7113
  };
7539
- return /* @__PURE__ */ jsxs(Fragment, { children: [
7540
- /* @__PURE__ */ jsx("div", { ref: highlightRef, className: "rk-hl-ring", style: { opacity: 0 } }),
7114
+ return /* @__PURE__ */ jsxs15(Fragment9, { children: [
7115
+ /* @__PURE__ */ jsx16("div", { ref: highlightRef, className: "rk-hl-ring", style: { opacity: 0 } }),
7541
7116
  clusters.map((cluster) => {
7542
7117
  const isCluster = cluster.ids.length > 1;
7543
7118
  const isActive = activeItemId !== null && cluster.ids.includes(activeItemId);
@@ -7547,7 +7122,7 @@ function Pins() {
7547
7122
  const status = statuses.size === 1 ? members[0]?.status : void 0;
7548
7123
  const dimmed = filter !== null && members.length > 0 && !members.some((item) => itemMatchesFilter(item, filter));
7549
7124
  const statusHint = isCluster ? status ? ` \xB7 all ${STATUS_LABELS[status] ?? status}` : "" : ` \xB7 ${STATUS_LABELS[members[0]?.status ?? "open"] ?? members[0]?.status}`;
7550
- return /* @__PURE__ */ jsx(
7125
+ return /* @__PURE__ */ jsx16(
7551
7126
  "div",
7552
7127
  {
7553
7128
  ref: (el) => {
@@ -7555,7 +7130,7 @@ function Pins() {
7555
7130
  else wrapRefs.current.delete(cluster.key);
7556
7131
  },
7557
7132
  className: `rk-pin-wrap${picking ? " rk-pin-ghost" : ""}`,
7558
- children: /* @__PURE__ */ jsx(
7133
+ children: /* @__PURE__ */ jsx16(
7559
7134
  "button",
7560
7135
  {
7561
7136
  type: "button",
@@ -7577,7 +7152,7 @@ function Pins() {
7577
7152
  cluster.key
7578
7153
  );
7579
7154
  }),
7580
- orphanCount > 0 && /* @__PURE__ */ jsxs(
7155
+ orphanCount > 0 && /* @__PURE__ */ jsxs15(
7581
7156
  "button",
7582
7157
  {
7583
7158
  type: "button",
@@ -7585,7 +7160,7 @@ function Pins() {
7585
7160
  style: { bottom: 16, left: "50%", transform: "translateX(-50%)" },
7586
7161
  onClick: () => dispatch({ type: "SET_PANEL", open: true }),
7587
7162
  children: [
7588
- /* @__PURE__ */ jsx(IconUnlink, { size: 13 }),
7163
+ /* @__PURE__ */ jsx16(IconUnlink, { size: 13 }),
7589
7164
  orphanCount,
7590
7165
  " unanchored item",
7591
7166
  orphanCount === 1 ? "" : "s"
@@ -7594,11 +7169,15 @@ function Pins() {
7594
7169
  )
7595
7170
  ] });
7596
7171
  }
7172
+
7173
+ // src/client/components/Toolbar.tsx
7174
+ import { useEffect as useEffect15, useLayoutEffect as useLayoutEffect3, useRef as useRef15, useState as useState13 } from "react";
7175
+ import { Fragment as Fragment10, jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
7597
7176
  var TOOLS = [
7598
- { tool: "select", hintId: "tool-select", label: "Select", icon: (n) => /* @__PURE__ */ jsx(IconTarget, { size: n }) },
7599
- { tool: "copy", hintId: "tool-copy", label: "Rewrite text", icon: (n) => /* @__PURE__ */ jsx(IconText, { size: n }) },
7600
- { tool: "comment", hintId: "tool-comment", label: "Comment", icon: (n) => /* @__PURE__ */ jsx(IconComment, { size: n }) },
7601
- { tool: "image", hintId: "tool-image", label: "Replace image", icon: (n) => /* @__PURE__ */ jsx(IconImage, { size: n }) }
7177
+ { tool: "select", hintId: "tool-select", label: "Select", icon: (n) => /* @__PURE__ */ jsx17(IconTarget, { size: n }) },
7178
+ { tool: "copy", hintId: "tool-copy", label: "Rewrite text", icon: (n) => /* @__PURE__ */ jsx17(IconText, { size: n }) },
7179
+ { tool: "comment", hintId: "tool-comment", label: "Comment", icon: (n) => /* @__PURE__ */ jsx17(IconComment, { size: n }) },
7180
+ { tool: "image", hintId: "tool-image", label: "Replace image", icon: (n) => /* @__PURE__ */ jsx17(IconImage, { size: n }) }
7602
7181
  ];
7603
7182
  var POS_KEY2 = "review-kit:toolbar-pos";
7604
7183
  var DRAG_THRESHOLD2 = 6;
@@ -7606,30 +7185,30 @@ function Toolbar() {
7606
7185
  const { selection, editor, peers, syncStatus, mode, tool } = useStore();
7607
7186
  const hasPeers = syncStatus !== "disabled" && peers.length > 0;
7608
7187
  const dispatch = useDispatch();
7609
- const ref = useRef(null);
7610
- const [pos, setPos] = useState(() => readStoredPos(POS_KEY2));
7611
- const drag = useRef(null);
7612
- const [dragging, setDragging] = useState(false);
7613
- const justDragged = useRef(false);
7188
+ const ref = useRef15(null);
7189
+ const [pos, setPos] = useState13(() => readStoredPos(POS_KEY2));
7190
+ const drag = useRef15(null);
7191
+ const [dragging, setDragging] = useState13(false);
7192
+ const justDragged = useRef15(false);
7614
7193
  const visible = editor === null || selection !== null;
7615
7194
  const applyPos = (next, persist) => {
7616
7195
  if (next === null && ref.current) ref.current.style.bottom = "";
7617
7196
  setPos(next);
7618
- storePos(POS_KEY2, next);
7197
+ if (persist) storePos(POS_KEY2, next);
7619
7198
  };
7620
- useLayoutEffect(() => {
7199
+ useLayoutEffect3(() => {
7621
7200
  const el = ref.current;
7622
7201
  if (!el || pos === null) return;
7623
7202
  const clamped = clampPos(pos.x, pos.y, el.offsetWidth, el.offsetHeight);
7624
- if (clamped.x !== pos.x || clamped.y !== pos.y) applyPos(clamped);
7203
+ if (clamped.x !== pos.x || clamped.y !== pos.y) applyPos(clamped, true);
7625
7204
  }, [pos, selection, hasPeers, visible]);
7626
- useEffect(() => {
7205
+ useEffect15(() => {
7627
7206
  const onResize = () => {
7628
7207
  const el = ref.current;
7629
7208
  const current = readStoredPos(POS_KEY2);
7630
7209
  if (!el || !current) return;
7631
7210
  const clamped = clampPos(current.x, current.y, el.offsetWidth, el.offsetHeight);
7632
- if (clamped.x !== current.x || clamped.y !== current.y) applyPos(clamped);
7211
+ if (clamped.x !== current.x || clamped.y !== current.y) applyPos(clamped, true);
7633
7212
  };
7634
7213
  window.addEventListener("resize", onResize);
7635
7214
  return () => window.removeEventListener("resize", onResize);
@@ -7694,7 +7273,7 @@ function Toolbar() {
7694
7273
  node.style.left = "0px";
7695
7274
  node.style.top = "0px";
7696
7275
  node.style.translate = `${snapped.x}px ${snapped.y}px`;
7697
- applyPos(snapped);
7276
+ applyPos(snapped, true);
7698
7277
  };
7699
7278
  window.addEventListener("pointermove", onMove, { passive: false });
7700
7279
  window.addEventListener("pointerup", onUp);
@@ -7721,7 +7300,7 @@ function Toolbar() {
7721
7300
  else if (event.key === "ArrowDown") dy = stepSize;
7722
7301
  else return;
7723
7302
  event.preventDefault();
7724
- applyPos(clampPos(rect.left + dx, rect.top + dy, el.offsetWidth, el.offsetHeight));
7303
+ applyPos(clampPos(rect.left + dx, rect.top + dy, el.offsetWidth, el.offsetHeight), true);
7725
7304
  };
7726
7305
  const open = (kind) => {
7727
7306
  if (!selection) return;
@@ -7736,7 +7315,7 @@ function Toolbar() {
7736
7315
  mode === "pick" && selection === null ? "rk-ghost" : ""
7737
7316
  ].filter(Boolean).join(" ");
7738
7317
  const style = pos !== null ? { left: 0, top: 0, translate: `${pos.x}px ${pos.y}px` } : void 0;
7739
- return /* @__PURE__ */ jsxs(
7318
+ return /* @__PURE__ */ jsxs16(
7740
7319
  "div",
7741
7320
  {
7742
7321
  ref,
@@ -7747,27 +7326,27 @@ function Toolbar() {
7747
7326
  onPointerDown,
7748
7327
  onClickCapture,
7749
7328
  children: [
7750
- /* @__PURE__ */ jsx(
7329
+ /* @__PURE__ */ jsx17(
7751
7330
  "button",
7752
7331
  {
7753
7332
  type: "button",
7754
7333
  className: "rk-toolbar-grip",
7755
7334
  "aria-label": "Move the toolbar \u2014 drag, or use arrow keys; double-click to reset its position",
7756
7335
  onKeyDown: onGripKeyDown,
7757
- onDoubleClick: () => applyPos(null),
7758
- children: /* @__PURE__ */ jsxs("svg", { width: "8", height: "14", viewBox: "0 0 8 14", "aria-hidden": "true", fill: "currentColor", children: [
7759
- /* @__PURE__ */ jsx("circle", { cx: "2", cy: "2", r: "1.1" }),
7760
- /* @__PURE__ */ jsx("circle", { cx: "6", cy: "2", r: "1.1" }),
7761
- /* @__PURE__ */ jsx("circle", { cx: "2", cy: "7", r: "1.1" }),
7762
- /* @__PURE__ */ jsx("circle", { cx: "6", cy: "7", r: "1.1" }),
7763
- /* @__PURE__ */ jsx("circle", { cx: "2", cy: "12", r: "1.1" }),
7764
- /* @__PURE__ */ jsx("circle", { cx: "6", cy: "12", r: "1.1" })
7336
+ onDoubleClick: () => applyPos(null, true),
7337
+ children: /* @__PURE__ */ jsxs16("svg", { width: "8", height: "14", viewBox: "0 0 8 14", "aria-hidden": "true", fill: "currentColor", children: [
7338
+ /* @__PURE__ */ jsx17("circle", { cx: "2", cy: "2", r: "1.1" }),
7339
+ /* @__PURE__ */ jsx17("circle", { cx: "6", cy: "2", r: "1.1" }),
7340
+ /* @__PURE__ */ jsx17("circle", { cx: "2", cy: "7", r: "1.1" }),
7341
+ /* @__PURE__ */ jsx17("circle", { cx: "6", cy: "7", r: "1.1" }),
7342
+ /* @__PURE__ */ jsx17("circle", { cx: "2", cy: "12", r: "1.1" }),
7343
+ /* @__PURE__ */ jsx17("circle", { cx: "6", cy: "12", r: "1.1" })
7765
7344
  ] })
7766
7345
  }
7767
7346
  ),
7768
- /* @__PURE__ */ jsx(PresenceStrip, {}),
7769
- hasPeers && /* @__PURE__ */ jsx("span", { className: "rk-toolbar-sep" }),
7770
- /* @__PURE__ */ jsx("div", { className: "rk-seg rk-tool-seg", role: "group", "aria-label": "Tools", children: TOOLS.map((entry) => /* @__PURE__ */ jsx(
7347
+ /* @__PURE__ */ jsx17(PresenceStrip, {}),
7348
+ hasPeers && /* @__PURE__ */ jsx17("span", { className: "rk-toolbar-sep" }),
7349
+ /* @__PURE__ */ jsx17("div", { className: "rk-seg rk-tool-seg", role: "group", "aria-label": "Tools", children: TOOLS.map((entry) => /* @__PURE__ */ jsx17(
7771
7350
  "button",
7772
7351
  {
7773
7352
  type: "button",
@@ -7779,9 +7358,9 @@ function Toolbar() {
7779
7358
  },
7780
7359
  entry.tool
7781
7360
  )) }),
7782
- selection && /* @__PURE__ */ jsx("span", { className: "rk-toolbar-sep" }),
7783
- selection && /* @__PURE__ */ jsxs(Fragment, { children: [
7784
- /* @__PURE__ */ jsxs(
7361
+ selection && /* @__PURE__ */ jsx17("span", { className: "rk-toolbar-sep" }),
7362
+ selection && /* @__PURE__ */ jsxs16(Fragment10, { children: [
7363
+ /* @__PURE__ */ jsxs16(
7785
7364
  "button",
7786
7365
  {
7787
7366
  type: "button",
@@ -7790,12 +7369,12 @@ function Toolbar() {
7790
7369
  title: `Rewrite text${keyHint("tool-copy")}`,
7791
7370
  onClick: () => open("copy"),
7792
7371
  children: [
7793
- /* @__PURE__ */ jsx(IconText, { size: 14 }),
7372
+ /* @__PURE__ */ jsx17(IconText, { size: 14 }),
7794
7373
  " Rewrite"
7795
7374
  ]
7796
7375
  }
7797
7376
  ),
7798
- hasImage && /* @__PURE__ */ jsxs(
7377
+ hasImage && /* @__PURE__ */ jsxs16(
7799
7378
  "button",
7800
7379
  {
7801
7380
  type: "button",
@@ -7804,12 +7383,12 @@ function Toolbar() {
7804
7383
  title: `Replace image${keyHint("tool-image")}`,
7805
7384
  onClick: () => open("image"),
7806
7385
  children: [
7807
- /* @__PURE__ */ jsx(IconImage, { size: 14 }),
7386
+ /* @__PURE__ */ jsx17(IconImage, { size: 14 }),
7808
7387
  " Image"
7809
7388
  ]
7810
7389
  }
7811
7390
  ),
7812
- /* @__PURE__ */ jsxs(
7391
+ /* @__PURE__ */ jsxs16(
7813
7392
  "button",
7814
7393
  {
7815
7394
  type: "button",
@@ -7818,32 +7397,32 @@ function Toolbar() {
7818
7397
  title: `Comment${keyHint("tool-comment")}`,
7819
7398
  onClick: () => open("comment"),
7820
7399
  children: [
7821
- /* @__PURE__ */ jsx(IconComment, { size: 14 }),
7400
+ /* @__PURE__ */ jsx17(IconComment, { size: 14 }),
7822
7401
  " Comment"
7823
7402
  ]
7824
7403
  }
7825
7404
  ),
7826
- /* @__PURE__ */ jsxs("button", { type: "button", role: "menuitem", className: "rk-toolbar-btn", onClick: () => open("style"), children: [
7827
- /* @__PURE__ */ jsx(IconStyle, { size: 14 }),
7405
+ /* @__PURE__ */ jsxs16("button", { type: "button", role: "menuitem", className: "rk-toolbar-btn", onClick: () => open("style"), children: [
7406
+ /* @__PURE__ */ jsx17(IconStyle, { size: 14 }),
7828
7407
  " Style"
7829
7408
  ] }),
7830
- /* @__PURE__ */ jsxs("button", { type: "button", role: "menuitem", className: "rk-toolbar-btn", onClick: () => open("move"), children: [
7831
- /* @__PURE__ */ jsx(IconMove, { size: 14 }),
7409
+ /* @__PURE__ */ jsxs16("button", { type: "button", role: "menuitem", className: "rk-toolbar-btn", onClick: () => open("move"), children: [
7410
+ /* @__PURE__ */ jsx17(IconMove, { size: 14 }),
7832
7411
  " Move"
7833
7412
  ] }),
7834
- /* @__PURE__ */ jsxs("button", { type: "button", role: "menuitem", className: "rk-toolbar-btn rk-danger", onClick: () => open("delete"), children: [
7835
- /* @__PURE__ */ jsx(IconTrash, { size: 14 }),
7413
+ /* @__PURE__ */ jsxs16("button", { type: "button", role: "menuitem", className: "rk-toolbar-btn rk-danger", onClick: () => open("delete"), children: [
7414
+ /* @__PURE__ */ jsx17(IconTrash, { size: 14 }),
7836
7415
  " Delete"
7837
7416
  ] }),
7838
- /* @__PURE__ */ jsx("span", { className: "rk-toolbar-sep" }),
7839
- /* @__PURE__ */ jsx(
7417
+ /* @__PURE__ */ jsx17("span", { className: "rk-toolbar-sep" }),
7418
+ /* @__PURE__ */ jsx17(
7840
7419
  "button",
7841
7420
  {
7842
7421
  type: "button",
7843
7422
  className: "rk-icon-btn",
7844
7423
  "aria-label": "Deselect",
7845
7424
  onClick: () => dispatch({ type: "SELECT", selection: null }),
7846
- children: /* @__PURE__ */ jsx(IconClose, { size: 13 })
7425
+ children: /* @__PURE__ */ jsx17(IconClose, { size: 13 })
7847
7426
  }
7848
7427
  )
7849
7428
  ] })
@@ -7851,32 +7430,42 @@ function Toolbar() {
7851
7430
  }
7852
7431
  );
7853
7432
  }
7433
+
7434
+ // src/client/components/Toast.tsx
7435
+ import { useEffect as useEffect16 } from "react";
7436
+ import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
7854
7437
  function ToastRow({ toast: toast2 }) {
7855
7438
  const dispatch = useDispatch();
7856
- useEffect(() => {
7439
+ useEffect16(() => {
7857
7440
  const timer = window.setTimeout(() => dispatch({ type: "DISMISS_TOAST", id: toast2.id }), 3500);
7858
7441
  return () => window.clearTimeout(timer);
7859
7442
  }, [toast2.id, dispatch]);
7860
- return /* @__PURE__ */ jsxs("div", { className: `rk-toast rk-${toast2.tone}`, role: "status", children: [
7861
- toast2.tone === "success" ? /* @__PURE__ */ jsx(IconCheck, { size: 13 }) : toast2.tone === "error" ? /* @__PURE__ */ jsx(IconWarning, { size: 13 }) : /* @__PURE__ */ jsx(IconComment, { size: 13 }),
7443
+ return /* @__PURE__ */ jsxs17("div", { className: `rk-toast rk-${toast2.tone}`, role: "status", children: [
7444
+ toast2.tone === "success" ? /* @__PURE__ */ jsx18(IconCheck, { size: 13 }) : toast2.tone === "error" ? /* @__PURE__ */ jsx18(IconWarning, { size: 13 }) : /* @__PURE__ */ jsx18(IconComment, { size: 13 }),
7862
7445
  toast2.message
7863
7446
  ] });
7864
7447
  }
7865
7448
  function ToastHost() {
7866
7449
  const { toasts } = useStore();
7867
7450
  if (toasts.length === 0) return null;
7868
- return /* @__PURE__ */ jsx("div", { className: "rk-toasts", "aria-live": "polite", children: toasts.map((toast2) => /* @__PURE__ */ jsx(ToastRow, { toast: toast2 }, toast2.id)) });
7451
+ return /* @__PURE__ */ jsx18("div", { className: "rk-toasts", "aria-live": "polite", children: toasts.map((toast2) => /* @__PURE__ */ jsx18(ToastRow, { toast: toast2 }, toast2.id)) });
7869
7452
  }
7453
+
7454
+ // src/client/App.tsx
7455
+ import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
7870
7456
  function App() {
7871
7457
  const state = useStore();
7872
7458
  const rawDispatch = useDispatch();
7873
7459
  const config = useConfig();
7874
7460
  const dispatch = useRealtimeSync(state, rawDispatch, config, reducer);
7875
- const saveTimer = useRef(null);
7461
+ const saveTimer = useRef16(null);
7876
7462
  const storageKey = config.storageKey ?? defaultStorageKey();
7877
7463
  useAppliedPreview(state, dispatch);
7878
7464
  useEmblemaAutoPush(state, dispatch, config);
7879
- useEffect(() => {
7465
+ useEffect17(() => {
7466
+ setEffectAssetsUrl(config.fxAssetsUrl);
7467
+ }, [config.fxAssetsUrl]);
7468
+ useEffect17(() => {
7880
7469
  let cancelled = false;
7881
7470
  void loadSession(storageKey).then((stored) => {
7882
7471
  if (cancelled) return;
@@ -7894,7 +7483,7 @@ function App() {
7894
7483
  };
7895
7484
  }, [storageKey, config.defaultAuthor, dispatch]);
7896
7485
  const { session } = state;
7897
- useEffect(() => {
7486
+ useEffect17(() => {
7898
7487
  if (!session) return;
7899
7488
  if (saveTimer.current !== null) window.clearTimeout(saveTimer.current);
7900
7489
  saveTimer.current = window.setTimeout(() => {
@@ -7905,9 +7494,9 @@ function App() {
7905
7494
  if (saveTimer.current !== null) window.clearTimeout(saveTimer.current);
7906
7495
  };
7907
7496
  }, [session, storageKey, config]);
7908
- useEffect(() => trackPointer(), []);
7909
- const focusHandled = useRef(false);
7910
- useEffect(() => {
7497
+ useEffect17(() => trackPointer(), []);
7498
+ const focusHandled = useRef16(false);
7499
+ useEffect17(() => {
7911
7500
  if (!session || focusHandled.current) return;
7912
7501
  focusHandled.current = true;
7913
7502
  const id = takeFocusItem();
@@ -7932,7 +7521,7 @@ function App() {
7932
7521
  cancelled = true;
7933
7522
  };
7934
7523
  }, [session, dispatch]);
7935
- useEffect(() => {
7524
+ useEffect17(() => {
7936
7525
  let last = window.location.pathname;
7937
7526
  const check = () => {
7938
7527
  if (window.location.pathname !== last) {
@@ -7947,9 +7536,9 @@ function App() {
7947
7536
  window.removeEventListener("popstate", check);
7948
7537
  };
7949
7538
  }, [dispatch]);
7950
- const stateRef = useRef(state);
7539
+ const stateRef = useRef16(state);
7951
7540
  stateRef.current = state;
7952
- useEffect(() => {
7541
+ useEffect17(() => {
7953
7542
  const onKeyDown = (event) => {
7954
7543
  const current = stateRef.current;
7955
7544
  for (const def of SHORTCUTS) {
@@ -7964,7 +7553,7 @@ function App() {
7964
7553
  document.addEventListener("keydown", onKeyDown);
7965
7554
  return () => document.removeEventListener("keydown", onKeyDown);
7966
7555
  }, [dispatch]);
7967
- useEffect(() => {
7556
+ useEffect17(() => {
7968
7557
  if (!state.editor) return;
7969
7558
  const editorEl = state.editor.element;
7970
7559
  const onPointerDown = (event) => {
@@ -7977,29 +7566,33 @@ function App() {
7977
7566
  }, [state.editor, dispatch]);
7978
7567
  if (!session) return null;
7979
7568
  if (state.hidden) {
7980
- return /* @__PURE__ */ jsx(DispatchContext.Provider, { value: dispatch, children: null });
7569
+ return /* @__PURE__ */ jsx19(DispatchContext.Provider, { value: dispatch, children: null });
7981
7570
  }
7982
- return /* @__PURE__ */ jsxs(DispatchContext.Provider, { value: dispatch, children: [
7983
- /* @__PURE__ */ jsx(PickLayer, {}),
7984
- /* @__PURE__ */ jsx(PeerCursors, {}),
7985
- /* @__PURE__ */ jsx(Toolbar, {}),
7986
- state.editor?.kind === "copy" && /* @__PURE__ */ jsx(InlineEditor, { editor: state.editor }),
7987
- state.editor?.kind === "image" && /* @__PURE__ */ jsx(ImageEditor, { editor: state.editor }),
7988
- state.editor && state.editor.kind !== "copy" && state.editor.kind !== "image" && /* @__PURE__ */ jsx(NoteComposer, { editor: state.editor }),
7989
- /* @__PURE__ */ jsx(Pins, {}),
7990
- /* @__PURE__ */ jsx(QuickComment, {}),
7991
- /* @__PURE__ */ jsx(Panel, {}),
7992
- /* @__PURE__ */ jsx(Badge, {}),
7993
- /* @__PURE__ */ jsx(CoachMark, {}),
7994
- /* @__PURE__ */ jsx(CheatSheet, {}),
7995
- /* @__PURE__ */ jsx(EmblemaConnect, {}),
7996
- /* @__PURE__ */ jsx(ConfirmHost, {}),
7997
- /* @__PURE__ */ jsx(ToastHost, {})
7571
+ return /* @__PURE__ */ jsxs18(DispatchContext.Provider, { value: dispatch, children: [
7572
+ /* @__PURE__ */ jsx19(PickLayer, {}),
7573
+ /* @__PURE__ */ jsx19(PeerCursors, {}),
7574
+ /* @__PURE__ */ jsx19(Toolbar, {}),
7575
+ state.editor?.kind === "copy" && /* @__PURE__ */ jsx19(InlineEditor, { editor: state.editor }),
7576
+ state.editor?.kind === "image" && /* @__PURE__ */ jsx19(ImageEditor, { editor: state.editor }),
7577
+ state.editor && state.editor.kind !== "copy" && state.editor.kind !== "image" && /* @__PURE__ */ jsx19(NoteComposer, { editor: state.editor }),
7578
+ /* @__PURE__ */ jsx19(Pins, {}),
7579
+ /* @__PURE__ */ jsx19(QuickComment, {}),
7580
+ /* @__PURE__ */ jsx19(Panel, {}),
7581
+ /* @__PURE__ */ jsx19(Badge, {}),
7582
+ /* @__PURE__ */ jsx19(CoachMark, {}),
7583
+ /* @__PURE__ */ jsx19(CheatSheet, {}),
7584
+ /* @__PURE__ */ jsx19(EmblemaConnect, {}),
7585
+ /* @__PURE__ */ jsx19(TerminalConnect, {}),
7586
+ /* @__PURE__ */ jsx19(ConfirmHost, {}),
7587
+ /* @__PURE__ */ jsx19(ToastHost, {})
7998
7588
  ] });
7999
7589
  }
7590
+
7591
+ // src/client/ReviewKit.tsx
7592
+ import { jsx as jsx20 } from "react/jsx-runtime";
8000
7593
  function ShadowHost({ config }) {
8001
- const [mount, setMount] = useState(null);
8002
- useEffect(() => {
7594
+ const [mount, setMount] = useState14(null);
7595
+ useEffect18(() => {
8003
7596
  const host = document.createElement("div");
8004
7597
  host.setAttribute(HOST_ATTR, "");
8005
7598
  host.style.position = "fixed";
@@ -8035,23 +7628,36 @@ function ShadowHost({ config }) {
8035
7628
  }, []);
8036
7629
  if (!mount) return null;
8037
7630
  return createPortal(
8038
- /* @__PURE__ */ jsx(StoreProvider, { config, children: /* @__PURE__ */ jsx(App, {}) }),
7631
+ /* @__PURE__ */ jsx20(StoreProvider, { config, children: /* @__PURE__ */ jsx20(App, {}) }),
8039
7632
  mount
8040
7633
  );
8041
7634
  }
8042
7635
  function ReviewKit(props) {
8043
- const [active2, setActive] = useState(false);
8044
- useEffect(() => {
8045
- if (!hostAllowed(props.allowedHosts)) return;
8046
- setActive(resolveActivation(props.token));
7636
+ const [active, setActive] = useState14(false);
7637
+ const { enabled, token, allowedHosts, devAutoOn } = props;
7638
+ const hostsKey = allowedHosts?.join("\0") ?? "";
7639
+ useEffect18(() => {
7640
+ if (enabled === false) {
7641
+ setActive(false);
7642
+ return;
7643
+ }
7644
+ setActive(resolveActivation({ token, allowedHosts, devAutoOn, enabled }));
8047
7645
  const onDeactivate = () => setActive(false);
8048
7646
  window.addEventListener(DEACTIVATE_EVENT, onDeactivate);
8049
7647
  return () => window.removeEventListener(DEACTIVATE_EVENT, onDeactivate);
8050
- }, [props.token, props.allowedHosts]);
8051
- if (!active2) return null;
8052
- return /* @__PURE__ */ jsx(ShadowHost, { config: props });
7648
+ }, [enabled, token, devAutoOn, hostsKey]);
7649
+ if (enabled === false || !active) return null;
7650
+ return /* @__PURE__ */ jsx20(ShadowHost, { config: props });
8053
7651
  }
8054
7652
 
8055
- export { ReviewKit, captureTarget, copySessionToClipboard, downloadSession, exportFilename, importSessionFile, resolveTarget, sessionToJson };
8056
- //# sourceMappingURL=chunk-YWBFAV57.js.map
8057
- //# sourceMappingURL=chunk-YWBFAV57.js.map
7653
+ export {
7654
+ captureTarget,
7655
+ resolveTarget,
7656
+ exportFilename,
7657
+ sessionToJson,
7658
+ downloadSession,
7659
+ copySessionToClipboard,
7660
+ importSessionFile,
7661
+ ReviewKit
7662
+ };
7663
+ //# sourceMappingURL=chunk-5MAFDRUI.js.map