PageSourceSearch

https://counter2.personyze.com/webdriver/simulator-live-debug.js?v=1790966721

js personyze.com collected 2026-10-03 20:20:32 UTC 656,287 bytes, 23,134 lines download raw bytes

1/*! Personyze on-site components (fresh-service/webdriver-apps) */
2(function()
3{'use strict';
4// ../../../../deno-dir/npm/registry.npmjs.org/preact/10.22.0/dist/preact.module.js
5var n;
6var l;
7var u;
8var t;
9var i;
10var o;
11var r;
12var f;
13var e;
14var c;
15var s;
16var a;
17var h = {};
18var p = [];
19var v = /acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;
20var y = Array.isArray;
21function d(n2, l3) {
22  for (var u4 in l3) n2[u4] = l3[u4];
23  return n2;
24}
25function w(n2) {
26  var l3 = n2.parentNode;
27  l3 && l3.removeChild(n2);
28}
29function _(l3, u4, t4) {
30  var i4, o3, r3, f4 = {};
31  for (r3 in u4) "key" == r3 ? i4 = u4[r3] : "ref" == r3 ? o3 = u4[r3] : f4[r3] = u4[r3];
32  if (arguments.length > 2 && (f4.children = arguments.length > 3 ? n.call(arguments, 2) : t4), "function" == typeof l3 && null != l3.defaultProps) for (r3 in l3.defaultProps) void 0 === f4[r3] && (f4[r3] = l3.defaultProps[r3]);
33  return g(l3, f4, i4, o3, null);
34}
35function g(n2, t4, i4, o3, r3) {
36  var f4 = {
37    type: n2,
38    props: t4,
39    key: i4,
40    ref: o3,
41    __k: null,
42    __: null,
43    __b: 0,
44    __e: null,
45    __d: void 0,
46    __c: null,
47    constructor: void 0,
48    __v: null == r3 ? ++u : r3,
49    __i: -1,
50    __u: 0
51  };
52  return null == r3 && null != l.vnode && l.vnode(f4), f4;
53}
54function k(n2) {
55  return n2.children;
56}
57function b(n2, l3) {
58  this.props = n2, this.context = l3;
59}
60function x(n2, l3) {
61  if (null == l3) return n2.__ ? x(n2.__, n2.__i + 1) : null;
62  for (var u4; l3 < n2.__k.length; l3++) if (null != (u4 = n2.__k[l3]) && null != u4.__e) return u4.__e;
63  return "function" == typeof n2.type ? x(n2) : null;
64}
65function C(n2) {
66  var l3, u4;
67  if (null != (n2 = n2.__) && null != n2.__c) {
68    for (n2.__e = n2.__c.base = null, l3 = 0; l3 < n2.__k.length; l3++) if (null != (u4 = n2.__k[l3]) && null != u4.__e) {
69      n2.__e = n2.__c.base = u4.__e;
70      break;
71    }
72    return C(n2);
73  }
74}
75function M(n2) {
76  (!n2.__d && (n2.__d = true) && i.push(n2) && !P.__r++ || o !== l.debounceRendering) && ((o = l.debounceRendering) || r)(P);
77}
78function P() {
79  var n2, u4, t4, o3, r3, e3, c3, s3;
80  for (i.sort(f); n2 = i.shift(); ) n2.__d && (u4 = i.length, o3 = void 0, e3 = (r3 = (t4 = n2).__v).__e, c3 = [], s3 = [], t4.__P && ((o3 = d({}, r3)).__v = r3.__v + 1, l.vnode && l.vnode(o3), O(t4.__P, o3, r3, t4.__n, t4.__P.namespaceURI, 32 & r3.__u ? [
81    e3
82  ] : null, c3, null == e3 ? x(r3) : e3, !!(32 & r3.__u), s3), o3.__v = r3.__v, o3.__.__k[o3.__i] = o3, j(c3, o3, s3), o3.__e != e3 && C(o3)), i.length > u4 && i.sort(f));
83  P.__r = 0;
84}
85function S(n2, l3, u4, t4, i4, o3, r3, f4, e3, c3, s3) {
86  var a3, v3, y3, d3, w3, _3 = t4 && t4.__k || p, g2 = l3.length;
87  for (u4.__d = e3, $(u4, l3, _3), e3 = u4.__d, a3 = 0; a3 < g2; a3++) null != (y3 = u4.__k[a3]) && "boolean" != typeof y3 && "function" != typeof y3 && (v3 = -1 === y3.__i ? h : _3[y3.__i] || h, y3.__i = a3, O(n2, y3, v3, i4, o3, r3, f4, e3, c3, s3), d3 = y3.__e, y3.ref && v3.ref != y3.ref && (v3.ref && N(v3.ref, null, y3), s3.push(y3.ref, y3.__c || d3, y3)), null == w3 && null != d3 && (w3 = d3), 65536 & y3.__u || v3.__k === y3.__k ? (e3 && !e3.isConnected && (e3 = x(v3)), e3 = I(y3, e3, n2)) : "function" == typeof y3.type && void 0 !== y3.__d ? e3 = y3.__d : d3 && (e3 = d3.nextSibling), y3.__d = void 0, y3.__u &= -196609);
88  u4.__d = e3, u4.__e = w3;
89}
90function $(n2, l3, u4) {
91  var t4, i4, o3, r3, f4, e3 = l3.length, c3 = u4.length, s3 = c3, a3 = 0;
92  for (n2.__k = [], t4 = 0; t4 < e3; t4++) r3 = t4 + a3, null != (i4 = n2.__k[t4] = null == (i4 = l3[t4]) || "boolean" == typeof i4 || "function" == typeof i4 ? null : "string" == typeof i4 || "number" == typeof i4 || "bigint" == typeof i4 || i4.constructor == String ? g(null, i4, null, null, null) : y(i4) ? g(k, {
93    children: i4
94  }, null, null, null) : void 0 === i4.constructor && i4.__b > 0 ? g(i4.type, i4.props, i4.key, i4.ref ? i4.ref : null, i4.__v) : i4) ? (i4.__ = n2, i4.__b = n2.__b + 1, f4 = L(i4, u4, r3, s3), i4.__i = f4, o3 = null, -1 !== f4 && (s3--, (o3 = u4[f4]) && (o3.__u |= 131072)), null == o3 || null === o3.__v ? (-1 == f4 && a3--, "function" != typeof i4.type && (i4.__u |= 65536)) : f4 !== r3 && (f4 === r3 + 1 ? a3++ : f4 > r3 ? s3 > e3 - r3 ? a3 += f4 - r3 : a3-- : f4 < r3 ? f4 == r3 - 1 && (a3 = f4 - r3) : a3 = 0, f4 !== t4 + a3 && (i4.__u |= 65536))) : (o3 = u4[r3]) && null == o3.ke
vendor: 4,430 bytes, lines 94-165
94y && o3.__e && 0 == (131072 & o3.__u) && (o3.__e == n2.__d && (n2.__d = x(o3)), V(o3, o3, false), u4[r3] = null, s3--);
95  if (s3) for (t4 = 0; t4 < c3; t4++) null != (o3 = u4[t4]) && 0 == (131072 & o3.__u) && (o3.__e == n2.__d && (n2.__d = x(o3)), V(o3, o3));
96}
97function I(n2, l3, u4) {
98  var t4, i4;
99  if ("function" == typeof n2.type) {
100    for (t4 = n2.__k, i4 = 0; t4 && i4 < t4.length; i4++) t4[i4] && (t4[i4].__ = n2, l3 = I(t4[i4], l3, u4));
101    return l3;
102  }
103  n2.__e != l3 && (u4.insertBefore(n2.__e, l3 || null), l3 = n2.__e);
104  do {
105    l3 = l3 && l3.nextSibling;
106  } while (null != l3 && 8 === l3.nodeType);
107  return l3;
108}
109function L(n2, l3, u4, t4) {
110  var i4 = n2.key, o3 = n2.type, r3 = u4 - 1, f4 = u4 + 1, e3 = l3[u4];
111  if (null === e3 || e3 && i4 == e3.key && o3 === e3.type && 0 == (131072 & e3.__u)) return u4;
112  if (t4 > (null != e3 && 0 == (131072 & e3.__u) ? 1 : 0)) for (; r3 >= 0 || f4 < l3.length; ) {
113    if (r3 >= 0) {
114      if ((e3 = l3[r3]) && 0 == (131072 & e3.__u) && i4 == e3.key && o3 === e3.type) return r3;
115      r3--;
116    }
117    if (f4 < l3.length) {
118      if ((e3 = l3[f4]) && 0 == (131072 & e3.__u) && i4 == e3.key && o3 === e3.type) return f4;
119      f4++;
120    }
121  }
122  return -1;
123}
124function T(n2, l3, u4) {
125  "-" === l3[0] ? n2.setProperty(l3, null == u4 ? "" : u4) : n2[l3] = null == u4 ? "" : "number" != typeof u4 || v.test(l3) ? u4 : u4 + "px";
126}
127function A(n2, l3, u4, t4, i4) {
128  var o3;
129  n: if ("style" === l3) if ("string" == typeof u4) n2.style.cssText = u4;
130  else {
131    if ("string" == typeof t4 && (n2.style.cssText = t4 = ""), t4) for (l3 in t4) u4 && l3 in u4 || T(n2.style, l3, "");
132    if (u4) for (l3 in u4) t4 && u4[l3] === t4[l3] || T(n2.style, l3, u4[l3]);
133  }
134  else if ("o" === l3[0] && "n" === l3[1]) o3 = l3 !== (l3 = l3.replace(/(PointerCapture)$|Capture$/i, "$1")), l3 = l3.toLowerCase() in n2 || "onFocusOut" === l3 || "onFocusIn" === l3 ? l3.toLowerCase().slice(2) : l3.slice(2), n2.l || (n2.l = {}), n2.l[l3 + o3] = u4, u4 ? t4 ? u4.u = t4.u : (u4.u = e, n2.addEventListener(l3, o3 ? s : c, o3)) : n2.removeEventListener(l3, o3 ? s : c, o3);
135  else {
136    if ("http://www.w3.org/2000/svg" == i4) l3 = l3.replace(/xlink(H|:h)/, "h").replace(/sName$/, "s");
137    else if ("width" != l3 && "height" != l3 && "href" != l3 && "list" != l3 && "form" != l3 && "tabIndex" != l3 && "download" != l3 && "rowSpan" != l3 && "colSpan" != l3 && "role" != l3 && l3 in n2) try {
138      n2[l3] = null == u4 ? "" : u4;
139      break n;
140    } catch (n3) {
141    }
142    "function" == typeof u4 || (null == u4 || false === u4 && "-" !== l3[4] ? n2.removeAttribute(l3) : n2.setAttribute(l3, u4));
143  }
144}
145function F(n2) {
146  return function(u4) {
147    if (this.l) {
148      var t4 = this.l[u4.type + n2];
149      if (null == u4.t) u4.t = e++;
150      else if (u4.t < t4.u) return;
151      return t4(l.event ? l.event(u4) : u4);
152    }
153  };
154}
155function O(n2, u4, t4, i4, o3, r3, f4, e3, c3, s3) {
156  var a3, h3, p3, v3, w3, _3, g2, m2, x2, C3, M2, P2, $2, I2, H, L2 = u4.type;
157  if (void 0 !== u4.constructor) return null;
158  128 & t4.__u && (c3 = !!(32 & t4.__u), r3 = [
159    e3 = u4.__e = t4.__e
160  ]), (a3 = l.__b) && a3(u4);
161  n: if ("function" == typeof L2) try {
162    if (m2 = u4.props, x2 = (a3 = L2.contextType) && i4[a3.__c], C3 = a3 ? x2 ? x2.props.value : a3.__ : i4, t4.__c ? g2 = (h3 = u4.__c = t4.__c).__ = h3.__E : ("prototype" in L2 && L2.prototype.render ? u4.__c = h3 = new L2(m2, C3) : (u4.__c = h3 = new b(m2, C3), h3.constructor = L2, h3.render = q), x2 && x2.sub(h3), h3.props = m2, h3.state || (h3.state = {}), h3.context = C3, h3.__n = i4, p3 = h3.__d = true, h3.__h = [], h3._sb = []), null == h3.__s && (h3.__s = h3.state), null != L2.getDerivedStateFromProps && (h3.__s == h3.state && (h3.__s = d({}, h3.__s)), d(h3.__s, L2.getDerivedStateFromProps(m2, h3.__s))), v3 = h3.props, w3 = h3.state, h3.__v = u4, p3) null == L2.getDerivedStateFromProps && null != h3.componentWillMount && h3.componentWillMount(), null != h3.componentDidMount && h3.__h.push(h3.componentDidMount);
163    else {
164      if (null == L2.getDerivedStateFromProps && m2 !== v3 && null != h3.componentWillReceiveProps && h3.componentWillReceiveProps(m2, C3), !h3.__e && (null != h3.shouldComponentUpdate && false === h3.shouldComponentUpdate(m2, h3.__s, C3) || u4.__v === t4.__v)) {
165        for (u4.__v !== t4.__v && (h3.props = m2, h3.state = h3.__s, h3.__d = false), u4.__e = t4.__e, u4.__k = t4.__k, u4.__k.forEac
vendor: 1,103 bytes, lines 165-181
165h(function(n3) {
166          n3 && (n3.__ = u4);
167        }), M2 = 0; M2 < h3._sb.length; M2++) h3.__h.push(h3._sb[M2]);
168        h3._sb = [], h3.__h.length && f4.push(h3);
169        break n;
170      }
171      null != h3.componentWillUpdate && h3.componentWillUpdate(m2, h3.__s, C3), null != h3.componentDidUpdate && h3.__h.push(function() {
172        h3.componentDidUpdate(v3, w3, _3);
173      });
174    }
175    if (h3.context = C3, h3.props = m2, h3.__P = n2, h3.__e = false, P2 = l.__r, $2 = 0, "prototype" in L2 && L2.prototype.render) {
176      for (h3.state = h3.__s, h3.__d = false, P2 && P2(u4), a3 = h3.render(h3.props, h3.state, h3.context), I2 = 0; I2 < h3._sb.length; I2++) h3.__h.push(h3._sb[I2]);
177      h3._sb = [];
178    } else do {
179      h3.__d = false, P2 && P2(u4), a3 = h3.render(h3.props, h3.state, h3.context), h3.state = h3.__s;
180    } while (h3.__d && ++$2 < 25);
181    h3.state = h3.__s, null != h3.getChildContext && (i4 = d(d({}, i4), h3.getChildContext())), p3 || null == h3.getSnapshotBeforeUpdate || (_3 = h3.getSnapshotBeforeUpdate(v3, w3)), S(n2, y(H = null != a3 && a3.type === k && null == a3.ke
181y ? a3.props.children : a3) ? H : [
182      H
183    ], u4, t4, i4, o3, r3, f4, e3, c3, s3), h3.base = u4.__e, u4.__u &= -161, h3.__h.length && f4.push(h3), g2 && (h3.__E = h3.__ = null);
184  } catch (n3) {
185    u4.__v = null, c3 || null != r3 ? (u4.__e = e3, u4.__u |= c3 ? 160 : 32, r3[r3.indexOf(e3)] = null) : (u4.__e = t4.__e, u4.__k = t4.__k), l.__e(n3, u4, t4);
186  }
187  else null == r3 && u4.__v === t4.__v ? (u4.__k = t4.__k, u4.__e = t4.__e) : u4.__e = z(t4.__e, u4, t4, i4, o3, r3, f4, c3, s3);
188  (a3 = l.diffed) && a3(u4);
189}
190function j(n2, u4, t4) {
191  u4.__d = void 0;
192  for (var i4 = 0; i4 < t4.length; i4++) N(t4[i4], t4[++i4], t4[++i4]);
193  l.__c && l.__c(u4, n2), n2.some(function(u5) {
194    try {
195      n2 = u5.__h, u5.__h = [], n2.some(function(n3) {
196        n3.call(u5);
197      });
198    } catch (n3) {
199      l.__e(n3, u5.__v);
200    }
201  });
202}
203function z(l3, u4, t4, i4, o3, r3, f4, e3, c3) {
204  var s3, a3, p3, v3, d3, _3, g2, m2 = t4.props, k3 = u4.props, b2 = u4.type;
205  if ("svg" === b2 ? o3 = "http://www.w3.org/2000/svg" : "math" === b2 ? o3 = "http://www.w3.org/1998/Math/MathML" : o3 || (o3 = "http://www.w3.org/1999/xhtml"), null != r3) {
206    for (s3 = 0; s3 < r3.length; s3++) if ((d3 = r3[s3]) && "setAttribute" in d3 == !!b2 && (b2 ? d3.localName === b2 : 3 === d3.nodeType)) {
207      l3 = d3, r3[s3] = null;
208      break;
209    }
210  }
211  if (null == l3) {
212    if (null === b2) return document.createTextNode(k3);
213    l3 = document.createElementNS(o3, b2, k3.is && k3), r3 = null, e3 = false;
214  }
215  if (null === b2) m2 === k3 || e3 && l3.data === k3 || (l3.data = k3);
216  else {
217    if (r3 = r3 && n.call(l3.childNodes), m2 = t4.props || h, !e3 && null != r3) for (m2 = {}, s3 = 0; s3 < l3.attributes.length; s3++) m2[(d3 = l3.attributes[s3]).name] = d3.value;
218    for (s3 in m2) if (d3 = m2[s3], "children" == s3) ;
219    else if ("dangerouslySetInnerHTML" == s3) p3 = d3;
220    else if ("key" !== s3 && !(s3 in k3)) {
221      if ("value" == s3 && "defaultValue" in k3 || "checked" == s3 && "defaultChecked" in k3) continue;
222      A(l3, s3, null, d3, o3);
223    }
224    for (s3 in k3) d3 = k3[s3], "children" == s3 ? v3 = d3 : "dangerouslySetInnerHTML" == s3 ? a3 = d3 : "value" == s3 ? _3 = d3 : "checked" == s3 ? g2 = d3 : "key" === s3 || e3 && "function" != typeof d3 || m2[s3] === d3 || A(l3, s3, d3, m2[s3], o3);
225    if (a3) e3 || p3 && (a3.__html === p3.__html || a3.__html === l3.innerHTML) || (l3.innerHTML = a3.__html), u4.__k = [];
226    else if (p3 && (l3.innerHTML = ""), S(l3, y(v3) ? v3 : [
227      v3
228    ], u4, t4, i4, "foreignObject" === b2 ? "http://www.w3.org/1999/xhtml" : o3, r3, f4, r3 ? r3[0] : t4.__k && x(t4, 0), e3, c3), null != r3) for (s3 = r3.length; s3--; ) null != r3[s3] && w(r3[s3]);
229    e3 || (s3 = "value", void 0 !== _3 && (_3 !== l3[s3] || "progress" === b2 && !_3 || "option" === b2 && _3 !== m2[s3]) && A(l3, s3, _3, m2[s3], o3), s3 = "checked", void 0 !== g2 && g2 !== l3[s3] && A(l3, s3, g2, m2[s3], o3));
230  }
231  return l3;
232}
233function N(n2, u4, t4) {
234  try {
235    "function" == typeof n2 ? n2(u4) : n2.current = u4;
236  } catch (n3) {
237    l.__e(n3, t4);
238  }
239}
240function V(n2, u4, t4) {
241  var i4, o3;
242  if (l.unmount && l.unmount(n2), (i4 = n2.ref) && (i4.current && i4.current !== n2.__e || N(i4, null, u4)), null != (i4 = n2.__c)) {
243    if (i4.componentWillUnmount) try {
244      i4.componentWillUnmount();
245    } catch (n3) {
246      l.__e(n3, u4);
247    }
248    i4.base = i4.__P = null;
249  }
250  if (i4 = n2.__k) for (o3 = 0; o3 < i4.length; o3++) i4[o3] && V(i4[o3], u4, t4 || "function" != typeof n2.type);
251  t4 || null == n2.__e || w(n2.__e), n2.__c = n2.__ = n2.__e = n2.__d = void 0;
252}
253function q(n2, l3, u4) {
254  return this.constructor(n2, u4);
255}
256function B(u4, t4, i4) {
257  var o3, r3, f4, e3;
258  l.__ && l.__(u4, t4), r3 = (o3 = "function" == typeof i4) ? null : i4 && i4.__k || t4.__k, f4 = [], e3 = [], O(t4, u4 = (!o3 && i4 || t4).__k = _(k, null, [
259    u4
260  ]), r3 || h, h, t4.namespaceURI, !o3 && i4 ? [
261    i4
262  ] : r3 ? null : t4.firstChild ? n.call(t4.childNodes) : null, f4, !o3 && i4 ? i4 : r3 ? r3.__e : t4.firstChild, o3, e3), j(f4, u4, e3);
263}
264n = p.slice, l = {
265  __e: function(n2, l3, u4, t4) {
266    for (var i4, o3, r3; l3 = l3.__; ) if ((i4 = l3.__c) && !i4.__) try {
267      if ((o3 = i4.constructor) && null != o3.getDerivedStateFromError && (i4.setState(o3.getDerivedStateFromError(n2)), r3 = i4.__d), null != i4.componentDidCatch && (i4.componentDidCatch(n2, t4 || {}), r3 = i4.__d), r3) return i4.__E = i4;
268    } catch (l4) {
269      n2 = l4;
270    }
271    throw n2;
272  }
273}, u = 0, t = function(n2) {
274  return null != n2 && null == n2.constructor;
275}, b.prototype.setState = function(n2, l3) {
276  var u4;
277  u4 = null != this.__s && this.__s !== this.state ? this.__s : this.__s = d({}, this.state), "function" == typeof n2 && (n2 = n2(d({}, u4), this.props)), n2 && d(u4, n2), null != n2 && this.__v && (l3 && this._sb.push(l3), M(this));
278}, b.prototype.forceUpdate = function(n2) {
279  this.__v && (this.__e = true, n2 && this.__h.push(n2), M(this));
280}, b.prototype.render = k, i = [], r = "function" == typeof Promise ? Promise.prototype.then.bind(Prom
280ise.resolve()) : setTimeout, f = function(n2, l3) {
281  return n2.__v.__b - l3.__v.__b;
282}, P.__r = 0, e = 0, c = F(false), s = F(true), a = 0;
283
284// src/components/live/liveStyle.ts
285var LIVE_STYLE = `	.le-root
286	{	--le-chrome: #12182A;
287		--le-chrome-2: #171D31;
288		--le-chrome-3: #1E2640;
289		--le-line: rgba(255, 255, 255, 0.08);
290		--le-line-2: rgba(255, 255, 255, 0.14);
291		--le-fg: #E8EDF7;
292		--le-muted: #94A3C1;
293		--le-muted-2: #6A7698;
294		--le-brand: #12B886;
295		--le-brand-soft: rgba(18, 184, 134, 0.14);
296		--le-brand-fg: #7BDFAE;
297		--le-info: #60A5FA;
298		--le-info-soft: rgba(96, 165, 250, 0.15);
299		--le-danger: #EF4444;
300		--le-danger-soft: rgba(239, 68, 68, 0.14);
301		--le-danger-fg: #FF9B9B;
302		--le-mono: ui-monospace, Menlo, Consolas, monospace;
303		--le-shadow: 0 1.5em 3em rgba(0, 0, 0, 0.35);
304
305		position: fixed;
306		z-index: 2147483646;
307		color: var(--le-fg);
308		direction: ltr;
309		text-align: left;
310		font: normal normal 400 13px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
311	}
312	.le-root *
313	{	box-sizing: border-box;
314	}
315	.le-root svg
316	{	flex: 0 0 auto;
317		width: 1.1em;
318		height: 1.1em;
319		fill: none;
320		stroke: currentColor;
321		stroke-width: 2;
322		stroke-linecap: round;
323		stroke-linejoin: round;
324	}
325	.le-root kbd
326	{	padding: 0 0.4em;
327		border: 1px solid var(--le-line);
328		border-radius: 0.3em;
329		background-color: rgba(255, 255, 255, 0.08);
330		color: var(--le-muted);
331		font-family: var(--le-mono);
332		font-size: 85%;
333	}
334
335	/* ===== tooltips =====
336		The design's own, instead of the browser's title attribute: a sentence of explanation fits,
337		it is styled like the rest of the chrome, and the delay is short enough to read as an answer
338		rather than an accident.
339
340		ONE FLOATING ELEMENT for all of them - TipLayer in liveTooltip.tsx - fixed to the window and
341		kept inside it. They used to be a ::after on each control, drawn in that control's box, so a
342		scrolling area clipped them: "Apply on" in the panel body came up cut off at the panel's left
343		edge (Jacob, 29 Sep 26). A control only carries data-tooltip (and data-tooltip-pos: top or
344		left, below by default).
345
346		position:relative stays on every trigger because layout had come to rely on it - see the
347		.le-peek rule, which re-asserts its own position over this one. */
348	.le-root [data-tooltip]
349	{	position: relative;
350	}
351	.le-root.le-tooltip
352	{	z-index: 2147483647;
353		left: 0;
354		top: 0;
355		width: max-content;
356		max-width: min(20em, calc(100vw - 16px));
357		padding: 0.45em 0.7em;
358		border: 1px solid var(--le-line-2);
359		border-radius: 0.45em;
360		background-color: #060913;
361		box-shadow: 0 0.6em 1.5em rgba(0, 0, 0, 0.5);
362		color: var(--le-fg);
363		font-size: 11.5px;
364		line-height: 1.4;
365		white-space: normal;
366		text-align: left;
367		pointer-events: none;
368		opacity: 0;
369		transition: opacity 0.12s ease-out;
370	}
371	.le-root.le-tooltip.le-on
372	{	opacity: 1;
373	}
374
375	/* ===== toolbar ===== */
376	.le-toolbar
377	{	/*	Narrow on purpose: this floats over the customer's page, and the tiles below stack their
378			icon over their label, so three of them fit in far less than the 42em the row layout
379			needed. */
380		width: 32em;
381		/*	A column so the body is the one part that gives: every other row is fixed height, and
382			the panel as a whole is capped to the window. Without a cap a tall panel simply hung off
383			the bottom with its Save button unreachable.
384
385			THE CAP IS A CONSTANT, AND THAT IS THE WHOLE POINT. It used to be calc(100vh - top),
386			set inline because only the component knows where it has been dragged to - which made
387			the panel's HEIGHT a function of its own position. Drag a
388			full-height panel down and it did not move: its top edge went down, the cap shrank by
389			exactly as much, and the bottom stayed put. It read as resizing rather than dragging,
390			and only on the panels tall enough to hit the cap - a widget with twenty settings -
391			which is exactly how it was reported (Jacob, 24 Aug 26: "instead of dragging, it
392			increased the size of the whole window... if the window only has four items I can drag
393			it"). Position and size are now independent: the cap is the window, and the toolbar
394			clamps its own top so the bottom edge stays on screen. */
395		/*	And no taller than a window somebody would open by hand. The cap alone let the busiest
396			tab - Insert widget, twenty-eight tiles - draw a full-height wall down the side of the
397			page (Jacob, 24 Aug 26: "it opens really tall, too big... it needs to be the same size
398			as the normal window, just with some scroll"). The body scrolls; the panel stays a
399			panel. It also leaves room to drag it, which a full-height one has nowhere to do. */
400		max-height: min(calc(100vh - 1rem), 42em);
401		display: flex;
402		flex-direction: column;
403		max-width: 96vw;
404		border: 1px solid var(--le-line);
405		border-radius: 1.1em;
406		background-color: var(--le-chrome);
407		box-shadow: var(--le-shadow);
408	}
409	.le-toolbar.le-dragging
410	{	box-shadow: 0 2em 4.5em rgba(0, 0, 0, 0.55);
411	}
412	.le-toolbar.le-collapsed
413	{	width: auto;
414		min-width: 34em;
415	}
416
417	.le-head
418	{	display: flex;
419		gap: 0.6em;
420		align-items: center;
421		padding: 0.6em 0.8em;
422		border-bottom: 1px solid var(--le-line);
423		cursor: grab;
424		user-select: none;
425	}
426	.le-toolbar.le-collapsed .le-head
427	{	border-bottom: none;
428	}
429	.le-head:active
430	{	cursor: grabbing;
431	}
432	/*	THE HANDLE HAS TO LOOK LIKE A HANDLE. It was two grey dots against a dark bar - the panel
433		moves, which matters more here than in most tools because the panel stands between you and
434		the page you are editing, and people were not finding it (Jacob, 25 Aug 26). Given a
435		surface of its own, so it reads as something you take hold of. */
436	.le-grip
437	{	display: inline-flex;
438		flex: 0 0 auto;
439		align-items: center;
440		font-weight: 600;
441		white-space: nowrap;
442		padding: 0.32em 0.5em;
443		border: 1px solid var(--le-info);
444		border-radius: 0.45em;
445		/*	COLOURED, because it is the only thing on this bar you GRAB rather than click, and it no
446			longer says so in words. Against the dark chrome and the outlined buttons beside it,
447			the one filled blue block reads as a handle at a glance. */
448		background-color: var(--le-info);
449		color: var(--le-chrome);
450		letter-spacing: -0.15em;
450 /* the two glyphs make one 2x3 dot grid, not two ellipses */
451		font-size: 104%;
452		cursor: grab;
453	}
454	/*	THE ROW MUST NOT OUTGROW THE PANEL. The toolbar is a fixed 32em and every icon button is
455		flex:0 0 auto, so anything that made the row wider pushed the LAST button - Close - out
456		past the panel's own rounded corner, floating on the page beside it. Reported with a
457		screenshot (Jacob, 28 Aug 26).
458
459		Dropping the word "Move" bought back the space, but not durably: the Navigate button grows
460		by " . ON" when it is on, which spends it again. So the two labelled buttons are the
461		ones that give, and the fixed-size icons keep their place. */
462	.le-head .le-mode
463	{	flex: 0 1 auto;
464		min-width: 0;
465		overflow: hidden;
466	}
467	.le-head:active .le-grip
468	{	border-color: var(--le-brand);
469		background-color: var(--le-brand-soft);
470		color: var(--le-brand-fg);
471		cursor: grabbing;
472	}
473	.le-grip:hover
474	{	border-color: var(--le-brand);
475		color: var(--le-brand-fg);
476	}
477	.le-spacer
478	{	flex: 10000 1 0;
479	}
480
481	.le-mode
482	{	display: inline-flex;
483		gap: 0.6em;
484		align-items: center;
485		padding: 0.35em 0.9em 0.35em 0.75em;
486		border: 1px solid var(--le-line);
487		border-radius: 999px;
488		background-color: rgba(255, 255, 255, 0.04);
489		color: var(--le-muted);
490		font: inherit;
491		font-weight: 700;
492		cursor: pointer;
493	}
494	.le-mode:hover
495	{	background-color: var(--le-chrome-2);
496		color: var(--le-fg);
497	}
498	.le-mode.le-on
499	{	border-color: rgba(96, 165, 250, 0.45);
500		background-color: var(--le-info-soft);
501		color: #CFE0FF;
502	}
503	.le-mode .le-dot
504	{	width: 0.6em;
505		height: 0.6em;
506		border-radius: 50%;
507		background-color: currentColor;
508		opacity: 0.35;
509	}
510	.le-mode.le-on .le-dot
511	{	background-color: var(--le-info);
512		opacity: 1;
513		box-shadow: 0 0 0 0.23em rgba(96, 165, 250, 0.25);
514	}
515
516	.le-icon
517	{	display: inline-flex;
518		flex: 0 0 auto;
519		align-items: center;
520		justify-content: center;
521		width: 2.15em;
522		height: 2.15em;
523		padding: 0;
524		border: 1px solid var(--le-line);
525		border-radius: 0.55em;
526		background: transparent;
527		color: var(--le-muted);
528		font: inherit;
529		cursor: pointer;
530	}
531	.le-icon:hover:not(:disabled)
532	{	background-color: var(--le-chrome-2);
533		color: var(--le-fg);
534	}
535	.le-icon:disabled
536	{	opacity: 0.35;
537		cursor: default;
538	}
539
540	/* ===== breadcrumb ===== */
541	.le-path
542	{	display: flex;
543		gap: 0.15em;
544		align-items: center;
545		overflow-x: auto;
546		padding: 0.6em 0.8em;
547		border-bottom: 1px solid var(--le-line);
548		background-color: var(--le-chrome-2);
549		white-space: nowrap;
550	}
551	.le-path::-webkit-scrollbar
552	{	height: 0.3em;
553	}
554	.le-path::-webkit-scrollbar-thumb
555	{	border-radius: 0.3em;
556		background-color: rgba(255, 255, 255, 0.1);
557	}
558	.le-crumb
559	{	display: inline-flex;
560		flex: 0 0 auto;
561		gap: 0.3em;
562		align-items: center;
563		padding: 0.3em 0.6em;
564		border: 1px solid transparent;
565		border-radius: 0.45em;
566		background: transparent;
567		color: var(--le-muted);
568		font-family: var(--le-mono);
569		font-size: 92%;
570		cursor: pointer;
571	}
572	.le-crumb:hover
573	{	background-color: var(--le-chrome-3);
574		color: var(--le-fg);
575	}
576	.le-crumb.le-on
577	{	border-color: rgba(18, 184, 134, 0.3);
578		background-color: var(--le-brand-soft);
579		color: var(--le-brand);
580	}
581	.le-crumb b
582	{	font-weight: 700;
583	}
584	.le-crumb span
585	{	opacity: 0.7;
586	}
587	.le-crumb-sep
588	{	padding: 0 0.15em;
589		color: var(--le-muted-2);
590		user-select: none;
591	}
592	.le-path-empty
593	{	color: var(--le-muted-2);
594		font-size: 92%;
595	}
596	.le-crumb-add
597	{	display: inline-grid;
598		flex: 0 0 auto;
599		place-items: center;
600		width: 1.6em;
601		height: 1.6em;
602		margin-left: 0.2em;
603		border: 1px dashed var(--le-line-2);
604		border-radius: 0.45em;
605		background: transparent;
606		color: var(--le-muted-2);
607		font: inherit;
608		line-height: 1;
609		cursor: pointer;
610	}
611	.le-crumb-add:hover
612	{	border-style: solid;
613		background-color: var(--le-chrome-3);
614		color: var(--le-fg);
615	}
616	.le-crumb-add.le-on
617	{	border-style: solid;
618		border-color: rgba(18, 184, 134, 0.3);
619		background-color: var(--le-brand-soft);
620		color: var(--le-brand);
621	}
622	.le-selrow
623	{	display: flex;
624		flex-wrap: wrap;
625		gap: 0.4em;
626		align-items: center;
627		padding: 0.55em 0.8em;
628		border-bottom: 1px solid var(--le-line);
629		background-color: var(--le-chrome-2);
630	}
631	.le-selrow .le-input
632	{	flex: 1 1 12em;
633		min-width: 0;
634		font-family: var(--le-mono);
635	}
636	.le-selrow .le-input.le-bad
637	{	border-color: var(--le-danger);
638	}
639	.le-selrow-err
640	{	flex: 1 0 100%;
641		color: var(--le-danger-fg);
642		font-size: 88%;
643	}
644	.le-grouprow
645	{	display: flex;
646		gap: 0.5em;
647		align-items: center;
648		padding: 0.45em 0.8em;
649		border-bottom: 1px solid var(--le-line);
650		background-color: var(--le-chrome-2);
651		color: var(--le-muted);
652		font-size: 92%;
653		cursor: pointer;
654	}
655	.le-grouprow:hover
656	{	color: var(--le-fg);
657	}
658	.le-grouprow.le-on
659	{	background-color: var(--le-brand-soft);
660		color: var(--le-fg);
661	}
662	.le-grouprow b
663	{	color: var(--le-brand-fg);
664	}
665	.le-grouprow code
666	{	overflow: hidden;
667		margin-left: auto;
668		max-width: 45%;
669		color: var(--le-muted-2);
670		font-family: var(--le-mono);
671		font-size: 92%;
672		white-space: nowrap;
673		text-overflow: ellipsis;
674	}
675
676	/* ===== smart row ===== */
677	.le-smart
678	{	display: flex;
679		gap: 0.6em;
680		align-items: center;
681		padding: 0.6em 0.75em;
682		border-bottom: 1px solid var(--le-line);
683		background-color: rgba(255, 255, 255, 0.015);
684	}
685	.le-badge
686	{	display: inline-flex;
687		flex: 0 0 auto;
688		gap: 0.4em;
689		align-items: center;
690		padding: 0.25em 0.7em;
691		border-radius: 999px;
692		background-color: var(--le-brand);
693		color: #08231C;
694		font-size: 77%;
695		font-weight: 900;
696		letter-spacing: 0.04em;
697		text-transform: uppercase;
698	}
699	.le-badge svg
700	{	width: 0.85em;
701		height: 0.85em;
702		stroke-width: 2.5;
703	}
704	.le-smart-tiles
705	{	display: flex;
706		flex: 10000 1 0;
707		gap: 0.45em;
708		overflow-x: auto;
709		scrollbar-width: thin;
710	}
711	.le-smart-tiles::-webkit-scrollbar
712	{	height: 0.3em;
713	}
714	.le-smart-tiles::-webkit-scrollbar-thumb
715	{	border-radius: 0.3em;
716		background-color: var(--le-line);
717	}
718	.le-smart-tile
719	{	display: inline-flex;
720		flex: 0 0 auto;
721		gap: 0.4em;
722		align-items: center;
723		padding: 0.45em 0.75em;
724		border: 1px solid var(--le-line);
725		border-radius: 0.6em;
726		background-color: var(--le-chrome-2);
727		color: var(--le-fg);
728		font: inherit;
729		font-size: 85%;
730		font-weight: 600;
731		white-space: nowrap;
732		cursor: pointer;
733	}
734	.le-smart-tile:hover
735	{	border-color: var(--le-line-2);
736		background-color: var(--le-chrome-3);
737	}
738	/*	THE ONE COLOURED CONTROL IN THE ROW, and deliberately so. Every other tile there edits the
739		element in front of you; this one opens the settings BEHIND it - the questions, the
740		segments, the end date - which is the thing nobody found because it looked like all the
741		rest (Jacob, twice). Amber rather than the editor's own green, which already means "this
742		is applied". */
743	.le-smart-tile.le-wkey
744	{	border-color: rgba(245, 158, 11, 0.55);
745		background-color: rgba(245, 158, 11, 0.16);
746		color: #FCD34D;
747	}
748	.le-smart-tile.le-wkey:hover
749	{	border-color: #F59E0B;
750		background-color: rgba(245, 158, 11, 0.28);
751		color: #FDE68A;
752	}
753	.le-smart-tile.le-wkey svg
754	{	opacity: 1;
755	}
756	.le-smart-tile svg
757	{	width: 0.95em;
758		height: 0.95em;
759		opacity: 0.8;
760	}
761
762	/* ===== tabs ===== */
763	.le-tabs
764	{	display: flex;
765		gap: 0.15em;
766		overflow-x: auto;
767		padding: 0.45em 0.75em 0;
768		border-bottom: 1px solid var(--le-line);
769	}
770	.le-tab
771	{	margin-bottom: -1px;
772		padding: 0.6em 0.9em;
773		border: none;
774		border-bottom: 2px solid transparent;
775		border-radius: 0.55em 0.55em 0 0;
776		background: transparent;
777		color: var(--le-muted);
778		font: inherit;
779		font-weight: 600;
780		white-space: nowrap;
781		cursor: pointer;
782	}
783	.le-tab:hover
784	{	background-color: rgba(255, 255, 255, 0.03);
785		color: var(--le-fg);
786	}
787	.le-tab.le-on
788	{	border-bottom-color: var(--le-brand);
789		color: var(--le-fg);
790	}
791
792	/* ===== tile grid ===== */
793	.le-body
794	{	/*	The only part that scrolls, and only once the toolbar has run out of window - the fixed
795			50vh cap this used to carry made the busiest tab scroll even when there was room below. */
796		flex: 1 1 auto;
797		min-height: 0;
798		overflow: auto;
799		padding: 0.75em;
800	}
801	.le-toolbar > *:not(.le-body)
802	{	flex: 0 0 auto;
803	}
804	.le-grid
805	{	display: grid;
806		grid-template-columns: repeat(3, 1fr);
807		gap: 0.6em;
808	}
809	/*	A widget tile says it can be dragged by behaving like something you can pick up. The cursor
810		is the whole affordance - a badge would be one more thing on a tile that already carries an
811		icon, a name and a shortcut. */
812	.le-tile[data-drag]
813	{	cursor: grab;
814	}
815	.le-tile[data-drag]:active
816	{	cursor: grabbing;
817	}
818	.le-tile
819	{	position: relative;
820		display: flex;
821		flex-direction: column;
822		gap: 0.45em;
823		align-items: center;
824		justify-content: center;
825		min-height: 5.6em;
826		padding: 0.8em 0.45em 0.7em;
827		border: 1px solid var(--le-line);
828		border-radius: 0.75em;
829		background-color: var(--le-chrome-2);
830		font: inherit;
831		text-align: center;
832		cursor: pointer;
833	}
834	.le-tile:hover
835	{	border-color: var(--le-line-2);
836		background-color: var(--le-chrome-3);
837	}
838	.le-tile:active
839	{	transform: translateY(1px);
840	}
841	.le-tile .le-tile-ico
842	{	display: grid;
843		place-items: center;
844		width: 2.15em;
845		height: 2.15em;
846		border-radius: 0.55em;
847		background-color: rgba(255, 255, 255, 0.06);
848		color: var(--le-fg);
849	}
850	.le-tile .le-tile-txt
851	{	width: 100%;
852		min-width: 0;
853	}
854	.le-tile h4
855	{	margin: 0;
856		color: var(--le-fg);
857		font-size: 92%;
858		font-weight: 700;
859		line-height: 1.2;
860	}
861	/*	The subtitle moved to the tile's tooltip. Three columns in a 32em toolbar leaves ~9em of
862		width, which is not a line of prose - and the icon with the title already says it. */
863	.le-tile p
864	{	display: none;
865	}
866	.le-tile > kbd
867	{	position: absolute;
868		top: 0.3em;
869		right: 0.35em;
870		opacity: 0.75;
871	}
872	.le-tile-badge
873	{	position: absolute;
874		top: 0.3em;
875		right: 0.35em;
876		padding: 0.1em 0.45em;
877		border-radius: 0.3em;
878		background-color: var(--le-brand);
879		color: #08231C;
880		font-size: 70%;
881		font-weight: 900;
882		letter-spacing: 0.04em;
883		text-transform: uppercase;
884	}
885
886	/* ===== sub-panel ===== */
887	.le-sub-head
888	{	display: flex;
889		gap: 0.6em;
890		align-items: center;
891		margin-bottom: 0.9em;
892		padding: 0.45em 0.3em 0.75em;
893		border-bottom: 1px solid var(--le-line);
894	}
895	.le-sub-head b
896	{	font-size: 100%;
897		font-weight: 800;
898	}
899	.le-back
900	{	display: inline-flex;
901		gap: 0.3em;
902		align-items: center;
903		padding: 0.3em 0.6em 0.3em 0.45em;
904		border: 1px solid var(--le-line);
905		border-radius: 0.45em;
906		background: transparent;
907		color: var(--le-muted);
908		font: inherit;
909		font-size: 92%;
910		font-weight: 600;
911		cursor: pointer;
912	}
913	.le-back:hover
914	{	border-color: var(--le-line-2);
915		background-color: var(--le-chrome-2);
916		color: var(--le-fg);
917	}
918	.le-target
919	{	display: inline-flex;
920		gap: 0.45em;
921		align-items: center;
922		max-width: 100%;
923		margin-bottom: 0.9em;
924		padding: 0.3em 0.6em;
925		border: 1px solid var(--le-line);
926		border-radius: 0.45em;
927		background-color: var(--le-chrome-2);
928		color: var(--le-muted);
929		font-family: var(--le-mono);
930		font-size: 85%;
931	}
932	.le-target::before
933	{	content: "";
934		flex: 0 0 auto;
935		width: 0.45em;
936		height: 0.45em;
937		border-radius: 50%;
938		background-color: var(--le-brand);
939		box-shadow: 0 0 0 0.23em var(--le-brand-soft);
940	}
941	.le-target code
942	{	overflow: hidden;
943		color: var(--le-brand);
944		font-family: inherit;
945		white-space: nowrap;
946		text-overflow: ellipsis;
947	}
948	/*	A chip in the footer beside Cancel and Save, green when on - the same "chosen" green as Put
949		it and Apply on, because it is the same kind of answer. */
950	.le-count-clicks
951	{	display: inline-flex;
952		flex: 0 0 auto;
953		gap: 0.4em;
954		align-items: center;
955		padding: 0.3em 0.6em;
956		border: 1px solid var(--le-line);
957		border-radius: 0.45em;
958		background-color: var(--le-chrome-2);
959		color: var(--le-muted);
960		font-size: 85%;
961		font-weight: 600;
962		white-space: nowrap;
963		cursor: pointer;
964		user-select: none;
965	}
966	.le-count-clicks:hover
967	{	border-color: var(--le-line-2);
968		color: var(--le-fg);
969	}
970	.le-count-clicks.le-on
971	{	border-color: var(--le-brand);
972		background-color: var(--le-brand-soft);
973		color: var(--le-brand-fg);
974	}
975	.le-count-clicks.le-locked
976	{	cursor: default;
977	}
978	.le-count-clicks .le-check
979	{	width: 1em;
980		height: 1em;
981		margin: 0;
982		accent-color: var(--le-brand);
983	}
984	/*	The same answer on a row of the changes list, as a toggle: green while that change counts
985		clicks, a quiet outline while it does not. */
986	.le-clicks
987	{	display: inline-flex;
988		flex: 0 0 auto;
989		gap: 0.3em;
990		align-items: center;
991		padding: 0.1em 0.45em;
992		border: 1px solid var(--le-line);
993		border-radius: 0.9em;
994		background: transparent;
995		color: var(--le-muted);
996		font: inherit;
997		font-size: 78%;
998		white-space: nowrap;
999		cursor: pointer;
1000	}
1001	.le-clicks svg
1002	{	width: 1em;
1003		height: 1em;
1004	}
1005	.le-clicks:hover:not(:disabled)
1006	{	border-color: var(--le-line-2);
1007		color: var(--le-fg);
1008	}
1009	.le-clicks.le-on
1010	{	border-color: var(--le-brand);
1011		background-color: var(--le-brand-soft);
1012		color: var(--le-brand-fg);
1013	}
1014	.le-clicks.le-warn
1015	{	border-color: rgba(245, 158, 11, 0.55);
1016		background-color: rgba(245, 158, 11, 0.16);
1017		color: #FCD34D;
1018	}
1019	.le-clicks:disabled
1020	{	opacity: 0.5;
1021		cursor: default;
1022	}
1023
1024	/*	Sections of one sub-panel. Segmented pills on purpose - the toolbar's own tabs are underlined,
1025		and these switch which part of a form you are on, not what the editor is offering. */
1026	.le-style-note
1027	{	margin-bottom: 0.8em;
1028	}
1029	.le-ptabs
1030	{	display: flex;
1031		gap: 0.15em;
1032		overflow-x: auto;
1033		margin-bottom: 1em;
1034		padding: 0.25em;
1035		border: 1px solid var(--le-line);
1036		border-radius: 0.6em;
1037		background-color: var(--le-chrome-2);
1038	}
1039	.le-ptabs::-webkit-scrollbar
1040	{	height: 0.25em;
1041	}
1042	.le-ptab
1043	{	flex: 1 1 auto;
1044		padding: 0.4em 0.7em;
1045		border: none;
1046		border-radius: 0.42em;
1047		background: transparent;
1048		color: var(--le-muted);
1049		font: inherit;
1050		font-size: 92%;
1051		font-weight: 600;
1052		white-space: nowrap;
1053		cursor: pointer;
1054	}
1055	.le-ptab:hover
1056	{	color: var(--le-fg);
1057	}
1058	.le-ptab.le-on
1059	{	background-color: var(--le-chrome-3);
1060		color: var(--le-fg);
1061		box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
1062	}
1063
1064	/*	The AI card as one line, for a panel where the whole card would push the controls off screen. */
1065	.le-aicard-open
1066	{	display: flex;
1067		width: 100%;
1068		gap: 0.6em;
1069		align-items: center;
1070		margin-bottom: 1em;
1071		padding: 0.5em 0.7em;
1072		border: 1px solid rgba(168, 85, 247, 0.28);
1073		border-radius: 0.6em;
1074		background: linear-gradient(160deg, rgba(168, 85, 247, 0.12), rgba(18, 184, 134, 0.05));
1075		color: var(--le-fg);
1076		font: inherit;
1077		font-weight: 700;
1078		text-align: left;
1079		cursor: pointer;
1080	}
1081	.le-aicard-open:hover
1082	{	border-color: rgba(168, 85, 247, 0.5);
1083	}
1084	.le-aicard-open > span:nth-child(2)
1085	{	flex: 1 1 auto;
1086	}
1087	.le-aicard-open > svg
1088	{	flex: 0 0 auto;
1089		color: var(--le-muted-2);
1090	}
1091
1092	.le-sec
1093	{	margin-bottom: 1.3em;
1094	}
1095	/*	A hairline beside the heading: the sections used to run together in one flat column, which is
1096		what made a long panel (Style has six of them) hard to scan. */
1097	.le-sec > h5
1098	{	display: flex;
1099		gap: 0.7em;
1100		align-items: center;
1101		margin: 0 0 0.7em;
1102		color: var(--le-muted-2);
1103		font-size: 77%;
1104		font-weight: 800;
1105		letter-spacing: 0.06em;
1106		text-transform: uppercase;
1107	}
1108	.le-sec > h5::after
1109	{	flex: 1 1 auto;
1110		height: 1px;
1111		background-color: var(--le-line);
1112		content: "";
1113	}
1114	.le-field
1115	{	display: grid;
1116		grid-template-columns: 7.2em 1fr;
1117		gap: 0.75em;
1118		align-items: center;
1119		min-height: 2.3em;
1120	}
1121	.le-field > label
1122	{	color: var(--le-muted);
1123		font-size: 92%;
1124		line-height: 1.2;
1125	}
1126	.le-field-group
1127	{	display: flex;
1128		flex-wrap: wrap;
1129		gap: 0.45em;
1130		align-items: center;
1131	}
1132	.le-hint
1133	{	margin: 0 0 0.6em;
1134		color: var(--le-muted);
1135		font-size: 85%;
1136		line-height: 1.5;
1137	}
1138
1139	.le-color
1140	{	display: flex;
1141		gap: 0.45em;
1142		align-items: center;
1143		padding: 0.23em 0.45em 0.23em 0.3em;
1144		border: 1px solid var(--le-line);
1145		border-radius: 0.45em;
1146		background-color: var(--le-chrome-2);
1147	}
1148	.le-color .le-swatch
1149	{	position: relative;
1150		overflow: hidden;
1151		width: 1.7em;
1152		height: 1.7em;
1153		padding: 0;
1154		/*	A ring rather than a border, and a light one: the swatch sits on the dark chrome, and a
1155			near-black colour (which a page's body text usually is) was indistinguishable from an
1156			empty box without it. */
1157		border: none;
1158		border-radius: 0.3em;
1159		box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.35), 0 0 0 1px rgba(0, 0, 0, 0.4);
1160		cursor: pointer;
1161	}
1162	/*	Nothing set: the diagonal says "no colour of its own" rather than showing a black square. */
1163	.le-color .le-swatch.le-empty
1164	{	background-image: linear-gradient(135deg, transparent 44%, var(--le-danger) 44%, var(--le-danger) 56%, transparent 56%);
1165		background-color: var(--le-chrome-3);
1166	}
1167	.le-color .le-swatch input
1168	{	position: absolute;
1169		inset: -0.3em;
1170		width: calc(100% + 0.6em);
1171		height: calc(100% + 0.6em);
1172		padding: 0;
1173		border: none;
1174		opacity: 0;
1175		cursor: pointer;
1176	}
1177	.le-color input[type=text]
1178	{	flex: 10000 1 0;
1179		min-width: 0;
1180		padding: 0.23em 0.15em;
1181		border: none;
1182		background: transparent;
1183		color: var(--le-fg);
1184		outline: none;
1185		font-family: var(--le-mono);
1186		font-size: 92%;
1187	}
1188
1189	.le-num
1190	{	display: inline-flex;
1191		align-items: center;
1192		border: 1px solid var(--le-line);
1193		border-radius: 0.45em;
1194		background-color: var(--le-chrome-2);
1195	}
1196	.le-num:focus-within,
1197	.le-color:focus-within
1198	{	border-color: rgba(18, 184, 134, 0.5);
1199	}
1200	.le-num input
1201	{	/*	Wide enough for a computed value with a decimal ("29.4") next to the spinner, which the
1202			browser draws INSIDE the box - at the old 4.5em the digits went under it. */
1203		width: 5.4em;
1204		padding: 0.4em 0.1em 0.4em 0.55em;
1205		border: none;
1206		background: transparent;
1207		color: var(--le-fg);
1208		outline: none;
1209		font: inherit;
1210		font-size: 92%;
1211		font-variant-numeric: tabular-nums;
1212		text-align: left;
1213	}
1214	.le-num .le-unit
1215	{	padding: 0.4em 0.6em;
1216		border-left: 1px solid var(--le-line);
1217		color: var(--le-muted);
1218		font-size: 92%;
1219		user-select: none;
1220	}
1221
1222	.le-input,
1223	.le-select
1224	{	max-width: 100%;
1225		padding: 0.4em 0.6em;
1226		border: 1px solid var(--le-line);
1227		border-radius: 0.45em;
1228		background-color: var(--le-chrome-2);
1229		color: var(--le-fg);
1230		outline: none;
1231		font: inherit;
1232		font-size: 92%;
1233	}
1234	.le-input
1235	{	width: 100%;
1236	}
1237	.le-input:focus,
1238	.le-area:focus
1239	{	border-color: rgba(18, 184, 134, 0.5);
1240	}
1241	.le-area
1242	{	width: 100%;
1243		min-height: 5em;
1244		padding: 0.6em 0.75em;
1245		border: 1px solid var(--le-line);
1246		border-radius: 0.55em;
1247		background-color: var(--le-chrome-2);
1248		color: var(--le-fg);
1249		outline: none;
1250		font: inherit;
1251		font-size: 100%;
1252		line-height: 1.4;
1253		resize: vertical;
1254	}
1255	.le-area.le-mono
1256	{	font-family: var(--le-mono);
1257		font-size: 92%;
1258	}
1259
1260	.le-icongroup
1261	{	display: inline-flex;
1262		gap: 0.15em;
1263		padding: 0.15em;
1264		border: 1px solid var(--le-line);
1265		border-radius: 0.45em;
1266		background-color: var(--le-chrome-2);
1267	}
1268	.le-icongroup button
1269	{	display: grid;
1270		place-items: center;
1271		width: 2.15em;
1272		height: 1.85em;
1273		border: none;
1274		border-radius: 0.3em;
1275		background: transparent;
1276		color: var(--le-muted);
1277		font: inherit;
1278		cursor: pointer;
1279	}
1280	.le-icongroup button:hover,
1281	.le-icongroup button.le-on
1282	{	background-color: var(--le-chrome-3);
1283		color: var(--le-fg);
1284	}
1285
1286	.le-presets
1287	{	display: flex;
1288		flex-wrap: wrap;
1289		gap: 0.45em;
1290	}
1291	.le-preset
1292	{	padding: 0.4em 0.75em;
1293		border: 1px solid var(--le-line);
1294		border-radius: 999px;
1295		background-color: var(--le-chrome-2);
1296		color: var(--le-muted);
1297		font: inherit;
1298		font-size: 92%;
1299		font-weight: 600;
1300		cursor: pointer;
1301	}
1302	.le-preset:hover
1303	{	border-color: var(--le-line-2);
1304		background-color: var(--le-chrome-3);
1305		color: var(--le-fg);
1306	}
1307
1308	.le-btn
1309	{	display: inline-flex;
1310		gap: 0.4em;
1311		align-items: center;
1312		justify-content: center;
1313		padding: 0.45em 0.9em;
1314		border: 1px solid var(--le-line);
1315		border-radius: 0.5em;
1316		background: transparent;
1317		color: var(--le-muted);
1318		font: inherit;
1319		font-size: 92%;
1320		font-weight: 600;
1321		white-space: nowrap;
1322		cursor: pointer;
1323	}
1324	.le-btn:hover:not(:disabled)
1325	{	border-color: var(--le-line-2);
1326		background-color: var(--le-chrome-3);
1327		color: var(--le-fg);
1328	}
1329	.le-btn:disabled
1330	{	opacity: 0.5;
1331		cursor: default;
1332	}
1333	.le-btn.le-primary
1334	{	border-color: transparent;
1335		background-color: var(--le-brand);
1336		color: #08231C;
1337		font-weight: 800;
1338	}
1339	.le-btn.le-primary:hover:not(:disabled)
1340	{	background-color: #15CE97;
1341	}
1342	.le-btn.le-danger
1343	{	border-color: rgba(239, 68, 68, 0.3);
1344		color: var(--le-danger-fg);
1345	}
1346	.le-btn.le-danger:hover:not(:disabled)
1347	{	background-color: var(--le-danger-soft);
1348		color: #FFF;
1349	}
1350	.le-btn-row-note
1351	{	flex: 1 1 auto;
1352		margin: 0 auto 0 0;
1353		font-size: 85%;
1354	}
1355	/*	ONE ROW, AND A ROW OF THE PANEL - not of the scrolling body above it.
1356
1357		It used to be sticky inside .le-body, which cost three things at once: a strip of dead
1358		space below it where the scrolled content showed through, a tooltip clipped by that
1359		element's own overflow:auto, and a footer tall enough - a full-width select with a paragraph
1360		under it - to crowd the panel it belongs to. As a sibling it sits flush on the bottom edge,
1361		needs no sticky, and can draw over the body. Reported with screenshots (Jacob, 26 Aug 26). */
1362	.le-foot
1363	{	display: flex;
1364		flex-wrap: wrap;
1365		align-items: center;
1366		gap: 0.5em;
1367		padding: 0.6em 0.75em;
1368		border-top: 1px solid var(--le-line);
1369		background: var(--le-chrome);
1370		border-radius: 0 0 1.1em 1.1em;
1371	}
1372	/*	Apply on to the left, the save group to the right. Neither shrinks - squeezed, the picker was
1373		cut to "Sa..." - so on a panel too narrow for both, the save group drops to a line of its own,
1374		still right-aligned and still whole. */
1375	.le-foot-scope
1376	{	display: flex;
1377		flex: 0 0 auto;
1378		gap: 0.5em;
1379		align-items: center;
1380		min-width: 0;
1381	}
1382	.le-foot-save
1383	{	display: flex;
1384		gap: 0.5em;
1385		align-items: center;
1386		margin-left: auto;
1387	}
1388	.le-scope-lbl
1389	{	display: inline-flex;
1390		align-items: center;
1391		gap: 0.3em;
1392		flex: 0 0 auto;
1393		font-size: 85%;
1394		opacity: 0.8;
1395		cursor: help;
1396	}
1397	.le-scope-lbl svg
1398	{	width: 1.05em;
1399		height: 1.05em;
1400		opacity: 0.7;
1401	}
1402	/*	Drawn as a choice already made - the green of the selected "Put it" button - because it is
1403		one: a dropdown in the footer's grey read as small print, and it decides which pages this
1404		edit reaches (Jacob, 28 Sep 26). The insert panels ask the same thing as buttons instead. */
1405	/*	Both classes: .le-scope-pick, further down, is the plain grey picker the changes list uses,
1406		and at equal weight the later rule won - the footer stayed grey. */
1407	.le-scope-pick.le-scope-foot
1408	{	flex: 0 0 auto;
1409		min-width: 7em;
1410		padding: 0.25em 0.45em;
1411		border-color: var(--le-brand);
1412		border-radius: 0.45em;
1413		background-color: var(--le-brand-soft);
1414		color: var(--le-brand-fg);
1415		font-size: 85%;
1416		font-weight: 600;
1417		cursor: pointer;
1418	}
1419	.le-scope-pick.le-scope-foot:hover
1420	{	border-color: var(--le-brand);
1421		color: var(--le-fg);
1422	}
1423	/*	In a Where section it is a field label like "Put it" above it, not footer small print. */
1424	.le-field > .le-scope-lbl
1425	{	opacity: 1;
1426	}
1427	/*	Two rows of the same buttons, one under the other: apart enough to read as two questions. */
1428	.le-where-scope
1429	{	margin-top: 0.5em;
1430	}
1431	/*	Same amber as the smart row's, for the same reason: this is the way INTO the widget, not
1432		another edit of the element it sits in. One colour, two places, one meaning. */
1433	.le-btn.le-wkey
1434	{	border-color: rgba(245, 158, 11, 0.55);
1435		background-color: rgba(245, 158, 11, 0.16);
1436		color: #FCD34D;
1437	}
1438	.le-btn.le-wkey:hover:not(:disabled)
1439	{	border-color: #F59E0B;
1440		background-color: rgba(245, 158, 11, 0.28);
1441		color: #FDE68A;
1442	}
1443
1444	/*	One answer for the whole action, above the rows. The rows are where a change says what it
1445		does; this is where somebody says what they meant. */
1446	.le-side-all
1447	{	display: flex;
1448		align-items: center;
1449		gap: 0.5em;
1450		padding: 0.5em 0.9em;
1451		border-bottom: 1px solid var(--le-line);
1452		font-size: 88%;
1453		color: var(--le-muted);
1454	}
1455	.le-side-all .le-scope-pick
1456	{	flex: 1 1 auto;
1457		font-size: 100%;
1458	}
1459
1460	/*	A choice of three, all three visible. A dropdown hides two of its answers until it is
1461		opened, and this is the one people change most often. */
1462	.le-seg
1463	{	display: flex;
1464		gap: 0.35em;
1465		width: 100%;
1466	}
1467	.le-seg-btn
1468	{	flex: 1 1 0;
1469		display: flex;
1470		flex-direction: column;
1471		align-items: center;
1472		gap: 0.25em;
1473		padding: 0.5em 0.3em;
1474		border: 1px solid var(--le-line);
1475		border-radius: 0.55em;
1476		background-color: var(--le-chrome-2);
1477		color: var(--le-muted);
1478		font: inherit;
1479		font-size: 85%;
1480		font-weight: 600;
1481		cursor: pointer;
1482	}
1483	.le-seg-btn svg
1484	{	width: 1.5em;
1485		height: 1.5em;
1486		fill: none;
1487		stroke: currentColor;
1488		stroke-width: 1.6;
1489		opacity: 0.85;
1490	}
1491	.le-seg-btn:hover
1492	{	border-color: var(--le-line-2);
1493		background-color: var(--le-chrome-3);
1494		color: var(--le-fg);
1495	}
1496	.le-seg-btn.le-on
1497	{	border-color: var(--le-brand);
1498		background-color: var(--le-brand-soft);
1499		color: var(--le-brand-fg);
1500	}
1501	.le-seg-btn.le-on svg
1502	{	opacity: 1;
1503	}
1504
1505	.le-timer
1506	{	display: flex;
1507		flex-direction: column;
1508		gap: 0.5em;
1509		width: 100%;
1510	}
1511	.le-timer .le-field
1512	{	margin: 0;
1513	}
1514	.le-inline-check
1515	{	display: flex;
1516		align-items: center;
1517		gap: 0.45em;
1518		margin-top: 0.4em;
1519		font-size: 88%;
1520		color: var(--le-muted);
1521		cursor: pointer;
1522	}
1523
1524	.le-scope-row
1525	{	display: flex;
1526		flex-wrap: wrap;
1527		align-items: center;
1528		gap: 0.4em;
1529		margin-top: 0.3em;
1530	}
1531	.le-scope-pick
1532	{	flex: 0 1 auto;
1533		max-width: 100%;
1534		padding: 0.1em 0.3em;
1535		border: 1px solid var(--le-line);
1536		border-radius: 0.3em;
1537		background: transparent;
1538		color: var(--le-muted);
1539		font: inherit;
1540		font-size: 80%;
1541	}
1542	.le-scope-pick:hover
1543	{	border-color: var(--le-line-2);
1544		color: var(--le-fg);
1545	}
1546	/*	Not a warning colour: the change is not broken, it is simply for somewhere else. A change
1547		that IS broken has no row at all. */
1548	.le-offpage
1549	{	padding: 0.05em 0.45em;
1550		border-radius: 0.9em;
1551		background: var(--le-info-soft);
1552		color: var(--le-info);
1553		font-size: 78%;
1554		white-space: nowrap;
1555	}
1556	.le-scope-chip
1557	{	display: inline-block;
1558		margin-left: 0.4em;
1559		padding: 0.05em 0.45em;
1560		border: 1px solid var(--le-line);
1561		border-radius: 0.9em;
1562		font-size: 78%;
1563		opacity: 0.75;
1564		vertical-align: 0.05em;
1565	}
1566
1567	/*	ALWAYS REACHABLE. A long panel \u2014 a widget with twenty settings, a list of FAQ rows \u2014 scrolled
1568		Save off the bottom, and a Save you have to hunt for reads as a panel with no way out
1569		(Jacob). Stuck to the bottom of the scrolling area instead, with the panel's own background
1570		behind it so the controls do not show through as they pass under.
1571		A z-index because a colour input's swatch and the rows editor both paint above the flow. */
1572	.le-btn-row
1573	{	position: sticky;
1574		bottom: 0;
1575		z-index: 3;
1576		background: var(--le-chrome);
1577		display: flex;
1578		gap: 0.6em;
1579		align-items: center;
1580		justify-content: flex-end;
1581		margin-top: 0.9em;
1582		padding-top: 0.9em;
1583		border-top: 1px solid var(--le-line);
1584	}
1585
1586	/* Formatting toolbar of the rich-text editor */
1587	.le-rt
1588	{	display: flex;
1589		flex-wrap: wrap;
1590		gap: 0.3em;
1591		margin-bottom: 0.6em;
1592	}
1593	.le-rt button
1594	{	min-width: 2.15em;
1595		height: 1.85em;
1596		padding: 0 0.45em;
1597		border: 1px solid var(--le-line);
1598		border-radius: 0.3em;
1599		background-color: var(--le-chrome-2);
1600		color: var(--le-fg);
1601		font: inherit;
1602		font-size: 92%;
1603		cursor: pointer;
1604	}
1605	.le-rt button:hover
1606	{	background-color: var(--le-chrome-3);
1607	}
1608
1609	/* ===== notices ===== */
1610	.le-notice
1611	{	display: flex;
1612		gap: 0.6em;
1613		align-items: flex-start;
1614		margin-bottom: 0.75em;
1615		padding: 0.6em 0.75em;
1616		border: 1px solid rgba(96, 165, 250, 0.25);
1617		border-radius: 0.6em;
1618		background-color: rgba(96, 165, 250, 0.07);
1619		color: var(--le-muted);
1620		font-size: 92%;
1621		line-height: 1.5;
1622	}
1623	.le-notice.le-warn
1624	{	border-color: rgba(239, 68, 68, 0.35);
1625		background-color: var(--le-danger-soft);
1626		color: var(--le-danger-fg);
1627	}
1628	.le-notice > div
1629	{	flex: 10000 1 0;
1630	}
1631	.le-notice b
1632	{	color: var(--le-fg);
1633	}
1634
1635	/* ===== navigate-mode banner ===== */
1636	.le-navbanner
1637	{	top: 1em;
1638		left: 50%;
1639		display: flex;
1640		gap: 0.6em;
1641		align-items: center;
1642		max-width: min(92vw, 42em);
1643		padding: 0.55em 1.1em 0.55em 0.9em;
1644		border: 1px solid rgba(96, 165, 250, 0.45);
1645		border-radius: 999px;
1646		/*	Opaque, unlike every other blue-tinted surface here: this one floats over the CUSTOMER's
1647			page rather than over the editor's own chrome. The mockup's translucent
1648			rgba(96,165,250,.15) was chosen against its near-black demo page; on a white site it
1649			turns into a smear with the page's own text showing through it. */
1650		background-color: var(--le-chrome);
1651		color: var(--le-fg);
1652		/*	No font-size of its own on purpose. The .le-root rule and this one land on the SAME
1653			element, so a percentage here re-bases on the host's initial 16px instead of the 13px
1654			every other surface inherits - which made this banner render ~13% larger than the rest
1655			of the chrome, and would put the em offsets below out of step with the sidebar's 32em. */
1656		font-weight: 500;
1657		line-height: 1.35;
1658		transform: translateX(-50%);
1659		box-shadow: 0 0.75em 2.3em rgba(0, 0, 0, 0.35);
1660		/*	Navigate mode means clicks reach the page like a visitor's. A chip pinned over the site's
1661			own menu must not be what swallows them - so the banner is see-through to the pointer,
1662			and the only interactive thing it ever holds opts back in below. */
1663		pointer-events: none;
1664	}
1665	/*	Centred on the page, not on the window: the sidebar covers the right 32em of it, and a banner
1666		centred on the whole viewport runs under it - which is where the end of the sentence went. */
1667	.le-navbanner.le-avoid-right
1668	{	transform: translateX(calc(-50% - 16em));
1669	}
1670	.le-navbanner.le-avoid-left
1671	{	transform: translateX(calc(-50% + 16em));
1672	}
1673	.le-navbanner b
1674	{	color: #FFF;
1675		font-weight: 700;
1676	}
1677	.le-navbanner kbd
1678	{	border-color: rgba(255, 255, 255, 0.25);
1679		background-color: rgba(255, 255, 255, 0.14);
1680		color: #FFF;
1681	}
1682	.le-navbanner svg
1683	{	color: #FBBF55;
1684	}
1685	.le-navbanner .le-btn
1686	{	flex: 0 0 auto;
1687		pointer-events: auto;
1688	}
1689
1690	/* ===== right sidebar ===== */
1691	.le-side
1692	{	/*	Below everything else the tool draws. Every .le-root shares one z-index, so which of two
1693			overlapping panels won came down to DOM order - and this one renders last, so it buried
1694			the floating toolbar whenever the two met. The toolbar is where the work happens and it
1695			is the one that moves, so it is the one that stays on top. (The panels also step around
1696			each other - see the docking effect in LiveTool - so this only settles the cases where
1697			they cannot both fit, like a 375px mobile preview.) */
1698		z-index: 2147483645;
1699		top: 0;
1700		right: 0;
1701		bottom: 0;
1702		display: flex;
1703		flex-direction: column;
1704		width: 32em;
1705		max-width: 92vw;
1706		border-left: 1px solid var(--le-line);
1707		background-color: var(--le-chrome);
1708		box-shadow: -0.75em 0 2.3em rgba(0, 0, 0, 0.35);
1709	}
1710	.le-side.le-left
1711	{	right: auto;
1712		left: 0;
1713		border-right: 1px solid var(--le-line);
1714		border-left: none;
1715		box-shadow: 0.75em 0 2.3em rgba(0, 0, 0, 0.35);
1716	}
1717	.le-side.le-collapsed
1718	{	transform: translateX(calc(100% - 2.15em));
1719	}
1720	.le-side.le-left.le-collapsed
1721	{	transform: translateX(calc(-100% + 2.15em));
1722	}
1723	.le-peek
1724	{	position: absolute;
1725		top: 50%;
1726		left: -1.1em;
1727		display: grid;
1728		place-items: center;
1729		width: 2.15em;
1730		height: 4.3em;
1731		border: 1px solid var(--le-line);
1732		border-left: none;
1733		border-radius: 0 1.1em 1.1em 0;
1734		background-color: var(--le-chrome);
1735		color: var(--le-muted);
1736		cursor: pointer;
1737		transform: translateY(-50%);
1738	}
1739	/*	Re-asserted above the [data-tooltip] rule near the top of this sheet, which sets
1740		position:relative on every tooltip trigger and outranks the .le-peek rule (0,2,0 against
1741		0,1,0). This tab is a trigger AND positions itself; losing that put it back in the flex flow,
1742		where it held a 4em band of nothing at the top of the sidebar while its own top:50% still
1743		drew it at the middle. Any other trigger that positions itself has to do the same. */
1744	.le-root.le-side .le-peek
1745	{	position: absolute;
1746	}
1747	.le-side.le-left .le-peek
1748	{	right: -1.1em;
1749		left: auto;
1750		border-right: none;
1751		border-left: 1px solid var(--le-line);
1752		border-radius: 1.1em 0 0 1.1em;
1753	}
1754
1755	.le-side-top
1756	{	display: flex;
1757		flex: 0 0 auto;
1758		gap: 0.75em;
1759		align-items: center;
1760		padding: 0.9em 1.1em;
1761		border-bottom: 1px solid var(--le-line);
1762	}
1763	.le-logo
1764	{	display: grid;
1765		flex: 0 0 auto;
1766		place-items: center;
1767		overflow: hidden;
1768		width: 2.3em;
1769		height: 2.3em;
1770		border-radius: 0.55em;
1771		background-color: #FFF;
1772	}
1773	.le-logo img
1774	{	display: block;
1775		width: 1.7em;
1776		height: 1.7em;
1777	}
1778	.le-side-title
1779	{	flex: 10000 1 0;
1780		min-width: 0;
1781	}
1782	.le-side-title b
1783	{	display: block;
1784		font-size: 108%;
1785		font-weight: 800;
1786	}
1787	.le-ctx
1788	{	display: flex;
1789		gap: 0.4em;
1790		align-items: center;
1791		overflow: hidden;
1792		color: var(--le-muted);
1793		font-size: 85%;
1794	}
1795	.le-ctx-campaign
1796	{	overflow: hidden;
1797		max-width: 42%;
1798		font-weight: 600;
1799		white-space: nowrap;
1800		text-overflow: ellipsis;
1801	}
1802	.le-ctx-action
1803	{	overflow: hidden;
1804		flex: 10000 1 0;
1805		min-width: 0;
1806		color: var(--le-fg);
1807		font-weight: 700;
1808		white-space: nowrap;
1809		text-overflow: ellipsis;
1810	}
1811
1812	.le-urlbar-wrap
1813	{	flex: 0 0 auto;
1814		padding: 0.75em 0.9em;
1815		border-bottom: 1px solid var(--le-line);
1816	}
1817	.le-urlbar
1818	{	display: flex;
1819		gap: 0.3em;
1820		align-items: center;
1821		padding: 0.3em 0.3em 0.3em 0.75em;
1822		border: 1px solid var(--le-line);
1823		border-radius: 0.7em;
1824		background-color: var(--le-chrome-2);
1825	}
1826	.le-urlbar input
1827	{	flex: 10000 1 0;
1828		min-width: 0;
1829		padding: 0.45em 0.15em;
1830		border: none;
1831		background: transparent;
1832		color: var(--le-fg);
1833		outline: none;
1834		font-family: var(--le-mono);
1835		font-size: 92%;
1836	}
1837	.le-urlbar .le-go
1838	{	padding: 0.45em 1.1em;
1839		border: none;
1840		border-radius: 0.45em;
1841		background-color: var(--le-brand);
1842		color: #08231C;
1843		font: inherit;
1844		font-weight: 800;
1845		cursor: pointer;
1846	}
1847
1848	.le-changes-head
1849	{	display: flex;
1850		flex: 0 0 auto;
1851		gap: 0.75em;
1852		align-items: center;
1853		padding: 0.75em 0.9em;
1854		border-bottom: 1px solid var(--le-line);
1855		background-color: var(--le-chrome-2);
1856	}
1857	.le-changes-head b
1858	{	font-size: 100%;
1859	}
1860	.le-count
1861	{	padding: 0.15em 0.6em;
1862		border: 1px solid var(--le-line);
1863		border-radius: 999px;
1864		background-color: rgba(255, 255, 255, 0.06);
1865		color: var(--le-muted);
1866		font-family: var(--le-mono);
1867		font-size: 85%;
1868	}
1869	.le-nav-row
1870	{	display: inline-flex;
1871		gap: 0.3em;
1872		align-items: center;
1873		margin-left: auto;
1874	}
1875	.le-nav-row .le-icon
1876	{	width: 1.85em;
1877		height: 1.85em;
1878	}
1879
1880	.le-discardrow
1881	{	display: flex;
1882		flex: 0 0 auto;
1883		flex-wrap: wrap;
1884		gap: 0.4em;
1885		align-items: center;
1886		justify-content: flex-end;
1887		padding: 0.5em 0.9em;
1888		border-bottom: 1px solid var(--le-line);
1889	}
1890	.le-discardrow > span
1891	{	flex: 1 1 10em;
1892		color: var(--le-muted);
1893		font-size: 92%;
1894	}
1895	.le-side-body
1896	{	display: grid;
1897		flex: 10000 1 auto;
1898		gap: 0.6em;
1899		align-content: start;
1900		overflow: auto;
1901		padding: 0.75em;
1902	}
1903	.le-change
1904	{	display: grid;
1905		gap: 0.6em;
1906		padding: 0.75em 0.9em;
1907		border: 1px solid var(--le-line);
1908		border-radius: 0.75em;
1909		background-color: var(--le-chrome-2);
1910		cursor: pointer;
1911	}
1912	.le-change.le-on
1913	{	border-color: rgba(18, 184, 134, 0.5);
1914		box-shadow: 0 0 0 1px rgba(18, 184, 134, 0.25);
1915	}
1916	.le-change.le-undone
1917	{	opacity: 0.5;
1918	}
1919	.le-change-top
1920	{	display: flex;
1921		gap: 0.6em;
1922		align-items: flex-start;
1923	}
1924	.le-tag
1925	{	flex: 0 0 auto;
1926		margin-top: 0.1em;
1927		padding: 0.23em 0.55em;
1928		border-radius: 0.3em;
1929		font-size: 77%;
1930		font-weight: 800;
1931		letter-spacing: 0.04em;
1932		line-height: 1.2;
1933		text-transform: uppercase;
1934	}
1935	.le-tag.le-t-text { background-color: rgba(96, 165, 250, 0.15); color: #7FB5FF }
1936	.le-tag.le-t-style { background-color: rgba(168, 85, 247, 0.15); color: #C59BFF }
1937	.le-tag.le-t-insert { background-color: rgba(18, 184, 134, 0.18); color: #4DD5A9 }
1938	.le-tag.le-t-delete { background-color: rgba(239, 68, 68, 0.15); color: #FF8787 }
1939	.le-tag.le-t-move { background-color: rgba(245, 158, 11, 0.15); color: #FBBF55 }
1940	.le-tag.le-t-behavior { background-color: rgba(96, 165, 250, 0.15); color: #7FB5FF }
1941	.le-tag.le-t-other { background-color: rgba(255, 255, 255, 0.08); color: var(--le-muted) }
1942	.le-change-desc
1943	{	flex: 10000 1 0;
1944		color: var(--le-fg);
1945		font-weight: 600;
1946		line-height: 1.35;
1947		word-break: break-word;
1948	}
1949	.le-selector
1950	{	display: inline-block;
1951		overflow: hidden;
1952		max-width: 100%;
1953		padding: 0.23em 0.55em;
1954		border-radius: 0.3em;
1955		background-color: rgba(255, 255, 255, 0.03);
1956		color: var(--le-muted);
1957		font-family: var(--le-mono);
1958		font-size: 85%;
1959		white-space: nowrap;
1960		text-overflow: ellipsis;
1961	}
1962	.le-diff
1963	{	display: grid;
1964		grid-template-columns: auto 1fr;
1965		gap: 0.3em 0.75em;
1966		padding: 0.6em 0.75em;
1967		border-radius: 0.45em;
1968		background-color: rgba(0, 0, 0, 0.22);
1969		font-family: var(--le-mono);
1970		font-size: 85%;
1971	}
1972	.le-diff .le-lbl
1973	{	padding-top: 0.15em;
1974		color: var(--le-muted-2);
1975		font-size: 77%;
1976		letter-spacing: 0.04em;
1977	}
1978	.le-diff .le-before
1979	{	overflow: hidden;
1980		color: #FF9B9B;
1981		text-decoration: line-through;
1982		text-decoration-color: rgba(255, 155, 155, 0.4);
1983		word-break: break-word;
1984	}
1985	.le-diff .le-after
1986	{	overflow: hidden;
1987		color: #7BDFAE;
1988		word-break: break-word;
1989	}
1990	.le-change-actions
1991	{	display: flex;
1992		gap: 0.45em;
1993		align-items: center;
1994		justify-content: flex-end;
1995	}
1996	.le-empty
1997	{	padding: 3em 1.5em;
1998		border: 1px dashed var(--le-line);
1999		border-radius: 0.75em;
2000		color: var(--le-muted);
2001		text-align: center;
2002	}
2003	.le-empty h4
2004	{	margin: 0 0 0.3em;
2005		color: var(--le-fg);
2006		font-size: 108%;
2007	}
2008	.le-empty p
2009	{	margin: 0;
2010		font-size: 92%;
2011		line-height: 1.5;
2012	}
2013
2014	.le-side-foot
2015	{	display: grid;
2016		flex: 0 0 auto;
2017		gap: 0.6em;
2018		padding: 0.9em;
2019		border-top: 1px solid var(--le-line);
2020	}
2021	.le-tip
2022	{	display: flex;
2023		flex: 0 0 auto;
2024		gap: 0.75em;
2025		align-items: flex-start;
2026		margin: 0.75em 0.9em;
2027		padding: 0.75em 0.9em;
2028		border: 1px solid rgba(96, 165, 250, 0.18);
2029		border-radius: 0.6em;
2030		background-color: rgba(96, 165, 250, 0.06);
2031		color: var(--le-muted);
2032		font-size: 92%;
2033	}
2034	.le-tip b
2035	{	color: var(--le-fg);
2036	}
2037	.le-tip button
2038	{	margin-left: auto;
2039		padding: 0 0.3em;
2040		border: none;
2041		background: transparent;
2042		color: var(--le-muted);
2043		font: inherit;
2044		cursor: pointer;
2045	}
2046
2047	/*	The way back after the editor has been put away. Top-centre and in the brand colour, be
2047cause
2048		while it is showing this is the ONLY control on screen - the sidebar and its Done button are
2049		hidden too - and in a corner it read as a dismissed notification. Nothing else of the editor
2050		is rendered meanwhile, so it cannot collide with the banners or the device pill. */
2051	.le-reopen
2052	{	top: 1.1em;
2053		left: 50%;
2054		transform: translateX(-50%);
2055	}
2056	.le-reopen-btn
2057	{	display: flex;
2058		gap: 0.6em;
2059		align-items: center;
2060		max-width: 92vw;
2061		padding: 0.7em 1.2em 0.7em 0.9em;
2062		border: none;
2063		border-radius: 999px;
2064		background-color: var(--le-brand);
2065		color: #08231C;
2066		font: inherit;
2067		font-weight: 800;
2068		white-space: nowrap;
2069		cursor: pointer;
2070		box-shadow: var(--le-shadow), 0 0 0 0.35em rgba(18, 184, 134, 0.2);
2071	}
2072	.le-reopen-btn:hover
2073	{	background-color: #16CE97;
2074	}
2075	.le-reopen-btn img
2076	{	display: block;
2077		flex: 0 0 auto;
2078		width: 1.4em;
2079		height: 1.4em;
2080	}
2081	.le-reopen-btn svg
2082	{	flex: 0 0 auto;
2083		width: 1.2em;
2084		height: 1.2em;
2085	}
2086	.le-reopen-n
2087	{	padding: 0.15em 0.6em;
2088		border-radius: 999px;
2089		background-color: rgba(0, 0, 0, 0.22);
2090		font-size: 85%;
2091		font-weight: 700;
2092	}
2093
2094	/* ===== device picker ===== */
2095	.le-devices
2096	{	bottom: 1.4em;
2097		left: 50%;
2098		display: flex;
2099		gap: 0.15em;
2100		padding: 0.3em;
2101		border: 1px solid var(--le-line);
2102		border-radius: 999px;
2103		background-color: var(--le-chrome);
2104		transform: translateX(-50%);
2105		box-shadow: var(--le-shadow);
2106	}
2107	.le-device
2108	{	display: inline-flex;
2109		gap: 0.45em;
2110		align-items: center;
2111		padding: 0.45em 0.9em;
2112		border: none;
2113		border-radius: 999px;
2114		background: transparent;
2115		color: var(--le-muted);
2116		font: inherit;
2117		font-size: 92%;
2118		font-weight: 600;
2119		cursor: pointer;
2120	}
2121	.le-device:hover
2122	{	background-color: var(--le-chrome-2);
2123		color: var(--le-fg);
2124	}
2125	.le-device.le-on
2126	{	background-color: var(--le-chrome-3);
2127		color: var(--le-fg);
2128	}
2129	.le-device .le-size
2130	{	opacity: 0.7;
2131		font-family: var(--le-mono);
2132		font-size: 77%;
2133	}
2134
2135	/* ===== in-panel AI card ===== */
2136	.le-aicard
2137	{	margin-bottom: 1em;
2138		padding: 0.75em;
2139		border: 1px solid rgba(168, 85, 247, 0.28);
2140		border-radius: 0.7em;
2141		background: linear-gradient(160deg, rgba(168, 85, 247, 0.1), rgba(18, 184, 134, 0.06));
2142	}
2143	.le-aicard-head
2144	{	display: flex;
2145		gap: 0.5em;
2146		align-items: center;
2147		margin-bottom: 0.6em;
2148	}
2149	.le-aicard-ico
2150	{	display: grid;
2151		flex: 0 0 auto;
2152		place-items: center;
2153		width: 1.7em;
2154		height: 1.7em;
2155		border-radius: 0.4em;
2156		background: linear-gradient(135deg, #A855F7, #12B886);
2157		color: #FFF;
2158	}
2159	.le-aicard-ico svg
2160	{	width: 0.95em;
2161		height: 0.95em;
2162	}
2163	.le-aicard-head h5
2164	{	margin: 0;
2165		font-size: 95%;
2166		font-weight: 700;
2167	}
2168	.le-aicard > .le-area
2169	{	margin-bottom: 0.5em;
2170	}
2171	.le-chips
2172	{	display: flex;
2173		flex-wrap: wrap;
2174		gap: 0.35em;
2175		margin-bottom: 0.6em;
2176	}
2177	.le-chip
2178	{	padding: 0.25em 0.65em;
2179		border: 1px solid var(--le-line-2);
2180		border-radius: 999px;
2181		background-color: rgba(255, 255, 255, 0.05);
2182		color: var(--le-muted);
2183		font: inherit;
2184		font-size: 88%;
2185		cursor: pointer;
2186	}
2187	.le-chip:hover:not(:disabled)
2188	{	border-color: rgba(168, 85, 247, 0.5);
2189		background-color: rgba(168, 85, 247, 0.15);
2190		color: var(--le-fg);
2191	}
2192	.le-chip:disabled
2193	{	opacity: 0.4;
2194		cursor: default;
2195	}
2196	.le-btn.le-ai
2197	{	width: 100%;
2198		justify-content: center;
2199		border-color: rgba(168, 85, 247, 0.45);
2200		background-color: rgba(168, 85, 247, 0.18);
2201		color: #E4CCFF;
2202	}
2203	.le-btn.le-ai:hover:not(:disabled)
2204	{	background-color: rgba(168, 85, 247, 0.3);
2205		color: #FFF;
2206	}
2207	.le-aicard-result
2208	{	margin-top: 0.7em;
2209		padding-top: 0.7em;
2210		border-top: 1px solid var(--le-line);
2211	}
2212	.le-aicard-label
2213	{	margin-bottom: 0.35em;
2214		color: var(--le-muted-2);
2215		font-size: 78%;
2216		font-weight: 700;
2217		letter-spacing: 0.06em;
2218		text-transform: uppercase;
2219	}
2220	.le-aicard-text
2221	{	max-height: 12em;
2222		overflow-y: auto;
2223		padding: 0.55em 0.7em;
2224		border: 1px solid var(--le-line);
2225		border-radius: 0.5em;
2226		background-color: rgba(0, 0, 0, 0.25);
2227		white-space: pre-wrap;
2228		word-break: break-word;
2229	}
2230	.le-aicard-text.le-mono
2231	{	font-family: var(--le-mono);
2232		font-size: 90%;
2233	}
2234	.le-aicard-result .le-btn-row
2235	{	/* The result already has the rule above it - the shared row would draw a second one. */
2236		margin-top: 0.5em;
2237		padding-top: 0;
2238		border-top: none;
2239	}
2240	/*	A style answer, as the rows it will be written as - so it can be read before it is applied. */
2241	.le-decls
2242	{	max-height: 14em;
2243		overflow-y: auto;
2244		border: 1px solid var(--le-line);
2245		border-radius: 0.5em;
2246		background-color: rgba(0, 0, 0, 0.25);
2247	}
2248	.le-decl
2249	{	display: flex;
2250		gap: 0.6em;
2251		align-items: center;
2252		padding: 0.35em 0.7em;
2253		border-bottom: 1px solid var(--le-line);
2254	}
2255	.le-decl:last-child
2256	{	border-bottom: none;
2257	}
2258	.le-decl code
2259	{	flex: 0 0 auto;
2260		color: var(--le-muted);
2261		font-family: var(--le-mono);
2262		font-size: 88%;
2263	}
2264	.le-decl span
2265	{	flex: 1 1 auto;
2266		overflow: hidden;
2267		font-family: var(--le-mono);
2268		font-size: 88%;
2269		text-align: right;
2270		text-overflow: ellipsis;
2271		white-space: nowrap;
2272	}
2273	.le-decl-swatch
2274	{	flex: 0 0 auto;
2275		width: 1.1em;
2276		height: 1.1em;
2277		border-radius: 0.25em;
2278		box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.35);
2279	}
2280
2281	/* ===== media library ===== */
2282	/*	A widget's list settings \u2014 the FAQ's questions, the wheel's segments. One editor for all of
2283		them, so a row is a small stack of labelled fields with its own number and a way out. */
2284	/*	A control that needs the width: the label goes above rather than 
2284into the 7.2em column, so
2285		a list of rows has the whole panel to lay out in. */
2286	/*	THE ONE TILE THAT MATTERS when a widget is selected. Everything else on that row edits the
2287		markup a widget produced \u2014 its text, its colours \u2014 which is almost never what somebody
2288		wants after clicking a countdown. So it is drawn as the primary action, in the accent the
2289		editor already uses for Save, rather than as the fifth grey tile along. */
2290	.le-tile.le-tile-primary
2291	{	border-color: var(--le-accent);
2292		background: color-mix(in srgb, var(--le-accent) 18%, var(--le-chrome-2));
2293	}
2294	.le-tile.le-tile-primary:hover
2295	{	background: color-mix(in srgb, var(--le-accent) 30%, var(--le-chrome-2));
2296	}
2297	.le-tile.le-tile-primary .le-tile-ico
2298	{	color: var(--le-accent);
2299	}
2300	.le-field-wide
2301	{	display: block;
2302	}
2303	.le-field-wide > label
2304	{	display: block;
2305		margin-bottom: 0.35em;
2306	}
2307	/*	AND THE CONTROL TAKES THE WIDTH IT WAS GIVEN. Dropping the label above freed the whole row,
2308		but the group is a wrapping flex row and a flex item is only as wide as its content - so a
2309		list of FAQ rows or numbered steps still drew in half the panel, each input a few words
2310		wide, with the other half empty beside it (Jacob's screenshots, 28 Sep 26). Stacked and
2311		stretched instead: a wide control is one thing per line, full width. */
2312	.le-field-wide > .le-field-group
2313	{	flex-direction: column;
2314		flex-wrap: nowrap;
2315		align-items: stretch;
2316	}
2317	/*	A widget's checkbox setting: the box, then its words, on one line across the panel. */
2318	.le-wcheck
2319	{	padding: 0.15em 0 0.3em;
2320	}
2321	.le-wcheck .le-inline-check
2322	{	margin-top: 0;
2323		color: var(--le-fg);
2324		font-size: 92%;
2325	}
2326	/*	Inside a row the labels go above too \u2014 a row is already inside one indent, and a second
2327		label column would leave the input a few characters wide. */
2328	.le-wr .le-field
2329	{	display: block;
2330	}
2331	.le-wr .le-field > label
2332	{	display: block;
2333		margin-bottom: 0.2em;
2334		opacity: 0.7;
2335		text-transform: none;
2336	}
2337	.le-wr .le-field-group > *
2338	{	width: 100%;
2339	}
2340	.le-wr
2341	{	display: flex;
2342		flex-direction: column;
2343		gap: 0.5em;
2344	}
2345	.le-wr-row
2346	{	border: 1px solid var(--le-line);
2347		border-radius: 0.4em;
2348		padding: 0.5em 0.6em;
2349		background: var(--le-chrome-2);
2350	}
2351	.le-wr-head
2352	{	display: flex;
2353		align-items: center;
2354		justify-content: space-between;
2355		margin-bottom: 0.3em;
2356		opacity: 0.8;
2357	}
2358	.le-wr-x
2359	{	background: transparent;
2360		border: 0;
2361		color: inherit;
2362		cursor: pointer;
2363		font-size: 130%;
2364		line-height: 1;
2365		padding: 0 0.2em;
2366	}
2367	.le-wr-x:hover
2368	{	color: #FCA5A5;
2369	}
2370	.le-wr-add
2371	{	align-self: flex-start;
2372		background: var(--le-chrome-3);
2373		border: 1px solid var(--le-line);
2374		border-radius: 0.35em;
2375		color: inherit;
2376		cursor: pointer;
2377		font: inherit;
2378		padding: 0.35em 0.8em;
2379	}
2380	.le-wr-add:hover
2381	{	background: var(--le-accent);
2382		color: #fff;
2383	}
2384	/*	A checkbox big enough to hit, in the editor's own colour rather than the browser's blue. */
2385	.le-check
2386	{	width: 1.15em;
2387		height: 1.15em;
2388		accent-color: var(--le-accent);
2389		cursor: pointer;
2390	}
2391	.le-ta
2392	{	min-height: 4.5em;
2393		resize: vertical;
2394		font: inherit;
2395	}
2396	/*	The widget list: scrolls rather than paging, because the whole point of showing it as a
2397		list is that you can read what is on offer without opening anything. Capped at about six
2398		rows so the Where field above it and the note below stay on screen with it. */
2399	.le-wl
2400	{	max-height: 13em;
2401		overflow-y: auto;
2402		display: flex;
2403		flex-direction: column;
2404		gap: 0.15em;
2405		border: 1px solid var(--le-line);
2406		border-radius: 0.4em;
2407		padding: 0.25em;
2408		background: var(--le-chrome-2);
2409	}
2410	.le-wl-item
2411	{	display: flex;
2412		align-items: center;
2413		gap: 0.5em;
2414		width: 100%;
2415		text-align: left;
2416		background: transparent;
2417		border: 0;
2418		border-radius: 0.3em;
2419		color: inherit;
2420		font: inherit;
2421		padding: 0.45em 0.6em;
2422		cursor: pointer;
2423	}
2424	.le-wl-item:hover
2425	{	background: var(--le-chrome-3);
2426	}
2427	.le-wl-item.on
2428	{	background: var(--le-accent);
2429		color: #fff;
2430	}
2431	.le-wl-name
2432	{	flex: 1 1 auto;
2433		overflow: hidden;
2434		text-overflow: ellipsis;
2435		white-space: nowrap;
2436	}
2437	.le-wl-note
2438	{	font-size: 85%;
2439		opacity: 0.75;
2440		line-height: 1.45;
2441	}
2442	/*	The icon grid. Names under the glyphs on purpose - the icon font comes cross-origin from the
2443		panel host and may not arrive, and a grid of empty boxes is worse than the text box this
2444		replaced. With the name there it still picks correctly either way. */
2445	.le-icogrid
2446	{	display: grid;
2447		/*	The field's value column is a FLEX ROW, so a grid dropped in it sizes to its content -
2448			which for auto-fill means one column, and 52 icons drew as 52 stacked rows. Width first,
2449			then the columns have something to fill. */
2450		width: 100%;
2451		grid-template-columns: repeat(auto-fill, minmax(5.2em, 1fr));
2452		gap: 0.35em;
2453		max-height: 16em;
2454		overflow-y: auto;
2455		margin-top: 0.5em;
2456	}
2457	.le-icogrid-i
2458	{	display: flex;
2459		flex-direction: column;
2460		align-items: center;
2461		gap: 0.25em;
2462		padding: 0.45em 0.2em;
2463		border: 1px solid var(--le-line);
2464		border-radius: 0.35em;
2465		background: none;
2466		color: inherit;
2467		cursor: pointer;
2468		font: inherit;
2469	}
2470	.le-icogrid-i:hover
2471	{	border-color: var(--le-line-2);
2472	}
2473	.le-icogrid-i.is-on
2474	{	border-color: var(--le-brand);
2475		background: var(--le-brand-soft);
2476	}
2477	.le-icogrid-i > span
2478	{	font-size: 78%;
2479		opacity: 0.8;
2480		overflow: hidden;
2481		text-overflow: ellipsis;
2482		white-space: nowrap;
2483		max-width: 100%;
2484	}
2485	.le-ico
2486	{	font-size: 1.25em;
2487		font-style: normal;
2488		line-height: 1;
2489		min-height: 1.25em;
2490	}
2491	.le-linkbtn
2492	{	background: none;
2493		border: 0;
2494		padding: 0;
2495		color: var(--le-brand-fg);
2496		cursor: pointer;
2497		font: inherit;
2498		text-decoration: underline;
2499	}
2500	.le-media-grid
2501	{	display: grid;
2502		grid-template-columns: repeat(auto-fill, minmax(8em, 1fr));
2503		gap: 0.6em;
2504	}
2505	.le-media
2506	{	display: flex;
2507		flex-direction: column;
2508		gap: 0.4em;
2509		padding: 0.5em;
2510		border: 1px solid var(--le-line);
2511		border-radius: 0.55em;
2512		background-color: var(--le-chrome-2);
2513		color: inherit;
2514		font: inherit;
2515		cursor: pointer;
2516	}
2517	.le-media:hover
2518	{	border-color: rgba(18, 184, 134, 0.5);
2519		background-color: var(--le-chrome-3);
2520	}
2521	.le-media-thumb
2522	{	display: grid;
2523		place-items: center;
2524		height: 5em;
2525		overflow: hidden;
2526		border-radius: 0.35em;
2527		/*	A checkerboard, so a transparent PNG does not read as a missing one. */
2528		background-color: #FFF;
2529		background-image:
2530			linear-gradient(45deg, #D8DDE8 25%, transparent 25%, transparent 75%, #D8DDE8 75%),
2531			linear-gradient(45deg, #D8DDE8 25%, transparent 25%, transparent 75%, #D8DDE8 75%);
2532		background-position: 0 0, 0.5em 0.5em;
2533		background-size: 1em 1em;
2534		color: var(--le-muted-2);
2535	}
2536	.le-media-thumb img
2537	{	max-width: 100%;
2538		max-height: 100%;
2539		object-fit: contain;
2540	}
2541	.le-media-name
2542	{	overflow: hidden;
2543		color: var(--le-muted);
2544		font-size: 85%;
2545		white-space: nowrap;
2546		text-overflow: ellipsis;
2547	}
2548
2549	/* ===== page-level AI (sidebar) ===== */
2550	.le-pageai
2551	{	flex: 0 0 auto;
2552		margin: 0 0.9em 0.6em;
2553		border: 1px solid rgba(168, 85, 247, 0.28);
2554		border-radius: 0.6em;
2555		background: linear-gradient(160deg, rgba(168, 85, 247, 0.12), rgba(18, 184, 134, 0.05));
2556	}
2557	.le-pageai-head
2558	{	display: flex;
2559		width: 100%;
2560		gap: 0.6em;
2561		align-items: center;
2562		padding: 0.6em 0.7em;
2563		border: none;
2564		background: transparent;
2565		color: inherit;
2566		font: inherit;
2567		text-align: left;
2568		cursor: pointer;
2569	}
2570	.le-pageai-ico
2571	{	display: grid;
2572		flex: 0 0 auto;
2573		place-items: center;
2574		width: 1.7em;
2575		height: 1.7em;
2576		border-radius: 0.4em;
2577		background: linear-gradient(135deg, #A855F7, #12B886);
2578		color: #FFF;
2579	}
2580	.le-pageai-ico svg
2581	{	width: 0.95em;
2582		height: 0.95em;
2583	}
2584	.le-pageai-txt
2585	{	flex: 1 1 auto;
2586		min-width: 0;
2587	}
2588	.le-pageai-txt b
2589	{	display: block;
2590		font-size: 95%;
2591		font-weight: 700;
2592	}
2593	.le-pageai-txt small
2594	{	display: block;
2595		overflow: hidden;
2596		color: var(--le-muted);
2597		font-size: 85%;
2598		white-space: nowrap;
2599		text-overflow: ellipsis;
2600	}
2601	.le-pageai-head > svg
2602	{	flex: 0 0 auto;
2603		color: var(--le-muted-2);
2604	}
2605	.le-pageai-body
2606	{	padding: 0 0.7em 0.7em;
2607	}
2608	.le-pageai-body .le-area
2609	{	min-height: 3.6em;
2610		margin-bottom: 0.5em;
2611	}
2612	.le-pageai-body .le-btn-bar
2613	{	justify-content: flex-end;
2614	}
2615	.le-pageai-body .le-btn.le-ai
2616	{	/* Full width is right for the card inside a sub-panel, not for a two-button row. */
2617		width: auto;
2618	}
2619
2620	/* ===== command palette ===== */
2621	.le-cmdk-overlay
2622	{	inset: 0;
2623		z-index: 2147483647;
2624		display: flex;
2625		align-items: flex-start;
2626		justify-content: center;
2627		padding-top: 12vh;
2628		background-color: rgba(4, 8, 20, 0.55);
2629	}
2630	.le-cmdk
2631	{	display: flex;
2632		flex-direction: column;
2633		width: min(38em, 92vw);
2634		max-height: 66vh;
2635		overflow: hidden;
2636		border: 1px solid var(--le-line-2);
2637		border-radius: 0.9em;
2638		background-color: var(--le-chrome);
2639		box-shadow: var(--le-shadow);
2640	}
2641	.le-cmdk-input
2642	{	display: flex;
2643		flex: 0 0 auto;
2644		gap: 0.6em;
2645		align-items: center;
2646		padding: 0.75em 0.95em;
2647		border-bottom: 1px solid var(--le-line);
2648	}
2649	.le-cmdk-input svg
2650	{	width: 1.2em;
2651		height: 1.2em;
2652		color: var(--le-muted-2);
2653	}
2654	.le-cmdk-input input
2655	{	flex: 1 1 auto;
2656		min-width: 0;
2657		padding: 0;
2658		border: none;
2659		outline: none;
2660		background: transparent;
2661		color: var(--le-fg);
2662		font: inherit;
2663		font-size: 115%;
2664	}
2665	.le-cmdk-input input::placeholder
2666	{	color: var(--le-muted-2);
2667	}
2668	.le-cmdk-list
2669	{	flex: 1 1 auto;
2670		min-height: 0;
2671		overflow-y: auto;
2672		padding: 0.4em;
2673	}
2674	.le-cmdk-group
2675	{	padding: 0.7em 0.6em 0.3em;
2676		color: var(--le-muted-2);
2677		font-size: 78%;
2678		font-weight: 700;
2679		letter-spacing: 0.06em;
2680		text-transform: uppercase;
2681	}
2682	.le-cmdk-item
2683	{	display: flex;
2684		width: 100%;
2685		gap: 0.7em;
2686		align-items: center;
2687		padding: 0.5em 0.6em;
2688		border: none;
2689		border-radius: 0.5em;
2690		background: transparent;
2691		color: inherit;
2692		font: inherit;
2693		text-align: left;
2694		cursor: pointer;
2695	}
2696	.le-cmdk-item.le-on
2697	{	background-color: var(--le-chrome-3);
2698	}
2699	.le-cmdk-txt
2700	{	flex: 1 1 auto;
2701		min-width: 0;
2702	}
2703	.le-cmdk-txt b
2704	{	display: block;
2705		overflow: hidden;
2706		font-weight: 600;
2707		white-space: nowrap;
2708		text-overflow: ellipsis;
2709	}
2710	.le-cmdk-txt small
2711	{	display: block;
2712		overflow: hidden;
2713		color: var(--le-muted);
2714		font-size: 85%;
2715		white-space: nowrap;
2716		text-overflow: ellipsis;
2717	}
2718	.le-cmdk-key
2719	{	flex: 0 0 auto;
2720		padding: 0.1em 0.45em;
2721		border: 1px solid var(--le-line);
2722		border-radius: 0.3em;
2723		background-color: rgba(255, 255, 255, 0.06);
2724		color: var(--le-muted);
2725		font-family: var(--le-mono);
2726		font-size: 82%;
2727	}
2728	.le-cmdk-empty
2729	{	padding: 1.6em 0.8em;
2730		color: var(--le-muted-2);
2731		text-align: center;
2732	}
2733	.le-cmdk-foot
2734	{	display: flex;
2735		flex: 0 0 auto;
2736		gap: 1.2em;
2737		padding: 0.55em 0.95em;
2738		border-top: 1px solid var(--le-line);
2739		background-color: var(--le-chrome-2);
2740		color: var(--le-muted-2);
2741		font-size: 82%;
2742	}
2743	.le-cmdk-foot kbd
2744	{	margin-right: 0.2em;
2745	}
2746
2747	/* ===== "Insert variable" picker ===== */
2748	.le-vp-overlay
2749	{	inset: 0;
2750		z-index: 2147483647;
2751		display: flex;
2752		align-items: flex-start;
2753		justify-content: center;
2754		padding-top: 6vh;
2755		background-color: rgba(4, 8, 20, 0.55);
2756	}
2757	.le-vp
2758	{	display: flex;
2759		flex-direction: column;
2760		width: min(46em, 92vw);
2761		max-height: 82vh;
2762		overflow: hidden;
2763		border: 1px solid var(--le-line-2);
2764		border-radius: 0.9em;
2765		background-color: var(--le-chrome);
2766		box-shadow: var(--le-shadow);
2767	}
2768	.le-vp-head
2769	{	display: flex;
2770		flex: 0 0 auto;
2771		gap: 0.6em;
2772		align-items: flex-start;
2773		padding: 0.85em 1.1em;
2774		border-bottom: 1px solid var(--le-line);
2775		background-color: var(--le-chrome-2);
2776	}
2777	.le-vp-head h3
2778	{	margin: 0;
2779		font-size: 108%;
2780		font-weight: 700;
2781	}
2782	.le-vp-head p
2783	{	margin: 0.15em 0 0;
2784		color: var(--le-muted);
2785		font-size: 88%;
2786	}
2787	.le-vp-head .le-icon
2788	{	margin-left: auto;
2789	}
2790	.le-vp-search
2791	{	flex: 0 0 auto;
2792		padding: 0.6em 1.1em;
2793		border-bottom: 1px solid var(--le-line);
2794	}
2795	.le-vp-body
2796	{	/*	Takes the leftover height, and is the ONLY part that gives it back - every sibling is
2797			flex 0 0 auto, so the func editor can't be squashed and the Insert button can't be
2798			pushed out of the modal on a short viewport. */
2799		flex: 1 1 auto;
2800		min-height: 0;
2801		overflow-y: auto;
2802		padding: 1em 1.1em;
2803	}
2804	.le-vp-sec
2805	{	margin-bottom: 1.4em;
2806	}
2807	.le-vp-sec h4
2808	{	margin: 0 0 0.5em;
2809		color: var(--le-muted);
2810		font-size: 81%;
2811		font-weight: 700;
2812		letter-spacing: 0.06em;
2813		text-transform: uppercase;
2814	}
2815	.le-vp-grid
2816	{	display: grid;
2817		grid-template-columns: repeat(auto-fill, minmax(13em, 1fr));
2818		gap: 0.3em;
2819	}
2820	.le-vp-var
2821	{	/*	overflow-wrap anywhere, NOT break-word - it's the one that also shrinks the min-content
2822			width, so a long token can't push its 1fr column past its share and scroll the body
2823			sideways. */
2824		display: block;
2825		min-width: 0;
2826		padding: 0.4em 0.55em;
2827		overflow-wrap: anywhere;
2828		border: 1px solid var(--le-line);
2829		border-radius: 0.4em;
2830		background-color: var(--le-chrome-2);
2831		color: var(--le-fg);
2832		text-align: left;
2833		font: inherit;
2834		font-size: 88%;
2835		cursor: pointer;
2836	}
2837	.le-vp-var:hover
2838	{	border-color: var(--le-line-2);
2839		background-color: var(--le-chrome-3);
2840	}
2841	.le-vp-var.le-on
2842	{	border-color: var(--le-brand);
2843		background-color: var(--le-brand-soft);
2844		color: var(--le-brand-fg);
2845	}
2846	.le-vp-tok
2847	{	margin-right: 0.4em;
2848		color: #F0B35C;
2849		font-family: var(--le-mono);
2850		font-size: 88%;
2851		font-weight: 700;
2852	}
2853	.le-vp-empty
2854	{	margin-bottom: 1em;
2855		color: var(--le-muted-2);
2856		font-size: 88%;
2857	}
2858	.le-vp-empty code
2859	{	font-family: var(--le-mono);
2860	}
2861	.le-vp-picked
2862	{	display: flex;
2863		flex: 0 0 auto;
2864		flex-direction: column;
2865		gap: 0.5em;
2866		max-height: 40vh;
2867		padding: 0.7em 1.1em;
2868		overflow-y: auto;
2869		border-top: 1px solid var(--le-line);
2870		background-color: var(--le-chrome-2);
2871	}
2872	.le-vp-expr
2873	{	color: #F0B35C;
2874		font-family: var(--le-mono);
2875		font-size: 88%;
2876		word-break: break-all;
2877	}
2878	.le-vp-funcs
2879	{	display: flex;
2880		flex-direction: column;
2881		gap: 0.3em;
2882	}
2883	.le-vp-funcs-hd
2884	{	color: var(--le-muted);
2885		font-size: 78%;
2886		font-weight: 700;
2887		letter-spacing: 0.05em;
2888		text-transform: uppercase;
2889	}
2890	.le-vp-func
2891	{	display: flex;
2892		flex-direction: column;
2893		gap: 0.3em;
2894		padding: 0.4em 0.5em;
2895		border: 1px solid var(--le-line);
2896		border-radius: 0.4em;
2897		background-color: var(--le-chrome-3);
2898	}
2899	.le-vp-func-hd
2900	{	display: flex;
2901		gap: 0.4em;
2902		align-items: center;
2903	}
2904	.le-vp-func-hd span
2905	{	flex: 10000 1 0;
2906		font-size: 88%;
2907		font-weight: 600;
2908	}
2909	.le-vp-func-hd button
2910	{	display: grid;
2911		place-items: center;
2912		padding: 0.1em;
2913		border: none;
2914		background: transparent;
2915		color: var(--le-muted);
2916		cursor: pointer;
2917	}
2918	.le-vp-func-hd button:hover
2919	{	color: var(--le-danger-fg);
2920	}
2921	.le-vp-func-arg
2922	{	display: flex;
2923		gap: 0.4em;
2924		align-items: center;
2925	}
2926	.le-vp-func-arg > span
2927	{	min-width: 9em;
2928		color: var(--le-muted);
2929		font-size: 83%;
2930	}
2931	.le-vp-foot
2932	{	display: flex;
2933		flex: 0 0 auto;
2934		gap: 0.6em;
2935		align-items: center;
2936		padding: 0.6em 1.1em;
2937		border-top: 1px solid var(--le-line);
2938		background-color: var(--le-chrome-2);
2939	}
2940	.le-vp-hint
2941	{	margin-right: auto;
2942		color: var(--le-muted-2);
2943		font-size: 83%;
2944	}
2945
2946	/*	The AI assistant's chat panel (AiPanel). It lives in the toolbar body like the editing
2947		sub-panels, so it inherits their width; only the transcript and the input row are its own.
2948	 */
2949	.le-ai-log
2950	{	display: flex;
2951		flex-direction: column;
2952		gap: 0.6em;
2953		overflow-y: auto;
2954		max-height: 24em;
2955		min-height: 8em;
2956		margin-bottom: 0.9em;
2957		padding: 0.7em;
2958		border: 1px solid var(--le-line);
2959		border-radius: 0.6em;
2960		background-color: var(--le-chrome-2);
2961	}
2962	.le-ai-hint
2963	{	color: var(--le-muted);
2964		font-size: 92%;
2965	}
2966	.le-ai-msg
2967	{	max-width: 92%;
2968		padding: 0.5em 0.75em;
2969		border-radius: 0.6em;
2970		white-space: pre-wrap;
2971		overflow-wrap: break-word;
2972	}
2973	.le-ai-user
2974	{	align-self: flex-end;
2975		border: 1px solid var(--le-line-2);
2976		background-color: var(--le-chrome-3);
2977	}
2978	.le-ai-assistant
2979	{	align-self: flex-start;
2980		border: 1px solid var(--le-brand-soft);
2981		background-color: var(--le-brand-soft);
2982		color: var(--le-fg);
2983	}
2984	.le-ai-status
2985	{	display: flex;
2986		gap: 0.45em;
2987		align-items: baseline;
2988		color: var(--le-muted-2);
2989		font-size: 85%;
2990	}
2991	.le-ai-status svg
2992	{	align-self: center;
2993	}
2994	.le-ai-error
2995	{	color: var(--le-danger-fg);
2996	}
2997	.le-ai-busy
2998	{	display: flex;
2999		gap: 0.6em;
3000		align-items: center;
3001		color: var(--le-muted);
3002		font-size: 92%;
3003	}
3004	.le-ai-pulse
3005	{	flex: 0 0 auto;
3006		width: 0.6em;
3007		height: 0.6em;
3008		border-radius: 50%;
3009		background-color: var(--le-info);
3010		animation: le-ai-pulse 1.1s ease-in-out infinite;
3011	}
3012	@keyframes le-ai-pulse
3013	{	0%, 100%
3014		{	opacity: 0.35;
3015			transform: scale(0.8);
3016		}
3017		50%
3018		{	opacity: 1;
3019			transform: scale(1.15);
3020		}
3021	}
3022	.le-ai-inputrow
3023	{	display: flex;
3024		gap: 0.6em;
3025		align-items: flex-end;
3026	}
3027	.le-ai-budget
3028	{	margin-top: 0.5em;
3029		font-size: 0.82em;
3030		color: var(--le-muted);
3031	}
3032	.le-ai-budget-out
3033	{	color: var(--le-danger-fg);
3034	}
3035	.le-ai-input
3036	{	flex: 1 1 auto;
3037		padding: 0.55em 0.7em;
3038		border: 1px solid var(--le-line-2);
3039		border-radius: 0.55em;
3040		background-color: var(--le-chrome-2);
3041		color: var(--le-fg);
3042		font: inherit;
3043		resize: vertical;
3044	}
3045	.le-ai-input:focus
3046	{	outline: none;
3047		border-color: var(--le-brand);
3048	}
3049	.le-ai-input:disabled
3050	{	opacity: 0.6;
3051	}
3052
3053	/*	Row of actions under an editor box (Insert variable / Variations). Wraps rather than
3054		overflowing: the sub-panel is only as wide as the toolbar. */
3055	.le-btn-bar
3056	{	display: flex;
3057		flex-wrap: wrap;
3058		gap: 0.5em;
3059		align-items: center;
3060		margin-top: 0.6em;
3061	}
3062	/* Count of active cases on the Variations button. */
3063	.le-count
3064	{	display: inline-grid;
3065		place-items: center;
3066		min-width: 1.4em;
3067		height: 1.4em;
3068		padding: 0 0.35em;
3069		border-radius: 999px;
3070		background-color: var(--le-brand);
3071		color: #08231C;
3072		font-size: 78%;
3073		font-weight: 800;
3074	}
3075
3076	/* ===== variations dialog ===== */
3077	.le-vd-overlay
3078	{	inset: 0;
3079		z-index: 2147483647;
3080		display: flex;
3081		align-items: flex-start;
3082		justify-content: center;
3083		padding-top: 5vh;
3084		background-color: rgba(4, 8, 20, 0.55);
3085	}
3086	.le-vd
3087	{	display: flex;
3088		flex-direction: column;
3089		width: min(52em, 94vw);
3090		max-height: 86vh;
3091		overflow: hidden;
3092		border: 1px solid var(--le-line-2);
3093		border-radius: 0.9em;
3094		background-color: var(--le-chrome);
3095		box-shadow: var(--le-shadow);
3096	}
3097	.le-vd-head
3098	{	display: flex;
3099		flex: 0 0 auto;
3100		gap: 0.6em;
3101		align-items: flex-start;
3102		padding: 0.85em 1.1em;
3103		border-bottom: 1px solid var(--le-line);
3104		background-color: var(--le-chrome-2);
3105	}
3106	.le-vd-head h3
3107	{	display: flex;
3108		gap: 0.4em;
3109		align-items: center;
3110		margin: 0;
3111		font-size: 108%;
3112		font-weight: 700;
3113	}
3114	.le-vd-head p
3115	{	margin: 0.15em 0 0;
3116		color: var(--le-muted);
3117		font-size: 88%;
3118	}
3119	.le-vd-tag
3120	{	color: var(--le-muted-2);
3121		font-size: 80%;
3122		font-weight: 400;
3123	}
3124	.le-vd-head .le-icon
3125	{	margin-left: auto;
3126	}
3127	.le-vd-body
3128	{	flex: 1 1 auto;
3129		min-height: 0;
3130		padding: 0.9em 1.1em;
3131		overflow-y: auto;
3132	}
3133	.le-vd-case,
3134	.le-vd-default
3135	{	margin-bottom: 0.9em;
3136		padding: 0.7em 0.8em;
3137		border: 1px solid var(--le-line);
3138		border-radius: 0.6em;
3139		background-color: var(--le-chrome-2);
3140	}
3141	.le-vd-default
3142	{	margin-bottom: 0;
3143		border-style: dashed;
3144	}
3145	.le-vd-case-head
3146	{	display: flex;
3147		gap: 0.4em;
3148		align-items: center;
3149		margin-bottom: 0.5em;
3150	}
3151	.le-vd-label
3152	{	display: block;
3153		margin: 0.6em 0 0.3em;
3154		color: var(--le-muted);
3155		font-size: 82%;
3156		font-weight: 600;
3157		letter-spacing: 0.04em;
3158		text-transform: uppercase;
3159	}
3160	.le-vd-case > .le-vd-label:first-of-type
3161	{	margin-top: 0;
3162	}
3163	.le-vd-content
3164	{	display: flex;
3165		gap: 0.5em;
3166		align-items: flex-start;
3167	}
3168	.le-vd-content > .le-area,
3169	.le-vd-content > .le-input
3170	{	flex: 10000 1 0;
3171	}
3172	.le-vd-foot
3173	{	display: flex;
3174		flex: 0 0 auto;
3175		gap: 0.6em;
3176		align-items: center;
3177		padding: 0.7em 1.1em;
3178		border-top: 1px solid var(--le-line);
3179		background-color: var(--le-chrome-2);
3180	}
3181
3182	/* ===== rule builder ===== */
3183	.le-rb
3184	{	display: flex;
3185		flex-direction: column;
3186		gap: 0.35em;
3187	}
3188	.le-rb-row
3189	{	display: flex;
3190		flex-wrap: wrap;
3191		gap: 0.35em;
3192		align-items: center;
3193	}
3194	.le-rb-comb
3195	{	flex: 0 0 auto;
3196		width: 4.5em;
3197	}
3198	.le-rb-if
3199	{	color: var(--le-muted-2);
3200		font-size: 88%;
3201		font-style: italic;
3202		text-align: center;
3203	}
3204	/*	The three controls share the row's width rather than each sizing to its own longest option:
3205		a visitor-data column name can be long, and without this the value box gets squeezed to
3206		nothing on a narrow editor window. */
3207	.le-rb-field
3208	{	flex: 3 1 8em;
3209		min-width: 0;
3210	}
3211	.le-rb-op
3212	{	flex: 2 1 7em;
3213		min-width: 0;
3214	}
3215	.le-rb-val
3216	{	flex: 2 1 6em;
3217		min-width: 0;
3218		width: auto;
3219	}
3220	.le-rb-foot
3221	{	display: flex;
3222		gap: 0.5em;
3223		align-items: center;
3224	}
3225	.le-rb-hint
3226	{	flex: 10000 1 0;
3227		color: var(--le-muted-2);
3228		font-size: 80%;
3229	}
3230	/*	Rules/SQL switch. A segmented pair rather than one toggle button, because it only ever
3231		appears alongside a condition that is already in one of the two modes - the point is to
3232		show WHICH, not just to offer the other. */
3233	.le-rb-modes
3234	{	display: inline-flex;
3235		flex: 0 0 auto;
3236		gap: 0.15em;
3237		padding: 0.15em;
3238		border: 1px solid var(--le-line);
3239		border-radius: 0.45em;
3240		background-color: var(--le-chrome-2);
3241	}
3242	.le-rb-modes button
3243	{	padding: 0.3em 0.6em;
3244		border: none;
3245		border-radius: 0.3em;
3246		background: transparent;
3247		color: var(--le-muted);
3248		font: inherit;
3249		font-size: 85%;
3250		font-weight: 600;
3251		white-space: nowrap;
3252		cursor: pointer;
3253	}
3254	.le-rb-modes button:hover:not(:disabled)
3255	{	background-color: var(--le-chrome-3);
3256		color: var(--le-fg);
3257	}
3258	.le-rb-modes button.le-on
3259	{	background-color: var(--le-chrome-3);
3260		color: var(--le-fg);
3261	}
3262	.le-rb-modes button:disabled
3263	{	opacity: 0.45;
3264		cursor: default;
3265	}
3266
3267	/*	The editor's own window is around 1200-1500px, so these are for a genuinely small screen -
3268		not for the ordinary case. A breakpoint at the mockup's 900px would make the sidebar cover
3269		the whole editing window.
3270	 */
3271	@media (max-width: 760px)
3272	{	.le-toolbar
3273		{	width: 95vw;
3274		}
3275		.le-grid
3276		{	grid-template-columns: repeat(2, 1fr);
3277		}
3278	}
3279	@media (max-width: 620px)
3280	{	.le-side
3281		{	width: 100vw;
3282			max-width: 100vw;
3283		}
3284		.le-peek
3285		{	display: none;
3286		}
3287	}
3288	@media (max-width: 1000px)
3289	{	/*	Too narrow to dodge the sidebar - shifting would only push the banner off the other edge. */
3290		.le-navbanner.le-avoid-right,
3291		.le-navbanner.le-avoid-left
3292		{	transform: translateX(-50%);
3293		}
3294	}
3295`;
3296
vendor: 715 bytes, lines 3297-3325
3297// ../../../../deno-dir/npm/registry.npmjs.org/preact/10.22.0/jsx-runtime/dist/jsxRuntime.module.js
3298var f2 = 0;
3299var i2 = Array.isArray;
3300function u2(e3, t4, n2, o3, i4, u4) {
3301  t4 || (t4 = {});
3302  var a3, c3, p3 = t4;
3303  if ("ref" in p3) for (c3 in p3 = {}, t4) "ref" == c3 ? a3 = t4[c3] : p3[c3] = t4[c3];
3304  var l3 = {
3305    type: e3,
3306    props: p3,
3307    key: n2,
3308    ref: a3,
3309    __k: null,
3310    __: null,
3311    __b: 0,
3312    __e: null,
3313    __d: void 0,
3314    __c: null,
3315    constructor: void 0,
3316    __v: --f2,
3317    __i: -1,
3318    __u: 0,
3319    __source: i4,
3320    __self: u4
3321  };
3322  if ("function" == typeof e3 && (a3 = e3.defaultProps)) for (c3 in a3) void 0 === p3[c3] && (p3[c3] = a3[c3]);
3323  return l.vnode && l.vnode(l3), l3;
3324}
3325
3326// ../../../../deno-dir/npm/registry.npmjs.org/preact/10.22.0/hooks/dist/hooks.module.js
3327var t2;
3328var r2;
3329var u3;
3330var i3;
3331var o2 = 0;
3332var f3 = [];
3333var c2 = [];
3334var e2 = l;
3335var a2 = e2.__b;
3336var v2 = e2.__r;
3337var l2 = e2.diffed;
3338var m = e2.__c;
3339var s2 = e2.unmount;
3340var d2 = e2.__;
3341function h2(n2, t4) {
3342  e2.__h && e2.__h(r2, n2, o2 || t4), o2 = 0;
3343  var u4 = r2.__H || (r2.__H = {
3344    __: [],
3345    __h: []
3346  });
3347  return n2 >= u4.__.length && u4.__.push({
3348    __V: c2
3349  }), u4.__[n2];
3350}
3351function p2(n2) {
3352  return o2 = 1, y2(D, n2);
3353}
3354function y2(n2, u4, i4) {
3355  var o3 = h2(t2++, 2);
3356  if (o3.t = n2, !o3.__c && (o3.__ = [
3357    i4 ? i4(u4) : D(void 0, u4),
3358    function(n3) {
3359      var t4 = o3.__N ? o3.__N[0] : o3.__[0], r3 = o3.t(t4, n3);
3360      t4 !== r3 && (o3.__N = [
3361        r3,
3362        o3.__[1]
3363      ], o3.__c.setState({}));
3364    }
3365  ], o3.__c = r2, !r2.u)) {
3366    var f4 = function(n3, t4, r3) {
3367      if (!o3.__c.__H) return true;
3368      var u5 = o3.__c.__H.__.filter(function(n4) {
3369        return !!n4.__c;
3370      });
3371      if (u5.every(function(n4) {
3372        return !n4.__N;
3373      })) return !c3 || c3.call(this, n3, t4, r3);
3374      var i5 = false;
3375      return u5.forEach(function(n4) {
3376        if (n4.__N) {
3377          var t5 = n4.__[0];
3378          n4.__ = n4.__N, n4.__N = void 0, t5 !== n4.__[0] && (i5 = true);
3379        }
3380      }), !(!i5 && o3.__c.props === n3) && (!c3 || c3.call(this, n3, t4, r3));
3381    };
3382    r2.u = true;
3383    var c3 = r2.shouldComponentUpdate, e3 = r2.componentWillUpdate;
3384    r2.componentWillUpdate = function(n3, t4, r3) {
3385      if (this.__e) {
3386        var u5 = c3;
3387        c3 = void 0, f4(n3, t4, r3), c3 = u5;
3388      }
3389      e3 && e3.call(this, n3, t4, r3);
3390    }, r2.shouldComponentUpdate = f4;
3391  }
3392  return o3.__N || o3.__;
3393}
3394function _2(n2, u4) {
3395  var i4 = h2(t2++, 3);
3396  !e2.__s && C2(i4.__H, u4) && (i4.__ = n2, i4.i = u4, r2.__H.__h.push(i4));
3397}
3398function F2(n2) {
3399  return o2 = 5, q2(function() {
3400    return {
3401      current: n2
3402    };
3403  }, []);
3404}
3405function q2(n2, r3) {
3406  var u4 = h2(t2++, 7);
3407  return C2(u4.__H, r3) ? (u4.__V = n2(), u4.i = r3, u4.__h = n2, u4.__V) : u4.__;
3408}
3409function j2() {
3410  for (var n2; n2 = f3.shift(); ) if (n2.__P && n2.__H) try {
3411    n2.__H.__h.forEach(z2), n2.__H.__h.forEach(B2), n2.__H.__h = [];
3412  } catch (t4) {
3413    n2.__H.__h = [], e2.__e(t4, n2.__v);
3414  }
3415}
3416e2.__b = function(n2) {
3417  r2 = null, a2 && a2(n2);
3418}, e2.__ = function(n2, t4) {
3419  n2 && t4.__k && t4.__k.__m && (n2.__m = t4.__k.__m), d2 && d2(n2, t4);
3420}, e2.__r = function(n2) {
3421  v2 && v2(n2), t2 = 0;
3422  var i4 = (r2 = n2.__c).__H;
3423  i4 && (u3 === r2 ? (i4.__h = [], r2.__h = [], i4.__.forEach(function(n3) {
3424    n3.__N && (n3.__ = n3.__N), n3.__V = c2, n3.__N = n3.i = void 0;
3425  })) : (i4.__h.forEach(z2), i4.__h.forEach(B2), i4.__h = [], t2 = 0)), u3 = r2;
3426}, e2.diffed = function(n2) {
3427  l2 && l2(n2);
3428  var t4 = n2.__c;
3429  t4 && t4.__H && (t4.__H.__h.length && (1 !== f3.push(t4) && i3 === e2.requestAnimationFrame || ((i3 = e2.requestAnimationFrame) || w2)(j2)), t4.__H.__.forEach(function(n3) {
3430    n3.i && (n3.__H = n3.i), n3.__V !== c2 && (n3.__ = n3.__V), n3.i = void 0, n3.__V = c2;
3431  })), u3 = r2 = null;
3432}, e2.__c = function(n2, t4) {
3433  t4.some(function(n3) {
3434    try {
3435      n3.__h.forEach(z2), n3.__h = n3.__h.filter(function(n4) {
3436        return !n4.__ || B2(n4);
3437      });
3438    } catch (r3) {
3439      t4.some(function(n4) {
3440        n4.__h && (n4.__h = []);
3441      }), t4 = [], e2.__e(r3, n3.__v);
3442    }
3443  }), m && m(n2, t4);
3444}, e2.unmount = function(n2) {
3445  s2 && s2(n2);
3446  var t4, r3 = n2.__c;
3447  r3 && r3.__H && (r3.__H.__.forEach(function(n3) {
3448    try {
3449      z2(n3);
3450    } catch (n4) {
3451      t4 = n4;
3452    }
3453  }), r3.__H = void 0, t4 && e2.__e(t4, r3.__v));
3454};
3455var k2 = "function" == typeof requestAnimationFrame;
3456function w2(n2) {
3457  var t4, r3 = function() {
3458    clearTimeout(u4), k2 && cancelAnimationFrame(t4), setTimeout(n2);
3459  }, u4 = setTimeout(r3, 100);
3460  k2 && (t4 = requestAnimationFrame(r3));
3461}
3462function z2(n2) {
3463  var t4 = r2, u4 = n2.__c;
3464  "function" == typeof u4 && (n2.__c = void 0, u4()), r2 = t4;
3465}
3466function B2(n2) {
3467  var t4 = r2;
3468  n2.__c = n2.__(), r2 = t4;
3469}
3470function C2(n2, t4) {
3471  return !n2 || n2.length !== t4.length || t4.some(function(t5, r3) {
3472    return t5 !== n2[r3];
3473  });
3474}
3475function D(n2, t4) {
3476  return "function" == typeof t4 ? t4(n2) : t4;
3477}
3478
3479// src/components/live/widgetCompile.ts
3480var TIMEOUT_MS = 8e3;
3481var pending = /* @__PURE__ */ new Map();
3482var seq = 0;
3483function resolveWidgetHtml(reqId, html) {
3484  const key = String(reqId ?? "");
3485  const found = pending.get(key);
3486  if (found) {
3487    pending.delete(key);
3488    clearTimeout(found.timer);
3489    found.resolve(typeof html == "string" && html ? html : null);
3490  }
3491}
3492function requestWidgetHtml(send, type, props) {
3493  const reqId = "w" + ++seq;
3494  return new Promise((resolve) => {
3495    const timer = setTimeout(() => {
3496      pending.delete(reqId);
3497      resolve(null);
3498    }, TIMEOUT_MS);
3499    pending.set(reqId, {
3500      resolve,
3501      timer
3502    });
3503    try {
3504      send({
3505        widget_compile: {
3506          req_id: reqId,
3507          type,
3508          props
3509        }
3510      });
3511    } catch {
3512      pending.delete(reqId);
3513      clearTimeout(timer);
3514      resolve(null);
3515    }
3516  });
3517}
3518
3519// src/components/live/pageFreeze.ts
3520var FROZEN_EVENTS = [
3521  "click",
3522  "auxclick",
3523  "submit",
3524  "dragstart"
3525];
3526function isOwnUi(ev, host) {
3527  const path = typeof ev.composedPath == "function" ? ev.composedPath() : [];
3528  if (path.indexOf(host) != -1) {
3529    return true;
3530  }
3531  return !!ev.target && host.contains(ev.target);
3532}
3533function installPageFreeze(doc, host) {
3534  function freeze(ev) {
3535    if (isOwnUi(ev, host)) {
3536      return;
3537    }
3538    ev.preventDefault();
3539    ev.stopPropagation();
3540  }
3541  for (const type of FROZEN_EVENTS) {
3542    doc.addEventListener(type, freeze, true);
3543  }
3544  return () => {
3545    for (const type of FROZEN_EVENTS) {
3546      doc.removeEventListener(type, freeze, true);
3547    }
3548  };
3549}
3550
3551// src/components/live/liveIcons.tsx
3552var li = {
3553  pencil: /* @__PURE__ */ u2("svg", {
3554    viewBox: "0 0 24 24",
3555    children: [
3556      /* @__PURE__ */ u2("path", {
3557        d: "M12 20h9"
3558      }),
3559      /* @__PURE__ */ u2("path", {
3560        d: "M16.5 3.5l4 4L7 21H3v-4z"
3561      })
3562    ]
3563  }),
3564  lines: /* @__PURE__ */ u2("svg", {
3565    viewBox: "0 0 24 24",
3566    children: /* @__PURE__ */ u2("path", {
3567      d: "M4 7h16M4 12h10M4 17h16"
3568    })
3569  }),
3570  palette: /* @__PURE__ */ u2("svg", {
3571    viewBox: "0 0 24 24",
3572    children: [
3573      /* @__PURE__ */ u2("circle", {
3574        cx: "13.5",
3575        cy: "6.5",
3576        r: ".5",
3577        fill: "currentColor"
3578      }),
3579      /* @__PURE__ */ u2("circle", {
3580        cx: "17.5",
3581        cy: "10.5",
3582        r: ".5",
3583        fill: "currentColor"
3584      }),
3585      /* @__PURE__ */ u2("circle", {
3586        cx: "8.5",
3587        cy: "7.5",
3588        r: ".5",
3589        fill: "currentColor"
3590      }),
3591      /* @__PURE__ */ u2("circle", {
3592        cx: "6.5",
3593        cy: "12.5",
3594        r: ".5",
3595        fill: "currentColor"
3596      }),
3597      /* @__PURE__ */ u2("path", {
3598        d: "M12 2a10 10 0 1 0 5 18c1 0 2-1 2-2v-1c0-1-1-2-2-2h-1a3 3 0 0 1-3-3V9a3 3 0 0 1 3-3h1c1 0 2-1 2-2A10 10 0 0 0 12 2z"
3599      })
3600    ]
3601  }),
3602  copy: /* @__PURE__ */ u2("svg", {
3603    viewBox: "0 0 24 24",
3604    children: [
3605      /* @__PURE__ */ u2("rect", {
3606        x: "8",
3607        y: "8",
3608        width: "12",
3609        height: "12",
3610        rx: "2"
3611      }),
3612      /* @__PURE__ */ u2("path", {
3613        d: "M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2"
3614      })
3615    ]
3616  }),
3617  trash: /* @__PURE__ */ u2("svg", {
3618    viewBox: "0 0 24 24",
3619    children: [
3620      /* @__PURE__ */ u2("polyline", {
3621        points: "3 6 5 6 21 6"
3622      }),
3623      /* @__PURE__ */ u2("path", {
3624        d: "M19 6l-1 14H6L5 6"
3625      }),
3626      /* @__PURE__ */ u2("path", {
3627        d: "M10 11v6M14 11v6"
3628      })
3629    ]
3630  }),
3631  code: /* @__PURE__ */ u2("svg", {
3632    viewBox: "0 0 24 24",
3633    children: [
3634      /* @__PURE__ */ u2("polyline", {
3635        points: "16 18 22 12 16 6"
3636      }),
3637      /* @__PURE__ */ u2("polyline", {
3638        points: "8 6 2 12 8 18"
3639      })
3640    ]
3641  }),
3642  move: /* @__PURE__ */ u2("svg", {
3643    viewBox: "0 0 24 24",
3644    children: [
3645      /* @__PURE__ */ u2("path", {
3646        d: "M5 9l-3 3 3 3"
3647      }),
3648      /* @__PURE__ */ u2("path", {
3649        d: "M19 9l3 3-3 3"
3650      }),
3651      /* @__PURE__ */ u2("path", {
3652        d: "M9 5l3-3 3 3"
3653      }),
3654      /* @__PURE__ */ u2("path", {
3655        d: "M9 19l3 3 3-3"
3656      }),
3657      /* @__PURE__ */ u2("path", {
3658        d: "M2 12h20M12 2v20"
3659      })
3660    ]
3661  }),
3662  spacing: /* @__PURE__ */ u2("svg", {
3663    viewBox: "0 0 24 24",
3664    children: [
3665      /* @__PURE__ */ u2("rect", {
3666        x: "4",
3667        y: "4",
3668        width: "16",
3669        height: "16"
3670      }),
3671      /* @__PURE__ */ u2("rect", {
3672        x: "8",
3673        y: "8",
3674        width: "8",
3675        height: "8",
3676        fill: "currentColor",
3677        opacity: ".2"
3678      })
3679    ]
3680  }),
3681  align: /* @__PURE__ */ u2("svg", {
3682    viewBox: "0 0 24 24",
3683    children: /* @__PURE__ */ u2("path", {
3684      d: "M4 6h16M4 12h10M4 18h16"
3685    })
3686  }),
3687  position: /* @__PURE__ */ u2("svg", {
3688    viewBox: "0 0 24 24",
3689    children: [
3690      /* @__PURE__ */ u2("circle", {
3691        cx: "12",
3692        cy: "12",
3693        r: "4"
3694      }),
3695      /* @__PURE__ */ u2("path", {
3696        d: "M12 2v2M12 20v2M2 12h2M20 12h2M4.9 4.9l1.5 1.5M17.6 17.6l1.5 1.5M4.9 19.1l1.5-1.5M17.6 6.4l1.5-1.5"
3697      })
3698    ]
3699  }),
3700  image: /* @__PURE__ */ u2("svg", {
3701    viewBox: "0 0 24 24",
3702    children: [
3703      /* @__PURE__ */ u2("rect", {
3704        x: "3",
3705        y: "5",
3706        width: "18",
3707        height: "14",
3708        rx: "2"
3709      }),
3710      /* @__PURE__ */ u2("circle", {
3711        cx: "8",
3712        cy: "11",
3713        r: "2"
3714      }),
3715      /* @__PURE__ */ u2("path", {
3716        d: "M21 15l-5-5-4 4-2-2-4 4"
3717      })
3718    ]
3719  }),
3720  link: /* @__PURE__ */ u2("svg", {
3721    viewBox: "0 0 24 24",
3722    children: [
3723      /* @__PURE__ */ u2("path", {
3724        d: "M10 13a5 5 0 0 1 0-7l1-1a5 5 0 0 1 7 7l-1 1"
3725      }),
3726      /* @__PURE__ */ u2("path", {
3727        d: "M14 11a5 5 0 0 1 0 7l-1 1a5 5 0 0 1-7-7l1-1"
3728      })
3729    ]
3730  }),
3731  button: /* @__PURE__ */ u2("svg", {
3732    viewBox: "0 0 24 24",
3733    children: /* @__PURE__ */ u2("rect", {
3734      x: "5",
3735      y: "8",
3736      width: "14",
3737      height: "8",
3738      rx: "2"
3739    })
3740  }),
3741  block: /* @__PURE__ */ u2("svg", {
3742    viewBox: "0 0 24 24",
3743    children: [
3744      /* @__PURE__ */ u2("rect", {
3745        x: "4",
3746        y: "4",
3747        width: "7",
3748        height: "7"
3749      }),
3750      /* @__PURE__ */ u2("rect", {
3751        x: "13",
3752        y: "4",
3753        width: "7",
3754        height: "7"
3755      }),
3756      /* @__PURE__ */ u2("rect", {
3757        x: "4",
3758        y: "13",
3759        width: "7",
3760        height: "7"
3761      }),
3762      /* @__PURE__ */ u2("rect", {
3763        x: "13",
3764        y: "13",
3765        width: "7",
3766        height: "7"
3767      })
3768    ]
3769  }),
3770  video: /* @__PURE__ */ u2("svg", {
3771    viewBox: "0 0 24 24",
3772    children: [
3773      /* @__PURE__ */ u2("polygon", {
3774        points: "23 7 16 12 23 17 23 7"
3775      }),
3776      /* @__PURE__ */ u2("rect", {
3777        x: "1",
3778        y: "5",
3779        width: "15",
3780        height: "14",
3781        rx: "2"
3782      })
3783    ]
3784  }),
3785  bolt: /* @__PURE__ */ u2("svg", {
3786    viewBox: "0 0 24 24",
3787    children: /* @__PURE__ */ u2("path", {
3788      d: "M13 10V3L4 14h7v7l9-11z"
3789    })
3790  }),
3791  clock: /* @__PURE__ */ u2("svg", {
3792    viewBox: "0 0 24 24",
3793    children: [
3794      /* @__PURE__ */ u2("circle", {
3795        cx: "12",
3796        cy: "12",
3797        r: "9"
3798      }),
3799      /* @__PURE__ */ u2("path", {
3800        d: "M12 7v5l3 2"
3801      })
3802    ]
3803  }),
3804  checkbox: /* @__PURE__ */ u2("svg", {
3805    viewBox: "0 0 24 24",
3806    children: [
3807      /* @__PURE__ */ u2("path", {
3808        d: "M9 11l3 3L22 4"
3809      }),
3810      /* @__PURE__ */ u2("path", {
3811        d: "M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"
3812      })
3813    ]
3814  }),
3815  chart: /* @__PURE__ */ u2("svg", {
3816    viewBox: "0 0 24 24",
3817    children: [
3818      /* @__PURE__ */ u2("path", {
3819        d: "M3 3v18h18"
3820      }),
3821      /* @__PURE__ */ u2("path", {
3822        d: "M7 14l3-3 3 3 5-5"
3823      })
3824    ]
3825  }),
3826  heading: /* @__PURE__ */ u2("svg", {
3827    viewBox: "0 0 24 24",
3828    children: /* @__PURE__ */ u2("path", {
3829      d: "M6 4v16M18 4v16M6 12h12"
3830    })
3831  }),
3832  upload: /* @__PURE__ */ u2("svg", {
3833    viewBox: "0 0 24 24",
3834    children: [
3835      /* @__PURE__ */ u2("path", {
3836        d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"
3837      }),
3838      /* @__PURE__ */ u2("polyline", {
3839        points: "17 8 12 3 7 8"
3840      }),
3841      /* @__PURE__ */ u2("path", {
3842        d: "M12 3v12"
3843      })
3844    ]
3845  }),
3846  list: /* @__PURE__ */ u2("svg", {
3847    viewBox: "0 0 24 24",
3848    children: [
3849      /* @__PURE__ */ u2("circle", {
3850        cx: "5",
3851        cy: "6",
3852        r: "1.2",
3853        fill: "currentColor"
3854      }),
3855      /* @__PURE__ */ u2("circle", {
3856        cx: "5",
3857        cy: "12",
3858        r: "1.2",
3859        fill: "currentColor"
3860      }),
3861      /* @__PURE__ */ u2("circle", {
3862        cx: "5",
3863        cy: "18",
3864        r: "1.2",
3865        fill: "currentColor"
3866      }),
3867      /* @__PURE__ */ u2("path", {
3868        d: "M10 6h10M10 12h10M10 18h10"
3869      })
3870    ]
3871  }),
3872  props: /* @__PURE__ */ u2("svg", {
3873    viewBox: "0 0 24 24",
3874    children: [
3875      /* @__PURE__ */ u2("path", {
3876        d: "M4 6h16M4 12h16M4 18h10"
3877      }),
3878      /* @__PURE__ */ u2("circle", {
3879        cx: "18",
3880        cy: "18",
3881        r: "2"
3882      })
3883    ]
3884  }),
3885  cursor: /* @__PURE__ */ u2("svg", {
3886    viewBox: "0 0 24 24",
3887    children: /* @__PURE__ */ u2("path", {
3888      d: "M3 3l7 18 2-8 8-2L3 3z"
3889    })
3890  }),
3891  search: /* @__PURE__ */ u2("svg", {
3892    viewBox: "0 0 24 24",
3893    children: [
3894      /* @__PURE__ */ u2("circle", {
3895        cx: "11",
3896        cy: "11",
3897        r: "7"
3898      }),
3899      /* @__PURE__ */ u2("path", {
3900        d: "M21 21l-4.3-4.3"
3901      })
3902    ]
3903  }),
3904  undo: /* @__PURE__ */ u2("svg", {
3905    viewBox: "0 0 24 24",
3906    children: [
3907      /* @__PURE__ */ u2("path", {
3908        d: "M9 14l-4-4 4-4"
3909      }),
3910      /* @__PURE__ */ u2("path", {
3911        d: "M5 10h9a5 5 0 0 1 0 10h-2"
3912      })
3913    ]
3914  }),
3915  redo: /* @__PURE__ */ u2("svg", {
3916    viewBox: "0 0 24 24",
3917    children: [
3918      /* @__PURE__ */ u2("path", {
3919        d: "M15 14l4-4-4-4"
3920      }),
3921      /* @__PURE__ */ u2("path", {
3922        d: "M19 10h-9a5 5 0 0 0 0 10h2"
3923      })
3924    ]
3925  }),
3926  chevronUp: /* @__PURE__ */ u2("svg", {
3927    viewBox: "0 0 24 24",
3928    children: /* @__PURE__ */ u2("path", {
3929      d: "M18 15l-6-6-6 6"
3930    })
3931  }),
3932  chevronDown: /* @__PURE__ */ u2("svg", {
3933    viewBox: "0 0 24 24",
3934    children: /* @__PURE__ */ u2("path", {
3935      d: "M6 9l6 6 6-6"
3936    })
3937  }),
3938  chevronLeft: /* @__PURE__ */ u2("svg", {
3939    viewBox: "0 0 24 24",
3940    children: /* @__PURE__ */ u2("path", {
3941      d: "M15 6l-6 6 6 6"
3942    })
3943  }),
3944  chevronRight: /* @__PURE__ */ u2("svg", {
3945    viewBox: "0 0 24 24",
3946    children: /* @__PURE__ */ u2("path", {
3947      d: "M9 6l6 6-6 6"
3948    })
3949  }),
3950  cross: /* @__PURE__ */ u2("svg", {
3951    viewBox: "0 0 24 24",
3952    children: /* @__PURE__ */ u2("path", {
3953      d: "M18 6L6 18M6 6l12 12"
3954    })
3955  }),
3956  warn: /* @__PURE__ */ u2("svg", {
3957    viewBox: "0 0 24 24",
3958    children: [
3959      /* @__PURE__ */ u2("path", {
3960        d: "M12 3l9 16H3z"
3961      }),
3962      /* @__PURE__ */ u2("path", {
3963        d: "M12 9v5M12 17h.01"
3964      })
3965    ]
3966  }),
3967  desktop: /* @__PURE__ */ u2("svg", {
3968    viewBox: "0 0 24 24",
3969    children: [
3970      /* @__PURE__ */ u2("rect", {
3971        x: "3",
3972        y: "4",
3973        width: "18",
3974        height: "12",
3975        rx: "1"
3976      }),
3977      /* @__PURE__ */ u2("path", {
3978        d: "M8 20h8M12 16v4"
3979      })
3980    ]
3981  }),
3982  tablet: /* @__PURE__ */ u2("svg", {
3983    viewBox: "0 0 24 24",
3984    children: [
3985      /* @__PURE__ */ u2("rect", {
3986        x: "5",
3987        y: "3",
3988        width: "14",
3989        height: "18",
3990        rx: "2"
3991      }),
3992      /* @__PURE__ */ u2("path", {
3993        d: "M11 18h2"
3994      })
3995    ]
3996  }),
3997  mobile: /* @__PURE__ */ u2("svg", {
3998    viewBox: "0 0 24 24",
3999    children: [
4000      /* @__PURE__ */ u2("rect", {
4001        x: "7",
4002        y: "2",
4003        width: "10",
4004        height: "20",
4005        rx: "2"
4006      }),
4007      /* @__PURE__ */ u2("path", {
4008        d: "M11 19h2"
4009      })
4010    ]
4011  }),
4012  /*	Every screen: a monitor with a phone in front of it - "Show on: All devices". */
4013  allDevices: /* @__PURE__ */ u2("svg", {
4014    viewBox: "0 0 24 24",
4015    children: [
4016      /* @__PURE__ */ u2("path", {
4017        d: "M15 15H4a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v3"
4018      }),
4019      /* @__PURE__ */ u2("path", {
4020        d: "M8 19h5M10 15v4"
4021      }),
4022      /* @__PURE__ */ u2("rect", {
4023        x: "16",
4024        y: "10",
4025        width: "6",
4026        height: "11",
4027        rx: "1"
4028      })
4029    ]
4030  }),
4031  send: /* @__PURE__ */ u2("svg", {
4032    viewBox: "0 0 24 24",
4033    children: [
4034      /* @__PURE__ */ u2("path", {
4035        d: "M22 2L11 13"
4036      }),
4037      /* @__PURE__ */ u2("path", {
4038        d: "M22 2l-7 20-4-9-9-4z"
4039      })
4040    ]
4041  }),
4042  play: /* @__PURE__ */ u2("svg", {
4043    viewBox: "0 0 24 24",
4044    children: /* @__PURE__ */ u2("polygon", {
4045      points: "5 3 19 12 5 21 5 3"
4046    })
4047  }),
4048  gear: /* @__PURE__ */ u2("svg", {
4049    viewBox: "0 0 24 24",
4050    children: [
4051      /* @__PURE__ */ u2("circle", {
4052        cx: "12",
4053        cy: "12",
4054        r: "3"
4055      }),
4056      /* @__PURE__ */ u2("path", {
4057        d: "M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"
4058      })
4059    ]
4060  }),
4061  reload: /* @__PURE__ */ u2("svg", {
4062    viewBox: "0 0 24 24",
4063    children: [
4064      /* @__PURE__ */ u2("path", {
4065        d: "M21 12a9 9 0 1 1-3-6.7L21 8"
4066      }),
4067      /* @__PURE__ */ u2("path", {
4068        d: "M21 3v5h-5"
4069      })
4070    ]
4071  }),
4072  dock: /* @__PURE__ */ u2("svg", {
4073    viewBox: "0 0 24 24",
4074    children: [
4075      /* @__PURE__ */ u2("rect", {
4076        x: "3",
4077        y: "4",
4078        width: "18",
4079        height: "16",
4080        rx: "2"
4081      }),
4082      /* @__PURE__ */ u2("path", {
4083        d: "M15 4v16"
4084      })
4085    ]
4086  }),
4087  text: /* @__PURE__ */ u2("svg", {
4088    viewBox: "0 0 24 24",
4089    children: /* @__PURE__ */ u2("path", {
4090      d: "M4 7V5h16v2M9 19h6M12 5v14"
4091    })
4092  }),
4093  ext: /* @__PURE__ */ u2("svg", {
4094    viewBox: "0 0 24 24",
4095    children: [
4096      /* @__PURE__ */ u2("path", {
4097        d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"
4098      }),
4099      /* @__PURE__ */ u2("polyline", {
4100        points: "15 3 21 3 21 9"
4101      }),
4102      /* @__PURE__ */ u2("path", {
4103        d: "M10 14L21 3"
4104      })
4105    ]
4106  }),
4107  braces: /* @__PURE__ */ u2("svg", {
4108    viewBox: "0 0 24 24",
4109    children: [
4110      /* @__PURE__ */ u2("path", {
4111        d: "M8 3H7a2 2 0 0 0-2 2v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5a2 2 0 0 0 2 2h1"
4112      }),
4113      /* @__PURE__ */ u2("path", {
4114        d: "M16 3h1a2 2 0 0 1 2 2v5a2 2 0 0 0 2 2 2 2 0 0 0-2 2v5a2 2 0 0 1-2 2h-1"
4115      })
4116    ]
4117  }),
4118  sparkle: /* @__PURE__ */ u2("svg", {
4119    viewBox: "0 0 24 24",
4120    children: [
4121      /* @__PURE__ */ u2("path", {
4122        d: "M12 3l1.8 5.2L19 10l-5.2 1.8L12 17l-1.8-5.2L5 10l5.2-1.8z"
4123      }),
4124      /* @__PURE__ */ u2("path", {
4125        d: "M19 15l.9 2.1L22 18l-2.1.9L19 21l-.9-2.1L16 18l2.1-.9z"
4126      })
4127    ]
4128  }),
4129  stop: /* @__PURE__ */ u2("svg", {
4130    viewBox: "0 0 24 24",
4131    children: /* @__PURE__ */ u2("rect", {
4132      x: "6",
4133      y: "6",
4134      width: "12",
4135      height: "12",
4136      rx: "1"
4137    })
4138  }),
4139  alignLeft: /* @__PURE__ */ u2("svg", {
4140    viewBox: "0 0 24 24",
4141    children: /* @__PURE__ */ u2("path", {
4142      d: "M4 6h16M4 12h10M4 18h16"
4143    })
4144  }),
4145  alignCenter: /* @__PURE__ */ u2("svg", {
4146    viewBox: "0 0 24 24",
4147    children: /* @__PURE__ */ u2("path", {
4148      d: "M4 6h16M7 12h10M4 18h16"
4149    })
4150  }),
4151  alignRight: /* @__PURE__ */ u2("svg", {
4152    viewBox: "0 0 24 24",
4153    children: /* @__PURE__ */ u2("path", {
4154      d: "M4 6h16M10 12h10M4 18h16"
4155    })
4156  }),
4157  alignJustify: /* @__PURE__ */ u2("svg", {
4158    viewBox: "0 0 24 24",
4159    children: /* @__PURE__ */ u2("path", {
4160      d: "M4 6h16M4 12h16M4 18h16"
4161    })
4162  }),
4163  drop: /* @__PURE__ */ u2("svg", {
4164    viewBox: "0 0 24 24",
4165    children: /* @__PURE__ */ u2("path", {
4166      d: "M12 3s6 6.6 6 10.5a6 6 0 0 1-12 0C6 9.6 12 3 12 3z"
4167    })
4168  }),
4169  scroll: /* @__PURE__ */ u2("svg", {
4170    viewBox: "0 0 24 24",
4171    children: /* @__PURE__ */ u2("path", {
4172      d: "M12 5v14M5 12l7 7 7-7"
4173    })
4174  }),
4175  eye: /* @__PURE__ */ u2("svg", {
4176    viewBox: "0 0 24 24",
4177    children: [
4178      /* @__PURE__ */ u2("path", {
4179        d: "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"
4180      }),
4181      /* @__PURE__ */ u2("circle", {
4182        cx: "12",
4183        cy: "12",
4184        r: "3"
4185      })
4186    ]
4187  }),
4188  info: /* @__PURE__ */ u2("svg", {
4189    viewBox: "0 0 24 24",
4190    children: [
4191      /* @__PURE__ */ u2("circle", {
4192        cx: "12",
4193        cy: "12",
4194        r: "9"
4195      }),
4196      /* @__PURE__ */ u2("path", {
4197        d: "M12 11v5M12 8h.01"
4198      })
4199    ]
4200  }),
4201  /*	Where a new element goes, drawn as what it looks like on the page: the picked element as a
4202  solid box, and the new one as a dashed one above it, inside it, or below it. */
4203  putBefore: /* @__PURE__ */ u2("svg", {
4204    viewBox: "0 0 24 24",
4205    children: [
4206      /* @__PURE__ */ u2("rect", {
4207        x: "4",
4208        y: "3",
4209        width: "16",
4210        height: "6",
4211        rx: "1",
4212        "stroke-dasharray": "3 2"
4213      }),
4214      /* @__PURE__ */ u2("rect", {
4215        x: "4",
4216        y: "13",
4217        width: "16",
4218        height: "8",
4219        rx: "1"
4220      })
4221    ]
4222  }),
4223  putInside: /* @__PURE__ */ u2("svg", {
4224    viewBox: "0 0 24 24",
4225    children: [
4226      /* @__PURE__ */ u2("rect", {
4227        x: "3",
4228        y: "4",
4229        width: "18",
4230        height: "16",
4231        rx: "1"
4232      }),
4233      /* @__PURE__ */ u2("rect", {
4234        x: "7",
4235        y: "12",
4236        width: "10",
4237        height: "5",
4238        rx: "1",
4239        "stroke-dasharray": "3 2"
4240      })
4241    ]
4242  }),
4243  putAfter: /* @__PURE__ */ u2("svg", {
4244    viewBox: "0 0 24 24",
4245    children: [
4246      /* @__PURE__ */ u2("rect", {
4247        x: "4",
4248        y: "3",
4249        width: "16",
4250        height: "8",
4251        rx: "1"
4252      }),
4253      /* @__PURE__ */ u2("rect", {
4254        x: "4",
4255        y: "15",
4256        width: "16",
4257        height: "6",
4258        rx: "1",
4259        "stroke-dasharray": "3 2"
4260      })
4261    ]
4262  }),
4263  scopePage: /* @__PURE__ */ u2("svg", {
4264    viewBox: "0 0 24 24",
4265    children: [
4266      /* @__PURE__ */ u2("path", {
4267        d: "M6 3h8l4 4v14H6z"
4268      }),
4269      /* @__PURE__ */ u2("path", {
4270        d: "M14 3v4h4"
4271      })
4272    ]
4273  }),
4274  scopeShape: /* @__PURE__ */ u2("svg", {
4275    viewBox: "0 0 24 24",
4276    children: [
4277      /* @__PURE__ */ u2("path", {
4278        d: "M8 6V4.5A1.5 1.5 0 0 1 9.5 3h9A1.5 1.5 0 0 1 20 4.5v11a1.5 1.5 0 0 1-1.5 1.5H17"
4279      }),
4280      /* @__PURE__ */ u2("rect", {
4281        x: "4",
4282        y: "6",
4283        width: "13",
4284        height: "15",
4285        rx: "1.5"
4286      }),
4287      /* @__PURE__ */ u2("path", {
4288        d: "M7 10h7M7 14h5"
4289      })
4290    ]
4291  }),
4292  scopeAny: /* @__PURE__ */ u2("svg", {
4293    viewBox: "0 0 24 24",
4294    children: [
4295      /* @__PURE__ */ u2("circle", {
4296        cx: "12",
4297        cy: "12",
4298        r: "9"
4299      }),
4300      /* @__PURE__ */ u2("path", {
4301        d: "M3 12h18"
4302      }),
4303      /* @__PURE__ */ u2("path", {
4304        d: "M12 3a14 14 0 0 1 0 18a14 14 0 0 1 0-18z"
4305      })
4306    ]
4307  })
4308};
4309
4310// src/components/live/liveKit.tsx
4311function Field(props) {
4312  return /* @__PURE__ */ u2("div", {
4313    class: props.wide ? "le-field le-field-wide" : "le-field",
4314    children: [
4315      /* @__PURE__ */ u2("label", {
4316        children: props.label
4317      }),
4318      /* @__PURE__ */ u2("div", {
4319        class: "le-field-group",
4320        children: props.children
4321      })
4322    ]
4323  });
4324}
4325function Section(props) {
4326  return /* @__PURE__ */ u2("div", {
4327    class: "le-sec",
4328    children: [
4329      props.title && /* @__PURE__ */ u2("h5", {
4330        children: props.title
4331      }),
4332      props.children
4333    ]
4334  });
4335}
4336function toHexColor(value) {
4337  const text = (value ?? "").trim();
4338  if (/^#[0-9A-F]{6}$/i.test(text)) {
4339    return text.toUpperCase();
4340  }
4341  if (/^#[0-9A-F]{3}$/i.test(text)) {
4342    return ("#" + text[1] + text[1] + text[2] + text[2] + text[3] + text[3]).toUpperCase();
4343  }
4344  const m2 = text.match(/^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)/i);
4345  if (m2) {
4346    const hex = (n2) => Math.max(0, Math.min(255, Math.round(parseFloat(n2)))).toString(16).padStart(2, "0");
4347    return ("#" + hex(m2[1]) + hex(m2[2]) + hex(m2[3])).toUpperCase();
4348  }
4349  return "";
4350}
4351function ColorInput(props) {
4352  const hex = toHexColor(props.value);
4353  const [draft, setDraft] = p2(null);
4354  const shown = draft ?? (hex || props.value);
4355  return /* @__PURE__ */ u2("div", {
4356    class: "le-color",
4357    children: [
4358      /* @__PURE__ */ u2("label", {
4359        class: "le-swatch" + (hex ? "" : " le-empty"),
4360        style: hex ? "background-color:" + hex : "",
4361        title: hex ? "Pick a color - now " + hex : "Pick a color",
4362        children: /* @__PURE__ */ u2("input", {
4363          type: "color",
4364          value: hex || "#000000",
4365          onInput: (ev) => {
4366            setDraft(null);
4367            props.onChange(ev.target.value.toUpperCase());
4368          }
4369        })
4370      }),
4371      /* @__PURE__ */ u2("input", {
4372        type: "text",
4373        value: shown,
4374        placeholder: "auto",
4375        spellcheck: false,
4376        onInput: (ev) => {
4377          const text = ev.target.value;
4378          setDraft(text);
4379          props.onChange(text);
4380        },
4381        onBlur: () => setDraft(null)
4382      })
4383    ]
4384  });
4385}
4386function NumInput(props) {
4387  return /* @__PURE__ */ u2("div", {
4388    class: "le-num",
4389    children: [
4390      /* @__PURE__ */ u2("input", {
4391        type: "number",
4392        value: props.value,
4393        min: props.min,
4394        max: props.max,
4395        step: props.step,
4396        onInput: (ev) => props.onChange(ev.target.value)
4397      }),
4398      /* @__PURE__ */ u2("span", {
4399        class: "le-unit",
4400        children: props.unit
4401      })
4402    ]
4403  });
4404}
4405function SelectInput(props) {
4406  return /* @__PURE__ */ u2("select", {
4407    class: "le-select",
4408    value: props.value,
4409    onChange: (ev) => props.onChange(ev.target.value),
4410    children: props.options.map(([value, label]) => /* @__PURE__ */ u2("option", {
4411      value,
4412      children: label
4413    }, value))
4414  });
4415}
4416function TextInput(props) {
4417  return /* @__PURE__ */ u2("input", {
4418    class: "le-input",
4419    type: "text",
4420    value: props.value,
4421    placeholder: props.placeholder,
4422    onInput: (ev) => props.onChange(ev.target.value)
4423  });
4424}
4425function IconGroup(props) {
4426  return /* @__PURE__ */ u2("div", {
4427    class: "le-icongroup",
4428    children: props.choices.map((choice) => /* @__PURE__ */ u2("button", {
4429      type: "button",
4430      title: choice.title,
4431      class: choice.value == props.value ? "le-on" : void 0,
4432      onClick: () => props.onChange(choice.value == props.value ? "" : choice.value),
4433      children: choice.icon ?? choice.text
4434    }, choice.value))
4435  });
4436}
4437function PanelTabs(props) {
4438  return /* @__PURE__ */ u2("div", {
4439    class: "le-ptabs",
4440    role: "tablist",
4441    children: props.tabs.map((tab) => /* @__PURE__ */ u2("button", {
4442      type: "button",
4443      role: "tab",
4444      "aria-selected": tab.id == props.value,
4445      class: "le-ptab" + (tab.id == props.value ? " le-on" : ""),
4446      title: tab.title,
4447      onClick: () => props.onChange(tab.id),
4448      children: tab.label
4449    }, tab.id))
4450  });
4451}
4452function CountClicksCheck(props) {
4453  const title = props.counted ? "Clicks on this element are already counted by an earlier change - switch that off in the list of changes." : (props.isInsert ? "Clicks on the new element" : "Clicks on this element") + " count toward this action's Clicks in the campaign report.";
4454  return /* @__PURE__ */ u2("label", {
4455    class: "le-count-clicks" + (props.on ? " le-on" : "") + (props.counted ? " le-locked" : ""),
4456    "data-tooltip": title,
4457    "data-tooltip-pos": "top",
4458    children: [
4459      /* @__PURE__ */ u2("input", {
4460        type: "checkbox",
4461        class: "le-check",
4462        checked: props.on,
4463        disabled: props.counted,
4464        onChange: (ev) => props.onChange(ev.target.checked)
4465      }),
4466      props.counted ? "Clicks counted" : "Count clicks"
4467    ]
4468  });
4469}
4470function SubPanelHead(props) {
4471  return /* @__PURE__ */ u2(k, {
4472    children: [
4473      /* @__PURE__ */ u2("div", {
4474        class: "le-sub-head",
4475        children: [
4476          /* @__PURE__ */ u2("button", {
4477            type: "button",
4478            class: "le-back",
4479            onClick: props.onBack,
4480            title: "Discard what you changed here and go back to the tiles - the same as Cancel below",
4481            children: [
4482              li.chevronLeft,
4483              "Back"
4484            ]
4485          }),
4486          /* @__PURE__ */ u2("b", {
4487            children: props.title
4488          }),
4489          /* @__PURE__ */ u2("div", {
4490            class: "le-spacer"
4491          }),
4492          props.extra
4493        ]
4494      }),
4495      /* @__PURE__ */ u2("div", {
4496        class: "le-target",
4497        children: [
4498          "Editing ",
4499          /* @__PURE__ */ u2("code", {
4500            title: props.target,
4501            children: props.target
4502          })
4503        ]
4504      })
4505    ]
4506  });
4507}
4508
4509// src/components/live/widgetCatalogue.ts
4510var WIDGET_CATALOGUE = [
4511  {
4512    "id": "countdown",
4513    "label": "Countdown",
4514    "html": '<div class="pz-block-timer" data-pz-timer="{&quot;type&quot;:&quot;restart_everytime&quot;,&quot;fixed_date_date&quot;:&quot;&quot;,&quot;fixed_date_timezone&quot;:&quot;&quot;,&quot;fixed_date_time&quot;:&quot;&quot;,&quot;cookie_based_days&quot;:0,&quot;cookie_based_hours&quot;:1,&quot;cookie_based_minutes&quot;:0,&quot;cookie_based_restart&quot;:0,&quot;cookie_based_restart_after&quot;:&quot;hours&quot;,&quot;cookie_based_restart_after_hours&quot;:1,&quot;cookie_based_restart_after_weekday&quot;:0,&quot;restart_everytime_days&quot;:0,&quot;restart_everytime_hours&quot;:1,&quot;restart_everytime_minutes&quot;:0}" style="text-align:center; margin:16px 0; display:flex; flex-wrap:wrap; gap:6px; justify-content:center;"><span style="background:#0F172A; color:#fff; padding:8px 12px; border-radius:6px; min-width:46px; display:inline-block; text-align:center;"><strong data-pz-timer-unit="d" style="font-size:18px; font-weight:800; display:block;">0</strong><small style="font-size:9px; opacity:0.7; text-transform:uppercase;">Day(s)</small></span><span style="background:#0F172A; color:#fff; padding:8px 12px; border-radius:6px; min-width:46px; display:inline-block; text-align:center;"><strong data-pz-timer-unit="h" style="font-size:18px; font-weight:800; display:block;">1</strong><small style="font-size:9px; opacity:0.7; text-transform:uppercase;">Hour(s)</small></span><span style="background:#0F172A; color:#fff; padding:8px 12px; border-radius:6px; min-width:46px; display:inline-block; text-align:center;"><strong data-pz-timer-unit="m" data-pz-timer-pad="2" style="font-size:18px; font-weight:800; display:block;">00</strong><small style="font-size:9px; opacity:0.7; text-transform:uppercase;">Minute(s)</small></span><span style="background:#0F172A; color:#fff; padding:8px 12px; border-radius:6px; min-width:46px; display:inline-block; text-align:center;"><strong data-pz-timer-unit="s" data-pz-timer-pad="2" style="font-size:18px; font-weight:800; display:block;">00</strong><small style="font-size:9px; opacity:0.7; text-transform:uppercase;">Second(s)</small></span></div>',
4515    "props": {
4516      "anim": "",
4517      "device": "",
4518      "cssMobile": "",
4519      "beside": "",
4520      "width": "",
4521      "boxAlign": "center",
4522      "spaceTop": "",
4523      "spaceBottom": "",
4524      "boxBg": "",
4525      "boxPad": "",
4526      "boxBorder": 0,
4527      "boxBorderColor": "#E2E8F0",
4528      "boxCorner": "",
4529      "boxShadow": "",
4530      "animLoop": "",
4531      "css": "",
4532      "timer": '{"type":"restart_everytime","fixed_date_date":"","fixed_date_timezone":"","fixed_date_time":"","cookie_based_days":0,"cookie_based_hours":1,"cookie_based_minutes":0,"cookie_based_restart":0,"cookie_based_restart_after":"hours","cookie_based_restart_after_hours":1,"cookie_based_restart_after_weekday":0,"restart_everytime_days":0,"restart_everytime_hours":1,"restart_everytime_minutes":0}',
4533      "capDays": "Day(s)",
4534      "capHours": "Hour(s)",
4535      "capMinutes": "Minute(s)",
4536      "capSeconds": "Second(s)",
4537      "cellBg": "#0F172A",
4538      "cellColor": "#fff",
4539      "styleKind": "boxes",
4540      "digitSize": 18,
4541      "boxRadius": 6
4542    },
4543    "settings": [
4544      {
4545        "key": "timer",
4546        "label": "Countdown",
4547        "kind": "timer",
4548        "tab": "content",
4549        "hint": "Fixed date, per-visitor cookie, or every visit \u2014 and, at the bottom, what happens when it reaches zero: sit at zeros, swap in a message, or disappear."
4550      },
4551      {
4552        "key": "capDays",
4553        "label": "Days caption (blank hides days)",
4554        "kind": "text",
4555        "tab": "content",
4556        "hint": "Blank caption hides the unit; hidden units roll into the next."
4557      },
4558      {
4559        "key": "capHours",
4560        "label": "Hours caption (blank hides hours)",
4561        "kind": "text",
4562        "tab": "content"
4563      },
4564      {
4565        "key": "capMinutes",
4566        "label": "Minutes caption (blank hides minutes)",
4567        "kind": "text",
4568        "tab": "content"
4569      },
4570      {
4571        "key": "capSeconds",
4572        "label": "Seconds caption (blank hides seconds)",
4573        "kind": "text",
4574        "tab": "content"
4575      },
4576      {
4577        "key": "styleKind",
4578        "label": "Style",
4579        "kind": "select",
4580        "tab": "style",
4581        "hint": "Boxes, pills, or bare digits with colons.",
4582        "options": [
4583          [
4584            "boxes",
4585            "Boxes"
4586          ],
4587          [
4588            "pills",
4589            "Pills"
4590          ],
4591          [
4592            "minimal",
4593            "Minimal \u2014 digits and colons"
4594          ]
4595        ]
4596      },
4597      {
4598        "key": "digitSize",
4599        "label": "Digit size",
4600        "kind": "number",
4601        "tab": "style",
4602        "unit": "px",
4603        "min": 12,
4604        "max": 64
4605      },
4606      {
4607        "key": "boxRadius",
4608        "label": "Corner radius",
4609        "kind": "number",
4610        "tab": "style",
4611        "unit": "px",
4612        "min": 0,
4613        "max": 100
4614      },
4615      {
4616        "key": "cellBg",
4617        "label": "Cell background",
4618        "kind": "color",
4619        "tab": "style"
4620      },
4621      {
4622        "key": "cellColor",
4623        "label": "Cell text",
4624        "kind": "color",
4625        "tab": "style"
4626      }
4627    ]
4628  },
4629  {
4630    "id": "coupon",
4631    "label": "Coupon",
4632    "html": '<div style="display:flex; align-items:stretch; max-width:280px; margin:12px auto; border-radius:8px; overflow:hidden; border:2px dashed #92400E;"><span class="pz-coupon-code" style="flex:1; background:#FEF3C7; color:#92400E; padding:10px 14px; font-family:monospace; font-weight:700; letter-spacing:2px; text-align:center;">WELCOME10</span><button type="button" class="pz-copy-code" data-copied-label="COPIED!" style="text-align:center; margin:0; text-transform:none; letter-spacing:normal; background:#92400E; color:#fff; border:0; padding:0 14px; font-weight:700; font-size:11px; cursor:pointer;">COPY</button></div>',
4633    "props": {
4634      "anim": "",
4635      "device": "",
4636      "cssMobile": "",
4637      "beside": "",
4638      "width": "",
4639      "boxAlign": "center",
4640      "spaceTop": "",
4641      "spaceBottom": "",
4642      "boxBg": "",
4643      "boxPad": "",
4644      "boxBorder": 0,
4645      "boxBorderColor": "#E2E8F0",
4646      "boxCorner": "",
4647      "boxShadow": "",
4648      "hoverBg": "",
4649      "hoverText": "",
4650      "report": "",
4651      "reportEvent": "",
4652      "reportValue": "",
4653      "animLoop": "",
4654      "css": "",
4655      "code": "WELCOME10",
4656      "bg": "#FEF3C7",
4657      "color": "#92400E",
4658      "borderStyle": "dashed",
4659      "copyLabel": "COPY",
4660      "copiedLabel": "COPIED!"
4661    },
4662    "settings": [
4663      {
4664        "key": "code",
4665        "label": "Coupon code",
4666        "kind": "text",
4667        "tab": "content"
4668      },
4669      {
4670        "key": "bg",
4671        "label": "Background",
4672        "kind": "color",
4673        "tab": "style"
4674      },
4675      {
4676        "key": "color",
4677        "label": "Text & border",
4678        "kind": "color",
4679        "tab": "style"
4680      },
4681      {
4682        "key": "borderStyle",
4683        "label": "Border style",
4684        "kind": "select",
4685        "tab": "style",
4686        "options": [
4687          [
4688            "dashed",
4689            "Dashed"
4690          ],
4691          [
4692            "solid",
4693            "Solid"
4694          ],
4695          [
4696            "dotted",
4697            "Dotted"
4698          ]
4699        ]
4700      },
4701      {
4702        "key": "copyLabel",
4703        "label": "Copy button label",
4704        "kind": "text",
4705        "tab": "content",
4706        "hint": "One click copies the code to the visitor\u2019s clipboard and briefly shows the \u201Ccopied\u201D label."
4707      },
4708      {
4709        "key": "copiedLabel",
4710        "label": "Label after copying",
4711        "kind": "text",
4712        "tab": "content"
4713      },
4714      {
4715        "key": "hoverBg",
4716        "label": "Background on hover",
4717        "kind": "color",
4718        "tab": "style",
4719        "hint": "What it changes to under the mouse. Blank keeps it still."
4720      },
4721      {
4722        "key": "hoverText",
4723        "label": "Text on hover",
4724        "kind": "color",
4725        "tab": "style"
4726      },
4727      {
4728        "key": "report",
4729        "label": "When clicked, report",
4730        "kind": "select",
4731        "tab": "content",
4732        "hint": "Beside the click itself, which is always counted.",
4733        "options": [
4734          [
4735            "",
4736            "Nothing extra"
4737          ],
4738          [
4739            "goal",
4740            "This action reached its goal"
4741          ],
4742          [
4743            "event",
4744            "A conversion goal"
4745          ],
4746          [
4747            "both",
4748            "Both"
4749          ]
4750        ]
4751      },
4752      {
4753        "key": "reportEvent",
4754        "label": "Which goal",
4755        "kind": "goal",
4756        "tab": "content",
4757        "hint": "Event-driven goals only \u2014 the kind that waits for a named event. Other goal types watch the page themselves and need nothing here."
4758      },
4759      {
4760        "key": "reportValue",
4761        "label": "Value",
4762        "kind": "text",
4763        "tab": "content",
4764        "hint": "Optional \u2014 a revenue amount, a plan name. Variables work here."
4765      }
4766    ]
4767  },
4768  {
4769    "id": "wheel",
4770    "label": "Prize Wheel",
4771    "html": '<div class="pz-block-wheel" data-pz-wheel="{&quot;w&quot;:[3,3,2,4,3,1],&quot;once&quot;:1,&quot;snd&quot;:0,&quot;cel&quot;:&quot;confetti&quot;}" style="text-align:center; margin:16px 0;"><div style="width:260px; max-width:100%; margin:0 auto;"><div style="position:relative; height:0; padding-bottom:100%;"><div class="pz-wheel-disc" style="position:absolute; inset:0; border-radius:50%; background:conic-gradient(from -30deg, #0EA5E9 0deg 60deg, #F1F5F9 60deg 120deg, #818CF8 120deg 180deg, #E2E8F0 180deg 240deg, #F97316 240deg 300deg, #10B981 300deg 360deg); box-shadow:0 6px 20px rgba(15,23,42,0.22); transition:transform 4.2s cubic-bezier(0.15,0.7,0.15,1);"><span style="position:absolute; inset:0; transform:rotate(0deg);"><span style="display:block; width:58%; margin:6% auto 0; text-align:center; font:700 12px/1.2 inherit; color:#0F172A; overflow:hidden;">10% off</span></span><span style="position:absolute; inset:0; transform:rotate(60deg);"><span style="display:block; width:58%; margin:6% auto 0; text-align:center; font:700 12px/1.2 inherit; color:#0F172A; overflow:hidden;">Free shipping</span></span><span style="position:absolute; inset:0; transform:rotate(120deg);"><span style="display:block; width:58%; margin:6% auto 0; text-align:center; font:700 12px/1.2 inherit; color:#0F172A; overflow:hidden;">20% off</span></span><span style="position:absolute; inset:0; transform:rotate(180deg);"><span style="display:block; width:58%; margin:6% auto 0; text-align:center; font:700 12px/1.2 inherit; color:#0F172A; overflow:hidden;">Try again</span></span><span style="position:absolute; inset:0; transform:rotate(240deg);"><span style="display:block; width:58%; margin:6% auto 0; text-align:center; font:700 12px/1.2 inherit; color:#0F172A; overflow:hidden;">5% off</span></span><span style="position:absolute; inset:0; transform:rotate(300deg);"><span style="display:block; width:58%; margin:6% auto 0; text-align:center; font:700 12px/1.2 inherit; color:#0F172A; overflow:hidden;">Free gift</span></span></div><div style="position:absolute; top:-6px; left:50%; margin-left:-9px; width:0; height:0; border-left:9px solid transparent; border-right:9px solid transparent; border-top:16px solid #0F172A; z-index:2;"></div></div></div><button type="button" class="pz-wheel-go" data-personyze-click-target="1" style="text-align:center; margin:0; text-transform:none; letter-spacing:normal; margin-top:14px; background:#DB2777; color:#FFFFFF; border:0; padding:11px 26px; border-radius:8px; font:inherit; font-size:14px; font-weight:700; cursor:pointer;">Spin the wheel</button><div class="pz-wheel-result" style="display:none; margin-top:12px; font-size:14px; color:#334155;"></div>
4771<i data-pz-wheel-prize="0" style="display:none"><b>You won 10% off!</b><div style="display:flex; align-items:stretch; max-width:280px; margin:12px auto; border-radius:8px; overflow:hidden; border:2px dashed #92400E;"><span class="pz-coupon-code" style="flex:1; background:#FEF3C7; color:#92400E; padding:10px 14px; font-family:monospace; font-weight:700; letter-spacing:2px; text-align:center;">SPIN10</span><button type="button" class="pz-copy-code" data-copied-label="COPIED!" style="text-align:center; margin:0; text-transform:none; letter-spacing:normal; background:#92400E; color:#fff; border:0; padding:0 14px; font-weight:700; font-size:11px; cursor:pointer;">COPY</button></div></i><i data-pz-wheel-prize="1" style="display:none"><b>Free shipping is yours.</b><div style="display:flex; align-items:stretch; max-width:280px; margin:12px auto; border-radius:8px; overflow:hidden; border:2px dashed #92400E;"><span class="pz-coupon-code" style="flex:1; background:#FEF3C7; color:#92400E; padding:10px 14px; font-family:monospace; font-weight:700; letter-spacing:2px; text-align:center;">FREESHIP</span><button type="button" class="pz-copy-code" data-copied-label="COPIED!" style="text-align:center; margin:0; text-transform:none; letter-spacing:normal; background:#92400E; color:#fff; border:0; padding:0 14px; font-weight:700; font-size:11px; cursor:pointer;">COPY</button></div></i><i data-pz-wheel-prize="2" style="display:none"><b>You won 20% off!</b><div style="display:flex; align-items:stretch; max-width:280px; margin:12px auto; border-radius:8px; overflow:hidden; border:2px dashed #92400E;"><span class="pz-coupon-code" style="flex:1; background:#FEF3C7; color:#92400E; padding:10px 14px; font-family:monospace; font-weight:700; letter-spacing:2px; text-align:center;">SPIN20</span><button type="button" class="pz-copy-code" data-copied-label="COPIED!" style="text-align:center; margin:0; text-transform:none; letter-spacing:normal; background:#92400E; color:#fff; border:0; padding:0 14px; font-weight:700; font-size:11px; cursor:pointer;">COPY</button></div></i><i data-pz-wheel-prize="3" style="display:none"><b>No luck this time.</b></i><i data-pz-wheel-prize="4" style="display:none"><b>You won 5% off!</b><div style="display:flex; align-items:stretch; max-width:280px; margin:12px auto; border-radius:8px; overflow:hidden; border:2px dashed #92400E;"><span class="pz-coupon-code" style="flex:1; background:#FEF3C7; color:#92400E; padding:10px 14px; font-family:monospace; font-weight:700; letter-spacing:2px; text-align:center;">SPIN5</span><button type="button" class="pz-copy-code" data-copied-label="COPIED!" style="text-align:center; margin:0; text-transform:none; letter-spacing:normal; background:#92400E; color:#fff; border:0; padding:0 14px; font-weight:700; font-size:11px; cursor:pointer;">COPY</button></div></i><i data-pz-wheel-prize="5" style="display:none"><b>A free gift \u2014 it is in the bag.</b><div style="display:flex; align-items:stretch; max-width:280px; margin:12px auto; border-radius:8px; overflow:hidden; border:2px dashed #92400E;"><span class="pz-coupon-code" style="flex:1; background:#FEF3C7; color:#92400E; padding:10px 14px; font-family:monospace; font-weight:700; letter-spacing:2px; text-align:center;">GIFT</span><button type="button" class="pz-copy-code" data-copied-label="COPIED!" style="text-align:center; margin:0; text-transform:none; letter-spacing:normal; background:#92400E; color:#fff; border:0; padding:0 14px; font-weight:700; font-size:11px; cursor:pointer;">COPY</button></div></i></div>',
4772    "props": {
4773      "anim": "",
4774      "device": "",
4775      "cssMobile": "",
4776      "beside": "",
4777      "width": "",
4778      "boxAlign": "center",
4779      "spaceTop": "",
4780      "spaceBottom": "",
4781      "boxBg": "",
4782      "boxPad": "",
4783      "boxBorder": 0,
4784      "boxBorderColor": "#E2E8F0",
4785      "boxCorner": "",
4786      "boxShadow": "",
4787      "report": "",
4788      "reportEvent": "",
4789      "reportValue": "",
4790      "css": "",
4791      "segments": '[{"label":"10% off","color":"#0EA5E9","weight":3,"prize":"You won 10% off!","code":"SPIN10"},{"label":"Free shipping","color":"#F1F5F9","weight":3,"prize":"Free shipping is y
4791ours.","code":"FREESHIP"},{"label":"20% off","color":"#818CF8","weight":2,"prize":"You won 20% off!","code":"SPIN20"},{"label":"Try again","color":"#E2E8F0","weight":4,"prize":"No luck this time.","code":""},{"label":"5% off","color":"#F97316","weight":3,"prize":"You won 5% off!","code":"SPIN5"},{"label":"Free gift","color":"#10B981","weight":1,"prize":"A free gift \u2014 it is in the bag.","code":"GIFT"}]',
4792      "size": 260,
4793      "spinText": "Spin the wheel",
4794      "spinBg": "#DB2777",
4795      "spinColor": "#FFFFFF",
4796      "pointer": "#0F172A",
4797      "labelColor": "#0F172A",
4798      "labelDir": "radial",
4799      "once": "1",
4800      "sound": "",
4801      "celebrate": "confetti",
4802      "codeStyle": "coupon",
4803      "codeBg": "#FEF3C7",
4804      "codeColor": "#92400E",
4805      "copyLabel": "COPY",
4806      "copiedLabel": "COPIED!"
4807    },
4808    "settings": [
4809      {
4810        "key": "segments",
4811        "label": "Segments",
4812        "kind": "segment_list",
4813        "tab": "content",
4814        "hint": "Label, colour, odds and what winning it says."
4815      },
4816      {
4817        "key": "spinText",
4818        "label": "Button label",
4819        "kind": "text",
4820        "tab": "content"
4821      },
4822      {
4823        "key": "size",
4824        "label": "Wheel size",
4825        "kind": "number",
4826        "tab": "style",
4827        "unit": "px",
4828        "min": 140,
4829        "max": 520
4830      },
4831      {
4832        "key": "spinBg",
4833        "label": "Button background",
4834        "kind": "color",
4835        "tab": "style"
4836      },
4837      {
4838        "key": "spinColor",
4839        "label": "Button text",
4840        "kind": "color",
4841        "tab": "style"
4842      },
4843      {
4844        "key": "pointer",
4845        "label": "Pointer colour",
4846        "kind": "color",
4847        "tab": "style"
4848      },
4849      {
4850        "key": "labelColor",
4851        "label": "Segment text",
4852        "kind": "color",
4853        "tab": "style"
4854      },
4855      {
4856        "key": "labelDir",
4857        "label": "Segment text direction",
4858        "kind": "select",
4859        "tab": "style",
4860        "hint": "Along the spoke, or upright so every label reads normally.",
4861        "options": [
4862          [
4863            "radial",
4864            "Along the segment"
4865          ],
4866          [
4867            "flat",
4868            "Upright \u2014 always horizontal"
4869          ]
4870        ]
4871      },
4872      {
4873        "key": "celebrate",
4874        "label": "When they win",
4875        "kind": "select",
4876        "tab": "content",
4877        "hint": "A little payoff when the wheel stops.",
4878        "options": [
4879          [
4880            "confetti",
4881            "Confetti"
4882          ],
4883          [
4884            "glow",
4885            "Glow and pulse"
4886          ],
4887          [
4888            "",
4889            "Nothing"
4890          ]
4891        ]
4892      },
4893      {
4894        "key": "sound",
4895        "label": "Sound while it spins",
4896        "kind": "checkbox",
4897        "tab": "content",
4898        "hint": "Ticks as it passes each segment, then a chime. Off by default \u2014 it starts on their click, never on its own."
4899      },
4900      {
4901        "key": "once",
4902        "label": "One spin per visitor",
4903        "kind": "checkbox",
4904        "tab": "content",
4905        "hint": "Their result is remembered and shown again instead of a second spin."
4906      },
4907      {
4908        "key": "codeStyle",
4909        "label": "How the prize code appears",
4910        "kind": "select",
4911        "tab": "content",
4912        "hint": "A claimable coupon with a Copy button, or plain text for prizes that are not codes.",
4913        "options": [
4914          [
4915            "coupon",
4916            "Coupon box with a Copy button"
4917          ],
4918          [
4919            "text",
4920            "Plain text"
4921          ]
4922        ]
4923      },
4924      {
4925        "key": "copyLabel",
4926        "label": "Copy button label",
4927        "kind": "text",
4928        "tab": "content"
4929      },
4930      {
4931        "key": "copiedLabel",
4932        "label": "Label after copying",
4933        "kind": "text",
4934        "tab": "content"
4935      },
4936      {
4937        "key": "codeBg",
4938        "label": "Coupon background",
4939        "kind": "color",
4940        "tab": "style"
4941      },
4942      {
4943        "key": "codeColor",
4944        "label": "Coupon text & border",
4945        "kind": "color",
4946        "tab": "style"
4947      },
4948      {
4949        "key": "report",
4950        "label": "When clicked, report",
4951        "kind": "select",
4952        "tab": "content",
4953        "hint": "Beside the click itself, which is always counted.",
4954        "options": [
4955          [
4956            "",
4957            "Nothing extra"
4958          ],
4959          [
4960            "goal",
4961            "This action reached its goal"
4962          ],
4963          [
4964            "event",
4965            "A conversion goal"
4966          ],
4967          [
4968            "both",
4969            "Both"
4970          ]
4971        ]
4972      },
4973      {
4974        "key": "reportEvent",
4975        "label": "Which goal",
4976        "kind": "goal",
4977        "tab": "content",
4978        "hint": "Event-driven goals only \u2014 the kind that waits for a named event. Other goal types watch the page themselves and need nothing here."
4979      },
4980      {
4981        "key": "reportValue",
4982        "label": "Value",
4983        "kind": "text",
4984        "tab": "content",
4985        "hint": "Optional \u2014 a revenue amount, a plan name. Variables work here."
4986      }
4987    ]
4988  },
4989  {
4990    "id": "progress",
4991    "label": "Progress Bar",
4992    "html": '<div class="pz-block-pbar" data-pz-pbar="{&quot;goal&quot;:100,&quot;pre&quot;:&quot;$&quot;,&quot;suf&quot;:&quot;&quot;}" style="margin:14px 0;"><i class="pz-pbar-value" style="display:none"></i><i class="pz-pbar-tpl" style="display:none">You are {left} away from free shipping</i><i class="pz-pbar-tpl-done" style="display:none">You unlocked free shipping!</i><div class="pz-pbar-msg" style="font-size:13px; font-weight:600; color:#0F172A; margin-bottom:7px; text-align:center;">You are $100 away from free shipping</div><div style="background:#E2E8F0; border-radius:999px; overflow:hidden; height:10px;"><div class="pz-pbar-fill" style="width:0%; height:100%; background:#10B981; border-radius:999px; transition:width 0.6s ease;"></div></div></div>',
4993    "props": {
4994      "anim": "",
4995      "device": "",
4996      "cssMobile": "",
4997      "beside": "",
4998      "width": "",
4999      "boxAlign": "center",
5000      "spaceTop": "",
5001      "spaceBottom": "",
5002      "boxBg": "",
5003      "boxPad": "",
5004      "boxBorder": 0,
5005      "boxBorderColor": "#E2E8F0",
5006      "boxCorner": "",
5007      "boxShadow": "",
5008      "css": "",
5009      "value": "",
5010      "goal": 100,
5011      "prefix": "$",
5012      "suffix": "",
5013      "text": "You are {left} away from free shipping",
5014      "doneText": "You unlocked free shipping!",
5015      "fill": "#10B981",
5016      "track": "#E2E8F0",
5017      "color": "#0F172A",
5018      "height": 10,
5019      "radius": 999,
5020      "demoValue": 65
5021    },
5022    "settings": [
5023      {
5024        "key": "value",
5025        "label": "Value",
5026        "kind": "visitor_field",
5027        "tab": "content",
5028        "hint": "The profile field holding the amount so far \u2014 a cart total, points, steps."
5029      },
5030      {
5031        "key": "goal",
5032        "label": "Goal",
5033        "kind": "number",
5034        "tab": "content",
5035        "min": 1,
5036        "max": 1e6
5037      },
5038      {
5039        "key": "text",
5040        "label": "Message",
5041        "kind": "text",
5042        "tab": "content",
5043        "hint": "{left}, {value} and {goal} fill in live."
5044      },
5045      {
5046        "key": "doneText",
5047        "label": "Message once reached",
5048        "kind": "text",
5049        "tab": "content"
5050      },
5051      {
5052        "key": "prefix",
5053        "label": "Before the number",
5054        "kind": "text",
5055        "tab": "content",
5056        "hint": "A currency symbol, usually."
5057      },
5058      {
5059        "key": "suffix",
5060        "label": "After the number",
5061        "kind": "text",
5062        "tab": "content",
5063        "hint": "Points, items, km \u2014 whatever it counts."
5064      },
5065      {
5066        "key": "demoValue",
5067        "label": "Preview value",
5068        "kind": "number",
5069        "tab": "content",
5070        "hint": "Editor only \u2014 what the bar shows here, where no visitor exists.",
5071        "min": 0,
5072        "max": 1e6
5073      },
5074      {
5075        "key": "fill",
5076        "label": "Bar colour",
5077        "kind": "color",
5078        "tab": "style"
5079      },
5080      {
5081        "key": "track",
5082        "label": "Track colour",
5083        "kind": "color",
5084        "tab": "style"
5085      },
5086      {
5087        "key": "color",
5088        "label": "Text colour",
5089        "kind": "color",
5090        "tab": "style"
5091      },
5092      {
5093        "key": "height",
5094        "label": "Bar thickness",
5095        "kind": "number",
5096        "tab": "style",
5097        "unit": "px",
5098        "min": 3,
5099        "max": 60
5100      },
5101      {
5102        "key": "radius",
5103        "label": "Corner radius",
5104        "kind": "number",
5105        "tab": "style",
5106        "unit": "px",
5107        "min": 0,
5108        "max": 999
5109      }
5110    ]
5111  },
5112  {
5113    "id": "stock",
5114    "label": "Stock Urgency",
5115    "html": '<div class="pz-block-stock" data-pz-stock="{&quot;max&quot;:10}" style="text-align:center; margin:12px 0;"><span style="display:inline-block; background:#FEE2E2; color:#B91C1C; border-radius:999px; padding:6px 14px; font-size:13px; font-weight:700;">Only <b class="pz-stock-n">3</b> left in stock</span></div>',
5116    "props": {
5117      "anim": "",
5118      "device": "",
5119      "cssMobile": "",
5120      "beside": "",
5121      "width": "",
5122      "boxAlign": "center",
5123      "spaceTop": "",
5124      "spaceBottom": "",
5125      "boxBg": "",
5126      "boxPad": "",
5127      "boxBorder": 0,
5128      "boxBorderColor": "#E2E8F0",
5129      "boxCorner": "",
5130      "boxShadow": "",
5131      "css": "",
5132      "value": "",
5133      "fixed": 3,
5134      "max": 10,
5135      "text": "Only {count} left in stock",
5136      "color": "#B91C1C",
5137      "bg": "#FEE2E2",
5138      "fontSize": 13,
5139      "align": "center",
5140      "demoValue": 3
5141    },
5142    "settings": [
5143      {
5144        "key": "value",
5145        "label": "Where the number comes from",
5146        "kind": "visitor_field",
5147        "tab": "content",
5148        "hint": "The field holding what is left. Leave it blank to type a number instead."
5149      },
5150      {
5151        "key": "fixed",
5152        "label": "Number",
5153        "kind": "number",
5154        "tab": "content",
5155        "hint": "Used when no field is chosen.",
5156        "min": 0,
5157        "max": 1e6
5158      },
5159      {
5160        "key": "text",
5161        "label": "Message",
5162        "kind": "text",
5163        "tab": "content",
5164        "hint": "{count} fills in live."
5165      },
5166      {
5167        "key": "max",
5168        "label": "Only show at or below",
5169        "kind": "number",
5170        "tab": "content",
5171        "hint": "Above this the badge hides itself \u2014 scarcity that is not scarce reads as a gimmick.",
5172        "min": 1,
5173        "max": 1e6
5174      },
5175      {
5176        "key": "demoValue",
5177        "label": "Preview number",
5178        "kind": "number",
5179        "tab": "content",
5180        "hint": "Editor only \u2014 what this shows here, where no visitor exists.",
5181        "min": 0,
5182        "max": 1e6
5183      },
5184      {
5185        "key": "bg",
5186        "label": "Background",
5187        "kind": "color",
5188        "tab": "style"
5189      },
5190      {
5191        "key": "color",
5192        "label": "Text colour",
5193        "kind": "color",
5194        "tab": "style"
5195      },
5196      {
5197        "key": "fontSize",
5198        "label": "Font size",
5199        "kind": "number",
5200        "tab": "style",
5201        "unit": "px",
5202        "min": 9,
5203        "max": 40
5204      },
5205      {
5206        "key": "align",
5207        "label": "Alignment",
5208        "kind": "align",
5209        "tab": "style"
5210      }
5211    ]
5212  },
5213  {
5214    "id": "button",
5215    "label": "CTA Button",
5216    "html": '<div style="text-align:center;"><a href="#" data-personyze-click-target="" style="background:#DB2777;color:#fff;border:0; display:inline-block; text-decoration:none;padding:12px 28px;border-radius:8px;font-size:14px;font-weight:700;cursor:pointer; margin:10px 0;">Call to Action</a></div>',
5217    "props": {
5218      "anim": "",
5219      "device": "",
5220      "cssMobile": "",
5221      "beside": "",
5222      "width": "",
5223      "boxAlign": "center",
5224      "spaceTop": "",
5225      "spaceBottom": "",
5226      "boxBg": "",
5227      "boxPad": "",
5228      "boxBorder": 0,
5229      "boxBorderColor": "#E2E8F0",
5230      "boxCorner": "",
5231      "boxShadow": "",
5232      "hoverBg": "",
5233      "hoverText": "",
5234      "report": "",
5235      "reportEvent": "",
5236      "reportValue": "",
5237      "animLoop": "",
5238      "css": "",
5239      "text": "Call to Action",
5240      "link": 'href="#" data-personyze-click-target="1"',
5241      "bg": "#DB2777",
5242      "color": "#fff",
5243      "radius": 8,
5244      "fontSize": 14,
5245      "weight": 700,
5246      "padX": 28,
5247      "padY": 12,
5248      "align": "center"
5249    },
5250    "settings": [
5251      {
5252        "key": "text",
5253        "label": "Label",
5254        "kind": "text",
5255        "tab": "content"
5256      },
5257      {
5258        "key": "link",
5259        "label": "Link",
5260        "kind": "link",
5261        "tab": "content"
5262      },
5263      {
5264        "key": "bg",
5265        "label": "Background",
5266        "kind": "color",
5267        "tab": "style"
5268      },
5269      {
5270        "key": "color",
5271        "label": "Text color",
5272        "kind": "color",
5273        "tab": "style"
5274      },
5275      {
5276        "key": "radius",
5277        "label": "Corner radius",
5278        "kind": "number",
5279        "tab": "style",
5280        "unit": "px",
5281        "min": 0,
5282        "max": 999
5283      },
5284      {
5285        "key": "fontSize",
5286        "label": "Font size",
5287        "kind": "number",
5288        "tab": "style",
5289        "unit": "px",
5290        "min": 8,
5291        "max": 48
5292      },
5293      {
5294        "key": "weight",
5295        "label": "Font weight",
5296        "kind": "number",
5297        "tab": "style",
5298        "min": 100,
5299        "max": 900
5300      },
5301      {
5302        "key": "padX",
5303        "label": "Padding, horizontal",
5304        "kind": "number",
5305        "tab": "style",
5306        "unit": "px",
5307        "min": 0,
5308        "max": 200
5309      },
5310      {
5311        "key": "padY",
5312        "label": "Padding, vertical",
5313        "kind": "number",
5314        "tab": "style",
5315        "unit": "px",
5316        "min": 0,
5317        "max": 200
5318      },
5319      {
5320        "key": "align",
5321        "label": "Alignment",
5322        "kind": "align",
5323        "tab": "style"
5324      },
5325      {
5326        "key": "hoverBg",
5327        "label": "Background on hover",
5328        "kind": "color",
5329        "tab": "style",
5330        "hint": "What it changes to under the mouse. Blank keeps it still."
5331      },
5332      {
5333        "key": "hoverText",
5334        "label": "Text on hover",
5335        "kind": "color",
5336        "tab": "style"
5337      },
5338      {
5339        "key": "report",
5340        "label": "When clicked, report",
5341        "kind": "select",
5342        "tab": "content",
5343        "hint": "Beside the click itself, which is always counted.",
5344        "options": [
5345          [
5346            "",
5347            "Nothing extra"
5348          ],
5349          [
5350            "goal",
5351            "This action reached its goal"
5352          ],
5353          [
5354            "event",
5355            "A conversion goal"
5356          ],
5357          [
5358            "both",
5359            "Both"
5360          ]
5361        ]
5362      },
5363      {
5364        "key": "reportEvent",
5365        "label": "Which goal",
5366        "kind": "goal",
5367        "tab": "content",
5368        "hint": "Event-driven goals only \u2014 the kind that waits for a named event. Other goal types watch the page themselves and need nothing here."
5369      },
5370      {
5371        "key": "reportValue",
5372        "label": "Value",
5373        "kind": "text",
5374        "tab": "content",
5375        "hint": "Optional \u2014 a revenue amount, a plan name. Variables work here."
5376      }
5377    ]
5378  },
5379  {
5380    "id": "headline",
5381    "label": "Headline & Subtitle",
5382    "html": '<h1 style="text-align:center;font-size:30px;font-weight:800;color:#0F172A;line-height:1.2; margin:14px 0 8px;">Your Main Headline Goes Here</h1><p style="text-align:center; font-size:14px;color:#64748B; margin:0 0 18px;">Add a short description or supporting text that highlights your value.</p>',
5383    "props": {
5384      "anim": "",
5385      "device": "",
5386      "cssMobile": "",
5387      "beside": "",
5388      "width": "",
5389      "boxAlign": "center",
5390      "spaceTop": "",
5391      "spaceBottom": "",
5392      "boxBg": "",
5393      "boxPad": "",
5394      "boxBorder": 0,
5395      "boxBorderColor": "#E2E8F0",
5396      "boxCorner": "",
5397      "boxShadow": "",
5398      "css": "",
5399      "headline": "Your Main Headline Goes Here",
5400      "sub": "Add a short description or supporting text that highlights your value.",
5401      "color": "#0F172A",
5402      "subColor": "#64748B",
5403      "fontSize": 30,
5404      "subSize": 14,
5405      "weight": 800,
5406      "align": "center"
5407    },
5408    "settings": [
5409      {
5410        "key": "headline",
5411        "label": "Headline",
5412        "kind": "text",
5413        "tab": "content"
5414      },
5415      {
5416        "key": "sub",
5417        "label": "Subtitle",
5418        "kind": "textarea",
5419        "tab": "content"
5420      },
5421      {
5422        "key": "color",
5423        "label": "Headline color",
5424        "kind": "color",
5425        "tab": "style"
5426      },
5427      {
5428        "key": "subColor",
5429        "label": "Subtitle color",
5430        "kind": "color",
5431        "tab": "style"
5432      },
5433      {
5434        "key": "fontSize",
5435        "label": "Headline size",
5436        "kind": "number",
5437        "tab": "style",
5438        "unit": "px",
5439        "min": 10,
5440        "max": 96
5441      },
5442      {
5443        "key": "subSize",
5444        "label": "Subtitle size",
5445        "kind": "number",
5446        "tab": "style",
5447        "unit": "px",
5448        "min": 10,
5449        "max": 40
5450      },
5451      {
5452        "key": "weight",
5453        "label": "Font weight",
5454        "kind": "number",
5455        "tab": "style",
5456        "min": 100,
5457        "max": 900
5458      },
5459      {
5460        "key": "align",
5461        "label": "Alignment",
5462        "kind": "align",
5463        "tab": "style"
5464      }
5465    ]
5466  },
5467  {
5468    "id": "paragraph",
5469    "label": "Paragraph",
5470    "html": '<p style="text-align:left;font-size:14px;color:#334155; margin:8px 0;">A new paragraph of text. Click to edit.</p>',
5471    "props": {
5472      "anim": "",
5473      "device": "",
5474      "cssMobile": "",
5475      "beside": "",
5476      "width": "",
5477      "boxAlign": "center",
5478      "spaceTop": "",
5479      "spaceBottom": "",
5480      "boxBg": "",
5481      "boxPad": "",
5482      "boxBorder": 0,
5483      "boxBorderColor": "#E2E8F0",
5484      "boxCorner": "",
5485      "boxShadow": "",
5486      "css": "",
5487      "text": "A new paragraph of text. Click to edit.",
5488      "color": "#334155",
5489      "fontSize": 14,
5490      "align": "left"
5491    },
5492    "settings": [
5493      {
5494        "key": "text",
5495        "label": "Text",
5496        "kind": "textarea",
5497        "tab": "content"
5498      },
5499      {
5500        "key": "color",
5501        "label": "Color",
5502        "kind": "color",
5503        "tab": "style"
5504      },
5505      {
5506        "key": "fontSize",
5507        "label": "Font size",
5508        "kind": "number",
5509        "tab": "style",
5510        "unit": "px",
5511        "min": 8,
5512        "max": 72
5513      },
5514      {
5515        "key": "align",
5516        "label": "Alignment",
5517        "kind": "align",
5518        "tab": "style"
5519      }
5520    ]
5521  },
5522  {
5523    "id": "chip",
5524    "label": "Chip / Pill",
5525    "html": '<div style="text-align:center"><span style="color:#047857;background:#ECFDF5;border:1px solid #A7F3D0;border-radius:999px;font-size:13px;font-weight:600;padding:6px 14px; display:inline-block;">\u2605 Special Offer</span></div>',
5526    "props": {
5527      "anim": "",
5528      "device": "",
5529      "cssMobile": "",
5530      "beside": "",
5531      "width": "",
5532      "boxAlign": "center",
5533      "spaceTop": "",
5534      "spaceBottom": "",
5535      "boxBg": "",
5536      "boxPad": "",
5537      "boxBorder": 0,
5538      "boxBorderColor": "#E2E8F0",
5539      "boxCorner": "",
5540      "boxShadow": "",
5541      "hoverBg": "",
5542      "hoverText": "",
5543      "report": "",
5544      "reportEvent": "",
5545      "reportValue": "",
5546      "animLoop": "",
5547      "css": "",
5548      "text": "\u2605 Special Offer",
5549      "color": "#047857",
5550      "bg": "#ECFDF5",
5551      "border": "#A7F3D0",
5552      "radius": 999,
5553      "fontSize": 13,
5554      "weight": 600,
5555      "align": "center"
5556    },
5557    "settings": [
5558      {
5559        "key": "text",
5560        "label": "Text",
5561        "kind": "text",
5562        "tab": "content"
5563      },
5564      {
5565        "key": "color",
5566        "label": "Text color",
5567        "kind": "color",
5568        "tab": "style"
5569      },
5570      {
5571        "key": "bg",
5572        "label": "Background",
5573        "kind": "color",
5574        "tab": "style"
5575      },
5576      {
5577        "key": "border",
5578        "label": "Border color",
5579        "kind": "color",
5580        "tab": "style"
5581      },
5582      {
5583        "key": "radius",
5584        "label": "Corner radius",
5585        "kind": "number",
5586        "tab": "style",
5587        "unit": "px",
5588        "min": 0,
5589        "max": 999
5590      },
5591      {
5592        "key": "fontSize",
5593        "label": "Font size",
5594        "kind": "number",
5595        "tab": "style",
5596        "unit": "px",
5597        "min": 8,
5598        "max": 72
5599      },
5600      {
5601        "key": "weight",
5602        "label": "Font weight",
5603        "kind": "number",
5604        "tab": "style",
5605        "min": 100,
5606        "max": 900
5607      },
5608      {
5609        "key": "align",
5610        "label": "Alignment",
5611        "kind": "align",
5612        "tab": "style"
5613      },
5614      {
5615        "key": "hoverBg",
5616        "label": "Background on hover",
5617        "kind": "color",
5618        "tab": "style",
5619        "hint": "What it changes to under the mouse. Blank keeps it still."
5620      },
5621      {
5622        "key": "hoverText",
5623        "label": "Text on hover",
5624        "kind": "color",
5625        "tab": "style"
5626      },
5627      {
5628        "key": "report",
5629        "label": "When clicked, report",
5630        "kind": "select",
5631        "tab": "content",
5632        "hint": "Beside the click itself, which is always counted.",
5633        "options": [
5634          [
5635            "",
5636            "Nothing extra"
5637          ],
5638          [
5639            "goal",
5640            "This action reached its goal"
5641          ],
5642          [
5643            "event",
5644            "A conversion goal"
5645          ],
5646          [
5647            "both",
5648            "Both"
5649          ]
5650        ]
5651      },
5652      {
5653        "key": "reportEvent",
5654        "label": "Which goal",
5655        "kind": "goal",
5656        "tab": "content",
5657        "hint": "Event-driven goals only \u2014 the kind that waits for a named event. Other goal types watch the page themselves and need nothing here."
5658      },
5659      {
5660        "key": "reportValue",
5661        "label": "Value",
5662        "kind": "text",
5663        "tab": "content",
5664        "hint": "Optional \u2014 a revenue amount, a plan name. Variables work here."
5665      }
5666    ]
5667  },
5668  {
5669    "id": "checklist",
5670    "label": "Checklist",
5671    "html": '<div style="background:#F8FAFC; border-radius:12px; padding:16px 18px; margin:18px 0;"><h4 style="margin:0 0 10px; font-size:14px; font-weight:700; color:#0F172A;">This section can highlight benefits:</h4><ul style="list-style:none; padding:0; margin:0;"><li style="display:flex; align-items:center; gap:10px; font-size:13px; color:#334155; margin-bottom:6px;"><span style="width:12px; height:12px; border-radius:50%; background:#10B981; flex:none; display:inline-block;"></span><span style="flex:1;">Highlight key feature or benefit #1</span></li><li style="display:flex; align-items:center; gap:10px; font-size:13px; color:#334155; margin-bottom:6px;"><span style="width:12px; height:12px; border-radius:50%; background:#10B981; flex:none; display:inline-block;"></span><span style="flex:1;">Highlight key feature or benefit #2</span></li><li style="display:flex; align-items:center; gap:10px; font-size:13px; color:#334155; margin-bottom:6px;"><span style="width:12px; height:12px; border-radius:50%; background:#10B981; flex:none; display:inline-block;"></span><span style="flex:1;">Highlight key feature or benefit #3</span></li></ul></div>',
5672    "props": {
5673      "anim": "",
5674      "device": "",
5675      "cssMobile": "",
5676      "beside": "",
5677      "width": "",
5678      "boxAlign": "center",
5679      "spaceTop": "",
5680      "spaceBottom": "",
5681      "boxBg": "",
5682      "boxPad": "",
5683      "boxBorder": 0,
5684      "boxBorderColor": "#E2E8F0",
5685      "boxCorner": "",
5686      "boxShadow": "",
5687      "css": "",
5688      "title": "This section can highlight benefits:",
5689      "items": [
5690        "Highlight key feature or benefit #1",
5691        "Highlight key feature or benefit #2",
5692        "Highlight key feature or benefit #3"
5693      ],
5694      "dotColor": "#10B981",
5695      "bg": "#F8FAFC"
5696    },
5697    "settings": [
5698      {
5699        "key": "title",
5700        "label": "Title",
5701        "kind": "text",
5702        "tab": "content"
5703      },
5704      {
5705        "key": "items",
5706        "label": "Items",
5707        "kind": "list",
5708        "tab": "content"
5709      },
5710      {
5711        "key": "dotColor",
5712        "label": "Bullet color",
5713        "kind": "color",
5714        "tab": "style"
5715      },
5716      {
5717        "key": "bg",
5718        "label": "Background",
5719        "kind": "color",
5720        "tab": "style"
5721      }
5722    ]
5723  },
5724  {
5725    "id": "divider",
5726    "label": "Divider",
5727    "html": '<hr style="border:0; border-top:1px solid #E2E8F0; margin:16px 0;">',
5728    "props": {
5729      "anim": "",
5730      "device": "",
5731      "cssMobile": "",
5732      "beside": "",
5733      "width": "",
5734      "boxAlign": "center",
5735      "spaceTop": "",
5736      "spaceBottom": "",
5737      "boxBg": "",
5738      "boxPad": "",
5739      "boxBorder": 0,
5740      "boxBorderColor": "#E2E8F0",
5741      "boxCorner": "",
5742      "boxShadow": "",
5743      "css": "",
5744      "color": "#E2E8F0",
5745      "thickness": 1,
5746      "marginY": 16
5747    },
5748    "settings": [
5749      {
5750        "key": "color",
5751        "label": "Color",
5752        "kind": "color",
5753        "tab": "style"
5754      },
5755      {
5756        "key": "thickness",
5757        "label": "Thickness",
5758        "kind": "number",
5759        "tab": "style",
5760        "unit": "px",
5761        "min": 1,
5762        "max": 20
5763      },
5764      {
5765        "key": "marginY",
5766        "label": "Vertical spacing",
5767        "kind": "number",
5768        "tab": "style",
5769        "unit": "px",
5770        "min": 0,
5771        "max": 200
5772      }
5773    ]
5774  },
5775  {
5776    "id": "icon",
5777    "label": "Icon",
5778    "html": '<div style="text-align:center; margin:10px 0;"><i class="$icon star" style="font-size:42px; color:#0F172A;"></i></div>',
5779    "props": {
5780      "anim": "",
5781      "device": "",
5782      "cssMobile": "",
5783      "beside": "",
5784      "width": "",
5785      "boxAlign": "center",
5786      "spaceTop": "",
5787      "spaceBottom": "",
5788      "boxBg": "",
5789      "boxPad": "",
5790      "boxBorder": 0,
5791      "boxBorderColor": "#E2E8F0",
5792      "boxCorner": "",
5793      "boxShadow": "",
5794      "css": "",
5795      "name": "star",
5796      "size": 42,
5797      "color": "#0F172A",
5798      "align": "center"
5799    },
5800    "settings": [
5801      {
5802        "key": "name",
5803        "label": "Icon",
5804        "kind": "icon",
5805        "tab": "content"
5806      },
5807      {
5808        "key": "size",
5809        "label": "Size",
5810        "kind": "number",
5811        "tab": "style",
5812        "unit": "px",
5813        "min": 8,
5814        "max": 200
5815      },
5816      {
5817        "key": "color",
5818        "label": "Color",
5819        "kind": "color",
5820        "tab": "style"
5821      },
5822      {
5823        "key": "align",
5824        "label": "Alignment",
5825        "kind": "align",
5826        "tab": "style"
5827      }
5828    ]
5829  },
5830  {
5831    "id": "rotatingtext",
5832    "label": "Rotating Text",
5833    "html": '<div class="pz-rotw" style="display:grid; text-align:center; font-size:14px; font-weight:600; color:#111827; margin:8px 0;"><style>@keyframes pz-rotf-3{0%{opacity:1}28.33%{opacity:1}33.33%{opacity:0}
583395%{opacity:0}100%{opacity:1}}@media (prefers-reduced-motion:reduce){.pz-rotw>div{animation:none !important}}</style><div style="grid-area:1/1; animation:pz-rotf-3 12s linear infinite; animation-delay:-12s;">Free shipping on orders over $50</div><div style="grid-area:1/1; opacity:0; animation:pz-rotf-3 12s linear infinite; animation-delay:-8s;">Extra 10% off today \u2014 code SAVE10</div><div style="grid-area:1/1; opacity:0; animation:pz-rotf-3 12s linear infinite; animation-delay:-4s;">New arrivals just dropped</div></div>',
5834    "props": {
5835      "anim": "",
5836      "device": "",
5837      "cssMobile": "",
5838      "beside": "",
5839      "width": "",
5840      "boxAlign": "center",
5841      "spaceTop": "",
5842      "spaceBottom": "",
5843      "boxBg": "",
5844      "boxPad": "",
5845      "boxBorder": 0,
5846      "boxBorderColor": "#E2E8F0",
5847      "boxCorner": "",
5848      "boxShadow": "",
5849      "css": "",
5850      "lines": [
5851        "Free shipping on orders over $50",
5852        "Extra 10% off today \u2014 code SAVE10",
5853        "New arrivals just dropped"
5854      ],
5855      "secs": 4,
5856      "mode": "fade",
5857      "fontSize": 14,
5858      "color": "#111827",
5859      "align": "center"
5860    },
5861    "settings": [
5862      {
5863        "key": "lines",
5864        "label": "Messages",
5865        "kind": "list",
5866        "tab": "content",
5867        "hint": "One shows at a time, in order \u2014 variables work too."
5868      },
5869      {
5870        "key": "secs",
5871        "label": "Seconds per message",
5872        "kind": "number",
5873        "tab": "style",
5874        "unit": "s",
5875        "min": 1,
5876        "max": 120
5877      },
5878      {
5879        "key": "mode",
5880        "label": "Transition",
5881        "kind": "select",
5882        "tab": "style",
5883        "options": [
5884          [
5885            "fade",
5886            "Cross-fade"
5887          ],
5888          [
5889            "slide",
5890            "Slide up"
5891          ]
5892        ]
5893      },
5894      {
5895        "key": "fontSize",
5896        "label": "Font size",
5897        "kind": "number",
5898        "tab": "style",
5899        "unit": "px",
5900        "min": 8,
5901        "max": 60
5902      },
5903      {
5904        "key": "color",
5905        "label": "Text color",
5906        "kind": "color",
5907        "tab": "style"
5908      },
5909      {
5910        "key": "align",
5911        "label": "Alignment",
5912        "kind": "align",
5913        "tab": "style"
5914      }
5915    ]
5916  },
5917  {
5918    "id": "faq",
5919    "label": "FAQ / Accordion",
5920    "html": `<style>.pz-faq-i>summary::-webkit-details-marker{display:none}.pz-faq-i>summary::marker{content:''}.pz-faq-i[open]>summary .pz-faq-mark{visibility:hidden}.pz-faq-i[open]>summary .pz-faq-mark::after{content:'\u2013'; visibility:visible}</style><div class="pz-block-faq" style="background:#FFFFFF; margin:14px 0; text-align:left;"><details class="pz-faq-i" style="border-bottom:1px solid #E2E8F0;"><summary style="list-style:none; cursor:pointer; display:flex; align-items:center; gap:10px; padding:12px 2px; font-size:14px; font-weight:700; color:#0F172A;"><span style="flex:1;">How long does delivery take?</span><span class="pz-faq-mark" style="flex:none; font-size:13px; line-height:1;">+</span></summary><div style="padding:0 2px 14px; font-size:13px; line-height:1.55; color:#475569;">Two to four working days, and you get a tracking link the moment it ships.</div></details><details class="pz-faq-i" style="border-bottom:1px solid #E2E8F0;"><summary style="list-style:none; cursor:pointer; display:flex; align-items:center; gap:10px; padding:12px 2px; font-size:14px; font-weight:700; color:#0F172A;"><span style="flex:1;">Can I return it?</span><span class="pz-faq-mark" style="flex:none; font-size:13px; line-height:1;">+</span></summary><div style="padding:0 2px 14px; font-size:13px; line-height:1.55; color:#475569;">Yes \u2014 thirty days, no questions, return shipping on us.</div></details></div>`,
5921    "props": {
5922      "anim": "",
5923      "device": "",
5924      "cssMobile": "",
5925      "beside": "",
5926      "width": "",
5927      "boxAlign": "center",
5928      "spaceTop": "",
5929      "spaceBottom": "",
5930      "boxBg": "",
5931      "boxPad": "",
5932      "boxBorder": 0,
5933      "boxBorderColor": "#E2E8F0",
5934      "boxCorner": "",
5935      "boxShadow": "",
5936      "css": "",
5937      "items": '[{"q":"How long does delivery take?","a":"Two to four working days, and you get a tracking link the moment it ships."},{"q":"Can I return it?","a":"Yes \u2014 thirty days, no questions, return shipping on us."}]',
5938      "openFirst": "",
5939      "bg": "#FFFFFF",
5940      "color": "#0F172A",
5941      "answerColor": "#475569",
5942      "border": "#E2E8F0",
5943      "fontSize": 14
5944    },
5945    "settings": [
5946      {
5947        "key": "items",
5948        "label": "Questions",
5949        "kind": "faq_list",
5950        "tab": "content",
5951        "hint": "Each one opens on its own; the rest stay closed."
5952      },
5953      {
5954        "key": "openFirst",
5955        "label": "Start with the first one open",
5956        "kind": "checkbox",
5957        "tab": "content"
5958      },
5959      {
5960        "key": "bg",
5961        "label": "Background",
5962        "kind": "color",
5963        "tab": "style"
5964      },
5965      {
5966        "key": "color",
5967        "label": "Question colour",
5968        "kind": "color",
5969        "tab": "style"
5970      },
5971      {
5972        "key": "answerColor",
5973        "label": "Answer colour",
5974        "kind": "color",
5975        "tab": "style"
5976      },
5977      {
5978        "key": "border",
5979        "label": "Divider colour",
5980        "kind": "color",
5981        "tab": "style"
5982      },
5983      {
5984        "key": "fontSize",
5985        "label": "Question size",
5986        "kind": "number",
5987        "tab": "style",
5988        "unit": "px",
5989        "min": 9,
5990        "max": 32
5991      }
5992    ]
5993  },
5994  {
5995    "id": "rating",
5996    "label": "Star Rating",
5997    "html": '<div class="pz-block-rating" style="text-align:center; margin:12px 0;"><span style="display:inline-flex; align-items:center; gap:8px;"><span style="position:relative; display:inline-block; font-size:20px; line-height:1; letter-spacing:2px; white-space:nowrap;"><span style="color:#E2E8F0;">\u2605\u2605\u2605\u2605\u2605</span><span style="position:absolute; left:0; top:0; width:90%; overflow:hidden; color:#B45309;">\u2605\u2605\u2605\u2605\u2605</span></span><b style="font-size:14px; color:#0F172A;">4.5</b><span style="font-size:12px; color:#64748B;">from 214 reviews</span></span></div>',
5998    "props": {
5999      "anim": "",
6000      "device": "",
6001      "cssMobile": "",
6002      "beside": "",
6003      "width": "",
6004      "boxAlign": "center",
6005      "spaceTop": "",
6006      "spaceBottom": "",
6007      "boxBg": "",
6008      "boxPad": "",
6009      "boxBorder": 0,
6010      "boxBorderColor": "#E2E8F0",
6011      "boxCorner": "",
6012      "boxShadow": "",
6013      "css": "",
6014      "source": "fixed",
6015      "value": 4.5,
6016      "outOf": 5,
6017      "note": "from 214 reviews",
6018      "color": "#B45309",
6019      "empty": "#E2E8F0",
6020      "size": 20,
6021      "align": "center",
6022      "showNumber": "1"
6023    },
6024    "settings": [
6025      {
6026        "key": "source",
6027        "label": "Where the score comes from",
6028        "kind": "select",
6029        "tab": "content",
6030        "hint": "A number you type, or the rating the catalog holds for the product this visitor last looked at.",
6031        "options": [
6032          [
6033            "fixed",
6034            "A number I set"
6035          ],
6036          [
6037            "catalog",
6038            "The visitor\u2019s last-viewed product"
6039          ]
6040        ]
6041      },
6042      {
6043        "key": "value",
6044        "label": "Score",
6045        "kind": "number",
6046        "tab": "content",
6047        "min": 0,
6048        "max": 5
6049      },
6050      {
6051        "key": "outOf",
6052        "label": "Out of",
6053        "kind": "number",
6054        "tab": "content",
6055        "min": 3,
6056        "max": 10
6057      },
6058      {
6059        "key": "note",
6060        "label": "Text beside the stars",
6061        "kind": "text",
6062        "tab": "content",
6063        "hint": "\u201Cfrom 214 reviews\u201D. Blank for stars alone."
6064      },
6065      {
6066        "key": "showNumber",
6067        "label": "Show the score as a number too",
6068        "kind": "checkbox",
6069        "tab": "content"
6070      },
6071      {
6072        "key": "color",
6073        "label": "Star colour",
6074        "kind": "color",
6075        "tab": "style"
6076      },
6077      {
6078        "key": "empty",
6079        "label": "Empty star colour",
6080        "kind": "color",
6081        "tab": "style"
6082      },
6083      {
6084        "key": "size",
6085        "label": "Star size",
6086        "kind": "number",
6087        "tab": "style",
6088        "unit": "px",
6089        "min": 8,
6090        "max": 60
6091      },
6092      {
6093        "key": "align",
6094        "label": "Alignment",
6095        "kind": "align",
6096        "tab": "style"
6097      }
6098    ]
6099  },
6100  {
6101    "id": "steps",
6102    "label": "Numbered Steps",
6103    "html": '<div style="display:flex; flex-wrap:wrap; gap:14px; margin:16px 0;"><div style="flex:1 1 0; min-width:0; display:flex; flex-direction:column; align-items:center; text-align:center; gap:8px;"><span style="flex:none; width:26px; height:26px; border-radius:50%; display:flex; align-items:center; justify-content:center; background:#0F172A; color:#FFFFFF; font-size:12px; font-weight:800;">1</span><span style="font-size:13px; line-height:1.45; color:#334155;">Sign up in thirty seconds</span></div><div style="flex:1 1 0; min-width:0; display:flex; flex-direction:column; align-items:center; text-align:center; gap:8px;"><span style="flex:none; width:26px; height:26px; border-radius:50%; display:flex; align-items:center; justify-content:center; background:#0F172A; color:#FFFFFF; font-size:12px; font-weight:800;">2</span><span style="font-size:13px; line-height:1.45; color:#334155;">Tell us what you like</span></div><div style="flex:1 1 0; min-width:0; display:flex; flex-direction:column; align-items:center; text-align:center; gap:8px;"><span style="flex:none; width:26px; height:26px; border-radius:50%; display:flex; align-items:center; justify-content:center; background:#0F172A; color:#FFFFFF; font-size:12px; font-weight:800;">3</span><span style="font-size:13px; line-height:1.45; color:#334155;">Get picks that fit</span></div></div>',
6104    "props": {
6105      "anim": "",
6106      "device": "",
6107      "cssMobile": "",
6108      "beside": "",
6109      "width": "",
6110      "boxAlign": "center",
6111      "spaceTop": "",
6112      "spaceBottom": "",
6113      "boxBg": "",
6114      "boxPad": "",
6115      "boxBorder": 0,
6116      "boxBorderColor": "#E2E8F0",
6117      "boxCorner": "",
6118      "boxShadow": "",
6119      "css": "",
6120      "items": [
6121        "Sign up in thirty seconds",
6122        "Tell us what you like",
6123        "Get picks that fit"
6124      ],
6125      "layout": "row",
6126      "numberBg": "#0F172A",
6127      "numberColor": "#FFFFFF",
6128      "color": "#334155",
6129      "fontSize": 13
6130    },
6131    "settings": [
6132      {
6133        "key": "items",
6134        "label": "Steps",
6135        "kind": "list",
6136        "tab": "content"
6137      },
6138      {
6139        "key": "layout",
6140        "label": "Layout",
6141        "kind": "select",
6142        "tab": "style",
6143        "hint": "Across for three short steps, down for longer ones.",
6144        "options": [
6145          [
6146            "row",
6147            "Across"
6148          ],
6149          [
6150            "col",
6151            "Down"
6152          ]
6153        ]
6154      },
6155      {
6156        "key": "numberBg",
6157        "label": "Number background",
6158        "kind": "color",
6159        "tab": "style"
6160      },
6161      {
6162        "key": "numberColor",
6163        "label": "Number colour",
6164        "kind": "color",
6165        "tab": "style"
6166      },
6167      {
6168        "key": "color",
6169        "label": "Text colour",
6170        "kind": "color",
6171        "tab": "style"
6172      },
6173      {
6174        "key": "fontSize",
6175        "label": "Font size",
6176        "kind": "number",
6177        "tab": "style",
6178        "unit": "px",
6179        "min": 9,
6180        "max": 32
6181      }
6182    ]
6183  },
6184  {
6185    "id": "compare",
6186    "label": "Comparison Table",
6187    "html": '<table style="width:100%; border-collapse:collapse; margin:16px 0; text-align:left;"><tr><th style="padding:9px 10px; text-align:left; font-size:13px;"></th><th style="padding:9px 10px; text-align:center; font-size:13px; font-weight:800; background:#0F172A; color:#FFFFFF;">Us</th><th style="padding:9px 10px; text-align:center; font-size:13px; font-weight:800; background:#0F172A; color:#FFFFFF;">Them</th></tr><tr><td style="padding:9px 10px; border-top:1px solid #E2E8F0; font-size:13px; color:#334155;">Free delivery</td><td style="padding:9px 10px; border-top:1px solid #E2E8F0; text-align:center; font-size:13px; color:#334155;"><span style="color:#047857; font-weight:800;">&#10003;</span></td><td style="padding:9px 10px; border-top:1px solid #E2E8F0; text-align:center; font-size:13px; color:#334155;"><span style="color:#CBD5E1; font-weight:800;">&#10005;
6187</span></td></tr><tr><td style="padding:9px 10px; border-top:1px solid #E2E8F0; font-size:13px; color:#334155;">Thirty-day returns</td><td style="padding:9px 10px; border-top:1px solid #E2E8F0; text-align:center; font-size:13px; color:#334155;"><span style="color:#047857; font-weight:800;">&#10003;</span></td><td style="padding:9px 10px; border-top:1px solid #E2E8F0; text-align:center; font-size:13px; color:#334155;">14 days</td></tr><tr><td style="padding:9px 10px; border-top:1px solid #E2E8F0; font-size:13px; color:#334155;">Support</td><td style="padding:9px 10px; border-top:1px solid #E2E8F0; text-align:center; font-size:13px; color:#334155;">7 days a week</td><td style="padding:9px 10px; border-top:1px solid #E2E8F0; text-align:center; font-size:13px; color:#334155;">Weekdays</td></tr></table>',
6188    "props": {
6189      "anim": "",
6190      "device": "",
6191      "cssMobile": "",
6192      "beside": "",
6193      "width": "",
6194      "boxAlign": "center",
6195      "spaceTop": "",
6196      "spaceBottom": "",
6197      "boxBg": "",
6198      "boxPad": "",
6199      "boxBorder": 0,
6200      "boxBorderColor": "#E2E8F0",
6201      "boxCorner": "",
6202      "boxShadow": "",
6203      "css": "",
6204      "colA": "Us",
6205      "colB": "Them",
6206      "rows": '[{"label":"Free delivery","a":"yes","b":"no"},{"label":"Thirty-day returns","a":"yes","b":"14 days"},{"label":"Support","a":"7 days a week","b":"Weekdays"}]',
6207      "headBg": "#0F172A",
6208      "headColor": "#FFFFFF",
6209      "yes": "#047857",
6210      "no": "#CBD5E1",
6211      "color": "#334155",
6212      "border": "#E2E8F0",
6213      "fontSize": 13
6214    },
6215    "settings": [
6216      {
6217        "key": "rows",
6218        "label": "Rows",
6219        "kind": "table_rows",
6220        "tab": "content",
6221        "hint": "Type yes or no for a tick or a cross; anything else shows as written."
6222      },
6223      {
6224        "key": "colA",
6225        "label": "First column heading",
6226        "kind": "text",
6227        "tab": "content"
6228      },
6229      {
6230        "key": "colB",
6231        "label": "Second column heading",
6232        "kind": "text",
6233        "tab": "content"
6234      },
6235      {
6236        "key": "headBg",
6237        "label": "Heading background",
6238        "kind": "color",
6239        "tab": "style"
6240      },
6241      {
6242        "key": "headColor",
6243        "label": "Heading text",
6244        "kind": "color",
6245        "tab": "style"
6246      },
6247      {
6248        "key": "yes",
6249        "label": "Tick colour",
6250        "kind": "color",
6251        "tab": "style"
6252      },
6253      {
6254        "key": "no",
6255        "label": "Cross colour",
6256        "kind": "color",
6257        "tab": "style"
6258      },
6259      {
6260        "key": "color",
6261        "label": "Text colour",
6262        "kind": "color",
6263        "tab": "style"
6264      },
6265      {
6266        "key": "border",
6267        "label": "Line colour",
6268        "kind": "color",
6269        "tab": "style"
6270      },
6271      {
6272        "key": "fontSize",
6273        "label": "Font size",
6274        "kind": "number",
6275        "tab": "style",
6276        "unit": "px",
6277        "min": 9,
6278        "max": 24
6279      }
6280    ]
6281  },
6282  {
6283    "id": "mediatext",
6284    "label": "Media + Text",
6285    "html": '<style>@media (max-width:767px){.pz-mt{flex-direction:column !important; align-items:stretch}.pz-mt>*{flex-basis:auto !important}}</style><div class="pz-mt" style="display:flex; align-items:center; gap:18px; margin:14px 0;"><div style="flex:1 1 0; min-width:0;"><h2 style="margin:0 0 8px; font-size:20px; font-weight:800; color:#0F172A;">A picture and a point</h2><p style="margin:0; font-size:14px; line-height:1.55; color:#334155;">Put the supporting words right next to the visual.</p></div></div>',
6286    "props": {
6287      "anim": "",
6288      "device": "",
6289      "cssMobile": "",
6290      "beside": "",
6291      "width": "",
6292      "boxAlign": "center",
6293      "spaceTop": "",
6294      "spaceBottom": "",
6295      "boxBg": "",
6296      "boxPad": "",
6297      "boxBorder": 0,
6298      "boxBorderColor": "#E2E8F0",
6299      "boxCorner": "",
6300      "boxShadow": "",
6301      "hoverBg": "",
6302      "hoverText": "",
6303      "report": "",
6304      "reportEvent": "",
6305      "reportValue": "",
6306      "css": "",
6307      "image": "",
6308      "imageAlt": "",
6309      "imagePos": "left",
6310      "imageWidth": 40,
6311      "headline": "A picture and a point",
6312      "text": "Put the supporting words right next to the visual.",
6313      "headlineColor": "#0F172A",
6314      "textColor": "#334155"
6315    },
6316    "settings": [
6317      {
6318        "key": "image",
6319        "label": "Image",
6320        "kind": "media",
6321        "tab": "content"
6322      },
6323      {
6324        "key": "imageAlt",
6325        "label": "Alt text",
6326        "kind": "text",
6327        "tab": "content"
6328      },
6329      {
6330        "key": "imagePos",
6331        "label": "Image side",
6332        "kind": "select",
6333        "tab": "style",
6334        "options": [
6335          [
6336            "left",
6337            "Left"
6338          ],
6339          [
6340            "right",
6341            "Right"
6342          ]
6343        ]
6344      },
6345      {
6346        "key": "imageWidth",
6347        "label": "Image width",
6348        "kind": "number",
6349        "tab": "style",
6350        "hint": "The share of the row the picture takes; the text gets the rest.",
6351        "unit": "%",
6352        "min": 15,
6353        "max": 70
6354      },
6355      {
6356        "key": "headline",
6357        "label": "Headline",
6358        "kind": "text",
6359        "tab": "content"
6360      },
6361      {
6362        "key": "text",
6363        "label": "Text",
6364        "kind": "textarea",
6365        "tab": "content"
6366      },
6367      {
6368        "key": "headlineColor",
6369        "label": "Headline color",
6370        "kind": "color",
6371        "tab": "style"
6372      },
6373      {
6374        "key": "textColor",
6375        "label": "Text color",
6376        "kind": "color",
6377        "tab": "style"
6378      },
6379      {
6380        "key": "hoverBg",
6381        "label": "Background on hover",
6382        "kind": "color",
6383        "tab": "style",
6384        "hint": "What it changes to under the mouse. Blank keeps it still."
6385      },
6386      {
6387        "key": "hoverText",
6388        "label": "Text on hover",
6389        "kind": "color",
6390        "tab": "style"
6391      },
6392      {
6393        "key": "report",
6394        "label": "When clicked, report",
6395        "kind": "select",
6396        "tab": "content",
6397        "hint": "Beside the click itself, which is always counted.",
6398        "options": [
6399          [
6400            "",
6401            "Nothing extra"
6402          ],
6403          [
6404            "goal",
6405            "This action reached its goal"
6406          ],
6407          [
6408            "event",
6409            "A conversion goal"
6410          ],
6411          [
6412            "both",
6413            "Both"
6414          ]
6415        ]
6416      },
6417      {
6418        "key": "reportEvent",
6419        "label": "Which goal",
6420        "kind": "goal",
6421        "tab": "content",
6422        "hint": "Event-driven goals only \u2014 the kind that waits for a named event. Other goal types watch the page themselves and need nothing here."
6423      },
6424      {
6425        "key": "reportValue",
6426        "label": "Value",
6427        "kind": "text",
6428        "tab": "content",
6429        "hint": "Optional \u2014 a revenue amount, a plan name. Variables work here."
6430      }
6431    ]
6432  },
6433  {
6434    "id": "menu",
6435    "label": "Menu",
6436    "html": '<style>.pz-menu-h-4277318641 a:hover{color:#0EA5E9}.pz-menu-sub{position:absolute; left:0; top:100%; z-index:5; display:none}.pz-menu-i:hover>.pz-menu-sub,.pz-menu-i:focus-within>.pz-menu-sub{display:block}.pz-menu-v .pz-menu-i>a,.pz-menu-v .pz-menu-i>span{padding:8px 0}.pz-menu-v .pz-menu-sub{position:static; box-shadow:none; padding-left:12px}@media (hover:none){.pz-menu-sub{position:static; display:block; box-shadow:none; padding-left:12px}}.pz-menu-bt{display:none; position:relative; cursor:pointer; align-items:center; gap:8px; padding:6px 0; user-select:none}.pz-menu-bt input{position:absolute; width:0; height:0; opacity:0}.pz-menu-bars{display:inline-block; width:22px; flex:none}.pz-menu-bars i{display:block; height:2px; margin:4px 0; background:currentColor; border-radius:2px}@supports selector(:has(*)){@media (max-width:767px){.pz-menu-cm{text-align:right!important}.pz-menu-cm.pz-menu-bl{text-align:left!important}.pz-menu-cm .pz-menu-bt{display:inline-flex}.pz-menu-cm .pz-menu-l{display:none!important}.pz-menu-cm:has(.pz-menu-cb:checked) .pz-menu-l{display:block!important}.pz-menu-cm:has(.pz-menu-cb:checked) .pz-menu-sub{position:static; display:block; box-shadow:none; padding-left:12px}}}</style><nav class="pz-block-menu pz-menu-h-4277318641 pz-menu-cm" style="text-align:center; margin:12px 0; font-size:14px; font-weight:600;"><label class="pz-menu-bt" style="color:#0F172A;"><input type="checkbox" class="pz-menu-cb" aria-label="Menu"><span class="pz-menu-bars"><i></i><i></i><i></i></span></label><ul class="pz-menu-l" style="list-style:none; margin:0; padding:0; display:inline-flex; gap:20px; flex-wrap:wrap; align-items:center;text-align:center;"><li class="pz-menu-i" style="list-style:none; position:relative;"><a href="/collections/all" data-personyze-click-target="" style="display:block; padding:6px 0; color:#0F172A; text-decoration:none; white-space:nowrap;">Shop</a><ul class="pz-menu-sub" style="list-style:none; margin:0; padding:6px 0; background:#FFFFFF; border-radius:10px; box-shadow:0 10px 30px rgba(15,23,42,0.16); min-width:100%;"><li style="list-style:none;"><a href="/collections/new" data-personyze-click-target="" style="display:block; padding:7px 14px; color:#0F172A; text-decoration:none; white-space:nowrap; font-size:13px;">New arrivals</a></li><li style="list-style:none;"><a href="/collections/best" data-personyze-click-target="" style="display:block; padding:7px 14px; color:#0F172A; text-decoration:none; white-space:nowrap; font-size:13px;">Best sellers</a></li><li style="list-style:none;"><a href="/collections/sale" data-personyze-click-target="" style="display:block; padding:7px 14px; color:#0F172A; text-decoration:none; white-space:nowrap; font-size:13px;">Sale</a></li></ul></li><li class="pz-menu-i" style="list-style:none; position:relative;"><a href="/pages/about" data-personyze-click-target="" style="display:block; padding:6px 0; color:#0F172A; text-decoration:none; white-space:nowrap;">About</a></li><li class="pz-menu-i" style="list-style:none; position:relative;"><a href="/pages/contact" data-personyze-click-target="" style="display:block; padding:6px 0; color:#0F172A; text-decoration:none; white-space:nowrap;">Contact</a></li></ul></nav>',
6437    "props": {
6438      "anim": "",
6439      "device": "",
6440      "cssMobile": "",
6441      "beside": "",
6442      "width": "",
6443      "boxAlign": "center",
6444      "spaceTop": "",
6445      "spaceBottom": "",
6446      "boxBg": "",
6447      "boxPad": "",
6448      "boxBorder": 0,
6449      "boxBorderColor": "#E2E8F0",
6450      "boxCorner": "",
6451      "boxShadow": "",
6452      "css": "",
6453      "items": '[{"label":"Shop","link":"href=\\"/collections/all\\" data-personyze-click-target=\\"1\\"","children":[{"label":"New arrivals","link":"href=\\"/collections/new\\" data-personyze-click-target=\\"1\\""},{"label":"Best sellers","link":"href=\\"/collections/best\\" data-personyze-click-target=\\"1\\""},{"label":"Sale","link":"href=\\"/collections/sale\\" data-personyze-click-target=\\"1\\""}]},{"label":"About","link":"href=\\"/pages/about\\" data-personyze-click-target=\\"1\\"","children":[]},{"label":"Contact","link":"href=\\"/pages/contact\\" data-personyze-click-target=\\"1\\"","children":[]}]',
6454      "orientation": "horizontal",
6455      "align": "center",
6456      "color": "#0F172A",
6457      "hoverColor": "#0EA5E9",
6458      "subBg": "#FFFFFF",
6459      "fontSize": 14,
6460      "gap": 20,
6461      "radius": 10,
6462      "collapse": "mobile",
6463      "burgerSide": "",
6464      "burgerLabel": ""
6465    },
6466    "settings": [
6467      {
6468        "key": "items",
6469        "label": "Menu entries",
6470        "kind": "menu_list",
6471        "tab": "content",
6472        "hint": "Each entry can hold its own sub-entries."
6473      },
6474      {
6475        "key": "orientation",
6476        "label": "Direction",
6477        "kind": "select",
6478        "tab": "style",
6479        "hint": "A row for a bar, a column for a side panel.",
6480        "options": [
6481          [
6482            "horizontal",
6483            "Across \u2014 a row"
6484          ],
6485          [
6486            "vertical",
6487            "Down \u2014 a column"
6488          ]
6489        ]
6490      },
6491      {
6492        "key": "align",
6493        "label": "Alignment",
6494        "kind": "align",
6495        "tab": "style"
6496      },
6497      {
6498        "key": "color",
6499        "label": "Text colour",
6500        "kind": "color",
6501        "tab": "style"
6502      },
6503      {
6504        "key": "hoverColor",
6505        "label": "Colour on hover",
6506        "kind": "color",
6507        "tab": "style"
6508      },
6509      {
6510        "key": "fontSize",
6511        "label": "Font size",
6512        "kind": "number",
6513        "tab": "style",
6514        "unit": "px",
6515        "min": 9,
6516        "max": 40
6517      },
6518      {
6519        "key": "gap",
6520        "label": "Spacing between entries",
6521        "kind": "number",
6522        "tab": "style",
6523        "unit": "px",
6524        "min": 0,
6525        "max": 80
6526      },
6527      {
6528        "key": "collapse",
6529        "label": "Collapse into a menu icon",
6530        "kind": "select",
6531        "tab": "content",
6532        "hint": "The entries hide behind a \u2630 icon and open when it is clicked.",
6533        "options": [
6534          [
6535            "",
6536            "Never \u2014 always show the entries"
6537          ],
6538          [
6539            "mobile",
6540            "On phones (\u2264767px)"
6541          ],
6542          [
6543            "always",
6544            "Always \u2014 icon on every screen"
6545          ]
6546        ]
6547      },
6548      {
6549        "key": "burgerSide",
6550        "label": "Icon sits",
6551        "kind": "select",
6552        "tab": "content",
6553        "hint": "Which end of the strip the menu icon sits at once the entries are collapsed behind it.",
6554        "options": [
6555          [
6556            "",
6557            "Right"
6558          ],
6559          [
6560            "left",
6561            "Left"
6562          ]
6563        ]
6564      },
6565      {
6566        "key": "burgerLabel",
6567        "label": "Text beside the icon",
6568        "kind": "text",
6569        "tab": "content",
6570        "hint": 'Blank for the icon alone \u2014 "Menu" if the icon needs a word.'
6571      },
6572      {
6573        "key": "subBg",
6574        "label": "Submenu background",
6575        "kind": "color",
6576        "tab": "style"
6577      },
6578      {
6579        "key": "radius",
6580        "label": "Submenu corner radius",
6581        "kind": "number",
6582        "tab": "style",
6583        "unit": "px",
6584        "min": 0,
6585        "max": 40
6586      }
6587    ]
6588  },
6589  {
6590    "id": "imageslider",
6591    "label": "Image Slider",
6592    "html": "",
6593    "props": {
6594      "anim": "",
6595      "device": "",
6596      "cssMobile": "",
6597      "beside": "",
6598      "width": "",
6599      "boxAlign": "center",
6600      "spaceTop": "",
6601      "spaceBottom": "",
6602      "boxBg": "",
6603      "boxPad": "",
6604      "boxBorder": 0,
6605      "boxBorderColor": "#E2E8F0",
6606      "boxCorner": "",
6607      "boxShadow": "",
6608      "css": "",
6609      "slides": '[{"url":""}]',
6610      "height": 220,
6611      "radius": 10,
6612      "link": ""
6613    },
6614    "settings": [
6615      {
6616        "key": "slides",
6617        "label": "Slides",
6618        "kind": "slide_list",
6619        "tab": "content",
6620        "hint": "One picture per slide; a second slide brings the arrows."
6621      },
6622      {
6623        "key": "height",
6624        "label": "Height",
6625        "kind": "number",
6626        "tab": "style",
6627        "unit": "px",
6628        "min": 60,
6629        "max": 800
6630      },
6631      {
6632        "key": "radius",
6633        "label": "Corner radius",
6634        "kind": "number",
6635        "tab": "style",
6636        "unit": "px",
6637        "min": 0,
6638        "max": 100
6639      },
6640      {
6641        "key": "link",
6642        "label": "Link for slides with none of their own",
6643        "kind": "link",
6644        "tab": "content"
6645      }
6646    ]
6647  },
6648  {
6649    "id": "logos",
6650    "label": "Logo Row",
6651    "html": "",
6652    "props": {
6653      "anim": "",
6654      "device": "",
6655      "cssMobile": "",
6656      "beside": "",
6657      "width": "",
6658      "boxAlign": "center",
6659      "spaceTop": "",
6660      "spaceBottom": "",
6661      "boxBg": "",
6662      "boxPad": "",
6663      "boxBorder": 0,
6664      "boxBorderColor": "#E2E8F0",
6665      "boxCorner": "",
6666      "boxShadow": "",
6667      "css": "",
6668      "logos": [
6669        ""
6670      ],
6671      "height": 32,
6672      "gap": 24,
6673      "gray": "1",
6674      "source": "",
6675      "withStories": "1",
6676      "count": 12,
6677      "order": "featured",
6678      "matchIndustry": "prefer",
6679      "matchCompanySize": "prefer",
6680      "matchCountry": "",
6681      "matchRegion": "",
6682      "matchCity": "",
6683      "matchDomain": "",
6684      "matchUrl": "",
6685      "matchTag": "",
6686      "matchTagAttr": ""
6687    },
6688    "settings": [
6689      {
6690        "key": "source",
6691        "label": "Logos come from",
6692        "kind": "select",
6693        "tab": "content",
6694        "hint": "Your customer logos on the Reviews page are picked for each visitor - the logos of their industry, company size or country first.",
6695        "options": [
6696          [
6697            "",
6698            "Logos I add here"
6699          ],
6700          [
6701            "imported",
6702            "The Reviews page"
6703          ]
6704        ]
6705      },
6706      {
6707        "key": "logos",
6708        "label": "Logos",
6709        "kind": "media_list",
6710        "tab": "content",
6711        "hint": "Transparent PNGs sit best."
6712      },
6713      {
6714        "key": "withStories",
6715        "label": "Also the company logos on testimonials and case studies",
6716        "kind": "checkbox",
6717        "tab": "content",
6718        "hint": "A company that also has a logo entry shows once."
6719      },
6720      {
6721        "key": "count",
6722        "label": "How many",
6723        "kind": "number",
6724        "tab": "content",
6725        "min": 1,
6726        "max": 24
6727      },
6728      {
6729        "key": "order",
6730        "label": "Order",
6731        "kind": "select",
6732        "tab": "content",
6733        "options": [
6734          [
6735            "featured",
6736            "Featured first"
6737          ],
6738          [
6739            "",
6740            "Newest first"
6741          ],
6742          [
6743            "random",
6744            "Random"
6745          ]
6746        ]
6747      },
6748      {
6749        "key": "matchIndustry",
6750        "label": "Industry",
6751        "kind": "select",
6752        "tab": "content",
6753        "options": [
6754          [
6755            "",
6756            "Ignore"
6757          ],
6758          [
6759            "prefer",
6760            "Matching logos first"
6761          ],
6762          [
6763            "either",
6764            "Hide logos tagged for others"
6765          ],
6766          [
6767            "only",
6768            "Only matching logos"
6769          ]
6770        ]
6771      },
6772      {
6773        "key": "matchCompanySize",
6774        "label": "Company size",
6775        "kind": "select",
6776        "tab": "content",
6777        "options": [
6778          [
6779            "",
6780            "Ignore"
6781          ],
6782          [
6783            "prefer",
6784            "Matching logos first"
6785          ],
6786          [
6787            "either",
6788            "Hide logos tagged for others"
6789          ],
6790          [
6791            "only",
6792            "Only matching logos"
6793          ]
6794        ]
6795      },
6796      {
6797        "key": "matchCountry",
6798        "label": "Country",
6799        "kind": "select",
6800        "tab": "content",
6801        "options": [
6802          [
6803            "",
6804            "Ignore"
6805          ],
6806          [
6807            "prefer",
6808            "Matching logos first"
6809          ],
6810          [
6811            "either",
6812            "Hide logos tagged for others"
6813          ],
6814          [
6815            "only",
6816            "Only matching logos"
6817          ]
6818        ]
6819      },
6820      {
6821        "key": "matchRegion",
6822        "label": "State or region",
6823        "kind": "select",
6824        "tab": "content",
6825        "options": [
6826          [
6827            "",
6828            "Ignore"
6829          ],
6830          [
6831            "prefer",
6832            "Matching logos first"
6833          ],
6834          [
6835            "either",
6836            "Hide logos tagged for others"
6837          ],
6838          [
6839            "only",
6840            "Only matching logos"
6841          ]
6842        ]
6843      },
6844      {
6845        "key": "matchCity",
6846        "label": "City",
6847        "kind": "select",
6848        "tab": "content",
6849        "options": [
6850          [
6851            "",
6852            "Ignore"
6853          ],
6854          [
6855            "prefer",
6856            "Matching logos first"
6857          ],
6858          [
6859            "either",
6860            "Hide logos tagged for others"
6861          ],
6862          [
6863            "only",
6864            "Only matching logos"
6865          ]
6866        ]
6867      },
6868      {
6869        "key": "matchDomain",
6870        "label": "Website",
6871        "kind": "select",
6872        "tab": "content",
6873        "options": [
6874          [
6875            "",
6876            "Ignore"
6877          ],
6878          [
6879            "prefer",
6880            "Matching logos first"
6881          ],
6882          [
6883            "either",
6884            "Hide logos tagged for others"
6885          ],
6886          [
6887            "only",
6888            "Only matching logos"
6889          ]
6890        ]
6891      },
6892      {
6893        "key": "matchUrl",
6894        "label": "Page path",
6895        "kind": "select",
6896        "tab": "content",
6897        "options": [
6898          [
6899            "",
6900            "Ignore"
6901          ],
6902          [
6903            "prefer",
6904            "Matching logos first"
6905          ],
6906          [
6907            "either",
6908            "Hide logos tagged for others"
6909          ],
6910          [
6911            "only",
6912            "Only matching logos"
6913          ]
6914        ]
6915      },
6916      {
6917        "key": "matchTag",
6918        "label": "Your own fields",
6919        "kind": "select",
6920        "tab": "content",
6921        "hint": "Logos tagged on the Reviews page with a visitor attribute\u2019s value, e.g. plan = pro.",
6922        "options": [
6923          [
6924            "",
6925            "Ignore"
6926          ],
6927          [
6928            "prefer",
6929            "Matching logos first"
6930          ],
6931          [
6932            "either",
6933            "Hide logos tagged for others"
6934          ],
6935          [
6936            "only",
6937            "Only matching logos"
6938          ]
6939        ]
6940      },
6941      {
6942        "key": "height",
6943        "label": "Logo height",
6944        "kind": "number",
6945        "tab": "style",
6946        "unit": "px",
6947        "min": 12,
6948        "max": 120
6949      },
6950      {
6951        "key": "gap",
6952        "label": "Spacing",
6953        "kind": "number",
6954        "tab": "style",
6955        "unit": "px",
6956        "min": 4,
6957        "max": 80
6958      },
6959      {
6960        "key": "gray",
6961        "label": "Mute the colors (grayscale)",
6962        "kind": "checkbox",
6963        "tab": "style",
6964        "hint": "The quiet editorial look."
6965      }
6966    ]
6967  },
6968  {
6969    "id": "testimonials",
6970    "label": "Testimonials",
6971    "html": "",
6972    "props": {
6973      "anim": "",
6974      "device": "",
6975      "cssMobile": "",
6976      "beside": "",
6977      "width": "",
6978      "boxAlign": "center",
6979      "spaceTop": "",
6980      "spaceBottom": "",
6981      "boxBg": "",
6982      "boxPad": "",
6983      "boxBorder": 0,
6984      "boxBorderColor": "#E2E8F0",
6985      "boxCorner": "",
6986      "boxShadow": "",
6987      "css": "",
6988      "items": '[{"q":"","name":"","img":""}]',
6989      "layout": "",
6990      "stars": "5",
6991      "starColor": "#B45309",
6992      "quoteColor": "#334155",
6993      "nameColor": "#64748B",
6994      "cardBg": "#F8FAFC",
6995      "source": "",
6996      "count": 3,
6997      "order": "",
6998      "minRating": "",
6999      "fromSource": "",
7000      "kinds": "review",
7001      "matchProduct": "",
7002      "matchCountry": "",
7003      "matchRegion": "",
7004      "matchCity": "",
7005      "matchGender": "",
7006      "matchIndustry": "",
7007      "matchCompanySize": "",
7008      "matchUrl": "",
7009      "matchDomain": "",
7010      "matchInterest": "",
7011      "matchCategory": "",
7012      "interestPick": "",
7013      "matchTag": "",
7014      "matchTagAttr": "",
7015      "showLogo": "1",
7016      "showCover": "1",
7017      "ctaText": "Read the story",
7018      "showTitle": "1",
7019      "maxLength": 280,
7020      "verifiedText": "Verified buyer",
7021      "showSource": "1",
7022      "viaText": "via",
7023      "showReply": "",
7024      "replyLabel": "Reply:",
7025      "about": "",
7026      "withPhotos": "",
7027      "showAvatar": "",
7028      "showProduct": "",
7029      "recommendText": "",
7030      "showRole": "1",
7031      "showPhotos": "1",
7032      "showDate": "1",
7033      "showLink": "",
7034      "linkText": "Read on {source}",
7035      "showSummary": "",
7036      "summaryText": "\u2605 {rating} \xB7 {n} reviews",
7037      "numberStyle": ""
7038    },
7039    "settings": [
7040      {
7041        "key": "source",
7042        "label": "Reviews come from",
7043        "kind": "select",
7044        "tab": "content",
7045        "hint": "Your saved reviews, testimonials and case studies: from a review platform, a file, your own review form or added by hand on the Reviews page.",
7046        "options": [
7047          [
7048            "",
7049            "Quotes I type here"
7050          ],
7051          [
7052            "imported",
7053            "The Reviews page"
7054          ]
7055        ]
7056      },
7057      {
7058        "key": "items",
7059        "label": "Testimonials",
7060        "kind": "quote_list",
7061        "tab": "content",
7062        "hint": "Quote, name, optional photo per row."
7063      },
7064      {
7065        "key": "count",
7066        "label": "How many",
7067        "kind": "number",
7068        "tab": "content",
7069        "min": 1,
7070        "max": 24
7071      },
7072      {
7073        "key": "order",
7074        "label": "Order",
7075        "kind": "select",
7076        "tab": "content",
7077        "options": [
7078          [
7079            "",
7080            "Newest first"
7081          ],
7082          [
7083            "best",
7084            "Highest rated first"
7085          ],
7086          [
7087            "featured",
7088            "Featured first"
7089          ],
7090          [
7091            "helpful",
7092            "Most helpful first"
7093          ],
7094          [
7095            "random",
7096            "Random"
7097          ]
7098        ]
7099      },
7100      {
7101        "key": "about",
7102        "label": "Reviews of",
7103        "kind": "select",
7104        "tab": "content",
7105        "options": [
7106          [
7107            "",
7108            "Anything"
7109          ],
7110          [
7111            "store",
7112            "Your business only"
7113          ],
7114          [
7115            "product",
7116            "Products only"
7117          ],
7118          [
7119            "content",
7120            "Pages & articles only"
7121          ]
7122        ]
7123      },
7124      {
7125        "key": "withPhotos",
7126        "label": "Only reviews with photos",
7127        "kind": "checkbox",
7128        "tab": "content"
7129      },
7130      {
7131        "key": "minRating",
7132        "label": "Minimum rating",
7133        "kind": "select",
7134        "tab": "content",
7135        "hint": "Hides reviews rated below it, and anything without a rating - most testimonials, case studies and press quotes have none.",
7136        "options": [
7137          [
7138            "",
7139            "Any rating"
7140          ],
7141          [
7142            "3",
7143            "3 stars and up"
7144          ],
7145          [
7146            "4",
7147            "4 stars and up"
7148          ],
7149          [
7150            "5",
7151            "5 stars only"
7152          ]
7153        ]
7154      },
7155      {
7156        "key": "matchProduct",
7157        "label": "Product",
7158        "kind": "select",
7159        "tab": "content",
7160        "hint": 'Reviews of the product on the page, or of the last one they bought. "Only" hides reviews of no product; "or of no product" keeps those too; "prefer" puts the matches first.',
7161        "options": [
7162          [
7163            "",
7164            "Any product"
7165          ],
7166          [
7167            "viewing",
7168            "Prefer the product being viewed"
7169          ],
7170          [
7171            "viewing_either",
7172            "The product being viewed, or of no product"
7173          ],
7174          [
7175            "viewing_only",
7176            "Only the product being viewed"
7177          ],
7178          [
7179            "last",
7180            "Prefer the last product they bought"
7181          ],
7182          [
7183            "last_either",
7184            "The last product they bought, or of no product"
7185          ],
7186          [
7187            "last_only",
7188            "Only the last product they bought"
7189          ]
7190        ]
7191      },
7192      {
7193        "key": "matchCountry",
7194        "label": "Country",
7195        "kind": "select",
7196        "tab": "content",
7197        "hint": "Matches when any of the review\u2019s values is the visitor\u2019s.",
7198        "options": [
7199          [
7200            "",
7201            "Ignore"
7202          ],
7203          [
7204            "prefer",
7205            "Prefer matching reviews (more matches first)"
7206          ],
7207          [
7208            "either",
7209            "Matching reviews, and ones with no value for this field"
7210          ],
7211          [
7212            "only",
7213            "Only matching reviews (ones with no value are hidden)"
7214          ]
7215        ]
7216      },
7217      {
7218        "key": "matchRegion",
7219        "label": "State or region",
7220        "kind": "select",
7221        "tab": "content",
7222        "hint": "Matches when any of the review\u2019s regions is the visitor\u2019s, by name (Texas) or code (TX).",
7223        "options": [
7224          [
7225            "",
7226            "Ignore"
7227          ],
7228          [
7229            "prefer",
7230            "Prefer matching reviews (more matches first)"
7231          ],
7232          [
7233            "either",
7234            "Matching reviews, and ones with no value for this field"
7235          ],
7236          [
7237            "only",
7238            "Only matching reviews (ones with no value are hidden)"
7239          ]
7240        ]
7241      },
7242      {
7243        "key": "matchCity",
7244        "label": "City",
7245        "kind": "select",
7246        "tab": "content",
7247        "hint": "Matches when any of the review\u2019s values is the visitor\u2019s.",
7248        "options": [
7249          [
7250            "",
7251            "Ignore"
7252          ],
7253          [
7254            "prefer",
7255            "Prefer matching reviews (more matches first)"
7256          ],
7257          [
7258            "either",
7259            "Matching reviews, and ones with no value for this field"
7260          ],
7261          [
7262            "only",
7263            "Only matching reviews (ones with no value are hidden)"
7264          ]
7265        ]
7266      },
7267      {
7268        "key": "matchGender",
7269        "label": "Gender",
7270        "kind": "select",
7271        "tab": "content",
7272        "hint": "Only visitors whose gender is known match - most are not.",
7273        "options": [
7274          [
7275            "",
7276            "Ignore"
7277          ],
7278          [
7279            "prefer",
7280            "Prefer matching reviews (more matches first)"
7281          ],
7282          [
7283            "either",
7284            "Matching reviews, and ones with no value for this field"
7285          ],
7286          [
7287            "only",
7288            "Only matching reviews (ones with no value are hidden)"
7289          ]
7290        ]
7291      },
7292      {
7293        "key": "matchIndustry",
7294        "label": "Industry",
7295        "kind": "select",
7296        "tab": "content",
7297        "hint": "Matches when any of the review\u2019s values is the visitor\u2019s.",
7298        "options": [
7299          [
7300            "",
7301            "Ignore"
7302          ],
7303          [
7304            "prefer",
7305            "Prefer matching reviews (more matches first)"
7306          ],
7307          [
7308            "either",
7309            "Matching reviews, and ones with no value for this field"
7310          ],
7311          [
7312            "only",
7313            "Only matching reviews (ones with no value are hidden)"
7314          ]
7315        ]
7316      },
7317      {
7318        "key": "matchCompanySize",
7319        "label": "Company size",
7320        "kind": "select",
7321        "tab": "content",
7322        "hint": "Matches when any of the review\u2019s values is the visitor\u2019s.",
7323        "options": [
7324          [
7325            "",
7326            "Ignore"
7327          ],
7328          [
7329            "prefer",
7330            "Prefer matching reviews (more matches first)"
7331          ],
7332          [
7333            "either",
7334            "Matching reviews, and ones with no value for this field"
7335          ],
7336          [
7337            "only",
7338            "Only matching reviews (ones with no value are hidden)"
7339          ]
7340        ]
7341      },
7342      {
7343        "key": "matchUrl",
7344        "label": "Page path",
7345        "kind": "select",
7346        "tab": "content",
7347        "hint": "Matches when any of the review\u2019s paths is the page: the path itself, or also every page under it when it ends with / or *. A lone / is the home page.",
7348        "options": [
7349          [
7350            "",
7351            "Ignore"
7352          ],
7353          [
7354            "prefer",
7355            "Prefer matching reviews (more matches first)"
7356          ],
7357          [
7358            "either",
7359            "Matching reviews, and ones with no value for this field"
7360          ],
7361          [
7362            "only",
7363            "Only matching reviews (ones with no value are hidden)"
7364          ]
7365        ]
7366      },
7367      {
7368        "key": "matchDomain",
7369        "label": "Website",
7370        "kind": "select",
7371        "tab": "content",
7372        "hint": "For an account with several sites: matches when any of the review\u2019s websites is the one the visitor is on.",
7373        "options": [
7374          [
7375            "",
7376            "Ignore"
7377          ],
7378          [
7379            "prefer",
7380            "Prefer matching reviews (more matches first)"
7381          ],
7382          [
7383            "either",
7384            "Matching reviews, and ones with no value for this field"
7385          ],
7386          [
7387            "only",
7388            "Only matching reviews (ones with no value are hidden)"
7389          ]
7390        ]
7391      },
7392      {
7393        "key": "matchInterest",
7394        "label": "Interests",
7395        "kind": "select",
7396        "tab": "content",
7397        "hint": "Reviews tagged with the visitor\u2019s interest.",
7398        "options": [
7399          [
7400            "",
7401            "Ignore"
7402          ],
7403          [
7404            "prefer",
7405            "Prefer matching reviews (more matches first)"
7406          ],
7407          [
7408            "either",
7409            "Matching reviews, and ones with no value for this field"
7410          ],
7411          [
7412            "only",
7413            "Only matching reviews (ones with no value are hidden)"
7414          ]
7415        ]
7416      },
7417      {
7418        "key": "matchCategory",
7419        "label": "Categories",
7420        "kind": "select",
7421        "tab": "content",
7422        "hint": "Reviews tagged with a category the visitor browsed.",
7423        "options": [
7424          [
7425            "",
7426            "Ignore"
7427          ],
7428          [
7429            "prefer",
7430            "Prefer matching reviews (more matches first)"
7431          ],
7432          [
7433            "either",
7434            "Matching reviews, and ones with no value for this field"
7435          ],
7436          [
7437            "only",
7438            "Only matching reviews (ones with no value are hidden)"
7439          ]
7440        ]
7441      },
7442      {
7443        "key": "interestPick",
7444        "label": "Which of the visitor\u2019s interests",
7445        "kind": "select",
7446        "tab": "content",
7447        "hint": "A visitor has many. The strongest is the one they came back to most.",
7448        "options": [
7449          [
7450            "",
7451            "Their strongest"
7452          ],
7453          [
7454            "latest",
7455            "Their latest"
7456          ],
7457          [
7458            "any",
7459            "Any of them"
7460          ]
7461        ]
7462      },
7463      {
7464        "key": "matchTag",
7465        "label": "Your own fields",
7466        "kind": "select",
7467        "tab": "content",
7468        "hint": "Reviews tagged on the Reviews page with a visitor attribute\u2019s value, e.g. plan = pro.",
7469        "options": [
7470          [
7471            "",
7472            "Ignore"
7473          ],
7474          [
7475            "prefer",
7476            "Prefer matching reviews (more matches first)"
7477          ],
7478          [
7479            "either",
7480            "Matching reviews, and ones with no value for this field"
7481          ],
7482          [
7483            "only",
7484            "Only matching reviews (ones with no value are hidden)"
7485          ]
7486        ]
7487      },
7488      {
7489        "key": "showTitle",
7490        "label": "Show the review title",
7491        "kind": "checkbox",
7492        "tab": "content"
7493      },
7494      {
7495        "key": "maxLength",
7496        "label": "Shorten reviews to",
7497        "kind": "number",
7498        "tab": "content",
7499        "unit": "characters",
7500        "min": 40,
7501        "max": 2e3
7502      },
7503      {
7504        "key": "showAvatar",
7505        "label": "Show the reviewer\u2019s photo",
7506        "kind": "checkbox",
7507        "tab": "content",
7508        "hint": "When the review platform has one."
7509      },
7510      {
7511        "key": "showPhotos",
7512        "label": "Show the reviewer\u2019s photos",
7513        "kind": "checkbox",
7514        "tab": "content",
7515        "hint": "Up to three, when the review has them."
7516      },
7517      {
7518        "key": "showDate",
7519        "label": "Show the date",
7520        "kind": "checkbox",
7521        "tab": "content"
7522      },
7523      {
7524        "key": "showProduct",
7525        "label": "Show what it is about",
7526        "kind": "checkbox",
7527        "tab": "content",
7528        "hint": "The product\u2019s name and variant, or the page\u2019s title - for a block that shows reviews of many products or pages."
7529      },
7530      {
7531        "key": "recommendText",
7532        "label": "\u201CWould recommend\u201D label",
7533        "kind": "text",
7534        "tab": "content",
7535        "hint": "Shown on reviews whose author said they would recommend it. Empty hides it."
7536      },
7537      {
7538        "key": "showLogo",
7539        "label": "Show the company logo",
7540        "kind": "checkbox",
7541        "tab": "content",
7542        "hint": "The customer\u2019s or publication\u2019s logo, on testimonials, case studies and press quotes that have one."
7543      },
7544      {
7545        "key": "showCover",
7546        "label": "Show the cover picture",
7547        "kind": "checkbox",
7548        "tab": "content",
7549        "hint": "A case study\u2019s picture, above the card."
7550      },
7551      {
7552        "key": "ctaText",
7553        "label": "Link label",
7554        "kind": "text",
7555        "tab": "content",
7556        "hint": "For a case study or article with a link and no label of its own."
7557      },
7558      {
7559        "key": "showRole",
7560        "label": "Show job title and company",
7561        "kind": "checkbox",
7562        "tab": "content",
7563        "hint": "For B2B testimonials, when the review has them."
7564      },
7565      {
7566        "key": "verifiedText",
7567        "label": "Verified buyer label",
7568        "kind": "text",
7569        "tab": "content",
7570        "hint": "Shown on reviews from verified buyers. Empty hides it."
7571      },
7572      {
7573        "key": "showSource",
7574        "label": "Show where the review came from",
7575        "kind": "checkbox",
7576        "tab": "content"
7577      },
7578      {
7579        "key": "viaText",
7580        "label": "Word before the platform",
7581        "kind": "text",
7582        "tab": "content"
7583      },
7584      {
7585        "key": "showReply",
7586        "label": "Show your reply",
7587        "kind": "checkbox",
7588        "tab": "content"
7589      },
7590      {
7591        "key": "replyLabel",
7592        "label": "Reply label",
7593        "kind": "text",
7594        "tab": "content"
7595      },
7596      {
7597        "key": "showLink",
7598        "label": "Link to the review on its platform",
7599        "kind": "checkbox",
7600        "tab": "content",
7601        "hint": "Opens in a new tab. Only reviews that have a link and a platform name."
7602      },
7603      {
7604        "key": "linkText",
7605        "label": "Platform link label",
7606        "kind": "text",
7607        "tab": "content",
7608        "hint": "The word source in curly braces stands for the platform\u2019s name."
7609      },
7610      {
7611        "key": "showSummary",
7612        "label": "Show the overall rating above the cards",
7613        "kind": "checkbox",
7614        "tab": "content",
7615        "hint": "Your average rating and how many rated reviews it is from, e.g. 4.8 from 1,234."
7616      },
7617      {
7618        "key": "summaryText",
7619        "label": "Overall rating text",
7620        "kind": "text",
7621        "tab": "content",
7622        "hint": "The words rating and n in curly braces stand for the average and the count."
7623      },
7624      {
7625        "key": "numberStyle",
7626        "label": "Number style",
7627        "kind": "select",
7628        "tab": "content",
7629        "hint": "How the count and the average are written. Pick the one your site\u2019s language uses.",
7630        "options": [
7631          [
7632            "",
7633            "1,234 and 4.8"
7634          ],
7635          [
7636            "dot",
7637            "1.234 and 4,8"
7638          ],
7639          [
7640            "space",
7641            "1 234 and 4,8"
7642          ]
7643        ]
7644      },
7645      {
7646        "key": "layout",
7647        "label": "Layout",
7648        "kind": "select",
7649        "tab": "style",
7650        "hint": "Slider shows one card at a time.",
7651        "options": [
7652          [
7653            "",
7654            "Stacked"
7655          ],
7656          [
7657            "slider",
7658            "Slider"
7659          ]
7660        ]
7661      },
7662      {
7663        "key": "stars",
7664        "label": "Stars",
7665        "kind": "select",
7666        "tab": "style",
7667        "options": [
7668          [
7669            "0",
7670            "None"
7671          ],
7672          [
7673            "5",
7674            "5 stars"
7675          ],
7676          [
7677            "4",
7678            "4 stars"
7679          ],
7680          [
7681            "3",
7682            "3 stars"
7683          ],
7684          [
7685            "2",
7686            "2 stars"
7687          ],
7688          [
7689            "one",
7690            "1 star"
7691          ]
7692        ]
7693      },
7694      {
7695        "key": "starColor",
7696        "label": "Star color",
7697        "kind": "color",
7698        "tab": "style"
7699      },
7700      {
7701        "key": "quoteColor",
7702        "label": "Quote color",
7703        "kind": "color",
7704        "tab": "style"
7705      },
7706      {
7707        "key": "nameColor",
7708        "label": "Name color",
7709        "kind": "color",
7710        "tab": "style"
7711      },
7712      {
7713        "key": "cardBg",
7714        "label": "Card background",
7715        "kind": "color",
7716        "tab": "style"
7717      }
7718    ]
7719  },
7720  {
7721    "id": "reviewrequest",
7722    "label": "Ask for a Review",
7723    "html": "",
7724    "props": {
7725      "anim": "",
7726      "device": "",
7727      "cssMobile": "",
7728      "beside": "",
7729      "width": "",
7730      "boxAlign": "center",
7731      "spaceTop": "",
7732      "spaceBottom": "",
7733      "boxBg": "",
7734      "boxPad": "",
7735      "boxBorder": 0,
7736      "boxBorderColor": "#E2E8F0",
7737      "boxCorner": "",
7738      "boxShadow": "",
7739      "css": "",
7740      "heading": "Enjoyed your experience?",
7741      "text": "A short review helps other people find us. It only takes a minute.",
7742      "buttonText": "Review us on {platform}",
7743      "google": "",
7744      "trustpilot": "",
7745      "g2": "",
7746      "capterra": "",
7747      "facebook": "",
7748      "customName": "",
7749      "customUrl": "",
7750      "product": "",
7751      "productImage": "1",
7752      "productButton": "Review {product}",
7753      "productAnchor": "",
7754      "layout": "",
7755      "bg": "#0F172A",
7756      "color": "#FFFFFF",
7757      "radius": 8,
7758      "align": "center"
7759    },
7760    "settings": [
7761      {
7762        "key": "product",
7763        "label": "Ask about",
7764        "kind": "select",
7765        "tab": "style",
7766        "hint": "With a product, the block shows only to visitors with a purchase on record, and {product} in the text becomes its name.",
7767        "options": [
7768          [
7769            "",
7770            "Their experience in general"
7771          ],
7772          [
7773            "last",
7774            "The last product they bought"
7775          ]
7776        ]
7777      },
7778      {
7779        "key": "heading",
7780        "label": "Heading",
7781        "kind": "text",
7782        "tab": "content"
7783      },
7784      {
7785        "key": "text",
7786        "label": "Text",
7787        "kind": "textarea",
7788        "tab": "content"
7789      },
7790      {
7791        "key": "buttonText",
7792        "label": "Button label",
7793        "kind": "text",
7794        "tab": "content",
7795        "hint": "{platform} is replaced by the platform\u2019s name."
7796      },
7797      {
7798        "key": "productImage",
7799        "label": "Show the product\u2019s picture",
7800        "kind": "checkbox",
7801        "tab": "style"
7802      },
7803      {
7804        "key": "productButton",
7805        "label": "Product review button",
7806        "kind": "text",
7807        "tab": "content",
7808        "hint": "Goes to the product\u2019s page on your site. Blank for none."
7809      },
7810      {
7811        "key": "productAnchor",
7812        "label": "Review form anchor",
7813        "kind": "text",
7814        "tab": "content",
7815        "hint": "Added to the product page link so it opens at your review form, like #judgeme_product_reviews."
7816      },
7817      {
7818        "key": "google",
7819        "label": "Google",
7820        "kind": "text",
7821        "tab": "content",
7822        "hint": "Your review link from Google Business Profile (Ask for reviews), or your Place ID."
7823      },
7824      {
7825        "key": "trustpilot",
7826        "label": "Trustpilot",
7827        "kind": "text",
7828        "tab": "content",
7829        "hint": "Your Trustpilot review link, or just your domain, like example.com."
7830      },
7831      {
7832        "key": "g2",
7833        "label": "G2",
7834        "kind": "text",
7835        "tab": "content"
7836      },
7837      {
7838        "key": "capterra",
7839        "label": "Capterra",
7840        "kind": "text",
7841        "tab": "content"
7842      },
7843      {
7844        "key": "facebook",
7845        "label": "Facebook",
7846        "kind": "text",
7847        "tab": "content"
7848      },
7849      {
7850        "key": "customName",
7851        "label": "Other platform name",
7852        "kind": "text",
7853        "tab": "content"
7854      },
7855      {
7856        "key": "customUrl",
7857        "label": "Other platform link",
7858        "kind": "text",
7859        "tab": "content",
7860        "hint": "A full link. It may contain a visitor variable, like a store\u2019s own review link."
7861      },
7862      {
7863        "key": "layout",
7864        "label": "Layout",
7865        "kind": "select",
7866        "tab": "style",
7867        "options": [
7868          [
7869            "",
7870            "Stacked"
7871          ],
7872          [
7873            "row",
7874            "Side by side"
7875          ]
7876        ]
7877      },
7878      {
7879        "key": "bg",
7880        "label": "Button background",
7881        "kind": "color",
7882        "tab": "style"
7883      },
7884      {
7885        "key": "color",
7886        "label": "Button text color",
7887        "kind": "color",
7888        "tab": "style"
7889      },
7890      {
7891        "key": "radius",
7892        "label": "Corner radius",
7893        "kind": "number",
7894        "tab": "style",
7895        "unit": "px",
7896        "min": 0,
7897        "max": 999
7898      },
7899      {
7900        "key": "align",
7901        "label": "Alignment",
7902        "kind": "align",
7903        "tab": "style"
7904      }
7905    ]
7906  },
7907  {
7908    "id": "slot",
7909    "label": "Personyze Placeholder",
7910    "html": '<div class="pz-slot pz-slot-slot-1"></div>',
7911    "props": {
7912      "anim": "",
7913      "device": "",
7914      "cssMobile": "",
7915      "beside": "",
7916      "width": "",
7917      "boxAlign": "center",
7918      "spaceTop": "",
7919      "spaceBottom": "",
7920      "boxBg": "",
7921      "boxPad": "",
7922      "boxBorder": 0,
7923      "boxBorderColor": "#E2E8F0",
7924      "boxCorner": "",
7925      "boxShadow": "",
7926      "css": "",
7927      "name": "slot-1",
7928      "minHeight": 0
7929    },
7930    "settings": [
7931      {
7932        "key": "name",
7933        "label": "Placeholder name",
7934        "kind": "text",
7935        "tab": "content",
7936        "hint": "Create a container with the CSS selector shown below, then point another action at that container."
7937      },
7938      {
7939        "key": "minHeight",
7940        "label": "Reserve height",
7941        "kind": "number",
7942        "tab": "style",
7943        "hint": "Holds the space open before the other action arrives, so the page does not jump.",
7944        "unit": "px",
7945        "min": 0,
7946        "max": 2e3
7947      }
7948    ]
7949  },
7950  {
7951    "id": "form",
7952    "label": "Form",
7953    "html": '<div style="max-width:420px; margin-left:auto; margin-right:auto;"><div class="$cform pzbform" ontplsubmit="var a=arguments;personyze.push([&#39;Submit&#39;,&#39;${action_id:html}&#39;,this._get_value_ex(),a[1],a[2]]);return true;" ontplaftersubmit="var r=this.querySelector(&#39;.pz-form-thanks&#39;); if (r) r.style.display=&#39;&#39;;var f=this.querySelectorAll(&#39;.pz-form-field,.pz-form-submit&#39;); for (var i=0;i&lt;f.length;i++) f[i].style.display=&#39;none&#39;;" style="margin:14px 0; text-align:left;"><div class="pz-form-field" style="margin-bottom:10px;"><label style="display:block; font-size:12px; font-weight:600; color:#334155; margin-bottom:4px;">Email *</label><input form="pzbform" name="email" type="email" required="1" data-prop-column="email" placeholder="[email protected]" style="width:100%; box-sizing:border-box; padding:9px 11px; border:1px solid #CBD5E1; border-radius:6px; font:inherit; font-size:14px;"></div>
7953<button form="pzbform" type="submit" class="pz-form-submit" style="text-align:center; margin:0; text-transform:none; letter-spacing:normal; background:#DB2777; color:#fff; border:0; box-sizing:border-box; width:100%; padding:11px 18px; border-radius:8px; font:inherit; font-size:14px; font-weight:700; cursor:pointer;">Subscribe</button><div class="pz-form-thanks" style="display:none; text-align:center; padding:14px 0; font-size:14px; color:#334155;">Thank you!</div></div></div>',
7954    "props": {
7955      "anim": "",
7956      "device": "",
7957      "cssMobile": "",
7958      "beside": "",
7959      "width": "420px",
7960      "boxAlign": "center",
7961      "spaceTop": "",
7962      "spaceBottom": "",
7963      "boxBg": "",
7964      "boxPad": "",
7965      "boxBorder": 0,
7966      "boxBorderColor": "#E2E8F0",
7967      "boxCorner": "",
7968      "boxShadow": "",
7969      "hoverBg": "",
7970      "hoverText": "",
7971      "report": "",
7972      "reportEvent": "",
7973      "reportValue": "",
7974      "css": "",
7975      "fields": '[{"label":"Email","column":"email","type":"email","placeholder":"[email protected]","required":true}]',
7976      "submitText": "Subscribe",
7977      "layout": "stacked",
7978      "submitBg": "#DB2777",
7979      "submitColor": "#fff",
7980      "thanksText": "Thank you!",
7981      "prefill": "",
7982      "after": "message",
7983      "redirectUrl": "",
7984      "redirectDelay": 2,
7985      "afterJs": ""
7986    },
7987    "settings": [
7988      {
7989        "key": "fields",
7990        "label": "Fields",
7991        "kind": "form_fields",
7992        "tab": "content",
7993        "hint": "Each field saves into the visitor\u2019s profile column you pick; submissions appear in the Forms report."
7994      },
7995      {
7996        "key": "layout",
7997        "label": "Field layout",
7998        "kind": "select",
7999        "tab": "style",
8000        "hint": "Side by side is the signup-bar shape; it wraps to stacked on phones.",
8001        "options": [
8002          [
8003            "stacked",
8004            "Stacked \u2014 one field per row"
8005          ],
8006          [
8007            "inline",
8008            "Side by side \u2014 fields and button on one row"
8009          ]
8010        ]
8011      },
8012      {
8013        "key": "submitText",
8014        "label": "Submit button label",
8015        "kind": "text",
8016        "tab": "content"
8017      },
8018      {
8019        "key": "thanksText",
8020        "label": "Thank-you message",
8021        "kind": "text",
8022        "tab": "content",
8023        "hint": "Replaces the fields after a successful submit."
8024      },
8025      {
8026        "key": "prefill",
8027        "label": "Fill in what we already know",
8028        "kind": "checkbox",
8029        "tab": "content",
8030        "hint": "A field whose profile column already has a value for this visitor arrives filled in. Empty for anyone we do not know."
8031      },
8032      {
8033        "key": "after",
8034        "label": "After a successful submit",
8035        "kind": "select",
8036        "tab": "content",
8037        "options": [
8038          [
8039            "message",
8040            "Show the thank-you message"
8041          ],
8042          [
8043            "redirect",
8044            "Send them to a page"
8045          ],
8046          [
8047            "close",
8048            "Close the popup"
8049          ]
8050        ]
8051      },
8052      {
8053        "key": "redirectUrl",
8054        "label": "Page to send them to",
8055        "kind": "url",
8056        "tab": "content"
8057      },
8058      {
8059        "key": "redirectDelay",
8060        "label": "Wait before leaving",
8061        "kind": "number",
8062        "tab": "content",
8063        "hint": "Long enough to read the thank-you. Zero leaves at once.",
8064        "unit": "s",
8065        "min": 0,
8066        "max": 30
8067      },
8068      {
8069        "key": "afterJs",
8070        "label": "Run JavaScript after submit",
8071        "kind": "source_javascript",
8072        "tab": "content",
8073        "hint": "Receives (form, values). For a conversion pixel, a dataLayer push, your own redirect."
8074      },
8075      {
8076        "key": "submitBg",
8077        "label": "Button background",
8078        "kind": "color",
8079        "tab": "style"
8080      },
8081      {
8082        "key": "submitColor",
8083        "label": "Button text",
8084        "kind": "color",
8085        "tab": "style"
8086      },
8087      {
8088        "key": "hoverBg",
8089        "label": "Background on hover",
8090        "kind": "color",
8091        "tab": "style",
8092        "hint": "What it changes to under the mouse. Blank keeps it still."
8093      },
8094      {
8095        "key": "hoverText",
8096        "label": "Text on hover",
8097        "kind": "color",
8098        "tab": "style"
8099      },
8100      {
8101        "key": "report",
8102        "label": "When clicked, report",
8103        "kind": "select",
8104        "tab": "content",
8105        "hint": "Beside the click itself, which is always counted.",
8106        "options": [
8107          [
8108            "",
8109            "Nothing extra"
8110          ],
8111          [
8112            "goal",
8113            "This action reached its goal"
8114          ],
8115          [
8116            "event",
8117            "A conversion goal"
8118          ],
8119          [
8120            "both",
8121            "Both"
8122          ]
8123        ]
8124      },
8125      {
8126        "key": "reportEvent",
8127        "label": "Which goal",
8128        "kind": "goal",
8129        "tab": "content",
8130        "hint": "Event-driven goals only \u2014 the kind that waits for a named event. Other goal types watch the page themselves and need nothing here."
8131      },
8132      {
8133        "key": "reportValue",
8134        "label": "Value",
8135        "kind": "text",
8136        "tab": "content",
8137        "hint": "Optional \u2014 a revenue amount, a plan name. Variables work here."
8138      }
8139    ]
8140  },
8141  {
8142    "id": "chat",
8143    "label": "AI Chat",
8144    "html": '<div class="pz-block-chat" data-pz-chat="{}" style="min-height:520px;"></div>',
8145    "props": {
8146      "anim": "",
8147      "device": "",
8148      "cssMobile": "",
8149      "beside": "",
8150      "width": "",
8151      "boxAlign": "center",
8152      "spaceTop": "",
8153      "spaceBottom": "",
8154      "boxBg": "",
8155      "boxPad": "",
8156      "boxBorder": 0,
8157      "boxBorderColor": "#E2E8F0",
8158      "boxCorner": "",
8159      "boxShadow": "",
8160      "css": "",
8161      "minHeight": 520,
8162      "withBubble": "",
8163      "accent": "",
8164      "greeting": "",
8165      "quick": "",
8166      "title": "",
8167      "placeholder": "",
8168      "send": "",
8169      "disclaimer": "",
8170      "show_notice": "",
8171      "ui_lang": "",
8172      "show_home": "",
8173      "home_title": "",
8174      "home_text": "",
8175      "follow_ups": "",
8176      "answer_feedback": "",
8177      "search_only": "",
8178      "knowledge_tag": "",
8179      "knowledge_miss": "wider",
8180      "catalogue_source": "",
8181      "show_items": "",
8182      "allow_human": "1",
8183      "meeting_offer": "",
8184      "own_instructions": "",
8185      "own_never": "",
8186      "event_started": "",
8187      "event_email": "",
8188      "event_human": "",
8189      "custom_css": ""
8190    },
8191    "settings": [
8192      {
8193        "key": "greeting",
8194        "label": "Greeting",
8195        "kind": "textarea",
8196        "tab": "content",
8197        "hint": "The first line the visitor reads. Blank follows the assistant."
8198      },
8199      {
8200        "key": "quick",
8201        "label": "Quick questions",
8202        "kind": "textarea",
8203        "tab": "content",
8204        "hint": "One per line. They appear as buttons before the visitor types anything."
8205      },
8206      {
8207        "key": "title",
8208        "label": "Header text",
8209        "kind": "text",
8210        "tab": "content",
8211        "hint": "From the assistant"
8212      },
8213      {
8214        "key": "placeholder",
8215        "label": "Message box",
8216        "kind": "text",
8217        "tab": "content",
8218        "hint": "From the assistant"
8219      },
8220      {
8221        "key": "send",
8222        "label": "Send button",
8223        "kind": "text",
8224        "tab": "content",
8225        "hint": "From the assistant"
8226      },
8227      {
8228        "key": "disclaimer",
8229        "label": "Small print",
8230        "kind": "text",
8231        "tab": "content",
8232        "hint": "A placement may reword the AI disclosure, never remove it."
8233      },
8234      {
8235        "key": "show_notice",
8236        "label": "Show the small print",
8237        "kind": "select",
8238        "tab": "content",
8239        "options": [
8240          [
8241            "",
8242            "From the assistant"
8243          ],
8244          [
8245            "1",
8246            "Yes"
8247          ],
8248          [
8249            "0",
8250            "No"
8251          ]
8252        ]
8253      },
8254      {
8255        "key": "ui_lang",
8256        "label": "The chat's own words",
8257        "kind": "select",
8258        "tab": "content",
8259        "options": [
8260          [
8261            "",
8262            "From the assistant"
8263          ],
8264          [
8265            "en",
8266            "English"
8267          ],
8268          [
8269            "page",
8270            "The page's language"
8271          ]
8272        ]
8273      },
8274      {
8275        "key": "show_home",
8276        "label": "Open on a home screen",
8277        "kind": "select",
8278        "tab": "content",
8279        "options": [
8280          [
8281            "",
8282            "From the assistant"
8283          ],
8284          [
8285            "1",
8286            "Yes"
8287          ],
8288          [
8289            "0",
8290            "No"
8291          ]
8292        ]
8293      },
8294      {
8295        "key": "home_title",
8296        "label": "Headline",
8297        "kind": "text",
8298        "tab": "content",
8299        "hint": "From the assistant"
8300      },
8301      {
8302        "key": "home_text",
8303        "label": "Line under it",
8304        "kind": "text",
8305        "tab": "content",
8306        "hint": "From the assistant"
8307      },
8308      {
8309        "key": "follow_ups",
8310        "label": "Suggest what to ask next",
8311        "kind": "select",
8312        "tab": "content",
8313        "options": [
8314          [
8315            "",
8316            "From the assistant"
8317          ],
8318          [
8319            "1",
8320            "Yes"
8321          ],
8322          [
8323            "0",
8324            "No"
8325          ]
8326        ]
8327      },
8328      {
8329        "key": "answer_feedback",
8330        "label": "Thumbs up and down on each answer",
8331        "kind": "select",
8332        "tab": "content",
8333        "options": [
8334          [
8335            "",
8336            "From the assistant"
8337          ],
8338          [
8339            "1",
8340            "Yes"
8341          ],
8342          [
8343            "0",
8344            "No"
8345          ]
8346        ]
8347      },
8348      {
8349        "key": "accent",
8350        "label": "Colour",
8351        "kind": "color",
8352        "tab": "style",
8353        "hint": "From the assistant"
8354      },
8355      {
8356        "key": "minHeight",
8357        "label": "Reserve height",
8358        "kind": "number",
8359        "tab": "style",
8360        "hint": "Holds the space open until the chat mounts, so the page does not jump.",
8361        "unit": "px",
8362        "min": 0,
8363        "max": 2e3
8364      },
8365      {
8366        "key": "knowledge_tag",
8367        "label": "Answer from",
8368        "kind": "knowledge_tag",
8369        "tab": "content",
8370        "hint": "Sources carrying one tag, instead of everything. Tag them on the Knowledge screen \u2014 the same tags campaigns and actions use."
8371      },
8372      {
8373        "key": "knowledge_miss",
8374        "label": "When it is asked something outside that",
8375        "kind": "select",
8376        "tab": "content",
8377        "options": [
8378          [
8379            "wider",
8380            "Answer from the whole knowledge base"
8381          ],
8382          [
8383            "wider_say",
8384            "Answer from the whole knowledge base, and say it is outside what this box covers"
8385          ],
8386          [
8387            "say",
8388            "Say it cannot help with that here"
8389          ]
8390        ]
8391      },
8392      {
8393        "key": "catalogue_source",
8394        "label": "Search which catalogue",
8395        "kind": "catalogue_source",
8396        "tab": "content",
8397        "hint": "One product feed instead of all of them. A shop that imports a feed per country can point this placement at the one its visitors read \u2014 so a Spanish page searches Spanish titles and never offers a product named in another language. Items that came from no feed at all are always included; they are yours rather than any storefront's."
8398      },
8399      {
8400        "key": "allow_human",
8401        "label": "Can it hand over to a person?",
8402        "kind": "select",
8403        "tab": "content",
8404        "hint": 'With this off the assistant never offers a person and never opens a case, whatever the visitor asks. Pair it with "When it does not know \u2192 take an email address instead" on the Behaviour screen, so somebody who needs a human still leaves you a way to reach them.',
8405        "options": [
8406          [
8407            "1",
8408            "Yes \u2014 pass them to the team"
8409          ],
8410          [
8411            "0",
8412            "No \u2014 take an email and reply later"
8413          ]
8414        ]
8415      },
8416      {
8417        "key": "own_instructions",
8418        "label": "Extra instructions",
8419        "kind": "textarea",
8420        "tab": "content",
8421        "hint": "Added to the assistant\u2019s own instructions, here only. It can make this placement stricter, never looser."
8422      },
8423      {
8424        "key": "own_never",
8425        "label": "It must never",
8426        "kind": "textarea",
8427        "tab": "content",
8428        "hint": "One rule per line, for this placement only."
8429      },
8430      {
8431        "key": "show_items",
8432        "label": "How it replies",
8433        "kind": "select",
8434        "tab": "content",
8435        "options": [
8436          [
8437            "",
8438            "An answer, with what it found underneath"
8439          ],
8440          [
8441            "text",
8442            "Text only \u2014 no cards"
8443          ]
8444        ]
8445      },
8446      {
8447        "key": "meeting_offer",
8448        "label": "Offering a meeting",
8449        "kind": "select",
8450        "tab": "content",
8451        "options": [
8452          [
8453            "",
8454            "From the assistant"
8455          ],
8456          [
8457            "off",
8458            "Do not offer meetings on these pages"
8459          ]
8460        ]
8461      },
8462      {
8463        "key": "event_started",
8464        "label": "When a conversation starts",
8465        "kind": "text",
8466        "tab": "content",
8467        "hint": "e.g. chat_started"
8468      },
8469      {
8470        "key": "event_email",
8471        "label": "When a visitor gives their email",
8472        "kind": "text",
8473        "tab": "content",
8474        "hint": "e.g. chat_lead"
8475      },
8476      {
8477        "key": "event_human",
8478        "label": "When it is passed to a person",
8479        "kind": "text",
8480        "tab": "content",
8481        "hint": "e.g. chat_handover"
8482      },
8483      {
8484        "key": "withBubble",
8485        "label": "May share a page with another chat.",
8486        "kind": "checkbox",
8487        "tab": "style",
8488        "hint": "Off by default: two chats open on one page is a question a visitor should not have to answer."
8489      },
8490      {
8491        "key": "custom_css",
8492        "label": "Extra CSS",
8493        "kind": "textarea",
8494        "tab": "content"
8495      }
8496    ]
8497  },
8498  {
8499    "id": "ask",
8500    "label": "AI Search",
8501    "html": '<div class="pz-block-chat pz-block-ask" data-pz-chat="{&quot;no_human&quot;:1,&quot;ask&quot;:1}"></div>',
8502    "props": {
8503      "anim": "",
8504      "device": "",
8505      "cssMobile": "",
8506      "beside": "",
8507      "width": "",
8508      "boxAlign": "center",
8509      "spaceTop": "",
8510      "spaceBottom": "",
8511      "boxBg": "",
8512      "boxPad": "",
8513      "boxBorder": 0,
8514      "boxBorderColor": "#E2E8F0",
8515      "boxCorner": "",
8516      "boxShadow": "",
8517      "css": "",
8518      "withBubble": "",
8519      "accent": "",
8520      "ask_placeholder": "",
8521      "ask_button": "",
8522      "ask_note": "",
8523      "results_only": "",
8524      "search_only": "",
8525      "knowledge_tag": "",
8526      "knowledge_miss": "wider",
8527      "catalogue_source": "",
8528      "show_items": "",
8529      "allow_human": "",
8530      "own_instructions": "",
8531      "own_never": ""
8532    },
8533    "settings": [
8534      {
8535        "key": "ask_placeholder",
8536        "label": "Ask",
8537        "kind": "text",
8538        "tab": "content",
8539        "hint": "What the empty box says. It has to explain itself before anybody types in it."
8540      },
8541      {
8542        "key": "ask_button",
8543        "label": "Button label",
8544        "kind": "text",
8545        "tab": "content",
8546        "hint": "From the assistant"
8547      },
8548      {
8549        "key": "ask_note",
8550        "label": "Small print",
8551        "kind": "text",
8552        "tab": "content",
8553        "hint": "The line under the box. It may be reworded, never removed \u2014 this is where it says an assistant answers, not a search engine."
8554      },
8555      {
8556        "key": "results_only",
8557        "label": "What happens when somebody asks",
8558        "kind": "select",
8559        "tab": "content",
8560        "options": [
8561          [
8562            "",
8563            "It answers the question"
8564          ],
8565          [
8566            "1",
8567            "It just lists what it found \u2014 no answer"
8568          ]
8569        ]
8570      },
8571      {
8572        "key": "acc
8572ent",
8573        "label": "Colour",
8574        "kind": "color",
8575        "tab": "style",
8576        "hint": "From the assistant"
8577      },
8578      {
8579        "key": "knowledge_tag",
8580        "label": "Answer from",
8581        "kind": "knowledge_tag",
8582        "tab": "content",
8583        "hint": "Sources carrying one tag, instead of everything. Tag them on the Knowledge screen \u2014 the same tags campaigns and actions use."
8584      },
8585      {
8586        "key": "knowledge_miss",
8587        "label": "When it is asked something outside that",
8588        "kind": "select",
8589        "tab": "content",
8590        "options": [
8591          [
8592            "wider",
8593            "Answer from the whole knowledge base"
8594          ],
8595          [
8596            "wider_say",
8597            "Answer from the whole knowledge base, and say it is outside what this box covers"
8598          ],
8599          [
8600            "say",
8601            "Say it cannot help with that here"
8602          ]
8603        ]
8604      },
8605      {
8606        "key": "catalogue_source",
8607        "label": "Search which catalogue",
8608        "kind": "catalogue_source",
8609        "tab": "content",
8610        "hint": "One product feed instead of all of them. A shop that imports a feed per country can point this placement at the one its visitors read \u2014 so a Spanish page searches Spanish titles and never offers a product named in another language. Items that came from no feed at all are always included; they are yours rather than any storefront's."
8611      },
8612      {
8613        "key": "allow_human",
8614        "label": "Can it hand over to a person?",
8615        "kind": "select",
8616        "tab": "content",
8617        "hint": "A search box that quietly turns into a support queue is not what the page put there, so this is off unless you switch it on. The assistant simply keeps answering \u2014 and if somebody really wants a person, a chat placement is the thing to give them.",
8618        "options": [
8619          [
8620            "",
8621            "No \u2014 it answers, and that is all"
8622          ],
8623          [
8624            "1",
8625            "Yes \u2014 it can pass the visitor to the team"
8626          ]
8627        ]
8628      },
8629      {
8630        "key": "own_instructions",
8631        "label": "Extra instructions",
8632        "kind": "textarea",
8633        "tab": "content",
8634        "hint": "Added to the assistant\u2019s own instructions, here only. It can make this placement stricter, never looser."
8635      },
8636      {
8637        "key": "own_never",
8638        "label": "It must never",
8639        "kind": "textarea",
8640        "tab": "content",
8641        "hint": "One rule per line, for this placement only."
8642      },
8643      {
8644        "key": "show_items",
8645        "label": "How it replies",
8646        "kind": "select",
8647        "tab": "content",
8648        "options": [
8649          [
8650            "",
8651            "An answer, with what it found underneath"
8652          ],
8653          [
8654            "text",
8655            "Text only \u2014 no cards"
8656          ]
8657        ]
8658      },
8659      {
8660        "key": "withBubble",
8661        "label": "May share a page with another chat.",
8662        "kind": "checkbox",
8663        "tab": "style",
8664        "hint": "Off by default: two chats open on one page is a question a visitor should not have to answer."
8665      }
8666    ]
8667  },
8668  {
8669    "id": "booking",
8670    "label": "Book a meeting",
8671    "html": '<div class="pz-block-booking" data-pz-booking="{}" style="min-height:460px;"></div>',
8672    "props": {
8673      "anim": "",
8674      "device": "",
8675      "cssMobile": "",
8676      "beside": "",
8677      "width": "",
8678      "boxAlign": "center",
8679      "spaceTop": "",
8680      "spaceBottom": "",
8681      "boxBg": "",
8682      "boxPad": "",
8683      "boxBorder": 0,
8684      "boxBorderColor": "#E2E8F0",
8685      "boxCorner": "",
8686      "boxShadow": "",
8687      "types": "",
8688      "look": "",
8689      "accent": "",
8690      "heading": "",
8691      "display": "inline",
8692      "buttonText": "",
8693      "minHeight": 460,
8694      "css": ""
8695    },
8696    "settings": [
8697      {
8698        "key": "types",
8699        "label": "Which meeting",
8700        "kind": "meeting_types",
8701        "tab": "content",
8702        "hint": "One meeting type goes straight to the times. Several lets the visitor choose first."
8703      },
8704      {
8705        "key": "display",
8706        "label": "How it appears",
8707        "kind": "select",
8708        "tab": "content",
8709        "hint": "A panel in the page is booked far more often than one behind a button.",
8710        "options": [
8711          [
8712            "inline",
8713            "In the design"
8714          ],
8715          [
8716            "button",
8717            "Behind a button"
8718          ]
8719        ]
8720      },
8721      {
8722        "key": "buttonText",
8723        "label": "Button label",
8724        "kind": "text",
8725        "tab": "content",
8726        "hint": "From the account"
8727      },
8728      {
8729        "key": "heading",
8730        "label": "Heading",
8731        "kind": "text",
8732        "tab": "content",
8733        "hint": "From the meeting type"
8734      },
8735      {
8736        "key": "look",
8737        "label": "Look",
8738        "kind": "select",
8739        "tab": "style",
8740        "options": [
8741          [
8742            "",
8743            "From the account"
8744          ],
8745          [
8746            "card",
8747            "Card"
8748          ],
8749          [
8750            "minimal",
8751            "Minimal"
8752          ],
8753          [
8754            "dark",
8755            "Dark"
8756          ],
8757          [
8758            "compact",
8759            "Compact"
8760          ]
8761        ]
8762      },
8763      {
8764        "key": "accent",
8765        "label": "Colour",
8766        "kind": "color",
8767        "tab": "style",
8768        "hint": "From the account"
8769      },
8770      {
8771        "key": "minHeight",
8772        "label": "Reserve height",
8773        "kind": "number",
8774        "tab": "style",
8775        "hint": "Holds the space open until the times arrive, so the design does not jump under the cursor.",
8776        "unit": "px",
8777        "min": 0,
8778        "max": 2e3
8779      }
8780    ]
8781  }
8782];
8783var WIDGET_GATES = {
8784  "form": "form",
8785  "chat": "chat",
8786  "ask": "chat",
8787  "booking": "meetings"
8788};
8789var FA_FONT = "PersonyzeFA47";
8790var ICON_LIBRARY = [
8791  [
8792    "shopping-cart",
8793    "f07a"
8794  ],
8795  [
8796    "cart-plus",
8797    "f217"
8798  ],
8799  [
8800    "cart-arrow-down",
8801    "f218"
8802  ],
8803  [
8804    "shopping-bag",
8805    "f290"
8806  ],
8807  [
8808    "credit-card",
8809    "f09d"
8810  ],
8811  [
8812    "tag",
8813    "f02b"
8814  ],
8815  [
8816    "tags",
8817    "f02c"
8818  ],
8819  [
8820    "gift",
8821    "f06b"
8822  ],
8823  [
8824    "user",
8825    "f007"
8826  ],
8827  [
8828    "users",
8829    "f0c0"
8830  ],
8831  [
8832    "heart",
8833    "f004"
8834  ],
8835  [
8836    "heart-o",
8837    "f08a"
8838  ],
8839  [
8840    "star",
8841    "f005"
8842  ],
8843  [
8844    "star-o",
8845    "f006"
8846  ],
8847  [
8848    "bookmark",
8849    "f02e"
8850  ],
8851  [
8852    "bookmark-o",
8853    "f097"
8854  ],
8855  [
8856    "bell",
8857    "f0f3"
8858  ],
8859  [
8860    "bell-o",
8861    "f0a2"
8862  ],
8863  [
8864    "fire",
8865    "f06d"
8866  ],
8867  [
8868    "bolt",
8869    "f0e7"
8870  ],
8871  [
8872    "check",
8873    "f00c"
8874  ],
8875  [
8876    "check-circle",
8877    "f058"
8878  ],
8879  [
8880    "times",
8881    "f00d"
8882  ],
8883  [
8884    "times-circle",
8885    "f057"
8886  ],
8887  [
8888    "arrow-right",
8889    "f061"
8890  ],
8891  [
8892    "arrow-left",
8893    "f060"
8894  ],
8895  [
8896    "angle-right",
8897    "f105"
8898  ],
8899  [
8900    "angle-left",
8901    "f104"
8902  ],
8903  [
8904    "chevron-right",
8905    "f054"
8906  ],
8907  [
8908    "chevron-left",
8909    "f053"
8910  ],
8911  [
8912    "search",
8913    "f002"
8914  ],
8915  [
8916    "envelope",
8917    "f0e0"
8918  ],
8919  [
8920    "phone",
8921    "f095"
8922  ],
8923  [
8924    "clock-o",
8925    "f017"
8926  ],
8927  [
8928    "calendar",
8929    "f073"
8930  ],
8931  [
8932    "map-marker",
8933    "f041"
8934  ],
8935  [
8936    "info-circle",
8937    "f05a"
8938  ],
8939  [
8940    "question-circle",
8941    "f059"
8942  ],
8943  [
8944    "exclamation-circle",
8945    "f06a"
8946  ],
8947  [
8948    "thumbs-up",
8949    "f164"
8950  ],
8951  [
8952    "home",
8953    "f015"
8954  ],
8955  [
8956    "cog",
8957    "f013"
8958  ],
8959  [
8960    "lock",
8961    "f023"
8962  ],
8963  [
8964    "eye",
8965    "f06e"
8966  ],
8967  [
8968    "download",
8969    "f019"
8970  ],
8971  [
8972    "external-link",
8973    "f08e"
8974  ],
8975  [
8976    "play",
8977    "f04b"
8978  ],
8979  [
8980    "paper-plane",
8981    "f1d8"
8982  ],
8983  [
8984    "facebook",
8985    "f09a"
8986  ],
8987  [
8988    "twitter",
8989    "f099"
8990  ],
8991  [
8992    "instagram",
8993    "f16d"
8994  ],
8995  [
8996    "whatsapp",
8997    "f232"
8998  ]
8999];
9000
9001// src/components/live/liveTiles.ts
9002var INSTANT_TOOLS = [
9003  "duplicate",
9004  "delete",
9005  "action-click"
9006];
9007var TABS = [
9008  {
9009    id: "basics",
9010    label: "Basics",
9011    title: "Core actions - edit text, style, duplicate, delete"
9012  },
9013  {
9014    id: "layout",
9015    label: "Layout",
9016    title: "Size, spacing, alignment, position"
9017  },
9018  {
9019    id: "insert",
9020    label: "Insert widget",
9021    title: "Add something to the page - a countdown, a coupon, a form field, an image"
9022  },
9023  {
9024    id: "behavior",
9025    label: "Behavior",
9026    title: "Interactions - on-click actions, delay"
9027  },
9028  {
9029    id: "tracking",
9030    label: "Tracking",
9031    title: "Analytics - click reporting, custom events"
9032  }
9033];
9034var TILES = [
9035  {
9036    tool: "text",
9037    tab: "basics",
9038    icon: "pencil",
9039    title: "Edit text",
9040    sub: "Type right on the page",
9041    key: "E"
9042  },
9043  {
9044    tool: "rich",
9045    tab: "basics",
9046    icon: "lines",
9047    title: "Rich editor",
9048    sub: "Bold, links, lists",
9049    key: "R"
9050  },
9051  {
9052    tool: "style",
9053    tab: "basics",
9054    icon: "palette",
9055    title: "Style",
9056    sub: "Color, font, bg",
9057    key: "S"
9058  },
9059  {
9060    tool: "replace-image",
9061    tab: "basics",
9062    icon: "image",
9063    title: "Replace image",
9064    sub: "New image URL"
9065  },
9066  {
9067    tool: "props",
9068    tab: "basics",
9069    icon: "props",
9070    title: "Properties",
9071    sub: "Link, alt, tooltip, id"
9072  },
9073  {
9074    tool: "duplicate",
9075    tab: "basics",
9076    icon: "copy",
9077    title: "Duplicate",
9078    sub: "Copy element",
9079    key: "D"
9080  },
9081  // Backspace has no printable name, so it is not looked up through `key` like the others -
9082  // [[LiveTool]] matches the event itself. The label is here only to be drawn on the tile.
9083  {
9084    tool: "delete",
9085    tab: "basics",
9086    icon: "trash",
9087    title: "Delete",
9088    sub: "Remove element",
9089    key: "\u232B"
9090  },
9091  {
9092    tool: "source",
9093    tab: "basics",
9094    icon: "code",
9095    title: "HTML source",
9096    sub: "Edit raw markup",
9097    key: "H"
9098  },
9099  // Layout
9100  {
9101    tool: "move",
9102    tab: "layout",
9103    alsoOn: "basics",
9104    icon: "move",
9105    title: "Move & resize",
9106    sub: "Drag corners",
9107    key: "M"
9108  },
9109  {
9110    tool: "spacing",
9111    tab: "layout",
9112    icon: "spacing",
9113    title: "Spacing",
9114    sub: "Margin, padding"
9115  },
9116  {
9117    tool: "alignment",
9118    tab: "layout",
9119    icon: "align",
9120    title: "Alignment",
9121    sub: "Text, flex & grid"
9122  },
9123  {
9124    tool: "position",
9125    tab: "layout",
9126    icon: "position",
9127    title: "Position",
9128    sub: "Z-index, pin, float"
9129  },
9130  {
9131    tool: "visibility",
9132    tab: "layout",
9133    icon: "eye",
9134    title: "Visibility",
9135    sub: "Show or hide per device",
9136    tooltip: "Hide this element on phones, tablets or desktops. Written as a media query, so it follows the visitor's real screen width"
9137  },
9138  // Insert
9139  {
9140    tool: "insert-image",
9141    tab: "insert",
9142    icon: "image",
9143    title: "Image",
9144    sub: "From URL"
9145  },
9146  {
9147    tool: "insert-link",
9148    tab: "insert",
9149    icon: "link",
9150    title: "Link",
9151    sub: "Destination URL"
9152  },
9153  /*	NO PLAIN BUTTON TILE. It inserted a bare `<button>` with a label and nothing else; the
9154  builder's CTA Button is the same element with its colours, padding, radius, link and hover
9155  behind it, and it is in the widget list below. Jacob's call, and the reason is that one of
9156  them can be edited afterwards and the other cannot. The old change shape still applies, so
9157  buttons inserted before this keep working.  */
9158  {
9159    tool: "insert-block",
9160    tab: "insert",
9161    icon: "block",
9162    title: "Block",
9163    sub: "Text or message box"
9164  },
9165  {
9166    tool: "insert-video",
9167    tab: "insert",
9168    icon: "video",
9169    title: "Video",
9170    sub: "MP4, audio, embed"
9171  },
9172  {
9173    tool: "insert-html",
9174    tab: "insert",
9175    icon: "code",
9176    title: "Custom HTML",
9177    sub: "Raw snippet"
9178  },
9179  /*	THE READY-MADE WIDGETS, one tile each, beside Image and Link rather than behind a button
9180  that opens a list — a palette is something you scan. They share one icon on purpose: what
9181  distinguishes them is the name, and ten hand-drawn glyphs would be ten things to keep
9182  true. Generated from the catalogue, so a widget added to the block builder appears here
9183  without this file being touched. */
9184  ...WIDGET_CATALOGUE.map((w3) => ({
9185    tool: `insert-widget-${w3.id}`,
9186    tab: "insert",
9187    icon: "block",
9188    title: w3.label,
9189    sub: "Widget"
9190  })),
9191  /*	Editing one that is already there. Offered on the Basics row for a widget rather than in
9192  Insert, because by then you are not adding anything — you clicked a countdown and want its
9193  end date. It leads that row for the same reason. */
9194  {
9195    tool: "widget-edit",
9196    tab: "basics",
9197    icon: "block",
9198    title: "Widget settings",
9199    sub: "What it says and how it looks",
9200    primary: true
9201  },
9202  // Behavior
9203  {
9204    tool: "onclick",
9205    tab: "behavior",
9206    icon: "bolt",
9207    title: "On click",
9208    sub: "Run JS, fire an action"
9209  },
9210  {
9211    tool: "scroll-to",
9212    tab: "behavior",
9213    icon: "scroll",
9214    title: "Scroll to",
9215    sub: "Jump to another element",
9216    tooltip: "Clicking this element scrolls the page to somewhere else on it - what an anchor link does, without needing one"
9217  },
9218  {
9219    tool: "delay",
9220    tab: "behavior",
9221    icon: "clock",
9222    title: "Delay execution",
9223    sub: "Wait for this element"
9224  },
9225  // Tracking
9226  {
9227    tool: "action-click",
9228    tab: "tracking",
9229    icon: "checkbox",
9230    title: "Action click",
9231    sub: "Count in this action's stats",
9232    badge: "Action",
9233    tooltip: "Clicks on this element count toward this action's Click metric in the campaign's statistics report"
9234  },
9235  {
9236    tool: "custom-event",
9237    tab: "tracking",
9238    icon: "chart",
9239    title: "Custom event",
9240    sub: "Trigger a goal or event"
9241  }
9242];
9243var TAG_HUMAN_NAMES = {
9244  b: "Bold text",
9245  strong: "Bold text",
9246  i: "Italic text",
9247  u: "Underlined text",
9248  s: "Linethrough text",
9249  strike: "Linethrough text",
9250  em: "Emphasized text",
9251  a: "Hyperlink",
9252  img: "Image",
9253  p: "Paragraph",
9254  center: "Center block",
9255  table: "Table element",
9256  tr: "Table row",
9257  th: "Table header cell",
9258  td: "Table cell",
9259  caption: "Table caption",
9260  thead: "Table head group",
9261  h1: "Heading, level 1",
9262  h2: "Heading, level 2",
9263  h3: "Heading, level 3",
9264  h4: "Heading, level 4",
9265  h5: "Heading, level 5",
9266  h6: "Heading, level 6",
9267  ol: "Ordered list",
9268  ul: "Unordered list",
9269  dl: "Definition list",
9270  li: "List item",
9271  dt: "Definition term",
9272  dd: "Description",
9273  form: "Form",
9274  input: "Input element",
9275  select: "Dropdown list",
9276  textarea: "Multiline text input element",
9277  button: "Button",
9278  div: "Divider",
9279  span: "Span element",
9280  iframe: "Inline frame",
9281  embed: "Embedded plugin",
9282  object: "Embedded ActiveX object",
9283  video: "Video",
9284  audio: "Audio",
9285  canvas: "Canvas",
9286  svg: "Scalable Vector Graphics (SVG)"
9287};
9288function tagHumanName(elem) {
9289  const tag = elem?.localName ?? "";
9290  return TAG_HUMAN_NAMES[tag] ?? tag.toUpperCase();
9291}
9292function canPutElem(elem, toElem) {
9293  if (!elem || !toElem || typeof elem != "string" && elem === toElem) {
9294    return {
9295      aside: false,
9296      inside: false
9297    };
9298  }
9299  const elemName = "[" + (typeof elem == "string" ? elem : elem.localName) + "]";
9300  const toElemName = "[" + toElem.localName + "]";
9301  const available = {
9302    aside: true,
9303    inside: true
9304  };
9305  switch (elemName) {
9306    case "[body]":
9307      available.aside = false;
9308      available.inside = false;
9309      break;
9310    case "[caption]":
9311    case "[thead]":
9312    case "[tbody]":
9313    case "[tfoot]":
9314      if (toElemName != "[table]") available.inside = false;
9315      if ("[caption][thead][tbody][tfoot]".indexOf(toElemName) == -1) available.aside = false;
9316      break;
9317    case "[tr]":
9318      if ("[caption][thead][tbody][tfoot]".indexOf(toElemName) == -1) available.inside = false;
9319      if (toElemName != "[tr]") available.aside = false;
9320      break;
9321    case "[th]":
9322    case "[td]":
9323      if (toElemName != "[tr]") available.inside = false;
9324      if ("[th][td]".indexOf(toElemName) == -1) available.aside = false;
9325      break;
9326    case "[li]":
9327      if ("[ul][ol][menu]".indexOf(toElemName) == -1) available.inside = false;
9328      if (toElemName != "[li]") available.aside = false;
9329      break;
9330  }
9331  switch (toElemName) {
9332    case "[body]":
9333      available.aside = false;
9334      break;
9335    case "[iframe]":
9336    case "[object]":
9337    case "[embed]":
9338    case "[video]":
9339    case "[audio]":
9340    case "[canvas]":
9341    case "[svg]":
9342    case "[img]":
9343    case "[hr]":
9344    case "[input]":
9345    case "[textarea]":
9346    case "[select]":
9347      available.inside = false;
9348      break;
9349    case "[table]":
9350      if ("[caption][thead][tbody][tfoot]".indexOf(elemName) == -1) available.inside = false;
9351      break;
9352    case "[caption]":
9353    case "[thead]":
9354    case "[tbody]":
9355    case "[tfoot]":
9356      if (elemName != "[tr]") available.inside = false;
9357      if ("[caption][thead][tbody][tfoot]".indexOf(elemName) == -1) available.aside = false;
9358      break;
9359    case "[tr]":
9360      if (elemName != "[tr]") available.aside = false;
9361      if ("[th][td]".indexOf(elemName) == -1) available.inside = false;
9362      break;
9363    case "[th]":
9364    case "[td]":
9365      if ("[th][td]".indexOf(elemName) == -1) available.aside = false;
9366      break;
9367    case "[ul]":
9368    case "[ol]":
9369    case "[menu]":
9370      if (elemName != "[li]") available.inside = false;
9371      break;
9372  }
9373  return available;
9374}
9375function editHtmlKind(elem) {
9376  if (!elem) {
9377    return 0;
9378  }
9379  const tag = elem.localName;
9380  if (tag == "table" || tag == "thead" || tag == "tbody" || tag == "tfoot" || tag == "tr" || tag == "iframe" || tag == "img" || tag == "hr" || tag == "br" || tag == "select" || tag == "textarea") {
9381    return 0;
9382  }
9383  if (tag == "input") {
9384    const type = elem.type;
9385    return type == "button" || type == "submit" || type == "reset" ? 0 : 2;
9386  }
9387  return 1;
9388}
9389function imageUrlOf(elem) {
9390  if (!elem) {
9391    return "";
9392  }
9393  const src = elem.src;
9394  if (src) {
9395    return src;
9396  }
9397  const background = getComputedStyle(elem).backgroundImage || "";
9398  const m2 = background.match(/^url\(["']?([^"')]*)/i);
9399  return m2 ? decodeURI(m2[1]) : "";
9400}
9401function dragCapability(elem) {
9402  if (!elem || elem == document.body || elem == document.documentElement) {
9403    return {
9404      canDrag: false,
9405      canResize: false
9406    };
9407  }
9408  const hasWidthProp = elem.width != null;
9409  const canResize = hasWidthProp || getComputedStyle(elem).display.indexOf("block") != -1;
9410  return {
9411    canDrag: true,
9412    canResize
9413  };
9414}
9415function tileOnTab(tile, tab) {
9416  return tile.tab == tab || tile.alsoOn == tab;
9417}
9418function widgetOfTile(tool) {
9419  return tool.startsWith("insert-widget-") ? tool.slice("insert-widget-".length) : "";
9420}
9421function availableTiles(elem, allow) {
9422  if (!elem) {
9423    return [];
9424  }
9425  const gateOpen = (gate) => !!allow?.[gate];
9426  const htmlKind = editHtmlKind(elem);
9427  const hasImage = !!imageUrlOf(elem);
9428  const { canDrag } = dragCapability(elem);
9429  const canInsert = canPutElem("span", elem);
9430  const isBody = elem == document.body;
9431  return TILES.filter((tile) => {
9432    switch (tile.tool) {
9433      case "text":
9434        return htmlKind != 0;
9435      case "rich":
9436      case "source":
9437        return htmlKind == 1;
9438      case "replace-image":
9439        return hasImage;
9440      case "move":
9441        return canDrag;
9442      case "duplicate":
9443      case "delete":
9444        return !isBody;
9445      case "insert-image":
9446      case "insert-link":
9447      case "insert-button":
9448      case "insert-block":
9449      case "insert-video":
9450      case "insert-html":
9451        return canInsert.inside || canInsert.aside;
9452      default: {
9453        const widget = widgetOfTile(tile.tool);
9454        if (!widget) {
9455          return true;
9456        }
9457        const gate = WIDGET_GATES[widget];
9458        return (!gate || gateOpen(gate)) && (canInsert.inside || canInsert.aside);
9459      }
9460    }
9461  });
9462}
9463function elementType(elem) {
9464  if (!elem) {
9465    return "";
9466  }
9467  const tag = elem.localName;
9468  if (tag == "img") return "image";
9469  if (tag == "a") return "link";
9470  if (tag == "button" || elem.matches('[role="button"]')) return "button";
9471  if (tag == "input" || tag == "textarea" || tag == "select") return "field";
9472  if (tag == "form") return "form";
9473  if (tag == "video" || tag == "audio" || tag == "iframe") return "media";
9474  if (/^h[1-6]$/.test(tag)) return "heading";
9475  if (tag == "ul" || tag == "ol") return "list";
9476  if (tag == "li") return "list-item";
9477  if (tag == "p" || tag == "span" || tag == "strong" || tag == "em") return "text";
9478  if (elem.children.length && (elem.textContent ?? "").trim().length < 200) return "container";
9479  return "generic";
9480}
9481var SMART_LABELS = {
9482  image: "Image",
9483  link: "Link",
9484  button: "Button",
9485  field: "Form field",
9486  form: "Form",
9487  media: "Media",
9488  heading: "Heading",
9489  list: "List",
9490  "list-item": "List item",
9491  text: "Text",
9492  container: "Container",
9493  generic: "Element"
9494};
9495var SMART_BADGE_ICONS = {
9496  image: "image",
9497  link: "link",
9498  button: "checkbox",
9499  field: "text",
9500  form: "send",
9501  media: "play",
9502  heading: "heading",
9503  list: "list",
9504  "list-item": "list",
9505  text: "pencil",
9506  container: "move",
9507  generic: "palette"
9508};
9509var SMART_TOOLS = {
9510  image: [
9511    "replace-image",
9512    "props",
9513    "style",
9514    "move"
9515  ],
9516  link: [
9517    "props",
9518    "text",
9519    "style",
9520    "onclick"
9521  ],
9522  button: [
9523    "text",
9524    "onclick",
9525    "style",
9526    "action-click"
9527  ],
9528  field: [
9529    "props",
9530    "style"
9531  ],
9532  form: [
9533    "props",
9534    "onclick"
9535  ],
9536  media: [
9537    "props",
9538    "style",
9539    "move"
9540  ],
9541  heading: [
9542    "text",
9543    "style",
9544    "source"
9545  ],
9546  text: [
9547    "text",
9548    "rich",
9549    "style"
9550  ],
9551  list: [
9552    "source",
9553    "style",
9554    "insert-block"
9555  ],
9556  "list-item": [
9557    "text",
9558    "duplicate",
9559    "delete"
9560  ],
9561  container: [
9562    "style",
9563    "spacing",
9564    "duplicate"
9565  ],
9566  generic: [
9567    "text",
9568    "style",
9569    "duplicate",
9570    "delete"
9571  ]
9572};
9573function widgetHostOf(elem) {
9574  for (let e3 = elem; e3; e3 = e3.parentElement) {
9575    if (e3.getAttribute?.("data-pz-widget")) {
9576      return e3;
9577    }
9578  }
9579  return null;
9580}
9581function smartRowFor(elem, allow) {
9582  const type = elementType(elem);
9583  if (!type) {
9584    return null;
9585  }
9586  const allowed = new Set(availableTiles(elem, allow).map((v3) => v3.tool));
9587  const tiles = new Array();
9588  if (widgetHostOf(elem)) {
9589    const wt = TILES.find((v3) => v3.tool == "widget-edit");
9590    if (wt) {
9591      tiles.push(wt);
9592    }
9593  }
9594  for (const tool of SMART_TOOLS[type] ?? SMART_TOOLS.generic) {
9595    const tile = TILES.find((v3) => v3.tool == tool);
9596    if (tile && allowed.has(tool)) {
9597      tiles.push(tile);
9598    }
9599  }
9600  return {
9601    label: SMART_LABELS[type] ?? "Element",
9602    badgeIcon: SMART_BADGE_ICONS[type] ?? "palette",
9603    tiles
9604  };
9605}
9606
9607// src/modules/jsapi/private/jsapi.ts
9608var N_ATTEMPTS = 3;
9609var CACHE_MAX_QUERIES = 32;
9610var MAX_URLENCODED_DATA = 7 * 1024;
9611var config = null;
9612var cache = /* @__PURE__ */ new Map();
9613function configureJsapi(newConfig) {
9614  config = newConfig;
9615  cache.clear();
9616}
9617function isJsapiConfigured() {
9618  return config != null;
9619}
9620function getJsapiServerId() {
9621  return config?.serverId ?? 0;
9622}
9623function getJsapiOrigin() {
9624  try {
9625    return config ? new URL(config.url, location.href).origin : "";
9626  } catch {
9627    return "";
9628  }
9629}
9630async function query(sql, params, noCache = false) {
9631  if (!config) {
9632    throw new Error("The Simulator has no database connection.");
9633  }
9634  const postQuery = {
9635    sql,
9636    params
9637  };
9638  const post = JSON.stringify(postQuery);
9639  if (!noCache) {
9640    const hit = cache.get(post);
9641    if (hit) {
9642      return hit;
9643    }
9644  }
9645  const isBig = post.length > MAX_URLENCODED_DATA;
9646  const response = await fetchRetrying(config.url, {
9647    mode: "cors",
9648    credentials: "include",
9649    headers: {
9650      "Content-Type": isBig ? "application/json" : "application/x-www-form-urlencoded"
9651    },
9652    method: "POST",
9653    body: isBig ? JSON.stringify({
9654      data: [
9655        postQuery
9656      ],
9657      server_id: config.serverId
9658    }) : `data=${encodeURIComponent("[" + post + "]")}&server_id=${config.serverId}`
9659  });
9660  if (!response.ok) {
9661    throw new Error(response.statusText || `Database request failed (${response.status})`);
9662  }
9663  const resultsets = toResultsets(await response.text());
9664  if (!noCache && resultsets.every((v3) => v3.fields)) {
9665    while (cache.size >= CACHE_MAX_QUERIES) {
9666      const oldest = cache.keys().next().value;
9667      if (oldest == void 0) {
9668        break;
9669      }
9670      cache.delete(oldest);
9671    }
9672    cache.set(post, resultsets);
9673  }
9674  return resultsets;
9675}
9676async function queryRow(sql, params, noCache = false) {
9677  const resultsets = await query(sql, params, noCache);
9678  return resultsets[0]?.[0];
9679}
9680async function queryRows(sql, params, noCache = false) {
9681  const resultsets = await query(sql, params, noCache);
9682  return resultsets[0] ?? [];
9683}
9684async function fetchRetrying(url, init) {
9685  let lastError;
9686  for (let i4 = 0; i4 < N_ATTEMPTS; i4++) {
9687    try {
9688      return await fetch(url, init);
9689    } catch (e3) {
9690      lastError = e3;
9691    }
9692  }
9693  console.error(lastError);
9694  throw new Error("Failed to reach the Personyze server. Please check your network connection.");
9695}
9696function toResultsets(text) {
9697  const pos = text.indexOf("\n\n//");
9698  const data = JSON.parse(pos == -1 ? text : text.slice(0, pos));
9699  const raw = data?.[0];
9700  if (raw?.error) {
9701    throw new Error(raw.error + "");
9702  }
9703  if (!Array.isArray(raw)) {
9704    throw new Error("Unexpected answer from the Personyze server.");
9705  }
9706  const resultsets = new Array();
9707  for (const rset of raw) {
9708    if (rset?.error) {
9709      throw new Error(rset.error + "");
9710    }
9711    const fields = rset?.fields;
9712    const rows = rset?.rows;
9713    const converted = [];
9714    if (fields && rows) {
9715      for (const row of rows) {
9716        const obj = {};
9717        for (let i4 = 0; i4 < fields.length; i4++) {
9718          obj[fields[i4]] = row[i4];
9719        }
9720        converted.push(obj);
9721      }
9722      converted.fields = fields;
9723    }
9724    resultsets.push(converted);
9725  }
9726  return resultsets;
9727}
9728
9729// src/components/useAsync.ts
9730function useAsync(load, deps, onError) {
9731  const [data, setData] = p2();
9732  const [isBusy, setIsBusy] = p2(true);
9733  const [tick, setTick] = p2(0);
9734  _2(() => {
9735    let isCurrent = true;
9736    setIsBusy(true);
9737    load().then((value) => {
9738      if (isCurrent) {
9739        setData(value);
9740        setIsBusy(false);
9741      }
9742    }, (error) => {
9743      if (isCurrent) {
9744        setIsBusy(false);
9745        onError(error);
9746      }
9747    });
9748    return () => {
9749      isCurrent = false;
9750    };
9751  }, [
9752    ...deps,
9753    tick
9754  ]);
9755  return {
9756    data,
9757    isBusy,
9758    reload: () => setTick((v3) => v3 + 1)
9759  };
9760}
9761
9762// src/modules/webaddress/mod.ts
9763var HAS_SCHEME = /^[a-z][a-z0-9+.-]*:\/\//i;
9764var OPAQUE_SCHEME = /^(?:mailto|tel|sms|data|javascript|file|blob|geo|whatsapp|skype|viber|callto):/i;
9765var BARE_HOST = /^[\w\-.]+\.[a-z]{2,}(?:[:/?#]|$)/i;
9766var BARE_LOCALHOST = /^localhost(?::\d+)?(?:[/?#]|$)/i;
9767var FILE_EXTENSION = /\.(?:html?|xht|xhtml|php\d?|asp|aspx|jsp|cgi|pl|py|rb|js|mjs|css|map|json|xml|rss|atom|txt|md|csv|tsv|pdf|zip|gz|png|jpe?g|gif|svg|webp|avif|ico|bmp|mp[34]|m4[av]|wav|ogg|webm|mov|avi|woff2?|ttf|otf|eot)$/i;
9768var CARRIES_VARIABLE = /\$\{|\{\{/;
9769function withScheme(raw) {
9770  const v3 = (raw ?? "").trim();
9771  if (!v3 || HAS_SCHEME.test(v3) || OPAQUE_SCHEME.test(v3) || CARRIES_VARIABLE.test(v3)) {
9772    return v3;
9773  }
9774  if (v3.startsWith("//")) {
9775    return "https:" + v3;
9776  }
9777  if (/^[/?#.]/.test(v3) || /\s/.test(v3)) {
9778    return v3;
9779  }
9780  if (!/[/?#:]/.test(v3) && FILE_EXTENSION.test(v3)) {
9781    return v3;
9782  }
9783  return BARE_HOST.test(v3) || BARE_LOCALHOST.test(v3) ? "https://" + v3 : v3;
9784}
9785
9786// src/components/live/MediaPicker.tsx
9787var MEDIA_QUERY = "SELECT id, filename, filename_inner, thumbnail, mime_type FROM uploaded_media ORDER BY mtime DESC LIMIT 200";
9788function mediaUrl(filename) {
9789  const origin = getJsapiOrigin();
9790  const serverId = getJsapiServerId();
9791  return !origin || !serverId || !filename ? "" : `${origin}/upload/${serverId}/${filename}`;
9792}
9793function MediaPicker(props) {
9794  const [search, setSearch] = p2("");
9795  const [error, setError] = p2("");
9796  const rows = useAsync(async () => !isJsapiConfigured() ? [] : await queryRows(MEDIA_QUERY), [], (e3) => {
9797    console.error(e3);
9798    setError(e3 instanceof Error ? e3.message : "Could not read the media library.");
9799  });
9800  const shown = q2(() => {
9801    const term = search.trim().toLowerCase();
9802    return (rows.data ?? []).filter((row) => (props.accept != "image" || String(row.mime_type ?? "").startsWith("image/")) && (!term || String(row.filename ?? "").toLowerCase().indexOf(term) != -1));
9803  }, [
9804    rows.data,
9805    search,
9806    props.accept
9807  ]);
9808  return /* @__PURE__ */ u2("div", {
9809    class: "le-root le-vd-overlay",
9810    onMouseDown: (ev) => {
9811      ev.stopPropagation();
9812      if (ev.target === ev.currentTarget) {
9813        props.onCancel();
9814      }
9815    },
9816    children: /* @__PURE__ */ u2("div", {
9817      class: "le-vd",
9818      role: "dialog",
9819      "aria-modal": "true",
9820      "aria-label": "Media library",
9821      children: [
9822        /* @__PURE__ */ u2("div", {
9823          class: "le-vd-head",
9824          children: [
9825            /* @__PURE__ */ u2("div", {
9826              children: [
9827                /* @__PURE__ */ u2("h3", {
9828                  children: [
9829                    li.image,
9830                    " Media library"
9831                  ]
9832                }),
9833                /* @__PURE__ */ u2("p", {
9834                  children: "Files uploaded to this Personyze account. Upload new ones from the panel."
9835                })
9836              ]
9837            }),
9838            /* @__PURE__ */ u2("button", {
9839              type: "button",
9840              class: "le-icon",
9841              title: "Close",
9842              onClick: props.onCancel,
9843              children: li.cross
9844            })
9845          ]
9846        }),
9847        /* @__PURE__ */ u2("div", {
9848          class: "le-vp-search",
9849          children: /* @__PURE__ */ u2("input", {
9850            class: "le-input",
9851            type: "text",
9852            value: search,
9853            placeholder: "Search by file name...",
9854            onInput: (ev) => setSearch(ev.target.value)
9855          })
9856        }),
9857        /* @__PURE__ */ u2("div", {
9858          class: "le-vd-body",
9859          children: error ? /* @__PURE__ */ u2("div", {
9860            class: "le-notice le-warn",
9861            children: [
9862              li.warn,
9863              /* @__PURE__ */ u2("div", {
9864                children: error
9865              })
9866            ]
9867          }) : rows.isBusy ? /* @__PURE__ */ u2("div", {
9868            class: "le-empty",
9869            children: /* @__PURE__ */ u2("p", {
9870              children: "Loading..."
9871            })
9872          }) : shown.length == 0 ? /* @__PURE__ */ u2("div", {
9873            class: "le-empty",
9874            children: [
9875              /* @__PURE__ */ u2("h4", {
9876                children: search ? "Nothing matches" : "No uploaded media"
9877              }),
9878              /* @__PURE__ */ u2("p", {
9879                children: search ? "Try a different name." : "Upload files from the Personyze panel, then they appear here."
9880              })
9881            ]
9882          }) : /* @__PURE__ */ u2("div", {
9883            class: "le-media-grid",
9884            children: shown.map((row) => {
9885              const url = mediaUrl(row.filename_inner);
9886              const thumb = row.thumbnail ? mediaUrl(row.thumbnail) : url;
9887              return /* @__PURE__ */ u2("button", {
9888                type: "button",
9889                class: "le-media",
9890                title: row.filename + " - " + url,
9891                onClick: () => props.onPick(url),
9892                children: [
9893                  /* @__PURE__ */ u2("span", {
9894                    class: "le-media-thumb",
9895                    children: String(row.mime_type ?? "").startsWith("image/") ? /* @__PURE__ */ u2("img", {
9896                      src: thumb,
9897                      alt: "",
9898                      loading: "lazy"
9899                    }) : li.video
9900                  }),
9901                  /* @__PURE__ */ u2("span", {
9902                    class: "le-media-name",
9903                    children: row.filename
9904                  })
9905                ]
9906              }, row.id);
9907            })
9908          })
9909        }),
9910        /* @__PURE__ */ u2("div", {
9911          class: "le-vd-foot",
9912          children: [
9913            /* @__PURE__ */ u2("div", {
9914              class: "le-spacer"
9915            }),
9916            /* @__PURE__ */ u2("button", {
9917              type: "button",
9918              class: "le-btn",
9919              onClick: props.onCancel,
9920              children: "Cancel"
9921            })
9922          ]
9923        })
9924      ]
9925    })
9926  });
9927}
9928function MediaUrlInput(props) {
9929  const [isOpen, setIsOpen] = p2(false);
9930  return /* @__PURE__ */ u2(k, {
9931    children: [
9932      /* @__PURE__ */ u2("div", {
9933        class: "le-field-group",
9934        style: "flex:1; min-width:0",
9935        children: [
9936          /* @__PURE__ */ u2("input", {
9937            class: "le-input",
9938            type: "text",
9939            style: "flex:1 1 8em; min-width:0",
9940            value: props.value,
9941            placeholder: props.placeholder ?? "https://...",
9942            onInput: (ev) => props.onChange(ev.target.value),
9943            /*	Leaving the box supplies the scheme for anything that can only be a
9944            host. A relative path or a bare filename — both legitimate here, the
9945            page being edited is on the customer's own site — is left as typed;
9946            see the rule in ../../modules/webaddress/mod.ts. */
9947            onBlur: (ev) => props.onChange(withScheme(ev.target.value))
9948          }),
9949          isJsapiConfigured() && /* @__PURE__ */ u2("button", {
9950            type: "button",
9951            class: "le-btn",
9952            title: "Pick from this account's uploaded media",
9953            onClick: () => setIsOpen(true),
9954            children: [
9955              li.image,
9956              "Library"
9957            ]
9958          })
9959        ]
9960      }),
9961      isOpen && /* @__PURE__ */ u2(MediaPicker, {
9962        accept: props.accept,
9963        onPick: (url) => {
9964          setIsOpen(false);
9965          props.onChange(url);
9966        },
9967        onCancel: () => setIsOpen(false)
9968      })
9969    ]
9970  });
9971}
9972
9973// src/components/live/accountLists.ts
9974var cache2 = /* @__PURE__ */ new Map();
9975function useList(key, sql, map, params) {
9976  const [state, setState] = p2({
9977    rows: [],
9978    loaded: false
9979  });
9980  _2(() => {
9981    if (!isJsapiConfigured()) {
9982      setState({
9983        rows: [],
9984        loaded: true
9985      });
9986      return;
9987    }
9988    let live = true;
9989    let p3 = cache2.get(key);
9990    if (!p3) {
9991      cache2.set(key, p3 = queryRows(sql, params));
9992    }
9993    p3.then((rows) => {
9994      if (live) setState({
9995        rows: map(rows),
9996        loaded: true
9997      });
9998    }, (error) => {
9999      cache2.delete(key);
10000      console.error(error);
10001      if (live) setState({
10002        rows: [],
10003        loaded: true
10004      });
10005    });
10006    return () => {
10007      live = false;
10008    };
10009  }, [
10010    key
10011  ]);
10012  return state;
10013}
10014function useGoals() {
10015  return useList("goals", `SELECT id, name, html_id FROM central.acc_containers
10016		 WHERE Find_in_set('goal event', container_types) AND Find_in_set('custom event', container_types)
10017		 ORDER BY name`, (rows) => rows.filter((r3) => String(r3.html_id ?? "").trim() !== "").map((r3) => ({
10018    name: String(r3.name ?? r3.html_id),
10019    event: String(r3.html_id)
10020  })));
10021}
10022function useProfileFields() {
10023  return useList("profile-fields", "SELECT name, title FROM other_columns_info WHERE table_name='social' ORDER BY ordinal DESC", (rows) => rows.map((r3) => ({
10024    name: String(r3.name),
10025    label: String(r3.title || r3.name)
10026  })));
10027}
10028function useKnowledgeTags() {
10029  return useList("knowledge-tags", "SELECT id, name FROM central.tags ORDER BY name", (rows) => rows.map((r3) => String(r3.name)).filter((n2) => n2 !== ""));
10030}
10031function useCatalogueFeeds() {
10032  return useList("catalogue-feeds", "SELECT DISTINCT data_sources FROM archive.site_db_products WHERE data_sources <> '' LIMIT 50", (rows) => [
10033    ...new Set(rows.flatMap((r3) => String(r3.data_sources ?? "").split(";")).map((x2) => x2.trim()).filter(Boolean))
10034  ].sort());
10035}
10036function useMeetingTypes() {
10037  return useList("meeting-types", `SELECT slug, title, is_active FROM central.acc_sched_event_types
10038		 WHERE server_id=@server_id ORDER BY is_active DESC, title LIMIT 200`, (rows) => rows.map((r3) => ({
10039    slug: String(r3.slug),
10040    title: String(r3.title || r3.slug),
10041    active: Number(r3.is_active) > 0
10042  })), {
10043    server_id: getJsapiServerId()
10044  });
10045}
10046
10047// src/components/live/panels/WidgetPanel.tsx
10048var ROW_KINDS = /* @__PURE__ */ new Set([
10049  "faq_list",
10050  "segment_list",
10051  "list",
10052  "quote_list",
10053  "table_rows",
10054  "menu_list",
10055  "slide_list",
10056  "media_list",
10057  "form_fields"
10058]);
10059var COLOR_KEY = /colou?r$|^bg$|bg$/i;
10060var IMAGE_KEY = /^(img|image|photo|avatar|thumb(nail)?)$/i;
10061var ROW_FIELDS = {
10062  faq_list: {
10063    q: {
10064      label: "Question"
10065    },
10066    a: {
10067      label: "Answer"
10068    }
10069  },
10070  quote_list: {
10071    q: {
10072      label: "Quote"
10073    },
10074    name: {
10075      label: "Name"
10076    },
10077    img: {
10078      label: "Photo",
10079      control: "image"
10080    }
10081  },
10082  table_rows: {
10083    label: {
10084      label: "Row"
10085    },
10086    a: {
10087      label: "First column"
10088    },
10089    b: {
10090      label: "Second column"
10091    }
10092  },
10093  segment_list: {
10094    label: {
10095      label: "Label"
10096    },
10097    color: {
10098      label: "Colour"
10099    },
10100    weight: {
10101      label: "Chance"
10102    },
10103    prize: {
10104      label: "Prize message"
10105    },
10106    code: {
10107      label: "Prize code"
10108    }
10109  },
10110  menu_list: {
10111    label: {
10112      label: "Label"
10113    },
10114    link: {
10115      label: "Link",
10116      control: "link"
10117    }
10118  },
10119  slide_list: {
10120    url: {
10121      label: "Picture",
10122      control: "image"
10123    },
10124    link: {
10125      label: "Link",
10126      control: "link"
10127    }
10128  },
10129  form_fields: {
10130    label: {
10131      label: "Label"
10132    },
10133    column: {
10134      label: "Saves to"
10135    },
10136    type: {
10137      label: "Type"
10138    },
10139    placeholder: {
10140      label: "Placeholder"
10141    },
10142    required: {
10143      label: "Required",
10144      control: "check"
10145    }
10146  }
10147};
10148function rowFieldLabel(kind, key) {
10149  const named = ROW_FIELDS[kind]?.[key]?.label;
10150  if (named) {
10151    return named;
10152  }
10153  const words = key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").toLowerCase();
10154  return words.charAt(0).toUpperCase() + words.slice(1);
10155}
10156function rowControlOf(kind, key) {
10157  const named = ROW_FIELDS[kind]?.[key]?.control;
10158  return named ?? (COLOR_KEY.test(key) ? "color" : IMAGE_KEY.test(key) ? "image" : "text");
10159}
10160function parseRows(value, shapeOf = value) {
10161  const asText = typeof shapeOf == "string";
10162  const model = toList(shapeOf);
10163  const bare = model.length > 0 && !(typeof model[0] == "object" && model[0]);
10164  const rows = toList(value).map((r3) => typeof r3 == "object" && r3 ? r3 : {
10165    value: r3
10166  });
10167  return {
10168    rows,
10169    bare,
10170    asText
10171  };
10172}
10173function toList(value) {
10174  let list = value;
10175  if (typeof list == "string") {
10176    try {
10177      list = JSON.parse(list || "[]");
10178    } catch {
10179      list = [];
10180    }
10181  }
10182  return Array.isArray(list) ? list : [];
10183}
10184function writeRows(next, { bare, asText }) {
10185  const out = bare ? next.map((r3) => String(r3.value ?? "")) : next;
10186  return asText ? JSON.stringify(out) : out;
10187}
10188function RowsEditor(props) {
10189  const parsed = parseRows(props.value, props.shapeOf);
10190  const { rows, bare } = parsed;
10191  const allKeys = [
10192    ...new Set(rows.flatMap((r3) => Object.keys(r3)))
10193  ];
10194  const nested = allKeys.filter((k3) => rows.some((r3) => typeof r3[k3] == "object" && r3[k3]));
10195  const keys = allKeys.filter((k3) => !nested.includes(k3));
10196  const write = (next) => props.onChange(writeRows(next, parsed));
10197  const setCell = (i4, key, v3) => {
10198    const next = rows.map((r3, j3) => j3 === i4 ? {
10199      ...r3,
10200      [key]: v3
10201    } : r3);
10202    write(next);
10203  };
10204  return /* @__PURE__ */ u2("div", {
10205    class: "le-wr",
10206    children: [
10207      rows.map((row, i4) => /* @__PURE__ */ u2("div", {
10208        class: "le-wr-row",
10209        children: [
10210          /* @__PURE__ */ u2("div", {
10211            class: "le-wr-head",
10212            children: [
10213              /* @__PURE__ */ u2("b", {
10214                children: [
10215                  "#",
10216                  i4 + 1
10217                ]
10218              }),
10219              /* @__PURE__ */ u2("button", {
10220                type: "button",
10221                class: "le-wr-x",
10222                title: "Remove this one",
10223                onClick: () => write(rows.filter((_3, j3) => j3 !== i4)),
10224                children: "\xD7"
10225              })
10226            ]
10227          }),
10228          keys.map((key) => {
10229            const control = rowControlOf(props.kind, key);
10230            const str2 = String(row[key] ?? "");
10231            return (
10232              // it is not a label anybody should read.
10233              /* @__PURE__ */ u2(Field, {
10234                label: bare ? "" : rowFieldLabel(props.kind, key),
10235                children: control == "color" ? /* @__PURE__ */ u2(ColorInput, {
10236                  value: str2,
10237                  onChange: (v3) => setCell(i4, key, v3)
10238                }) : control == "image" ? /* @__PURE__ */ u2(MediaUrlInput, {
10239                  value: str2,
10240                  accept: "image",
10241                  onChange: (v3) => setCell(i4, key, String(v3 ?? ""))
10242                }
10242) : control == "link" ? /* @__PURE__ */ u2(LinkInput, {
10243                  value: str2,
10244                  onChange: (v3) => setCell(i4, key, v3)
10245                }) : control == "check" ? /* @__PURE__ */ u2("input", {
10246                  type: "checkbox",
10247                  class: "le-check",
10248                  checked: row[key] === true || str2 !== "" && str2 !== "0" && str2 !== "false",
10249                  onChange: (ev) => setCell(i4, key, ev.target.checked)
10250                }) : /* @__PURE__ */ u2(TextInput, {
10251                  value: str2,
10252                  onChange: (v3) => setCell(i4, key, v3)
10253                })
10254              }, key)
10255            );
10256          })
10257        ]
10258      }, i4)),
10259      /* @__PURE__ */ u2("button", {
10260        type: "button",
10261        class: "le-wr-add",
10262        onClick: () => {
10263          const last = rows[rows.length - 1] ?? {};
10264          const blank = {};
10265          for (const k3 of keys.length ? keys : [
10266            "value"
10267          ]) {
10268            blank[k3] = COLOR_KEY.test(k3) || !isNaN(Number(last[k3])) ? last[k3] ?? "" : "";
10269          }
10270          write([
10271            ...rows,
10272            blank
10273          ]);
10274        },
10275        children: "+ Add"
10276      }),
10277      !rows.length && /* @__PURE__ */ u2("div", {
10278        class: "le-wl-note",
10279        children: "Nothing here yet \u2014 add the first one."
10280      }),
10281      nested.length > 0 && /* @__PURE__ */ u2("div", {
10282        class: "le-wl-note",
10283        children: [
10284          nested.join(", "),
10285          ": edited in the Personyze panel, and kept as it is here."
10286        ]
10287      })
10288    ]
10289  });
10290}
10291var TIMER_MODES = [
10292  [
10293    "fixed_date",
10294    "To a date",
10295    "Everyone sees the same deadline"
10296  ],
10297  [
10298    "cookie_based",
10299    "Per visitor",
10300    "Starts when this visitor first sees it"
10301  ],
10302  [
10303    "restart_everytime",
10304    "Every visit",
10305    "Starts again on every page view"
10306  ]
10307];
10308function TimerInput(props) {
10309  let cfg = {};
10310  try {
10311    const parsed = JSON.parse(String(props.value ?? "{}"));
10312    if (parsed && typeof parsed == "object") {
10313      cfg = parsed;
10314    }
10315  } catch {
10316  }
10317  const type = String(cfg.type ?? "restart_everytime");
10318  const put = (patch) => props.onChange(JSON.stringify({
10319    ...cfg,
10320    ...patch
10321  }));
10322  const num = (key) => String(cfg[key] ?? "");
10323  const asDate = (() => {
10324    const secs = Number(cfg.fixed_date_date) || 0;
10325    return secs > 0 ? new Date(secs * 1e3).toISOString().slice(0, 10) : "";
10326  })();
10327  const duration = (prefix) => [
10328    [
10329      "days",
10330      "Days"
10331    ],
10332    [
10333      "hours",
10334      "Hours"
10335    ],
10336    [
10337      "minutes",
10338      "Minutes"
10339    ]
10340  ].map(([unit, label]) => /* @__PURE__ */ u2(Field, {
10341    label,
10342    children: /* @__PURE__ */ u2(NumInput, {
10343      value: num(prefix + "_" + unit),
10344      unit: "",
10345      min: 0,
10346      max: 999,
10347      onChange: (v3) => put({
10348        [prefix + "_" + unit]: Number(v3) || 0
10349      })
10350    })
10351  }, unit));
10352  return /* @__PURE__ */ u2("div", {
10353    class: "le-timer",
10354    children: [
10355      /* @__PURE__ */ u2("div", {
10356        class: "le-seg",
10357        children: TIMER_MODES.map(([id, label, hint]) => /* @__PURE__ */ u2("button", {
10358          type: "button",
10359          class: "le-seg-btn" + (type == id ? " le-on" : ""),
10360          title: hint,
10361          onClick: () => put({
10362            type: id
10363          }),
10364          children: label
10365        }, id))
10366      }),
10367      type == "fixed_date" ? /* @__PURE__ */ u2(k, {
10368        children: [
10369          /* @__PURE__ */ u2(Field, {
10370            label: "Date",
10371            children: /* @__PURE__ */ u2("input", {
10372              type: "date",
10373              class: "le-input",
10374              value: asDate,
10375              onInput: (ev) => {
10376                const v3 = ev.target.value;
10377                const [y3, m2, d3] = v3.split("-").map(Number);
10378                put({
10379                  fixed_date_date: v3 ? Math.floor(Date.UTC(y3, m2 - 1, d3) / 1e3) : 0
10380                });
10381              }
10382            })
10383          }),
10384          /* @__PURE__ */ u2(Field, {
10385            label: "Time",
10386            children: /* @__PURE__ */ u2("input", {
10387              type: "time",
10388              class: "le-input",
10389              value: String(cfg.fixed_date_time ?? ""),
10390              onInput: (ev) => put({
10391                fixed_date_time: ev.target.value
10392              })
10393            })
10394          }),
10395          /* @__PURE__ */ u2(Field, {
10396            label: "Hours from UTC",
10397            children: /* @__PURE__ */ u2(NumInput, {
10398              value: num("fixed_date_timezone"),
10399              unit: "h",
10400              min: -12,
10401              max: 14,
10402              onChange: (v3) => put({
10403                fixed_date_timezone: Number(v3) || 0
10404              })
10405            })
10406          })
10407        ]
10408      }) : type == "cookie_based" ? /* @__PURE__ */ u2(k, {
10409        children: duration("cookie_based")
10410      }) : /* @__PURE__ */ u2(k, {
10411        children: duration("restart_everytime")
10412      })
10413    ]
10414  });
10415}
10416function LinkInput(props) {
10417  const href = props.value.match(/href="([^"]*)"/)?.[1] ?? "";
10418  const blank = /target="_blank"/.test(props.value);
10419  const write = (url, newTab) => props.onChange(`href="${url.replace(/"/g, "&quot;")}"` + (newTab ? ' target="_blank" rel="noopener"' : "") + ' data-personyze-click-target="1"');
10420  return /* @__PURE__ */ u2(k, {
10421    children: [
10422      /* @__PURE__ */ u2(TextInput, {
10423        value: href,
10424        placeholder: "https://...",
10425        onChange: (v3) => write(v3, blank)
10426      }),
10427      /* @__PURE__ */ u2("label", {
10428        class: "le-inline-check",
10429        children: [
10430          /* @__PURE__ */ u2("input", {
10431            type: "checkbox",
10432            class: "le-check",
10433            checked: blank,
10434            onChange: (ev) => write(href, ev.target.checked)
10435          }),
10436          "Open in a new tab"
10437        ]
10438      })
10439    ]
10440  });
10441}
10442function ListInput(props) {
10443  const { value, rows, loaded } = props;
10444  if (loaded && rows.length === 0) {
10445    return /* @__PURE__ */ u2(k, {
10446      children: [
10447        /* @__PURE__ */ u2(TextInput, {
10448          value,
10449          onChange: props.onChange
10450        }),
10451        props.note && /* @__PURE__ */ u2("div", {
10452          class: "le-wl-note",
10453          children: props.note
10454        })
10455      ]
10456    });
10457  }
10458  const known = value === "" || rows.some((r3) => r3[0] === value);
10459  return /* @__PURE__ */ u2(k, {
10460    children: [
10461      /* @__PURE__ */ u2(SelectInput, {
10462        value,
10463        options: [
10464          [
10465            "",
10466            props.emptyLabel
10467          ],
10468          ...rows,
10469          // Gone, or not arrived. Either way it stays selected rather than resetting.
10470          ...known ? [] : [
10471            [
10472              value,
10473              `${value} \u2014 not in this account`
10474            ]
10475          ]
10476        ],
10477        onChange: props.onChange
10478      }),
10479      !known && /* @__PURE__ */ u2("div", {
10480        class: "le-wl-note",
10481        children: "Nothing in this account is called that any more."
10482      })
10483    ]
10484  });
10485}
10486function GoalInput(props) {
10487  const { rows, loaded } = useGoals();
10488  return /* @__PURE__ */ u2(ListInput, {
10489    value: props.value,
10490    rows: rows.map((g2) => [
10491      g2.event,
10492      g2.name
10493    ]),
10494    loaded,
10495    emptyLabel: "(no goal)",
10496    note: 'No event-driven goal in this account yet. Create one under Goals, type "JavaScript code".',
10497    onChange: props.onChange
10498  });
10499}
10500function ProfileFieldInput(props) {
10501  const { rows, loaded } = useProfileFields();
10502  const simple = /^\$\{(?:u->)?([\w-]+)\}$/.exec(props.value);
10503  const custom = props.value !== "" && !simple;
10504  if (custom) {
10505    return /* @__PURE__ */ u2(k, {
10506      children: [
10507        /* @__PURE__ */ u2(TextInput, {
10508          value: props.value,
10509          onChange: props.onChange
10510        }),
10511        /* @__PURE__ */ u2("div", {
10512          class: "le-wl-note",
10513          children: "Kept as written - this is not a plain profile field."
10514        })
10515      ]
10516    });
10517  }
10518  return /* @__PURE__ */ u2(ListInput, {
10519    value: simple ? simple[1] : "",
10520    rows: rows.map((c3) => [
10521      c3.name,
10522      c3.label
10523    ]),
10524    loaded,
10525    emptyLabel: "(no field)",
10526    note: "This account has no profile fields yet.",
10527    onChange: (v3) => props.onChange(v3 ? "${" + v3 + "}" : "")
10528  });
10529}
10530function IconInput(props) {
10531  const [q3, setQ] = p2("");
10532  const origin = getJsapiOrigin();
10533  const list = ICON_LIBRARY.filter(([name]) => !q3 || name.indexOf(q3.toLowerCase()) !== -1);
10534  return /* @__PURE__ */ u2(k, {
10535    children: [
10536      origin && /* @__PURE__ */ u2("style", {
10537        children: `@font-face{font-family:'${FA_FONT}';font-display:swap;src:url('${origin}/font-awesome/fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('${origin}/font-awesome/fonts/fontawesome-webfont.woff?v=4.7.0') format('woff')}` + ICON_LIBRARY.map(([n2, c3]) => `.le-ico-${n2}::before{content:"\\${c3}"}`).join("")
10538      }),
10539      /* @__PURE__ */ u2(TextInput, {
10540        value: q3,
10541        placeholder: "Search icons\u2026",
10542        onChange: setQ
10543      }),
10544      /* @__PURE__ */ u2("div", {
10545        class: "le-icogrid",
10546        children: list.map(([name]) => /* @__PURE__ */ u2("button", {
10547          type: "button",
10548          title: name,
10549          class: "le-icogrid-i" + (name === props.value ? " is-on" : ""),
10550          onClick: () => props.onChange(name),
10551          children: [
10552            /* @__PURE__ */ u2("i", {
10553              class: `le-ico le-ico-${name}`,
10554              style: {
10555                fontFamily: FA_FONT
10556              }
10557            }),
10558            /* @__P
10558URE__ */ u2("span", {
10559              children: name
10560            })
10561          ]
10562        }, name))
10563      }),
10564      list.length === 0 && /* @__PURE__ */ u2("div", {
10565        class: "le-wl-note",
10566        children: "No icon matches that."
10567      }),
10568      props.value !== "" && !ICON_LIBRARY.some(([n2]) => n2 === props.value) && /* @__PURE__ */ u2("div", {
10569        class: "le-wl-note",
10570        children: [
10571          'Currently set to "',
10572          props.value,
10573          '", which is not in this list.'
10574        ]
10575      })
10576    ]
10577  });
10578}
10579function UrlInput(props) {
10580  let resolved = "";
10581  const here = globalThis.location?.href;
10582  if (here && props.value !== "" && !/^(https?:|\/\/|\/|#|\{|\$)/i.test(props.value)) {
10583    try {
10584      resolved = new URL(/^[\w.-]+\.[a-z]{2,}(\/|$)/i.test(props.value) ? "https://" + props.value : props.value, here).href;
10585    } catch {
10586      resolved = "";
10587    }
10588  }
10589  return /* @__PURE__ */ u2(k, {
10590    children: [
10591      /* @__PURE__ */ u2(TextInput, {
10592        value: props.value,
10593        placeholder: "https://\u2026",
10594        onChange: props.onChange
10595      }),
10596      resolved !== "" && /* @__PURE__ */ u2("div", {
10597        class: "le-wl-note",
10598        children: [
10599          "Goes to ",
10600          resolved,
10601          " ",
10602          /* @__PURE__ */ u2("button", {
10603            type: "button",
10604            class: "le-linkbtn",
10605            onClick: () => props.onChange(resolved),
10606            children: "use this"
10607          })
10608        ]
10609      })
10610    ]
10611  });
10612}
10613function MeetingTypesInput(props) {
10614  const { rows, loaded } = useMeetingTypes();
10615  const picked = String(props.value ?? "").split(",").map((x2) => x2.trim()).filter(Boolean);
10616  if (!loaded) {
10617    return picked.length ? /* @__PURE__ */ u2("div", {
10618      class: "le-wl-note",
10619      children: picked.join(", ")
10620    }) : null;
10621  }
10622  if (rows.length === 0) {
10623    return /* @__PURE__ */ u2(k, {
10624      children: [
10625        /* @__PURE__ */ u2(TextInput, {
10626          value: props.value,
10627          onChange: props.onChange
10628        }),
10629        /* @__PURE__ */ u2("div", {
10630          class: "le-wl-note",
10631          children: "No meeting types yet \u2014 create them under Meetings in the Personyze panel."
10632        })
10633      ]
10634    });
10635  }
10636  const known = new Set(rows.map((r3) => r3.slug));
10637  const unknown = picked.filter((s3) => !known.has(s3));
10638  const toggle = (slug, on) => {
10639    const next = rows.map((r3) => r3.slug).filter((s3) => s3 === slug ? on : picked.includes(s3));
10640    props.onChange([
10641      ...next,
10642      ...unknown
10643    ].join(","));
10644  };
10645  return /* @__PURE__ */ u2(k, {
10646    children: [
10647      rows.map((r3) => /* @__PURE__ */ u2("label", {
10648        class: "le-inline-check",
10649        children: [
10650          /* @__PURE__ */ u2("input", {
10651            type: "checkbox",
10652            class: "le-check",
10653            checked: picked.includes(r3.slug),
10654            onChange: (ev) => toggle(r3.slug, ev.target.checked)
10655          }),
10656          r3.title,
10657          r3.active ? "" : " (switched off)"
10658        ]
10659      }, r3.slug)),
10660      unknown.length > 0 && /* @__PURE__ */ u2("div", {
10661        class: "le-wl-note",
10662        children: [
10663          "Kept, but no meeting type is called this any more: ",
10664          unknown.join(", ")
10665        ]
10666      })
10667    ]
10668  });
10669}
10670function KnowledgeTagInput(props) {
10671  const { rows, loaded } = useKnowledgeTags();
10672  return /* @__PURE__ */ u2(ListInput, {
10673    value: props.value,
10674    rows: rows.map((n2) => [
10675      n2,
10676      n2
10677    ]),
10678    loaded,
10679    emptyLabel: "The whole knowledge base",
10680    note: "No tags yet \u2014 tag a knowledge source first.",
10681    onChange: props.onChange
10682  });
10683}
10684function FeedInput(props) {
10685  const { rows, loaded } = useCatalogueFeeds();
10686  return /* @__PURE__ */ u2(ListInput, {
10687    value: props.value,
10688    rows: rows.length < 2 ? [] : rows.map((n2) => [
10689      n2,
10690      n2
10691    ]),
10692    loaded,
10693    emptyLabel: "Every feed",
10694    note: "This account imports from one feed, so there is nothing to narrow to.",
10695    onChange: props.onChange
10696  });
10697}
10698function Control(props) {
10699  const { setting, value } = props;
10700  const str2 = value == void 0 ? "" : String(value);
10701  if (ROW_KINDS.has(setting.kind)) {
10702    return /* @__PURE__ */ u2(RowsEditor, {
10703      kind: setting.kind,
10704      value,
10705      shapeOf: props.shapeOf,
10706      onChange: props.onChange
10707    });
10708  }
10709  switch (setting.kind) {
10710    case "timer":
10711      return /* @__PURE__ */ u2(TimerInput, {
10712        value,
10713        onChange: props.onChange
10714      });
10715    case "media":
10716      return /* @__PURE__ */ u2(MediaUrlInput, {
10717        value: str2,
10718        accept: "image",
10719        onChange: props.onChange
10720      });
10721    case "link":
10722      return /* @__PURE__ */ u2(LinkInput, {
10723        value: str2,
10724        onChange: props.onChange
10725      });
10726    case "source_javascript":
10727      return /* @__PURE__ */ u2("textarea", {
10728        class: "le-area",
10729        rows: 4,
10730        value: str2,
10731        onInput: (ev) => props.onChange(ev.target.value)
10732      });
10733    case "color":
10734      return /* @__PURE__ */ u2(ColorInput, {
10735        value: str2,
10736        onChange: props.onChange
10737      });
10738    case "number":
10739      return /* @__PURE__ */ u2(NumInput, {
10740        value: str2,
10741        unit: setting.unit ?? "px",
10742        min: setting.min,
10743        max: setting.max,
10744        onChange: props.onChange
10745      });
10746    case "select":
10747    case "align":
10748      return /* @__PURE__ */ u2(SelectInput, {
10749        value: str2,
10750        options: setting.options ?? [],
10751        onChange: props.onChange
10752      });
10753    case "checkbox":
10754      return /* @__PURE__ */ u2("input", {
10755        type: "checkbox",
10756        class: "le-check",
10757        checked: str2 !== "" && str2 !== "0",
10758        onChange: (ev) => props.onChange(ev.target.checked ? "1" : "")
10759      });
10760    case "textarea":
10761      return /* @__PURE__ */ u2("textarea", {
10762        class: "le-input le-ta",
10763        value: str2,
10764        onInput: (ev) => props.onChange(ev.target.value)
10765      });
10766    case "goal":
10767      return /* @__PURE__ */ u2(GoalInput, {
10768        value: str2,
10769        onChange: props.onChange
10770      });
10771    case "visitor_field":
10772      return /* @__PURE__ */ u2(ProfileFieldInput, {
10773        value: str2,
10774        onChange: props.onChange
10775      });
10776    case "icon":
10777      return /* @__PURE__ */ u2(IconInput, {
10778        value: str2,
10779        onChange: props.onChange
10780      });
10781    case "url":
10782      return /* @__PURE__ */ u2(UrlInput, {
10783        value: str2,
10784        onChange: props.onChange
10785      });
10786    case "knowledge_tag":
10787      return /* @__PURE__ */ u2(KnowledgeTagInput, {
10788        value: str2,
10789        onChange: props.onChange
10790      });
10791    case "catalogue_source":
10792      return /* @__PURE__ */ u2(FeedInput, {
10793        value: str2,
10794        onChange: props.onChange
10795      });
10796    case "meeting_types":
10797      return /* @__PURE__ */ u2(MeetingTypesInput, {
10798        value: str2,
10799        onChange: props.onChange
10800      });
10801    default:
10802      return /* @__PURE__ */ u2(TextInput, {
10803        value: str2,
10804        onChange: props.onChange
10805      });
10806  }
10807}
10808function WidgetPanel(props) {
10809  const [tab, setTab] = p2("content");
10810  const shown = props.entry.settings.filter((s3) => s3.tab === tab);
10811  return /* @__PURE__ */ u2(k, {
10812    children: [
10813      /* @__PURE__ */ u2(PanelTabs, {
10814        tabs: [
10815          {
10816            id: "content",
10817            label: "Content",
10818            title: "What it says"
10819          },
10820          {
10821            id: "style",
10822            label: "Style",
10823            title: "How it looks"
10824          }
10825        ],
10826        value: tab,
10827        onChange: setTab
10828      }),
10829      shown.length === 0 ? /* @__PURE__ */ u2("div", {
10830        class: "le-wl-note",
10831        children: "Nothing to set here for this one."
10832      }) : shown.map((s3) => (
10833        /*	A CHECKBOX IS ONE LINE: the box, then what it means. In the label column its
10834        words wrapped to three lines beside a lone box (Jacob's screenshot, 28 Sep 26),
10835        and a label beside a box reads as a heading, not as the thing you tick. */
10836        s3.kind === "checkbox" ? /* @__PURE__ */ u2("div", {
10837          class: "le-wcheck",
10838          children: [
10839            /* @__PURE__ */ u2("label", {
10840              class: "le-inline-check",
10841              children: [
10842                /* @__PURE__ */ u2(Control, {
10843                  setting: s3,
10844                  value: props.values[s3.key],
10845                  shapeOf: props.entry.props[s3.key],
10846                  onChange: (v3) => props.onChange(s3.key, v3)
10847                }),
10848                s3.label
10849              ]
10850            }),
10851            s3.hint && /* @__PURE__ */ u2("div", {
10852              class: "le-wl-note",
10853              children: s3.hint
10854            })
10855          ]
10856        }, s3.key) : (
10857          /*	A list setting takes the whole panel: label above, rows below. Squeezed
10858          							into the label column it left each field a few characters wide.
10859          
10860          							The icon grid is here for the same reason and it is worth naming: in the
10861          							value column its `auto-fill` had room for exactly ONE column, so 52 icons
10862          							drew as 52 stacked rows - a grid that is a list, which is the control it
10863          							replaced. Only visible by looking at it; the render test counted 52
10864          							buttons and was perfectly happy. */
10865          /* @__PURE__ */ u2(Field, {
10866            label: s3.label,
10867            wide: ROW_KINDS.has(s3.kind) || s3.kind === "textarea" || s3.kind === "icon",
10868            children: [
10869              /* @__PURE__ */ u2(Control, {
10870                setting: s3,
10871                value: props.values[s3.key],
10872                shapeOf: props.entry.props[s3.key],
10873                onChange: (v3) => props.onChange(s3.key, v3)
10874              }),
10875              s3.hint && /* @__PURE__ */ u2("div", {
10876                class: "le-wl-note",
10877                children: s3.hint
10878              })
10879            ]
10880          }, s3.key)
10881        )
10882      ))
10883    ]
10884  });
10885}
10886
10887// src/components/live/panels/WidgetEditPanel.tsx
10888function savedProps(elem) {
10889  try {
10890    const v3 = JSON.parse(elem.getAttribute("data-pz-props") || "{}");
10891    return v3 && typeof v3 == "object" && !Array.isArray(v3) ? v3 : {};
10892  } catch {
10893    return {};
10894  }
10895}
10896function WidgetEditPanel(props) {
10897  const { session } = props;
10898  const host = widgetHostOf(session.elem);
10899  const id = host?.getAttribute("data-pz-widget") ?? "";
10900  const entry = WIDGET_CATALOGUE.find((e3) => e3.id == id);
10901  const [values, setValues] = p2(() => ({
10902    ...entry?.props ?? {},
10903    ...host ? savedProps(host) : {}
10904  }));
10905  const [html, setHtml] = p2("");
10906  _2(() => {
10907    const prev = session.prevChanges?.[0];
10908    if (!entry || !html) {
10909      return;
10910    }
10911    const source = prev?.change ?? findInsertChange(session.prevChanges);
10912    if (!source) {
10913      return;
10914    }
10915    const change = structuredClone(source);
10916    const spec = change.e;
10917    if (!Array.isArray(spec)) {
10918      return;
10919    }
10920    spec[1] = {
10921      ...spec[1] ?? {},
10922      "data-pz-widget": entry.id,
10923      "data-pz-props": JSON.stringify(values)
10924    };
10925    spec[3] = html;
10926    session.prevChanges = [
10927      {
10928        n: prev?.n ?? 0,
10929        change
10930      }
10931    ];
10932    session.onPreview();
10933  });
10934  if (!entry || !host) {
10935    return null;
10936  }
10937  return /* @__PURE__ */ u2(Section, {
10938    title: entry.label,
10939    children: [
10940      /* @__PURE__ */ u2(WidgetPanel, {
10941        entry,
10942        values,
10943        onChange: (key, value) => {
10944          const next = {
10945            ...values,
10946            [key]: value
10947          };
10948          setValues(next);
10949          if (session.report) {
10950            void requestWidgetHtml(session.report, entry.id, next).then((fresh) => {
10951              if (fresh != null) {
10952                setHtml(fresh);
10953              }
10954            });
10955          }
10956        }
10957      }),
10958      html === "" && /* @__PURE__ */ u2("div", {
10959        class: "le-wl-note",
10960        children: "Nothing to show yet - this one needs content before it appears on the page."
10961      }),
10962      !session.report && /* @__PURE__ */ u2("div", {
10963        class: "le-wl-note",
10964        children: "The Personyze window is not reachable, so these settings cannot be redrawn. Reopen the editor from the panel to change them."
10965      })
10966    ]
10967  });
10968}
10969function findInsertChange(prev) {
10970  return prev?.find((p3) => Array.isArray(p3.change?.e))?.change;
10971}
10972
10973// src/modules/ailive/private/serialize.ts
10974var OPAQUE = /* @__PURE__ */ new Set([
10975  "script",
10976  "style",
10977  "noscript",
10978  "template",
10979  "svg",
10980  "iframe",
10981  "object",
10982  "embed",
10983  "canvas",
10984  "video",
10985  "audio",
10986  "select"
10987]);
10988var SHOWN_ATTRS = [
10989  [
10990    "href",
10991    60
10992  ],
10993  [
10994    "src",
10995    60
10996  ],
10997  [
10998    "alt",
10999    40
11000  ],
11001  [
11002    "title",
11003    40
11004  ],
11005  [
11006    "type",
11007    20
11008  ],
11009  [
11010    "name",
11011    30
11012  ],
11013  [
11014    "role",
11015    20
11016  ],
11017  [
11018    "aria-label",
11019    40
11020  ],
11021  [
11022    "placeholder",
11023    40
11024  ]
11025];
11026var MAX_CLASSES = 5;
11027var MAX_TEXT_CHARS = 80;
11028var MAX_CHILDREN = 30;
11029var MAX_DEPTH = 30;
11030var XML_ELEMENT_NODE = 1;
11031var XML_TEXT_NODE = 3;
11032function pageSkeleton(ignore, maxChars = 6e4) {
11033  const out = new Array();
11034  let spent = 0;
11035  let truncated = false;
11036  function emit(line) {
11037    if (spent + line.length > maxChars) {
11038      truncated = true;
11039      return false;
11040    }
11041    out.push(line);
11042    spent += line.length + 1;
11043    return true;
11044  }
11045  function walk(elem, depth) {
11046    if (ignore.indexOf(elem) != -1) {
11047      return true;
11048    }
11049    const indent = "	".repeat(depth);
11050    if (depth > MAX_DEPTH) {
11051      return emit(indent + "\u2026");
11052    }
11053    let line = indent + "<" + elem.localName;
11054    if (elem.id) {
11055      line += " #" + elem.id;
11056    }
11057    const classes = Array.from(elem.classList).slice(0, MAX_CLASSES);
11058    if (classes.length) {
11059      line += " ." + classes.join(".");
11060    }
11061    for (const [name, max] of SHOWN_ATTRS) {
11062      const value = elem.getAttribute(name);
11063      if (value) {
11064        line += ` ${name}="${cut(value, max)}"`;
11065      }
11066    }
11067    line += ">";
11068    const isOpaque = OPAQUE.has(elem.localName);
11069    if (!isOpaque) {
11070      let text = "";
11071      for (let node = elem.firstChild; node; node = node.nextSibling) {
11072        if (node.nodeType == XML_TEXT_NODE) {
11073          text += node.data;
11074        }
11075      }
11076      text = text.replace(/\s+/g, " ").trim();
11077      if (text) {
11078        line += ` "${cut(text, MAX_TEXT_CHARS)}"`;
11079      }
11080    }
11081    if (!emit(line)) {
11082      return false;
11083    }
11084    if (isOpaque) {
11085      return true;
11086    }
11087    let nShown = 0;
11088    let nSkipped = 0;
11089    for (let node = elem.firstChild; node; node = node.nextSibling) {
11090      if (node.nodeType != XML_ELEMENT_NODE) {
11091        continue;
11092      }
11093      if (nShown >= MAX_CHILDREN) {
11094        nSkipped++;
11095        continue;
11096      }
11097      if (!walk(node, depth + 1)) {
11098        return false;
11099      }
11100      nShown++;
11101    }
11102    if (nSkipped) {
11103      return emit("	".repeat(depth + 1) + `\u2026 ${nSkipped} more elements`);
11104    }
11105    return true;
11106  }
11107  walk(document.body, 0);
11108  if (truncated) {
11109    out.push("\u2026 (the skeleton was cut here - the page continues)");
11110  }
11111  return out.join("\n");
11112}
11113function elemHtml(elem, maxChars) {
11114  const html = elem.outerHTML;
11115  if (html.length <= maxChars) {
11116    return html;
11117  }
11118  return html.slice(0, maxChars) + `
11119<!-- truncated: ${html.length - maxChars} more characters; the full element is ${html.length} characters -->`;
11120}
11121function cut(text, maxChars) {
11122  return text.length <= maxChars ? text : text.slice(0, maxChars - 1) + "\u2026";
11123}
11124
11125// src/modules/pageeditor/private/changes.ts
11126var URL_NOISE = /^(utm_|_S_T$|gclid$|fbclid$|msclkid$|dclid$|ttclid$|twclid$|igshid$|mc_eid$|_ga$)/i;
11127function scopeUrlKey(href) {
11128  try {
11129    const url = new URL(href);
11130    const names = [];
11131    url.searchParams.forEach((_v, name) => {
11132      if (!URL_NOISE.test(name)) names.push(name);
11133    });
11134    names.sort();
11135    const query2 = names.map((name) => name + "=" + url.searchParams.get(name)).join("&");
11136    return url.origin + url.pathname.replace(/\/$/, "") + (query2 ? "?" + query2 : "");
11137  } catch {
11138    return href;
11139  }
11140}
11141function relatedChanges(changes, index) {
11142  const self = changes[index]?.qelem_id;
11143  if (typeof self != "string" || !self) {
11144    return [
11145      index
11146    ];
11147  }
11148  const inside = (outer, inner) => inner.length > outer.length && inner.startsWith(outer) && /^[\s>]/.test(inner.slice(outer.length));
11149  const out = [];
11150  for (let i4 = 0; i4 < changes.length; i4++) {
11151    const other = changes[i4].qelem_id;
11152    if (typeof other == "string" && other && (other == self || inside(self, other) || inside(other, self))) {
11153      out.push(i4);
11154    }
11155  }
11156  return out.length ? out : [
11157    index
11158  ];
11159}
11160var CLICK_EVENT = Object.freeze({
11161  onwhat: "click",
11162  action_id: -1,
11163  action_state: "extra"
11164});
11165function isClickEvent(event) {
11166  const e3 = event ?? {};
11167  return e3.onwhat == "click" && Number(e3.action_id) == -1 && e3.action_state == "extra";
11168}
11169function countsClicks(change) {
11170  return Array.isArray(change.events) && change.events.some(isClickEvent);
11171}
11172function eventsWithClicks(change, on) {
11173  const others = (Array.isArray(change.events) ? change.events : []).filter((e3) => !isClickEvent(e3));
11174  const next = on ? [
11175    ...others,
11176    {
11177      ...CLICK_EVENT
11178    }
11179  ] : others;
11180  return next.length ? next : void 0;
11181}
11182var PHONE_SCREEN_MEDIA = "(max-width: 767px)";
11183function deviceAllows(device, isPhone) {
11184  return device == "mobile" ? isPhone : device == "desktop" ? !isPhone : true;
11185}
11186function isPhoneScreen() {
11187  return !!globalThis.matchMedia?.(PHONE_SCREEN_MEDIA).matches;
11188}
11189function sameScopeUrl(recorded, current) {
11190  return !recorded || scopeUrlKey(recorded) == scopeUrlKey(current);
11191}
11192function varRanges(html) {
11193  const ranges = new Array();
11194  let plainFrom = 0;
11195  for (let i4 = 0, iEnd = html.length; i4 < iEnd; i4++) {
11196    if (html.charCodeAt(i4) != 36 || html.charAt(i4 + 1) != "{") {
11197      continue;
11198    }
11199    const from = i4;
11200    let depth = 0;
11201    let quote2 = "";
11202    let j3 = i4 + 1;
11203    for (; j3 < iEnd; j3++) {
11204      const c3 = html.charAt(j3);
11205      if (quote2) {
11206        if (c3 == "\\") {
11207          j3++;
11208        } else if (c3 == quote2) {
11209          quote2 = "";
11210        }
11211      } else if (c3 == '"' || c3 == "'" || c3 == "`") {
11212        quote2 = c3;
11213      } else if (c3 == "{") {
11214        depth++;
11215      } else if (c3 == "}") {
11216        if (--depth == 0) {
11217          break;
11218        }
11219      }
11220    }
11221    if (j3 >= iEnd) {
11222      break;
11223    }
11224    if (from > plainFrom) {
11225      ranges.push({
11226        from: plainFrom,
11227        to: from,
11228        canInsertVar: true
11229      });
11230    }
11231    ranges.push({
11232      from,
11233      to: j3 + 1,
11234      canInsertVar: false
11235    });
11236    plainFrom = j3 + 1;
11237    i4 = j3;
11238  }
11239  if (plainFrom < html.length || ranges.length == 0) {
11240    ranges.push({
11241      from: plainFrom,
11242      to: html.length,
11243      canInsertVar: true
11244    });
11245  }
11246  return ranges;
11247}
11248var PART_THRESHOLD = 0.1;
11249function getEditTextOrHtml(base, cur, isHtml) {
11250  function isValidHtml(html) {
11251    const div = document.createElement("div");
11252    div.innerHTML = html;
11253    return div.innerHTML == html;
11254  }
11255  function indexInCur(substr, findFrom) {
11256    let pos2 = cur.indexOf(substr, findFrom);
11257    if (pos2 != -1) {
11258      for (const { to, canInsertVar } of curTokens) {
11259        if (to > pos2) {
11260          if (canInsertVar) {
11261            break;
11262          }
11263          pos2 = cur.indexOf(substr, to);
11264        }
11265      }
11266    }
11267    return pos2;
11268  }
11269  function lastIndexInCur(substr, findFrom) {
11270    let pos2 = cur.lastIndexOf(substr, findFrom);
11271    if (pos2 != -1) {
11272      for (let i5 = curTokens.length - 1; i5 >= 0; i5--) {
11273        const { from, canInsertVar } = curTokens[i5];
11274        if (from <= pos2) {
11275          if (canInsertVar) {
11276            break;
11277          }
11278          pos2 = cur.lastIndexOf(substr, from - 1);
11279          if (pos2 == -1) {
11280            break;
11281          }
11282        }
11283      }
11284    }
11285    return pos2;
11286  }
11287  const baseLen = base.length;
11288  const curLen = cur.length;
11289  const baseTokens = varRanges(base);
11290  const curTokens = varRanges(cur);
11291  const baseFirstToken = baseTokens[0];
11292  const baseLastToken = baseTokens[baseTokens.length - 1];
11293  let basePrefixPos = -1;
11294  let basePrefixLen = -1;
11295  const maxBasePrefixLen = !baseFirstToken?.canInsertVar ? 0 : baseFirstToken.to;
11296  let baseSuffixPos = -1;
11297  let baseSuffixLen = -1;
11298  const maxBaseSuffixLen = !baseLastToken?.canInsertVar ? 0 : baseLastToken.to - baseLastToken.from;
11299  let i4 = 0;
11300  for (let inside = false; i4 < baseLen * (1 - PART_THRESHOLD) && basePrefixPos == -1; i4++) {
11301    const c3 = base.charAt(baseLen - i4 - 1);
11302    if (!inside) {
11303      if (baseLen - i4 <= maxBasePrefixLen) {
11304        for (let from = 0; true; from = basePrefixPos + 1) {
11305          const prefix = base.slice(0, baseLen - i4);
11306          basePrefixPos = indexInCur(prefix, from);
11307          if (isHtml && basePrefixPos != -1) {
11308            if (prefix.indexOf("<") == -1) {
11309              const tmp = cur.slice(0, basePrefixPos);
11310              const lt = tmp.lastIndexOf("<");
11311              const gt = tmp.lastIndexOf(">");
11312              if (lt != -1 && (gt == -1 || lt > gt)) {
11313                continue;
11314              }
11315            }
11316            if (!isValidHtml(prefix)) {
11317              basePrefixPos = -1;
11318            }
11319          }
11320          break;
11321        }
11322      }
11323      inside = isHtml && c3 == ">";
11324    } else {
11325      inside = c3 != "<";
11326    }
11327  }
11328  basePrefixLen = baseLen - i4 + 1;
11329  if (basePrefixPos == -1 || basePrefixLen != baseLen) {
11330    i4 = 0;
11331    for (let inside = false; i4 < baseLen * (1 - PART_THRESHOLD) && baseSuffixPos == -1; i4++) {
11332      const c3 = base.charAt(i4);
11333      if (!inside) {
11334        if (baseLen - i4 <= maxBaseSuffixLen) {
11335          for (let from = curLen; true; from = baseSuffixPos - 1) {
11336            const suffix = base.slice(i4);
11337            baseSuffixPos = lastIndexInCur(suffix, from);
11338            if (isHtml && baseSuffixPos != -1) {
11339              if (suffix.indexOf(">") == -1) {
11340                const tmp = cur.slice(baseSuffixPos + baseLen - i4);
11341                const gt = tmp.indexOf(">");
11342                const lt = tmp.indexOf("<");
11343                if (gt != -1 && (lt == -1 || gt < lt)) {
11344                  continue;
11345                }
11346              }
11347              if (!isValidHtml(suffix)) {
11348                baseSuffixPos = -1;
11349              }
11350            }
11351            break;
11352          }
11353        }
11354        inside = isHtml && c3 == "<";
11355      } else {
11356        inside = c3 != ">";
11357      }
11358    }
11359    baseSuffixLen = baseLen - i4 + 1;
11360  }
11361  const overlap = basePrefixPos + basePrefixLen - baseSuffixPos;
11362  if (basePrefixPos != -1 && baseSuffixPos != -1 && overlap > 0) {
11363    if (basePrefixLen >= baseSuffixLen) {
11364      if (basePrefixPos >= baseSuffixPos) {
11365        baseSuffixPos = -1;
11366      } else {
11367        baseSuffixPos += overlap;
11368        baseSuffixLen -= overlap;
11369        if (baseSuffixLen < baseLen * PART_THRESHOLD) {
11370          baseSuffixPos = -1;
11371        }
11372      }
11373    } else {
11374      if (basePrefixPos >= baseSuffixPos) {
11375        basePrefixPos = -1;
11376      } else {
11377        basePrefixLen -= overlap;
11378        if (basePrefixLen < baseLen * PART_THRESHOLD) {
11379          basePrefixPos = -1;
11380        }
11381      }
11382    }
11383  }
11384  const actions = new Array();
11385  let pos = 0;
11386  if (basePrefixPos > pos) {
11387    actions.push(cur.substr(pos, basePrefixPos));
11388  }
11389  if (basePrefixPos != -1) {
11390    if (basePrefixLen == baseLen) {
11391      actions.push([
11392        0
11393      ]);
11394    } else if (basePrefixLen <= baseLen / 2) {
11395      actions.push([
11396        0,
11397        basePrefixLen
11398      ]);
11399    } else {
11400      actions.push([
11401        0,
11402        basePrefixLen - baseLen
11403      ]);
11404    }
11405    pos = basePrefixPos + basePrefixLen;
11406  }
11407  if (baseSuffixPos > pos) {
11408    actions.push(cur.slice(pos, baseSuffixPos));
11409  }
11410  if (baseSuffixPos != -1) {
11411    if (basePrefixLen + baseSuffixLen == baseLen) {
11412      actions.push([
11413        basePrefixLen
11414      ]);
11415    } else if (baseSuffixLen <= baseLen / 2) {
11416      actions.push([
11417        -baseSuffixLen
11418      ]);
11419    } else {
11420      actions.push([
11421        baseLen - baseSuffixLen
11422      ]);
11423    }
11424    pos = baseSuffixPos + baseSuffixLen;
11425  }
11426  if (pos < cur.length) {
11427    actions.push(cur.slice(pos));
11428  }
11429  return actions;
11430}
11431function applyTextSplices(base, splices) {
11432  let text = "";
11433  for (const splice of splices) {
11434    if (typeof splice == "string") {
11435      text += splice;
11436    } else {
11437      let pos = splice[0];
11438      let len = (splice.length > 1 ? splice[1] : 0) | 0;
11439      if (pos < 0) {
11440        pos += base.length;
11441      }
11442      if (len <= 0) {
11443        len = base.length + len - pos;
11444      }
11445      text += base.substr(pos, len);
11446    }
11447  }
11448  return text;
11449}
11450
11451// src/modules/picker/private/fingerprint.ts
11452var FP_DEPTH = 6;
11453var FP_CLASSES = 2;
11454var VOLATILE = /^(is-|has-|js-|ng-|v-|sc-|css-)|(^|-)(active|open|opened|closed|hover|hovered|focus|focused|selected|current|sticky|stuck|visible|hidden|show|shown|collapsed|expanded|loading|loaded|animated|animating|in|out)$/i;
11455var GENERATED = /^[a-z]*[0-9a-f]{6,}$|^(css|sc|jsx|emotion)-|\d{4,}|-[0-9a-f]{6,}$|-\d{3,}$/i;
11456function isStableClass(name) {
11457  return name.length > 1 && name.length < 40 && !VOLATILE.test(name) && !GENERATED.test(name);
11458}
11459function stepOf(elem) {
11460  const classes = Array.from(elem.classList ?? []).filter(isStableClass).slice(0, FP_CLASSES);
11461  return classes.length ? {
11462    t: elem.localName,
11463    c: classes
11464  } : {
11465    t: elem.localName
11466  };
11467}
11468function fingerprintOf(elem, depth = FP_DEPTH) {
11469  const out = [];
11470  for (let node = elem; node && out.length < depth; node = node.parentElement) {
11471    const tag = node.localName;
11472    if (tag === "body" || tag === "html") {
11473      break;
11474    }
11475    out.push(stepOf(node));
11476  }
11477  return out;
11478}
11479function fingerprintMatches(elem, fp) {
11480  if (!fp || !fp.length) {
11481    return true;
11482  }
11483  let node = elem;
11484  for (const step of fp) {
11485    if (!node || node.localName !== step.t) {
11486      return false;
11487    }
11488    if (step.c?.length) {
11489      const has = Array.from(node.classList ?? []);
11490      if (!step.c.some((c3) => has.includes(c3))) {
11491        return false;
11492      }
11493    }
11494    node = node.parentElement;
11495  }
11496  return true;
11497}
11498
11499// src/modules/picker/private/selector.ts
11500var RE_VOLATILE_TAIL = /[_\-.][0-9A-F]{4,}$/i;
11501var RE_CSS_IN_JS = /^(?:css|sc|jsx|emotion|svelte|ng)-[0-9a-z]{4,}$/i;
11502var RE_OWN_CLASS = /^(?:\$|_st|personyze|qs-)/i;
11503function isStableName(name) {
11504  return !!name && !RE_VOLATILE_TAIL.test(name) && !RE_CSS_IN_JS.test(name) && !RE_OWN_CLASS.test(name);
11505}
11506function cssEscape(name) {
11507  return typeof CSS != "undefined" && CSS.escape ? CSS.escape(name) : name.replace(/[^\w-]/g, (ch) => "\\" + ch);
11508}
11509function matchesOnly(selector, elem) {
11510  if (!selector) {
11511    return false;
11512  }
11513  try {
11514    const found = elem.ownerDocument.querySelectorAll(selector);
11515    return found.length == 1 && found[0] == elem;
11516  } catch {
11517    return false;
11518  }
11519}
11520function indexOfType(elem) {
11521  let index = 1;
11522  for (const sib of elem.parentElement?.children ?? []) {
11523    if (sib == elem) {
11524      break;
11525    }
11526    if (sib.localName == elem.localName) {
11527      index++;
11528    }
11529  }
11530  return index;
11531}
11532function groupStep(localName, classNames) {
11533  let step = localName;
11534  for (const name of classNames) {
11535    if (isStableName(name)) {
11536      step += "." + cssEscape(name);
11537    }
11538  }
11539  return step;
11540}
11541function elemToStep(elem) {
11542  const step = groupStep(elem.localName, elem.classList);
11543  let nMatching = 0;
11544  for (const sib of elem.parentElement?.children ?? []) {
11545    try {
11546      if (sib.matches(step)) {
11547        nMatching++;
11548      }
11549    } catch {
11550      nMatching = 2;
11551      break;
11552    }
11553  }
11554  return nMatching > 1 ? `${step}:nth-of-type(${indexOfType(elem)})` : step;
11555}
11556function structuralPath(elem) {
11557  const doc = elem.ownerDocument;
11558  const steps = new Array();
11559  for (let node = elem; node && node != doc.documentElement; node = node.parentElement) {
11560    if (node == doc.body) {
11561      steps.unshift("body");
11562      break;
11563    }
11564    steps.unshift(`${node.localName}:nth-of-type(${indexOfType(node)})`);
11565  }
11566  return steps.join(" > ");
11567}
11568function elemToSelector(elem) {
11569  const doc = elem.ownerDocument;
11570  if (!doc || !doc.body || !doc.body.contains(elem)) {
11571    return "";
11572  }
11573  const steps = new Array();
11574  for (let node = elem; node && node != doc.body && node != doc.documentElement; node = node.parentElement) {
11575    const id = node.getAttribute("id") ?? "";
11576    if (isStableName(id) && doc.querySelectorAll("#" + cssEscape(id)).length == 1) {
11577      steps.unshift("#" + cssEscape(id));
11578      break;
11579    }
11580    steps.unshift(elemToStep(node));
11581    const selector2 = steps.join(" > ");
11582    if (matchesOnly(selector2, elem)) {
11583      return selector2;
11584    }
11585  }
11586  const selector = steps.join(" > ");
11587  return matchesOnly(selector, elem) ? selector : structuralPath(elem);
11588}
11589var RE_NTH_OF_TYPE = /:nth-of-type\(\d+\)/g;
11590function elemToGroupSelector(elem) {
11591  const unique = elemToSelector(elem);
11592  if (!unique) {
11593    return "";
11594  }
11595  const loose = unique.replace(RE_NTH_OF_TYPE, "");
11596  if (findMatches(loose, elem.ownerDocument).length > 1) {
11597    return loose;
11598  }
11599  const own = groupStep(elem.localName, elem.classList);
11600  if (own.indexOf(".") != -1 && findMatches(own, elem.ownerDocument).length > 1) {
11601    return own;
11602  }
11603  return loose;
11604}
11605function findMatches(selector, doc = document) {
11606  if (!selector) {
11607    return [];
11608  }
11609  try {
11610    return Array.from(doc.querySelectorAll(selector));
11611  } catch {
11612    return [];
11613  }
11614}
11615function describeElem(elem) {
11616  let text = elem.localName;
11617  if (elem.id) {
11618    text += "#" + elem.id;
11619  }
11620  for (const name of elem.classList) {
11621    text += "." + name;
11622  }
11623  return text;
11624}
11625function ancestorChain(elem) {
11626  const doc = elem.ownerDocument;
11627  const chain = new Array();
11628  for (let node = elem; node && node != doc.body && node != doc.documentElement; node = node.parentElement) {
11629    chain.unshift(node);
11630  }
11631  return chain;
11632}
11633
11634// src/modules/picker/private/picker.ts
11635var Z_INDEX = 2147483646;
11636var Picker = class {
11637  #layer;
11638  #highlights = new Array();
11639  #select;
11640  #hovered;
11641  #isTracking = false;
11642  #timer = 0;
11643  /**	Outline these elements until the next call. `label` writes the element's name into the
11644  corner of the box, which is what makes a hover read as "this is what you'd get".
11645  **/
11646  highlight(elems, color = "#12B886", label = false) {
11647    this.#highlights = [];
11648    for (const elem of elems) {
11649      if (elem && elem.isConnected) {
11650        this.#highlights.push({
11651          elem,
11652          color,
11653          label
11654        });
11655      }
11656    }
11657    this.#draw();
11658    this.#trackViewport(this.#highlights.length > 0);
11659  }
11660  /**	Enter picking mode: highlight what's under the cursor, and swallow the next click.
11661  **/
11662  selectBegin(options) {
11663    this.selectEnd();
11664    this.#select = options;
11665    this.#hovered = void 0;
11666    for (const type of [
11667      "mousemove",
11668      "mouseover",
11669      "click",
11670      "mousedown",
11671      "mouseup",
11672      "keydown"
11673    ]) {
11674      document.addEventListener(type, this.#onEvent, true);
11675    }
11676  }
11677  selectEnd() {
11678    if (this.#select) {
11679      this.#select = void 0;
11680      this.#hovered = void 0;
11681      for (const type of [
11682        "mousemove",
11683        "mouseover",
11684        "click",
11685        "mousedown",
11686        "mouseup",
11687        "keydown"
11688      ]) {
11689        document.removeEventListener(type, this.#onEvent, true);
11690      }
11691      this.highlight([]);
11692    }
11693  }
11694  isSelecting() {
11695    return !!this.#select;
11696  }
11697  destroy() {
11698    this.selectEnd();
11699    this.highlight([]);
11700    this.#layer?.remove();
11701    this.#layer = void 0;
11702  }
11703  /**	Single capture-phase handler for the whole picking session. Capture phase and
11704  `stopPropagation()` are what keep the page from reacting: the click never reaches the
11705  page's own listeners, and `preventDefault()` keeps links and submit buttons inert.
11706  **/
11707  #onEvent = (event) => {
11708    const select = this.#select;
11709    if (!select) {
11710      return;
11711    }
11712    if (event.type == "keydown") {
11713      if (event.key == "Escape") {
11714        event.preventDefault();
11715        event.stopPropagation();
11716        select.onSelect(null);
11717      }
11718      return;
11719    }
11720    const target = this.#targetOf(event, select.ignore);
11721    if (!target) {
11722      if (this.#hovered) {
11723        this.#hovered = void 0;
11724        this.highlight([]);
11725        select.onHover?.(null);
11726      }
11727      return;
11728    }
11729    event.preventDefault();
11730    event.stopPropagation();
11731    if (event.type == "click") {
11732      select.onSelect(target);
11733    } else if (event.type == "mousemove" || event.type == "mouseover") {
11734      if (this.#hovered != target) {
11735        this.#hovered = target;
11736        this.highlight([
11737          target
11738        ], "#12B886", true);
11739        select.onHover?.(target);
11740      }
11741    }
11742  };
11743  /**	The element the event points at, or null when it's the tool's own UI. `composedPath()` is
11744  what sees through the panel's shadow root, whose events retarget to the host element.
11745  **/
11746  #targetOf(event, ignore) {
11747    const path = typeof event.composedPath == "function" ? event.composedPath() : [];
11748    const target = path[0] ?? event.target;
11749    if (!(target instanceof Element)) {
11750      return null;
11751    }
11752    for (const root of ignore) {
11753      if (root == target || root.contains(target) || path.indexOf(root) != -1) {
11754        return null;
11755      }
11756    }
11757    return target;
11758  }
11759  #draw = () => {
11760    const layer = this.#ensureLayer();
11761    while (layer.childNodes.length > this.#highlights.length) {
11762      layer.lastChild.remove();
11763    }
11764    while (layer.childNodes.length < this.#highlights.length) {
11765      const box = document.createElement("div");
11766      box.appendChild(document.createElement("span"));
11767      layer.appendChild(box);
11768    }
11769    for (let i4 = 0; i4 < this.#highlights.length; i4++) {
11770      const { elem, color, label } = this.#highlights[i4];
11771      const box = layer.childNodes[i4];
11772      const rect = elem.getBoundingClientRect();
11773      box.style.cssText = `position:absolute; box-sizing:border-box; pointer-events:none; border:2px solid ${color}; background-color:${color}22; border-radius:2px; left:${rect.left + globalThis.scrollX}px; top:${rect.top + globalThis.scrollY}px; width:${Math.max(rect.width, 4)}px; height:${Math.max(rect.height, 4)}px`;
11774      const tag = box.firstChild;
11775      tag.textContent = !label ? "" : describeElem(elem).slice(0, 80);
11776      tag.style.cssText = !label ? "display:none" : `position:absolute; left:0; top:-1.5em; max-width:100%; overflow:hidden; white-space:nowrap; box-sizing:border-box; padding:0 0.4em; border-radius:2px; background-color:${color}; color:#08231C; font:600 11px/1.5em ui-monospace, Menlo, Consolas, monospace`;
11777    }
11778  };
11779  #ensureLayer() {
11780    let layer = this.#layer;
11781    if (!layer?.isConnected) {
11782      layer = document.createElement("div");
11783      layer.style.cssText = `position:absolute; left:0; top:0; width:0; height:0; margin:0; padding:0; border:none; pointer-events:none; z-index:${Z_INDEX}`;
11784      document.body.appendChild(layer);
11785      this.#layer = layer;
11786    }
11787    return layer;
11788  }
11789  /**	Highlight boxes are placed in page coordinates, so they only need redrawing when the page
11790  moves under them - but an element can also be resized by the page itself, hence the poll.
11791  **/
11792  #trackViewport(on) {
11793    if (on != this.#isTracking) {
11794      this.#isTracking = on;
11795      if (on) {
11796        globalThis.addEventListener("scroll", this.#draw, true);
11797        globalThis.addEventListener("resize", this.#draw);
11798        this.#timer = setInterval(this.#draw, 500);
11799      } else {
11800        globalThis.removeEventListener("scroll", this.#draw, true);
11801        globalThis.removeEventListener("resize", this.#draw);
11802        clearInterval(this.#timer);
11803      }
11804    }
11805  }
11806};
11807
11808// src/modules/util/mod.ts
11809var RE_CSS_RULE = /([^;:\s]+)\s*:\s*([^;]*[^;\s])/g;
11810function parseStyleAttr(style) {
11811  const result = {};
11812  if (style) {
11813    RE_CSS_RULE.lastIndex = 0;
11814    let m2;
11815    while (m2 = RE_CSS_RULE.exec(style)) {
11816      result[m2[1].toLowerCase()] = m2[2];
11817    }
11818  }
11819  return result;
11820}
11821function styleRecordToCss(record) {
11822  let result = "";
11823  for (const name in record) {
11824    const value = record[name];
11825    if (value != void 0 && value !== "") {
11826      result += (result ? "; " : "") + name + ":" + value;
11827    }
11828  }
11829  return result;
11830}
11831
11832// src/modules/tracker/private/tracker.ts
11833function getPageUrl() {
11834  try {
11835    return getTracker()?.get_location?.()?.href || location.href;
11836  } catch {
11837    return location.href;
11838  }
11839}
11840function getTracker() {
11841  return globalThis._S_T;
11842}
11843function getPanelWindow() {
11844  const scope = globalThis;
11845  const thisWindow = globalThis;
11846  return scope._S_T_OPENER ?? globalThis.opener ?? (parent != thisWindow ? parent : null);
11847}
11848
11849// src/modules/pageeditor/private/pageEditor.ts
11850var XML_ELEMENT_NODE2 = 1;
11851var XML_TEXT_NODE2 = 3;
11852var XML_CDATA_SECTION_NODE = 4;
11853var elemPrevChanges = /* @__PURE__ */ new WeakMap();
11854var insertedBy = /* @__PURE__ */ new WeakMap();
11855function cloneElem(elem) {
11856  const copy = elem.cloneNode(true);
11857  const changes = elemPrevChanges.get(elem);
11858  if (changes) {
11859    elemPrevChanges.set(copy, changes.map((v3) => ({
11860      n: v3.n,
11861      change: v3.change
11862    })));
11863  }
11864  const insert = insertedBy.get(elem);
11865  if (insert) {
11866    insertedBy.set(copy, insert);
11867  }
11868  return copy;
11869}
11870function insertOf(elem) {
11871  return insertedBy.get(elem);
11872}
11873function getElemPrevChanges(elem) {
11874  return elemPrevChanges.get(elem) ?? [];
11875}
11876function resolveQelem(qelemId, findAll) {
11877  if (qelemId != void 0) {
11878    const tracker = getTracker();
11879    if (tracker?.qelem) {
11880      try {
11881        const found = tracker.qelem(qelemId, findAll);
11882        if (findAll) {
11883          return Array.isArray(found) ? found.filter((v3) => v3 instanceof Element) : [];
11884        }
11885        return found instanceof Element ? found : null;
11886      } catch (e3) {
11887        console.error(e3);
11888      }
11889    }
11890    if (typeof qelemId == "string") {
11891      try {
11892        if (findAll) {
11893          return Array.from(document.querySelectorAll(qelemId));
11894        }
11895        return document.querySelector(qelemId);
11896      } catch {
11897      }
11898    }
11899  }
11900  return findAll ? [] : null;
11901}
11902function qelemIdToText(qelemId) {
11903  if (qelemId == void 0) {
11904    return "";
11905  }
11906  if (typeof qelemId == "string") {
11907    return qelemId;
11908  }
11909  try {
11910    return getTracker()?.qelem_id_to_css?.(qelemId) ?? JSON.stringify(qelemId);
11911  } catch {
11912    return JSON.stringify(qelemId);
11913  }
11914}
11915function isTextNode(node) {
11916  return !!node && (node.nodeType == XML_TEXT_NODE2 || node.nodeType == XML_CDATA_SECTION_NODE);
11917}
11918function getAttributes(elem) {
11919  const attrs = {};
11920  for (const attr of elem.attributes) {
11921    attrs[attr.name] = attr.value;
11922  }
11923  return attrs;
11924}
11925function setAttributes(elem, attrs) {
11926  if (attrs) {
11927    for (const name in attrs) {
11928      const value = attrs[name];
11929      try {
11930        if (value == null) {
11931          elem.removeAttribute(name);
11932        } else {
11933          elem.setAttribute(name, value);
11934        }
11935      } catch (e3) {
11936        console.error(e3);
11937      }
11938    }
11939  }
11940}
11941function newElemChanges(spec) {
11942  if (typeof spec == "string") {
11943    return document.createTextNode(spec);
11944  }
11945  const [nodeName, nodeAttrs, nodeChildNodes, nodeInnerHtml] = spec;
11946  let elem = document.createElement(nodeName);
11947  if ((nodeName == "video" || nodeName == "audio") && typeof elem.play != "function") {
11948    elem = document.createElement("embed");
11949  }
11950  setAttributes(elem, nodeAttrs);
11951  if (nodeInnerHtml != null) {
11952    elem.innerHTML = nodeInnerHtml;
11953  } else if (nodeChildNodes) {
11954    for (const child of nodeChildNodes) {
11955      elem.appendChild(newElemChanges(child));
11956    }
11957  }
11958  return elem;
11959}
11960function patchChildren(elem, newElem) {
11961  let child = elem.firstChild;
11962  let newChild = newElem.firstChild;
11963  while (child || newChild) {
11964    let next = child?.nextSibling ?? null;
11965    let newNext = newChild?.nextSibling ?? null;
11966    if (!child) {
11967      elem.appendChild(newChild);
11968    } else if (!newChild) {
11969      elem.removeChild(child);
11970    } else if (child.nodeType != XML_ELEMENT_NODE2) {
11971      if (child.nodeType == XML_TEXT_NODE2 && newChild.nodeType == XML_TEXT_NODE2) {
11972        child.data = newChild.data;
11973      } else {
11974        elem.removeChild(child);
11975        newNext = newChild;
11976      }
11977    } else if (newChild.nodeType != XML_ELEMENT_NODE2) {
11978      elem.insertBefore(newChild, child);
11979      next = child;
11980    } else if (child.nodeName != newChild.nodeName) {
11981      let hasElemToPatch = false;
11982      for (let n2 = newNext; n2 && !hasElemToPatch; n2 = n2.nextElementSibling) {
11983        hasElemToPatch = child.nodeName == n2.nodeName;
11984      }
11985      if (hasElemToPatch) {
11986        elem.insertBefore(newChild, child);
11987        next = child;
11988      } else {
11989        elem.removeChild(child);
11990        newNext = newChild;
11991      }
11992    } else {
11993      const names = new Array();
11994      for (const attr of newChild.attributes) {
11995        try {
11996          child.setAttribute(attr.name, attr.value);
11997          names.push(attr.name);
11998        } catch (e3) {
11999          console.error(e3);
12000        }
12001      }
12002      const childAttrs = child.attributes;
12003      for (let i4 = 0; i4 < childAttrs.length; i4++) {
12004        const attr = childAttrs[i4];
12005        if (names.indexOf(attr.name) == -1) {
12006          child.removeAttribute(attr.name);
12007          i4--;
12008        }
12009      }
12010      patchChildren(child, newChild);
12011    }
12012    child = next;
12013    newChild = newNext;
12014  }
12015}
12016function editTextOrHtml(node, isHtml, splices) {
12017  const base = isHtml ? node.innerHTML : node.data;
12018  const text = applyTextSplices(base, splices);
12019  if (!isHtml) {
12020    node.data = text;
12021  } else {
12022    const replacement = document.createElement(node.nodeName);
12023    replacement.innerHTML = text;
12024    patchChildren(node, replacement);
12025  }
12026}
12027function pickNode(refNode, where) {
12028  return where == "before" ? refNode.previousSibling : where == "after" ? refNode.nextSibling : refNode.lastChild;
12029}
12030function putElem(elem, newNode, where, removeNodes) {
12031  let elemParentNode = null;
12032  let elemNextSibling = null;
12033  if (newNode.contains(elem)) {
12034    elemParentNode = newNode.parentNode;
12035    elemNextSibling = newNode.nextSibling;
12036  }
12037  const beforeNode = where == "before" ? elem : where == "after" ? elem.nextSibling : null;
12038  const insideNode = where == "inside" ? elem : elem.parentNode;
12039  insideNode?.insertBefore(newNode, beforeNode);
12040  elemParentNode?.insertBefore(elem, elemNextSibling);
12041  removeNodes.push({
12042    node: newNode,
12043    beforeNode,
12044    insideNode
12045  });
12046}
12047function applyHtmlChangeOnElem(elem, change, removeNodes, addNodes) {
12048  let changed = false;
12049  if (change.inner_html) {
12050    editTextOrHtml(elem, true, change.inner_html);
12051    changed = true;
12052  }
12053  if (change.text) {
12054    const { where } = change;
12055    let node = pickNode(elem, where);
12056    if (isTextNode(node)) {
12057      let refNode = node;
12058      while (true) {
12059        const furtherNode = pickNode(refNode, where == "inside" ? "before" : where);
12060        if (!isTextNode(furtherNode)) {
12061          break;
12062        }
12063        if (furtherNode.length > 0) {
12064          if (where == "after") {
12065            node.data = node.data + furtherNode.data;
12066            addNodes.push({
12067              node: furtherNode,
12068              afterNode: refNode
12069            });
12070          } else {
12071            node.data = furtherNode.data + node.data;
12072            addNodes.push({
12073              node: furtherNode,
12074              beforeNode: refNode
12075            });
12076          }
12077          furtherNode.parentNode?.removeChild(furtherNode);
12078        } else {
12079          refNode = furtherNode;
12080        }
12081      }
12082    } else {
12083      node = document.createTextNode("");
12084      putElem(elem, node, where, removeNodes);
12085    }
12086    editTextOrHtml(node, false, change.text);
12087  }
12088  if (elem.nodeType == XML_ELEMENT_NODE2) {
12089    setAttributes(elem, change.attrs);
12090  }
12091  if (change.style) {
12092    changed = true;
12093  }
12094  if (change.attrs) {
12095    if (change.attrs["class"] != null) {
12096      elem.className = change.attrs["class"];
12097    }
12098    changed = true;
12099  }
12100  if (change.dup) {
12101    for (let k3 = change.dup - 1; k3 >= 0; k3--) {
12102      const copy = cloneElem(elem);
12103      elem.parentNode?.insertBefore(copy, elem);
12104      removeNodes.push({
12105        node: copy,
12106        beforeNode: elem
12107      });
12108      changed = true;
12109    }
12110  }
12111  if (change.mv) {
12112    const target = resolveQelem(change.mv_qelem_id);
12113    if (target) {
12114      const stub = document.createTextNode("");
12115      elem.parentNode?.insertBefore(stub, elem);
12116      putElem(target, cloneElem(elem), change.mv, removeNodes);
12117      elem.parentNode?.removeChild(elem);
12118      elem = stub;
12119      changed = true;
12120    }
12121  }
12122  if (change.rm_shallow && elem.firstChild) {
12123    while (elem.firstChild) {
12124      removeNodes.push({
12125        node: elem.firstChild,
12126        beforeNode: elem
12127      });
12128      elem.parentNode?.insertBefore(elem.firstChild, elem);
12129    }
12130    elem.parentNode?.removeChild(elem);
12131    elem = removeNodes.pop().node;
12132    changed = true;
12133  }
12134  if (change.rm || change.rm_shallow && !elem.firstChild) {
12135    const stub = document.createTextNode("");
12136    elem.parentNode?.insertBefore(stub, elem);
12137    elem.parentNode?.removeChild(elem);
12138    elem = stub;
12139    changed = true;
12140  }
12141  if (change.e_shallow || change.e) {
12142    const added = newElemChanges(change.e_shallow ?? change.e);
12143    if (change.e) {
12144      insertedBy.set(added, change);
12145      putElem(elem, added, change.where, removeNodes);
12146    } else {
12147      let nNext = Number(change.n_next) || 1;
12148      elem.parentNode?.insertBefore(added, elem);
12149      added.appendChild(elem);
12150      while (--nNext > 0 && added.nextSibling) {
12151        const sibling = added.nextSibling;
12152        const copy = cloneElem(sibling);
12153        sibling.parentNode?.removeChild(sibling);
12154        addNodes.push({
12155          node: sibling,
12156          afterNode: elem
12157        });
12158        added.appendChild(copy);
12159        removeNodes.push({
12160          node: added.firstChild,
12161          insideNode: added
12162        });
12163      }
12164      elem = added;
12165    }
12166    changed = true;
12167  }
12168  if (change.events) {
12169    changed = true;
12170  }
12171  if (change.delay) {
12172    changed = true;
12173  }
12174  return changed ? elem : null;
12175}
12176var ElemSnapshot = class {
12177  change = null;
12178  #elem;
12179  #qelemId;
12180  /**	Taken here, and not when the change is saved, because by then the element may be a wrapper
12181  that did not exist yet, or a copy that replaced the one the user pointed at. **/
12182  #fp;
12183  #elems = [];
12184  #backupElems = [];
12185  #removeNodes = [];
12186  #addNodes = [];
12187  #nSnap;
12188  constructor(elem, qelemId, nSnap) {
12189    this.#elem = elem;
12190    this.#nSnap = nSnap;
12191    this.#fp = elem.nodeType == XML_ELEMENT_NODE2 ? fingerprintOf(elem) : [];
12192    this.#qelemId = qelemId ?? (elem.nodeType == XML_ELEMENT_NODE2 ? elemToSelector(elem) : "");
12193    for (const found of resolveQelem(this.#qelemId, true)) {
12194      this.#elems.push(found);
12195      this.#backupElems.push(cloneElem(found));
12196    }
12197    if (this.#elems.length == 0) {
12198      this.#elems.push(elem);
12199      this.#backupElems.push(cloneElem(elem));
12200    }
12201    this.undoOrRedo();
12202  }
12203  getSubjElem() {
12204    return this.#elem;
12205  }
12206  getQelemId() {
12207    return this.#qelemId;
12208  }
12209  /**	Swap the working copies with the pristine ones, and the inserted nodes with the removed one
12209s.
12210  Being its own inverse is what makes this both undo and redo.
12211  **/
12212  undoOrRedo(withScroll) {
12213    for (let i4 = 0; i4 < this.#elems.length; i4++) {
12214      const curElem = this.#elems[i4];
12215      const { parentElement } = curElem;
12216      if (parentElement) {
12217        parentElement.insertBefore(this.#backupElems[i4], curElem);
12218        parentElement.removeChild(curElem);
12219        if (curElem == this.#elem) {
12220          this.#elem = this.#backupElems[i4];
12221        }
12222      }
12223    }
12224    const tmpElems = this.#elems;
12225    this.#elems = this.#backupElems;
12226    this.#backupElems = tmpElems;
12227    for (const { node } of this.#removeNodes) {
12228      node.parentNode?.removeChild(node);
12229    }
12230    for (const ref of this.#addNodes) {
12231      if (ref.beforeNode) {
12232        ref.beforeNode.parentNode?.insertBefore(ref.node, ref.beforeNode);
12233      } else if (ref.afterNode) {
12234        ref.afterNode.parentNode?.insertBefore(ref.node, ref.afterNode.nextSibling);
12235      } else {
12236        ref.insideNode?.appendChild(ref.node);
12237      }
12238    }
12239    const tmpNodes = this.#removeNodes;
12240    this.#removeNodes = this.#addNodes;
12241    this.#addNodes = tmpNodes;
12242    if (withScroll && this.#elem?.nodeType == XML_ELEMENT_NODE2) {
12243      try {
12244        this.#elem.scrollIntoView({
12245          block: "center"
12246        });
12247      } catch (e3) {
12248        console.error(e3);
12249      }
12250    }
12251    return this.#elem;
12252  }
12253  /**	What the element's ancestry looked like when this snapshot opened. **/
12254  getFingerprint() {
12255    return this.#fp;
12256  }
12257  /**	Measure what the user did to the working copy and record it. Returns the subject element, or
12258  undefined when nothing changed.
12259  **/
12260  saveDiff() {
12261    let backupElem;
12262    for (let i4 = 0; i4 < this.#elems.length; i4++) {
12263      if (this.#elems[i4] === this.#elem) {
12264        backupElem = this.#backupElems[i4];
12265        break;
12266      }
12267    }
12268    if (!backupElem || this.#elem.nodeType != XML_ELEMENT_NODE2) {
12269      return;
12270    }
12271    const elem = this.#elem;
12272    const change = {};
12273    analizeAttrAndStyleChanges(backupElem, elem, change);
12274    if (elem.innerHTML != backupElem.innerHTML) {
12275      change.inner_html = getEditTextOrHtml(backupElem.innerHTML, elem.innerHTML, true);
12276    }
12277    if (change.style || change.attrs || change.inner_html) {
12278      change.qelem_id = this.#qelemId;
12279      return this.#applyHtmlChange(change, true) ?? void 0;
12280    }
12281  }
12282  /**	Record a change the caller describes outright (delete, duplicate, insert, an event...).
12283  **/
12284  customChange(change) {
12285    if (!change) {
12286      return this.#elem;
12287    }
12288    change.qelem_id = this.#qelemId;
12289    return this.#applyHtmlChange(change);
12290  }
12291  /**	Run `change` over every element the selector matched. `isEdited` means the subject element
12292  already carries the new innerHTML (the user typed it), so only its siblings need that part.
12293  **/
12294  #applyHtmlChange(change, isEdited) {
12295    for (let i4 = 0; i4 < this.#elems.length; i4++) {
12296      const elem = this.#elems[i4];
12297      const alreadyChanged = !!change.inner_html && isEdited && elem == this.#elem;
12298      let changeToApply = change;
12299      if (alreadyChanged) {
12300        changeToApply = {
12301          ...change
12302        };
12303        delete changeToApply.inner_html;
12304      }
12305      let after = applyHtmlChangeOnElem(elem, changeToApply, this.#removeNodes, this.#addNodes);
12306      if (alreadyChanged) {
12307        after = elem;
12308      }
12309      if (after && after != elem) {
12310        this.#elems[i4] = after;
12311        if (elem === this.#elem) {
12312          this.#elem = after;
12313        }
12314      }
12315      if (!after) {
12316        return null;
12317      }
12318      const prev = elemPrevChanges.get(elem);
12319      if (prev) {
12320        prev.push({
12321          n: this.#nSnap,
12322          change
12323        });
12324      } else {
12325        elemPrevChanges.set(elem, [
12326          {
12327            n: this.#nSnap,
12328            change
12329          }
12330        ]);
12331      }
12332    }
12333    this.change = change;
12334    return this.#elem;
12335  }
12336};
12337function analizeAttrAndStyleChanges(backupElem, elem, change) {
12338  const attrs = {};
12339  const elemAttributes = getAttributes(elem);
12340  const backupAttributes = getAttributes(backupElem);
12341  for (const name in elemAttributes) {
12342    if (name != "style" && name != "contenteditable" && backupAttributes[name] != elemAttributes[name]) {
12343      attrs[name] = elemAttributes[name];
12344      change.attrs = attrs;
12345    }
12346  }
12347  for (const name in backupAttributes) {
12348    if (name != "style" && name != "contenteditable" && !(name in attrs) && elemAttributes[name] != backupAttributes[name]) {
12349      attrs[name] = null;
12350      change.attrs = attrs;
12351    }
12352  }
12353  const elemStyle = elem.style;
12354  const backupStyle = backupElem.style;
12355  const style = {};
12356  const styleRuleNames = new Array();
12357  for (const name in elemStyle) {
12358    if (name != "cssText" && name != "length" && isNaN(parseInt(name)) && typeof elemStyle[name] == "string") {
12359      if (elemStyle[name] != backupStyle[name]) {
12360        styleRuleNames.push(name);
12361        style[name] = elemStyle[name];
12362      }
12363    }
12364  }
12365  styleRuleNames.sort();
12366  for (let i4 = 0, iEnd = styleRuleNames.length - 1; i4 < iEnd; i4++) {
12367    if (styleRuleNames[i4 + 1].indexOf(styleRuleNames[i4]) == 0) {
12368      delete style[styleRuleNames[i4]];
12369      styleRuleNames.splice(i4--, 1);
12370      iEnd--;
12371    }
12372  }
12373  let styleAttr = "";
12374  for (const name in style) {
12375    if (style[name] !== "") {
12376      styleAttr += "; " + name.replace(/[A-Z]/g, (m2) => "-" + m2.toLowerCase()) + ":" + style[name];
12377    }
12378  }
12379  if (styleAttr != (backupAttributes.style || "")) {
12380    const rules = parseStyleAttr((elemAttributes.style || "") + styleAttr);
12381    attrs.style = styleRecordToCss(rules) || null;
12382    change.attrs = attrs;
12383  }
12384}
12385function pageUrl() {
12386  return getPageUrl();
12387}
12388function changeAppliesHere(change) {
12389  if (change.scope == "page") {
12390    return sameScopeUrl(change.scope_url, pageUrl());
12391  }
12392  if (change.scope == "shape" && change.scope_fp?.length) {
12393    const elems = resolveQelem(change.qelem_id, true);
12394    return elems.length == 0 || elems.some((elem) => elem.nodeType == XML_ELEMENT_NODE2 && fingerprintMatches(elem, change.scope_fp));
12395  }
12396  return true;
12397}
12398var PageEditor = class {
12399  #snaps = [];
12400  #curSnap = null;
12401  #n = 0;
12402  #options;
12403  #pendingTimer = 0;
12404  #bypass = false;
12405  /**	The change objects this editor is REPLAYING rather than authoring - see [[#stampScope]].
12406  
12407  		Membership, not a flag that is on for a while: replaying is asynchronous (it waits, a second
12408  		at a time, for an element that has not rendered yet) and the user can edit something else in
12409  		the meantime. A flag would be on during that edit and silently leave it unscoped.
12410  	 **/
12411  #replayed = /* @__PURE__ */ new WeakSet();
12412  constructor(options = {}) {
12413    this.#options = options;
12414  }
12415  /**	Begin editing `elem`. Returns the working copy the caller must use from now on - the element
12416  it passed in is no longer the one in the page.
12417  **/
12418  moveCursor(elem, qelemId) {
12419    this.discardAtCursor();
12420    this.#curSnap = new ElemSnapshot(elem, qelemId, this.#n);
12421    return this.#curSnap.getSubjElem();
12422  }
12423  /**	The `qelem_id` the open snapshot is recording against. **/
12424  cursorQelemId() {
12425    return this.#curSnap?.getQelemId();
12426  }
12427  hasCursor() {
12428    return !!this.#curSnap;
12429  }
12430  changeDup(dupTimes) {
12431    return this.#requireCursor().customChange({
12432      dup: dupTimes
12433    });
12434  }
12435  changeMv(where, toElem, toQelemId) {
12436    const snap = this.#requireCursor();
12437    try {
12438      toQelemId = toQelemId || (toElem ? elemToSelector(toElem) : "");
12439      return snap.customChange({
12440        mv: where,
12441        mv_qelem_id: toQelemId
12442      });
12443    } catch (e3) {
12444      console.error(e3);
12445      return null;
12446    }
12447  }
12448  changeRm() {
12449    return this.#requireCursor().customChange({
12450      rm: true
12451    });
12452  }
12453  changeCustom(change) {
12454    return this.#requireCursor().customChange(change);
12455  }
12456  changesCustom(changes) {
12457    let result = null;
12458    for (const change of changes) {
12459      result = this.changeCustom(change) || result;
12460    }
12461    return result;
12462  }
12463  /**	Close the open snapshot, keeping whatever it turned out to be. A snapshot that amounts to
12464  nothing is rolled back instead of becoming an empty entry.
12465  **/
12466  saveAtCursor() {
12467    let elem;
12468    const snap = this.#curSnap;
12469    if (snap) {
12470      if (!snap.change) {
12471        elem = snap.saveDiff();
12472      }
12473      if (snap.change) {
12474        this.#stampScope(snap);
12475        this.#snaps[this.#n++] = snap;
12476        this.#snaps.length = this.#n;
12477        this.#curSnap = null;
12478        this.#options.onChange?.();
12479      } else {
12480        elem = snap.undoOrRedo();
12481      }
12482      this.#curSnap = null;
12483    }
12484    return elem;
12485  }
12486  discardAtCursor() {
12487    if (this.#curSnap) {
12488      this.#curSnap.undoOrRedo();
12489      this.#curSnap = null;
12490    }
12491  }
12492  undo(withScroll) {
12493    this.discardAtCursor();
12494    if (this.#n > 0) {
12495      this.#snaps[this.#n - 1].undoOrRedo(withScroll);
12496      this.#n--;
12497      this.#options.onChange?.();
12498    }
12499  }
12500  redo(withScroll) {
12501    this.discardAtCursor();
12502    if (this.#n < this.#snaps.length) {
12503      this.#snaps[this.#n].undoOrRedo(withScroll);
12504      this.#n++;
12505      this.#options.onChange?.();
12506    }
12507  }
12508  canUndo() {
12509    return this.#n > 0;
12510  }
12511  canRedo() {
12512    return this.#n < this.#snaps.length;
12513  }
12514  getNChanges(includeUndone) {
12515    return includeUndone ? this.#snaps.length : this.#n;
12516  }
12517  getChanges(includeUndone) {
12518    const changes = new Array();
12519    const till = includeUndone ? this.#snaps.length : this.#n;
12520    for (let i4 = 0; i4 < till; i4++) {
12521      changes.push(this.#snaps[i4].change);
12522    }
12523    return changes;
12524  }
12525  /**	Replay a change list onto this page - what happens when the panel hands the action's stored
12526  		edits to a freshly loaded page.
12527  
12528  		Asynchronous because an element may simply not be there yet: the legacy retried once a second
12529  		and offered a "Bypass" button, and so does this ([[bypassPending]]).
12530  	 **/
12531  addChanges(changes) {
12532    clearTimeout(this.#pendingTimer);
12533    if (!changes || changes.length == 0) {
12534      this.#options.onPending?.(null);
12535      return Promise.resolve();
12536    }
12537    const remaining = changes.map((v3) => structuredClone(v3));
12538    return new Promise((resolve) => {
12539      const addRemaining = () => {
12540        while (remaining.length) {
12541          const change = remaining[0];
12542          const elem = resolveQelem(change.qelem_id);
12543          if (!elem) {
12544            if (this.#bypass) {
12545              this.#bypass = false;
12546              remaining.shift();
12547              continue;
12548            }
12549            this.#options.onPending?.({
12550              change,
12551              target: qelemIdToText(change.qelem_id)
12552            });
12553            this.#pendingTimer = setTimeout(addRemaining, 1e3);
12554            return;
12555          }
12556          remaining.shift();
12557          this.#replayed.add(change);
12558          this.moveCursor(elem, change.qelem_id);
12559          this.changeCustom(change);
12560          this.saveAtCursor();
12561        }
12562        this.#options.onPending?.(null);
12563        resolve();
12564      };
12565      addRemaining();
12566    });
12567  }
12568  /**	Skip the change [[addChanges]] is waiting on. **/
12569  bypassPending() {
12570    this.#bypass = true;
12571  }
12572  /**	Apply a change list without waiting for anything - used when the caller already knows every
12573  element is there.
12574  **/
12575  addChangesSync(changes, areNew) {
12576    if (changes) {
12577      for (const change of changes) {
12578        if (!areNew) {
12579          this.#replayed.add(change);
12580        }
12581        const elem = resolveQelem(change.qelem_id);
12582        if (elem) {
12583          this.moveCursor(elem, change.qelem_id);
12584          this.changeCustom(change);
12585          this.saveAtCursor();
12586        }
12587      }
12588    }
12589  }
12590  /**	Undo everything and forget it. `noUndo` leaves the page as it is and only clears the list,
12591  which is what a navigation to another URL needs.
12592  **/
12593  reset(noUndo) {
12594    clearTimeout(this.#pendingTimer);
12595    this.discardAtCursor();
12596    if (!noUndo) {
12597      while (this.#n > 0) {
12598        this.#snaps[this.#n - 1].undoOrRedo();
12599        this.#n--;
12600      }
12601    }
12602    this.#n = 0;
12603    this.#snaps = [];
12604    this.#options.onPending?.(null);
12605    this.#options.onChange?.();
12606  }
12607  getElemPrevChanges(elem) {
12608    return getElemPrevChanges(elem);
12609  }
12610  /**	Replace an already-recorded change in place: undo back to it, substitute, redo the rest. Used
12611  by the on-click editor, which edits the events of a change made earlier rather than adding a
12612  new one. Port of `set_elem_prev_changes_changes()`.
12613  **/
12614  setElemPrevChangesChanges(elem, changesChanges) {
12615    this.discardAtCursor();
12616    if (changesChanges) {
12617      for (const { change, n: nSnap } of changesChanges) {
12618        const neChanges = elemPrevChanges.get(elem);
12619        let iOrig = -1;
12620        if (neChanges) {
12621          for (let j3 = 0; j3 < neChanges.length; j3++) {
12622            if (neChanges[j3].n == nSnap) {
12623              iOrig = j3;
12624              break;
12625            }
12626          }
12627        }
12628        if (iOrig == -1) {
12629          throw new Error(`Change #${nSnap} not found`);
12630        }
12631        const changeOrig = neChanges[iOrig].change;
12632        if (JSON.stringify(change) != JSON.stringify(changeOrig)) {
12633          const forRedo = new Array();
12634          while (this.#n > nSnap) {
12635            forRedo.unshift(this.#snaps[this.#n - 1].change);
12636            this.undo();
12637          }
12638          forRedo[0] = change;
12639          this.addChangesSync(forRedo);
12640        }
12641      }
12642    }
12643  }
12644  /**	Record how widely this change is allowed to land, at the moment it becomes an entry.
12645  
12646  		TWO THINGS ARE DELIBERATELY LEFT ALONE. A change that is only being REPLAYED - the action's
12647  		stored list coming back onto the page, or the undo/redo dance `setElemPrevChangesChanges`
12648  		does - keeps whatever it already said, including saying nothing. And a change that already
12649  		carries a scope is never re-stamped. Between them, an action authored before this existed
12650  		goes on applying exactly where it always did, even after it is opened and re-saved.
12651  	 **/
12652  #stampScope(snap) {
12653    const change = snap.change;
12654    if (!change || this.#replayed.has(change) || change.scope != void 0) {
12655      return;
12656    }
12657    const device = this.#options.getDevice?.();
12658    if (device) {
12659      change.device = device;
12660    }
12661    const scope = this.#options.getScope?.() ?? "any";
12662    change.scope = scope;
12663    if (scope == "page") {
12664      change.scope_url = pageUrl();
12665    } else if (scope == "shape") {
12666      const fp = snap.getFingerprint();
12667      if (fp.length) {
12668        change.scope_fp = fp;
12669      }
12670    }
12671  }
12672  /**	Change how widely an ALREADY RECORDED change may land.
12673  
12674  		Answering "Apply on" at save time is answering it at the only moment somebody is thinking
12675  		about that one edit - but getting it wrong then meant undoing the edit and making it again.
12676  		The scope is not part of what the change DOES, so it can be rewritten in place: the entry
12677  		keeps its selector, its splices and its position in the list.
12678  
12679  		The fingerprint is taken FROM THE PAGE AS IT IS NOW, not from what the snapshot remembers.
12680  		They are the same thing while the editor is still on the page the change was made on, and
12681  		on any other page there is nothing honest to record - so a shape that cannot be measured
12682  		here leaves the change unscoped rather than pinned to the wrong structure.
12683  	 **/
12684  /**	Would [[setChangeScope]] accept this, without doing it? Asked when a whole group of changes
12685  is being re-scoped together: half a group narrowed and half left wide is the state the
12686  grouping exists to prevent.
12687  **/
12688  canSetChangeScope(index, scope) {
12689    const change = this.#snaps[index]?.change;
12690    if (!change) {
12691      return false;
12692    }
12693    if (scope != "shape") {
12694      return true;
12695    }
12696    const elem = resolveQelem(change.qelem_id);
12697    return elem?.nodeType == XML_ELEMENT_NODE2 && fingerprintOf(elem).length > 0;
12698  }
12699  setChangeScope(index, scope) {
12700    const change = this.#snaps[index]?.change;
12701    if (!change) {
12702      return false;
12703    }
12704    let fp = [];
12705    if (scope == "shape") {
12706      const elem = resolveQelem(change.qelem_id);
12707      fp = elem?.nodeType == XML_ELEMENT_NODE2 ? fingerprintOf(elem) : [];
12708      if (!fp.length) {
12709        return false;
12710      }
12711    }
12712    delete change.scope_url;
12713    delete change.scope_fp;
12714    change.scope = scope;
12715    if (scope == "page") {
12716      change.scope_url = pageUrl();
12717    } else if (fp.length) {
12718      change.scope_fp = fp;
12719    }
12720    this.#options.onChange?.();
12721    return true;
12722  }
12723  /**	Which screens an ALREADY RECORDED change is for - the changes list's "Show on". Like the scope,
12724  not part of what the change does, so it is rewritten in place. '' is every screen. **/
12725  setChangeDevice(index, device) {
12726    const change = this.#snaps[index]?.change;
12727    if (!change) {
12728      return false;
12729    }
12730    if (device) {
12731      change.device = device;
12732    } else {
12733      delete change.device;
12734    }
12735    this.#options.onChange?.();
12736    return true;
12737  }
12738  /**	Count clicks on an ALREADY RECORDED change's element, or stop counting them - the changes
12739  list's click toggle. Like the scope, this is not part of what the change does to the page:
12740  the event only wires up the visitor's click, so it is rewritten in place and the entry keeps
12741  its selector, its splices and its position. A removed element has nothing to click.
12742  **/
12743  setChangeClicks(index, on) {
12744    const change = this.#snaps[index]?.change;
12745    if (!change || change.rm || change.rm_shallow) {
12746      return false;
12747    }
12748    const events = eventsWithClicks(change, on);
12749    if (events) {
12750      change.events = events;
12751    } else {
12752      delete change.events;
12753    }
12754    this.#options.onChange?.();
12755    return true;
12756  }
12757  #requireCursor() {
12758    const snap = this.#curSnap;
12759    if (!snap) {
12760      throw new Error("Call moveCursor(elem) first");
12761    }
12762    return snap;
12763  }
12764};
12765
12766// src/modules/ailive/private/aiLiveSession.ts
12767var MAX_ROUNDS = 10;
12768var SKELETON_MAX_CHARS = 6e4;
12769var SELECTED_HTML_MAX_CHARS = 4e3;
12770var GET_HTML_DEFAULT_CHARS = 2e4;
12771var GET_HTML_MAX_CHARS = 5e4;
12772var MAX_LISTED_MATCHES = 10;
12773var MAX_CHANGES_PER_CALL = 50;
12774var DEFAULT_STYLE_PROPS = [
12775  "display",
12776  "position",
12777  "top",
12778  "left",
12779  "right",
12780  "bottom",
12781  "z-index",
12782  "float",
12783  "width",
12784  "height",
12785  "margin",
12786  "padding",
12787  "border",
12788  "border-radius",
12789  "box-sizing",
12790  "flex-direction",
12791  "flex-wrap",
12792  "justify-content",
12793  "align-items",
12794  "gap",
12795  "grid-template-columns",
12796  "color",
12797  "background-color",
12798  "background-image",
12799  "opacity",
12800  "overflow",
12801  "font-family",
12802  "font-size",
12803  "font-weight",
12804  "line-height",
12805  "text-align",
12806  "text-transform"
12807];
12808var CHANGE_EFFECT_FIELDS = [
12809  "inner_html",
12810  "text",
12811  "attrs",
12812  "dup",
12813  "mv",
12814  "rm",
12815  "rm_shallow",
12816  "e",
12817  "e_shallow",
12818  "events",
12819  "delay"
12820];
12821var AiLiveSession = class {
12822  transcript = new Array();
12823  isBusy = false;
12824  #options;
12825  #messages;
12826  #budget;
12827  #isStopRequested = false;
12828  constructor(options) {
12829    this.#options = options;
12830  }
12831  /**	What was left of the AI allowance as of the last turn, or `undefined` before the first one.
12832  The panel reads it to keep its meter honest while a session runs.
12833  **/
12834  get budget() {
12835    return this.#budget;
12836  }
12837  /**	Send one user prompt and run the tool loop until the model answers in text (or the round cap
12838  or the user stops it). Rejections never propagate - a failure becomes an `error` entry.
12839  **/
12840  async send(prompt) {
12841    if (this.isBusy || !prompt.trim()) {
12842      return;
12843    }
12844    this.isBusy = true;
12845    this.#isStopRequested = false;
12846    this.#say("user", prompt);
12847    try {
12848      let request = this.#promptRequest(prompt.trim());
12849      for (let round = 0; true; round++) {
12850        const messagesBefore = this.#messages;
12851        const turn = await this.#callServer(request);
12852        this.#messages = turn.messages;
12853        if (turn.text) {
12854          this.#say("assistant", turn.text);
12855        }
12856        if (!turn.tool_calls.length) {
12857          break;
12858        }
12859        if (this.#isStopRequested || round >= MAX_ROUNDS) {
12860          this.#messages = messagesBefore;
12861          this.#say("status", this.#isStopRequested ? "Stopped." : "The AI did not finish in the allotted number of steps. You can rephrase or continue.");
12862          break;
12863        }
12864        const results = new Array();
12865        for (const call of turn.tool_calls) {
12866          results.push({
12867            id: call.id,
12868            ...this.#runTool(call.name, call.input)
12869          });
12870        }
12871        request = {
12872          messages: this.#messages,
12873          tool_results: results
12874        };
12875      }
12876    } catch (e3) {
12877      console.error(e3);
12878      this.#say("error", e3 instanceof Error ? e3.message : "The AI request failed.");
12879    }
12880    this.isBusy = false;
12881    this.#options.onUpdate();
12882  }
12883  /**	Ask the running loop to wind down after the turn in flight. **/
12884  stop() {
12885    this.#isStopRequested = true;
12886  }
12887  #say(role, text) {
12888    this.transcript.push({
12889      role,
12890      text
12891    });
12892    this.#options.onUpdate();
12893  }
12894  /**	The request of a user-prompt turn: the page context once (the first turn), the current
12895  selection each time (it may have moved between prompts).
12896  **/
12897  #promptRequest(prompt) {
12898    const request = {
12899      prompt
12900    };
12901    if (this.#messages) {
12902      request.messages = this.#messages;
12903    } else {
12904      request.page = {
12905        url: location.href,
12906        title: document.title,
12907        viewport: `${globalThis.innerWidth}x${globalThis.innerHeight}`,
12908        skeleton: pageSkeleton([
12909          this.#options.hostElem
12910        ], SKELETON_MAX_CHARS)
12911      };
12912    }
12913    const selected = this.#options.getSelected();
12914    if (selected) {
12915      const selector = elemToSelector(selected);
12916      if (selector) {
12917        request.selected = {
12918          selector,
12919          html: elemHtml(selected, SELECTED_HTML_MAX_CHARS)
12920        };
12921      }
12922    }
12923    return request;
12924  }
12925  async #callServer(request) {
12926    const row = await queryRow("CALL acc_actions.ai_live_edit(@request, @action_id, @action_version)", {
12927      request: JSON.stringify(request),
12928      action_id: this.#options.getActionId(),
12929      action_version: 0
12930    }, true);
12931    const parsed = JSON.parse(String(row?.result ?? ""));
12932    if (!Array.isArray(parsed?.messages) || !Array.isArray(parsed?.tool_calls)) {
12933      throw new Error("Unexpected answer from the AI service.");
12934    }
12935    if (parsed.budget && typeof parsed.budget.actionsLeft == "number") {
12936      this.#budget = parsed.budget;
12937    }
12938    return {
12939      messages: parsed.messages,
12940      text: typeof parsed.text == "string" ? parsed.text : "",
12941      tool_calls: parsed.tool_calls,
12942      budget: parsed.budget
12943    };
12944  }
12945  /**	Execute one tool call against the live page. Anything wrong comes back as an error result
12946  for the model to react to, not as an exception - a bad selector is the model's mistake to
12947  fix, not ours to crash on.
12948  **/
12949  #runTool(name, input) {
12950    try {
12951      switch (name) {
12952        case "query":
12953          return this.#toolQuery(str(input.selector));
12954        case "get_html":
12955          return this.#toolGetHtml(str(input.selector), Number(input.max_chars) || GET_HTML_DEFAULT_CHARS);
12956        case "get_computed_style":
12957          return this.#toolGetComputedStyle(str(input.selector), Array.isArray(input.props) ? input.props.map((v3) => v3 + "") : void 0);
12958        case "get_boxes":
12959          return this.#toolGetBoxes(str(input.selector));
12960        case "propose_changes":
12961          return this.#toolProposeChanges(str(input.summary), input.changes);
12962      }
12963      return {
12964        content: `Unknown tool: ${name}`,
12965        is_error: true
12966      };
12967    } catch (e3) {
12968      console.error(e3);
12969      return {
12970        content: e3 instanceof Error ? e3.message : "The tool failed",
12971        is_error: true
12972      };
12973    }
12974  }
12975  #find(selector) {
12976    if (!selector) {
12977      throw new Error("No selector given");
12978    }
12979    try {
12980      return Array.from(document.querySelectorAll(selector)).filter((v3) => v3 != this.#options.hostElem);
12981    } catch {
12982      throw new Error(`Not a valid CSS selector: ${selector}`);
12983    }
12984  }
12985  #toolQuery(selector) {
12986    const found = this.#find(selector);
12987    this.#say("status", `Checked selector ${selector} \u2014 ${found.length} match${found.length == 1 ? "" : "es"}`);
12988    if (!found.length) {
12989      return {
12990        content: "0 elements match."
12991      };
12992    }
12993    const lines = [
12994      `${found.length} element${found.length == 1 ? "" : "s"} match.`
12995    ];
12996    for (let i4 = 0, iEnd = Math.min(found.length, MAX_LISTED_MATCHES); i4 < iEnd; i4++) {
12997      const elem = found[i4];
12998      const rect = elem.getBoundingClientRect();
12999      const text = (elem.textContent ?? "").replace(/\s+/g, " ").trim();
13000      lines.push(`${i4 + 1}. ${describeElem(elem)} (${Math.round(rect.width)}x${Math.round(rect.height)} at ${Math.round(rect.left)},${Math.round(rect.top)})${text ? ` "${text.length > 60 ? text.slice(0, 59) + "\u2026" : text}"` : ""}`);
13001    }
13002    if (found.length > MAX_LISTED_MATCHES) {
13003      lines.push(`\u2026 and ${found.length - MAX_LISTED_MATCHES} more`);
13004    }
13005    return {
13006      content: lines.join("\n")
13007    };
13008  }
13009  #toolGetHtml(selector, maxChars) {
13010    const found = this.#find(selector);
13011    this.#say("status", `Read the HTML of ${selector}`);
13012    if (!found.length) {
13013      return {
13014        content: `Nothing matches ${selector}`,
13015        is_error: true
13016      };
13017    }
13018    return {
13019      content: elemHtml(found[0], Math.min(Math.max(maxChars, 100), GET_HTML_MAX_CHARS))
13020    };
13021  }
13022  #toolGetComputedStyle(selector, props) {
13023    const found = this.#find(selector);
13024    this.#say("status", `Read the computed style of ${selector}`);
13025    if (!found.length) {
13026      return {
13027        content: `Nothing matches ${selector}`,
13028        is_error: true
13029      };
13030    }
13031    const style = getComputedStyle(found[0]);
13032    const names = props?.length ? props : DEFAULT_STYLE_PROPS;
13033    const lines = new Array();
13034    for (const nameRaw of names.slice(0, 100)) {
13035      const name = nameRaw.trim();
13036      if (name) {
13037        lines.push(`${name}: ${style.getPropertyValue(name) || "(empty)"}`);
13038      }
13039    }
13040    return {
13041      content: lines.join("\n")
13042    };
13043  }
13044  #toolGetBoxes(selector) {
13045    const found = this.#find(selector);
13046    this.#say("status", `Measured ${selector}`);
13047    if (!found.length) {
13048      return {
13049        content: `Nothing matches ${selector}`,
13050        is_error: true
13051      };
13052    }
13053    const lines = new Array();
13054    for (let i4 = 0, iEnd = Math.min(found.length, MAX_LISTED_MATCHES); i4 < iEnd; i4++) {
13055      const elem = found[i4];
13056      const rect = elem.getBoundingClientRect();
13057      const style = getComputedStyle(elem);
13058      const isVisible = rect.width > 0 && rect.height > 0 && style.display != "none" && style.visibility != "hidden";
13059      lines.push(`${i4 + 1}. ${describeElem(elem)}: left=${Math.round(rect.left)} top=${Math.round(rect.top)} width=${Math.round(rect.width)} height=${Math.round(rect.height)}${isVisible ? "" : " (not visible)"}`);
13060    }
13061    lines.push(`(viewport: ${globalThis.innerWidth}x${globalThis.innerHeight}, page scrolled to ${Math.round(globalThis.scrollX)},${Math.round(globalThis.scrollY)})`);
13062    return {
13063      content: lines.join("\n")
13064    };
13065  }
13066  /**	Validate every record, then apply them all through the editor. All-or-nothing: a rejected
13067  record means nothing was applied, and the error names it - so the model can fix that one
13068  record instead of guessing which half of its edit went through.
13069  **/
13070  #toolProposeChanges(summary, changesRaw) {
13071    if (!Array.isArray(changesRaw) || !changesRaw.length) {
13072      return {
13073        content: "No change records were given.",
13074        is_error: true
13075      };
13076    }
13077    if (changesRaw.length > MAX_CHANGES_PER_CALL) {
13078      return {
13079        content: `Too many change records (${changesRaw.length}). Use selectors that match groups of elements instead.`,
13080        is_error: true
13081      };
13082    }
13083    const changes = new Array();
13084    for (let i4 = 0; i4 < changesRaw.length; i4++) {
13085      const change = changesRaw[i4];
13086      const error = validateChange(change);
13087      if (error) {
13088        return {
13089          content: `Change #${i4 + 1} was rejected, and NO changes were applied: ${error}`,
13090          is_error: true
13091        };
13092      }
13093      changes.push(change);
13094    }
13095    for (const change of changes) {
13096      delete change.scope;
13097      delete change.scope_url;
13098      delete change.scope_fp;
13099    }
13100    this.#options.editor.addChangesSync(changes, true);
13101    this.#say("status", `Applied ${changes.length} change${changes.length == 1 ? "" : "s"}${summary ? " \u2014 " + summary : ""}`);
13102    return {
13103      content: `Applied ${changes.length} change record${changes.length == 1 ? "" : "s"}. The user sees the result now.`
13104    };
13105  }
13106};
13107function str(value) {
13108  return typeof value == "string" ? value : "";
13109}
13110function validateChange(change) {
13111  if (typeof change != "object" || change == null || Array.isArray(change)) {
13112    return "a change record must be a JSON object";
13113  }
13114  const record = change;
13115  if (typeof record.qelem_id != "string" || !record.qelem_id) {
13116    return "qelem_id (a CSS selector string) is required";
13117  }
13118  if (!CHANGE_EFFECT_FIELDS.some((name) => record[name] != void 0)) {
13119    return `the record has no effect - none of ${CHANGE_EFFECT_FIELDS.join(", ")} is set`;
13120  }
13121  if (resolveQelem(record.qelem_id, true).length == 0) {
13122    return `the selector ${record.qelem_id} matches no element on the page (selectors must match elements that are on the page before your changes)`;
13123  }
13124  if (record.mv != void 0) {
13125    if (typeof record.mv_qelem_id != "string" || !record.mv_qelem_id) {
13126      return "mv requires mv_qelem_id (a CSS selector string)";
13127    }
13128    if (!resolveQelem(record.mv_qelem_id)) {
13129      return `the move target ${record.mv_qelem_id} matches no element on the page`;
13130    }
13131  }
13132  if ((record.e != void 0 || record.text != void 0) && record.where !== "before" && record.where !== "after" && record.where !== "inside") {
13133    return `'e' and 'text' require where: 'before' | 'after' | 'inside'`;
13134  }
13135  return void 0;
13136}
13137
13138// src/modules/ailive/private/aiSuggest.ts
13139async function aiSuggest(request) {
13140  const payload = {
13141    prompt: request.instruction,
13142    suggest: {
13143      kind: request.kind,
13144      current: request.current,
13145      context: request.context ?? ""
13146    },
13147    page: {
13148      url: location.href,
13149      title: document.title,
13150      viewport: "",
13151      skeleton: ""
13152    }
13153  };
13154  const selector = request.elem ? elemToSelector(request.elem) : "";
13155  if (selector) {
13156    payload.selected = {
13157      selector
13158    };
13159  }
13160  const row = await queryRow("CALL acc_actions.ai_live_edit(@request, @action_id, @action_version)", {
13161    request: JSON.stringify(payload),
13162    action_id: request.actionId,
13163    action_version: 0
13164  }, true);
13165  const raw = String(row?.result ?? "");
13166  if (!raw) {
13167    throw new Error("The AI service returned an empty answer. Your editing session may have expired - reopen the editor from Personyze.");
13168  }
13169  let parsed;
13170  try {
13171    parsed = JSON.parse(raw);
13172  } catch {
13173    throw new Error("The AI service answered with something unreadable: " + raw.slice(0, 120));
13174  }
13175  const text = typeof parsed?.text == "string" ? parsed.text.trim() : "";
13176  if (!text) {
13177    if (Array.isArray(parsed?.tool_calls) && parsed.tool_calls.length) {
13178      throw new Error("The AI service is running an older version that does not support in-panel suggestions yet. The AI assistant button still works.");
13179    }
13180    throw new Error("The AI returned nothing. Try rephrasing what you want.");
13181  }
13182  return stripCodeFence(text);
13183}
13184function stripCodeFence(text) {
13185  const m2 = text.match(/^```[^\n]*\n([\s\S]*)\n?```$/);
13186  return m2 ? m2[1].trim() : text;
13187}
13188function parseDeclarations(text) {
13189  let body = stripCodeFence(text).trim();
13190  const rule = body.match(/^[^{}]*\{([\s\S]*)\}\s*$/);
13191  if (rule) {
13192    body = rule[1];
13193  }
13194  const out = new Array();
13195  for (const part of body.split(/[;\n]+/)) {
13196    const m2 = part.match(/^\s*([-a-zA-Z][-a-zA-Z0-9]*)\s*:\s*(.+?)\s*$/);
13197    if (m2 && !out.some((v3) => v3[0] == m2[1].toLowerCase())) {
13198      out.push([
13199        m2[1].toLowerCase(),
13200        m2[2].replace(/;+$/, "").trim()
13201      ]);
13202    }
13203  }
13204  return out;
13205}
13206
13207// src/modules/toolbridge/private/toolbridge.ts
13208var ACK_TIMEOUT = 4e4;
13209var RESEND_EACH = 1e3;
13210var GET_VALUE_TRIES = 60;
13211var MESSAGE_TYPE_GET_VALUE = "(GET_VALUE)";
13212var MESSAGE_TYPE_VALUE = "(VALUE)";
13213var MESSAGE_TYPE_SLAVE = "slave";
13214var ToolBridge = class {
13215  #options;
13216  #pending = new Array();
13217  #timer = 0;
13218  #isStopped = false;
13219  #lastValue = null;
13220  constructor(options) {
13221    this.#options = options;
13222  }
13223  /**	Start listening, then ask the panel for the value being edited. Resolves true once the panel
13224  has answered, false if it never did.
13225  **/
13226  async start() {
13227    globalThis.addEventListener("message", this.#onMessage);
13228    for (let i4 = 0; i4 < GET_VALUE_TRIES; i4++) {
13229      if (this.#isStopped || await this.#send({
13230        get_value: true
13231      }, MESSAGE_TYPE_GET_VALUE)) {
13232        return !this.#isStopped;
13233      }
13234    }
13235    return false;
13236  }
13237  /**	Hand the result back to the panel. Resolves true once the panel acknowledges it - the caller
13238  must not close the window before that, or the result is lost.
13239  **/
13240  setValue(value) {
13241    this.#lastValue = value;
13242    return this.#send({
13243      value
13244    }, MESSAGE_TYPE_VALUE);
13245  }
13246  /**	Push an incremental update to the panel - for the WYSIWYG editor, `{page_is_loaded, href,
13247  		n_changes, html_changes}`. Resolves true once the panel acknowledges it.
13248  
13249  		Only the latest one matters, so an unanswered previous update is superseded rather than
13250  		queued: a burst of keystrokes must not turn into a backlog of resends.
13251  	 **/
13252  reportMessage(messageData) {
13253    return this.#send({
13254      message_data: messageData
13255    }, MESSAGE_TYPE_SLAVE);
13256  }
13257  destroy() {
13258    this.#isStopped = true;
13259    globalThis.removeEventListener("message", this.#onMessage);
13260    clearInterval(this.#timer);
13261    this.#timer = 0;
13262    for (const pending2 of this.#pending.splice(0)) {
13263      pending2.resolve(false);
13264    }
13265  }
13266  #send(message, messageType) {
13267    message.buffer_key = this.#options.bufferKey;
13268    message.message_type = messageType;
13269    for (let i4 = this.#pending.length - 1; i4 >= 0; i4--) {
13270      if (this.#pending[i4].message.message_type == messageType) {
13271        this.#pending.splice(i4, 1)[0].resolve(false);
13272      }
13273    }
13274    this.#post(message);
13275    return new Promise((resolve) => {
13276      const now = Date.now();
13277      this.#pending.push({
13278        message,
13279        resolve,
13280        startTime: now,
13281        lastTime: now
13282      });
13283      this.#startStopResend();
13284    });
13285  }
13286  /**	Reply that needs no acknowledgement of its own: it IS the acknowledgement of `messageType`.
13287  **/
13288  #postAck(messageType, extra) {
13289    this.#post({
13290      ...extra,
13291      buffer_key: this.#options.bufferKey,
13292      message_type: messageType
13293    });
13294  }
13295  #post(message) {
13296    getPanelWindow()?.postMessage(message, this.#options.guiOrigin);
13297  }
13298  #onMessage = (event) => {
13299    const { guiOrigin, bufferKey, onValue, onMessageData } = this.#options;
13300    if (guiOrigin != "*" && event.origin != guiOrigin) {
13301      return;
13302    }
13303    const message = event.data;
13304    if (!message || typeof message != "object") {
13305      return;
13306    }
13307    const messageType = typeof message.message_type == "string" ? message.message_type : "";
13308    if (!messageType || message.buffer_key != bufferKey) {
13309      return;
13310    }
13311    if ("value" in message) {
13312      const value = message.value;
13313      if (value && typeof value == "object") {
13314        onValue(value);
13315      }
13316    } else if (message.message_data && typeof message.message_data == "object") {
13317      onMessageData?.(message.message_data);
13318    }
13319    const i4 = this.#pending.findIndex((v3) => v3.message.message_type == messageType);
13320    if (i4 != -1) {
13321      this.#pending.splice(i4, 1)[0].resolve(true);
13322      this.#startStopResend();
13323    } else if (message.get_value === true) {
13324      this.#postAck(messageType, {
13325        value: this.#lastValue
13326      });
13327    } else if ("value" in message || message.message_data) {
13328      this.#postAck(messageType);
13329    }
13330  };
13331  #startStopResend() {
13332    if (this.#pending.length == 0) {
13333      clearInterval(this.#timer);
13334      this.#timer = 0;
13335    } else if (this.#timer == 0) {
13336      this.#timer = setInterval(() => {
13337        const now = Date.now();
13338        for (let i4 = this.#pending.length - 1; i4 >= 0; i4--) {
13339          const pending2 = this.#pending[i4];
13340          if (now - pending2.startTime > ACK_TIMEOUT) {
13341            this.#pending.splice(i4, 1);
13342            pending2.resolve(false);
13343          } else if (now - pending2.lastTime > RESEND_EACH) {
13344            pending2.lastTime = now;
13345            this.#post(pending2.message);
13346          }
13347        }
13348        this.#startStopResend();
13349      }, RESEND_EACH);
13350    }
13351  }
13352};
13353
13354// src/components/live/CommandPalette.tsx
13355function filterCommands(commands, query2) {
13356  const terms = query2.toLowerCase().split(/\s+/).filter((v3) => v3);
13357  if (!terms.length) {
13358    return commands;
13359  }
13360  return commands.filter((command) => {
13361    const text = (command.label + " " + (command.hint ?? "") + " " + command.group).toLowerCase();
13362    return terms.every((term) => text.indexOf(term) != -1);
13363  });
13364}
13365function CommandPalette(props) {
13366  const [query2, setQuery] = p2("");
13367  const [active, setActive] = p2(0);
13368  const inputRef = F2(null);
13369  const listRef = F2(null);
13370  const items = q2(() => filterCommands(props.commands, query2), [
13371    props.commands,
13372    query2
13373  ]);
13374  const cur = Math.min(active, Math.max(0, items.length - 1));
13375  _2(() => {
13376    inputRef.current?.focus();
13377  }, []);
13378  _2(() => {
13379    listRef.current?.querySelector(".le-on")?.scrollIntoView({
13380      block: "nearest"
13381    });
13382  }, [
13383    cur,
13384    items.length
13385  ]);
13386  function run(command) {
13387    if (command) {
13388      props.onClose();
13389      command.run();
13390    }
13391  }
13392  function onKeyDown(ev) {
13393    switch (ev.key) {
13394      case "ArrowDown":
13395        ev.preventDefault();
13396        setActive(cur + 1 < items.length ? cur + 1 : 0);
13397        break;
13398      case "ArrowUp":
13399        ev.preventDefault();
13400        setActive(cur > 0 ? cur - 1 : items.length - 1);
13401        break;
13402      case "Enter":
13403        ev.preventDefault();
13404        run(items[cur]);
13405        break;
13406      case "Escape":
13407        ev.preventDefault();
13408        props.onClose();
13409        break;
13410    }
13411  }
13412  let lastGroup = "";
13413  return /* @__PURE__ */ u2("div", {
13414    class: "le-root le-cmdk-overlay",
13415    onMouseDown: (ev) => {
13416      if (ev.target == ev.currentTarget) props.onClose();
13417    },
13418    children: /* @__PURE__ */ u2("div", {
13419      class: "le-cmdk",
13420      role: "dialog",
13421      "aria-label": "Command palette",
13422      children: [
13423        /* @__PURE__ */ u2("div", {
13424          class: "le-cmdk-input",
13425          children: [
13426            li.search,
13427            /* @__PURE__ */ u2("input", {
13428              ref: inputRef,
13429              type: "text",
13430              value: query2,
13431              placeholder: "Search actions, tools, changes...",
13432              autocomplete: "off",
13433              "aria-label": "Search commands",
13434              onInput: (ev) => {
13435                setQuery(ev.target.value);
13436                setActive(0);
13437              },
13438              onKeyDown
13439            }),
13440            /* @__PURE__ */ u2("kbd", {
13441              children: "Esc"
13442            })
13443          ]
13444        }),
13445        /* @__PURE__ */ u2("div", {
13446          class: "le-cmdk-list",
13447          ref: listRef,
13448          children: items.length == 0 ? /* @__PURE__ */ u2("div", {
13449            class: "le-cmdk-empty",
13450            children: [
13451              'Nothing matches "',
13452              query2,
13453              '".'
13454            ]
13455          }) : items.map((command, i4) => {
13456            const head = command.group != lastGroup ? command.group : "";
13457            lastGroup = command.group;
13458            return /* @__PURE__ */ u2(k, {
13459              children: [
13460                head && /* @__PURE__ */ u2("div", {
13461                  class: "le-cmdk-group",
13462                  children: head
13463                }, "g" + i4),
13464                /* @__PURE__ */ u2("button", {
13465                  type: "button",
13466                  class: "le-cmdk-item" + (i4 == cur ? " le-on" : ""),
13467                  // Selecting with the mouse must not steal the focus from the box.
13468                  onMouseDown: (ev) => ev.preventDefault(),
13469                  onMouseEnter: () => setActive(i4),
13470                  onClick: () => run(command),
13471                  children: [
13472                    /* @__PURE__ */ u2("div", {
13473                      class: "le-cmdk-txt",
13474                      children: [
13475                        /* @__PURE__ */ u2("b", {
13476                          children: command.label
13477                        }),
13478                        command.hint && /* @__PURE__ */ u2("small", {
13479                          children: command.hint
13480                        })
13481                      ]
13482                    }),
13483                    command.shortcut && /* @__PURE__ */ u2("span", {
13484                      class: "le-cmdk-key",
13485                      children: command.shortcut
13486                    })
13487                  ]
13488                }, i4)
13489              ]
13490            });
13491          })
13492        }),
13493        /* @__PURE__ */ u2("div", {
13494          class: "le-cmdk-foot",
13495          children: [
13496            /* @__PURE__ */ u2("span", {
13497              children: [
13498                /* @__PURE__ */ u2("kbd", {
13499                  children: "\u2191"
13500                }),
13501                /* @__PURE__ */ u2("kbd", {
13502                  children: "\u2193"
13503                }),
13504                " to navigate"
13505              ]
13506            }),
13507            /* @__PURE__ */ u2("span", {
13508              children: [
13509                /* @__PURE__ */ u2("kbd", {
13510                  children: "\u21B5"
13511                }),
13512                " to select"
13513              ]
13514            }),
13515            /* @__PURE__ */ u2("span", {
13516              children: [
13517                /* @__PURE__ */ u2("kbd", {
13518                  children: "Esc"
13519                }),
13520                " to close"
13521              ]
13522            })
13523          ]
13524        })
13525      ]
13526    })
13527  });
13528}
13529
13530// src/components/live/Toolbar.tsx
13531var MAX_CRUMBS = 5;
13532function topInView(want, root) {
13533  return Math.max(8, Math.min(want, globalThis.innerHeight - root.offsetHeight - 8));
13534}
13535function Toolbar(props) {
13536  const rootRef = F2(null);
13537  const dragRef = F2(null);
13538  const [isSelectorOpen, setIsSelectorOpen] = p2(false);
13539  _2(() => {
13540    const onMove = (ev) => {
13541      const drag = dragRef.current;
13542      const root = rootRef.current;
13543      if (drag && root) {
13544        ev.preventDefault();
13545        props.onMove({
13546          left: Math.max(8, Math.min(globalThis.innerWidth - root.offsetWidth - 8, ev.clientX - drag.dx)),
13547          top: topInView(ev.clientY - drag.dy, root)
13548        });
13549      }
13550    };
13551    const onUp = () => {
13552      dragRef.current = null;
13553      rootRef.current?.classList.remove("le-dragging");
13554    };
13555    document.addEventListener("mousemove", onMove, true);
13556    document.addEventListener("mouseup", onUp, true);
13557    return () => {
13558      document.removeEventListener("mousemove", onMove, true);
13559      document.removeEventListener("mouseup", onUp, true);
13560    };
13561  }, []);
13562  _2(() => {
13563    const root = rootRef.current;
13564    if (!root || typeof ResizeObserver != "function") {
13565      return;
13566    }
13567    const obs = new ResizeObserver(() => {
13568      if (!dragRef.current) {
13569        const want = topInView(props.position.top, root);
13570        if (want != props.position.top) {
13571          props.onMove({
13572            ...props.position,
13573            top: want
13574          });
13575        }
13576      }
13577    });
13578    obs.observe(root);
13579    return () => obs.disconnect();
13580  }, [
13581    props.position.top,
13582    props.position.left
13583  ]);
13584  function onHeadDown(ev) {
13585    if (ev.target.closest("button")) {
13586      return;
13587    }
13588    const root = rootRef.current;
13589    if (root) {
13590      const rect = root.getBoundingClientRect();
13591      dragRef.current = {
13592        dx: ev.clientX - rect.left,
13593        dy: ev.clientY - rect.top
13594      };
13595      root.classList.add("le-dragging");
13596      ev.preventDefault();
13597    }
13598  }
13599  const crumbs = props.selected ? ancestorChain(props.selected) : [];
13600  const shown = crumbs.slice(-MAX_CRUMBS);
13601  const smart = smartRowFor(props.selected, props.allow);
13602  const tiles = availableTiles(props.selected, props.allow);
13603  return /* @__PURE__ */ u2("div", {
13604    ref: rootRef,
13605    class: "le-root le-toolbar" + (props.isCollapsed ? " le-collapsed" : ""),
13606    style: "left:" + props.position.left + "px; top:" + props.position.top + "px",
13607    role: "dialog",
13608    "aria-label": "Personyze Live Editor",
13609    children: [
13610      /* @__PURE__ */ u2("div", {
13611        class: "le-head",
13612        onMouseDown: onHeadDown,
13613        children: [
13614          /* @__PURE__ */ u2("span", {
13615            class: "le-grip",
13616            "aria-label": "Drag to move the editor",
13617            "data-tooltip": "Drag anywhere on this bar to move the editor out of your way",
13618            children: "\u22EE\u22EE"
13619          }),
13620          /* @__PURE__ */ u2("button", {
13621            type: "button",
13622            class: "le-mode" + (props.isNavigate ? " le-on" : ""),
13623            "data-tooltip": "Turn on to click through the page like a visitor. While off, clicks select elements to edit.  Shortcut: N",
13624            onClick: () => props.onNavigate(!props.isNavigate),
13625            children: [
13626              /* @__PURE__ */ u2("span", {
13627                class: "le-dot"
13628              }),
13629              li.cursor,
13630              props.isNavigate ? "Navigate \xB7 ON" : "Navigate"
13631            ]
13632          }),
13633          props.hasAi && /* @__PURE__ */ u2("button", {
13634            type: "button",
13635            class: "le-mode" + (props.isAiOpen ? " le-on" : ""),
13636            "data-tooltip": "Describe an edit in plain words, and the AI makes it on this page",
13637            onClick: () => props.onAi(!props.isAiOpen),
13638            children: [
13639              li.sparkle,
13640              "AI"
13641            ]
13642          }),
13643          /* @__PURE__ */ u2("div", {
13644            class: "le-spacer"
13645          }),
13646          /* @__PURE__ */ u2("button", {
13647            type: "button",
13648            class: "le-icon",
13649            "data-tooltip": "Search every action, tool and change  \u2318K",
13650            onClick: props.onSearch,
13651            children: li.search
13652          }),
13653          /* @__PURE__ */ u2("button", {
13654            type: "button",
13655            class: "le-icon",
13656            "data-tooltip": "Undo the last change  \u2318Z",
13657            disabled: !props.canUndo,
13658            onClick: props.onUndo,
13659            children: li.undo
13660          }),
13661          /* @__PURE__ */ u2("button", {
13662            type: "button",
13663            class: "le-icon",
13664            "data-tooltip": "Redo  \u2318\u21E7Z",
13665            disabled: !props.canRedo,
13666            onClick: props.onRedo,
13667            children: li.redo
13668          }),
13669          /* @__PURE__ */ u2("button", {
13670            type: "button",
13671            class: "le-icon",
13672            "data-tooltip": (props.isCollapsed ? "Expand the toolbar" : "Collapse the toolbar to one line") + "  \u2318\\",
13673            onClick: () => props.onCollapse(!props.isCollapsed),
13674            children: props.isCollapsed ? li.chevronDown : li.chevronUp
13675          }),
13676          /* @__PURE__ */ u2("button", {
13677            type: "button",
13678            class: "le-icon",
13679            "data-tooltip": "Put the editor away. Your changes stay on the page, and a button to bring it back appears in the corner.",
13680            onClick: props.onClose,
13681            children: li.cross
13682          })
13683        ]
13684      }),
13685      !props.isCollapsed && /* @__PURE__ */ u2(k, {
13686        children: [
13687          /* @__PURE__ */ u2("div", {
13688            class: "le-path",
13689            "aria-label": "Selected element path",
13690            children: [
13691              shown.length == 0 ? /* @__PURE__ */ u2("span", {
13692                class: "le-path-empty",
13693                children: "Hover the page and click an element to edit it."
13694              }) : shown.map((node, i4) => {
13695                const isCurrent = i4 == shown.length - 1;
13696                const meta = node.id ? "#" + node.id : node.classList.length ? "." + node.classList[0] : "";
13697                return /* @__PURE__ */ u2(k, {
13698                  children: [
13699                    i4 > 0 && /* @__PURE__ */ u2("span", {
13700                      class: "le-crumb-sep",
13701                      children: "\u203A"
13702                    }, "s" + i4),
13703                    /* @__PURE__ */ u2("button", {
13704                      type: "button",
13705                      class: "le-crumb" + (isCurrent ? " le-on" : ""),
13706                      title: isCurrent ? "The selected element - " + tagHumanName(node) : "Select the parent: " + node.localName + meta,
13707                      onMouseEnter: () => props.onHoverCrumb(node),
13708                      onMouseLeave: () => props.onHoverCrumb(null),
13709                      onClick: () => props.onSelect(node),
13710                      children: [
13711                        /* @__PURE__ */ u2("b", {
13712                          children: node.localName
13713                        }),
13714                        meta && /* @__PURE__ */ u2("span", {
13715                          children: meta
13716                        })
13717                      ]
13718                    }, "c" + i4)
13719                  ]
13720                });
13721              }),
13722              /* @__PURE__ */ u2("button", {
13723                type: "button",
13724                class: "le-crumb-add" + (isSelectorOpen ? " le-on" : ""),
13725                title: "Pick by CSS selector - for an element the mouse cannot reach",
13726                onClick: () => setIsSelectorOpen((v3) => !v3),
13727                children: "+"
13728              })
13729            ]
13730          }),
13731          isSelectorOpen && /* @__PURE__ */ u2(SelectorRow, {
13732            onSelect: props.onSelectBySelector,
13733            onClose: () => setIsSelectorOpen(false)
13734          }),
13735          props.group && !props.subpanel && /* @__PURE__ */ u2("label", {
13736            class: "le-grouprow" + (props.isGroup ? " le-on" : ""),
13737            title: "Record the edit against " + props.group.selector + ", so it applies to every one of them - on this page and on any other page that has them",
13738            children: [
13739              /* @__PURE__ */ u2("input", {
13740                type: "checkbox",
13741                checked: props.isGroup,
13742                onChange: (ev) => props.onGroup(ev.target.checked)
13743              }),
13744              /* @__PURE__ */ u2("span", {
13745                children: [
13746                  "Apply to all ",
13747                  /* @__PURE__ */ u2("b", {
13748                    children: props.group.count
13749                  }),
13750                  " elements like this one"
13751                ]
13752              }),
13753              /* @__PURE__ */ u2("code", {
13754                children: props.group.selector
13755              })
13756            ]
13757          }),
13758          props.subpanel ? /* @__PURE__ */ u2(k, {
13759            children: [
13760              /* @__PURE__ */ u2("div", {
13761                class: "le-body",
13762                children: props.subpanel
13763              }),
13764              props.subpanelFoot
13765            ]
13766          }) : /* @__PURE__ */ u2(k, {
13767            children: [
13768              props.notice && /* @__PURE__ */ u2("div", {
13769                class: "le-notice le-warn",
13770                children: [
13771                  li.warn,
13772                  /* @__PURE__ */ u2("div", {
13773                    children: props.notice
13774                  }),
13775                  /* @__PURE__ */ u2("button", {
13776                    type: "button",
13777                    class: "le-icon",
13778                    title: "Dismiss",
13779                    onClick: props.onDismissNotice,
13780                    children: li.cross
13781                  })
13782                ]
13783              }),
13784              smart && smart.tiles.length > 0 && /* @__PURE__ */ u2("div", {
13785                class: "le-smart",
13786                children: [
13787                  /* @__PURE__ */ u2("span", {
13788                    class: "le-badge",
13789                    children: [
13790                      li[smart.badgeIcon],
13791                      smart.label
13792                    ]
13793                  }),
13794                  /* @__PURE__ */ u2("div", {
13795                    class: "le-smart-tiles",
13796                    children: smart.tiles.map((tile) => /* @__PURE__ */ u2("button", {
13797                      type: "button",
13798                      class: "le-smart-tile" + (tile.tool == "widget-edit" ? " le-wkey" : ""),
13799                      title: tile.tooltip ?? tile.sub,
13800                      onClick: () => props.onTool(tile.tool),
13801                      children: [
13802                        li[tile.icon],
13803                        tile.title
13804                      ]
13805                    }, tile.tool))
13806                  })
13807                ]
13808              }),
13809              /* @__PURE__ */ u2("div", {
13810                class: "le-tabs",
13811                role: "tablist",
13812                children: TABS.map((tab) => /* @__PURE__ */ u2("button", {
13813                  type: "button",
13814                  class: "le-tab" + (tab.id == props.tab ? " le-on" : ""),
13815                  title: tab.title,
13816                  onClick: () => props.onTab(tab.id),
13817                  children: tab.label
13818                }, tab.id))
13819              }),
13820              /* @__PURE__ */ u2("div", {
13821                class: "le-body",
13822                children: /* @__PURE__ */ u2(TileGrid, {
13823                  tiles: tiles.filter((v3) => tileOnTab(v3, props.tab)),
13824                  hasSelection: !!props.selected,
13825                  onTool: props.onTool,
13826                  onDragTool: props.onDragTool
13827                })
13828              })
13829            ]
13830          })
13831        ]
13832      })
13833    ]
13834  });
13835}
13836function SelectorRow(props) {
13837  const [selector, setSelector] = p2("");
13838  const [error, setError] = p2("");
13839  const inputRef = F2(null);
13840  _2(() => {
13841    inputRef.current?.focus();
13842  }, []);
13843  function go() {
13844    const text = selector.trim();
13845    if (!text) {
13846      return;
13847    }
13848    const message = props.onSelect(text);
13849    setError(message);
13850    if (!message) {
13851      props.onClose();
13852    }
13853  }
13854  return /* @__PURE__ */ u2("div", {
13855    class: "le-selrow",
13856    children: [
13857      /* @__PURE__ */ u2("input", {
13858        ref: inputRef,
13859        class: "le-input" + (error ? " le-bad" : ""),
13860        type: "text",
13861        value: selector,
13862        placeholder: "e.g. .hero .cta, #nav a:last-child",
13863        "aria-label": "CSS selector",
13864        onInput: (ev) => {
13865          setSelector(ev.target.value);
13866          setError("");
13867        },
13868        onKeyDown: (ev) => {
13869          if (ev.key == "Enter") {
13870            go();
13871          } else if (ev.key == "Escape") {
13872            props.onClose();
13873          }
13874        }
13875      }),
13876      /* @__PURE__ */ u2("button", {
13877        type: "button",
13878        class: "le-btn le-primary",
13879        onClick: go,
13880        children: "Select"
13881      }),
13882      /* @__PURE__ */ u2("button", {
13883        type: "button",
13884        class: "le-btn",
13885        onClick: props.onClose,
13886        children: "Cancel"
13887      }),
13888      error && /* @__PURE__ */ u2("div", {
13889        class: "le-selrow-err",
13890        children: error
13891      })
13892    ]
13893  });
13894}
13895function TileGrid(props) {
13896  if (!props.hasSelection) {
13897    return /* @__PURE__ */ u2("div", {
13898      class: "le-empty",
13899      children: [
13900        /* @__PURE__ */ u2("h4", {
13901          children: "Nothing selected"
13902        }),
13903        /* @__PURE__ */ u2("p", {
13904          children: "Click any element on the page to see what you can do with it."
13905        })
13906      ]
13907    });
13908  }
13909  if (props.tiles.length == 0) {
13910    return /* @__PURE__ */ u2("div", {
13911      class: "le-empty",
13912      children: [
13913        /* @__PURE__ */ u2("h4", {
13914          children: "Not for this element"
13915        }),
13916        /* @__PURE__ */ u2("p", {
13917          children: "Nothing on this tab applies to what you selected. Try another tab, or pick its parent in the path above."
13918        })
13919      ]
13920    });
13921  }
13922  return /* @__PURE__ */ u2("div", {
13923    class: "le-grid",
13924    children: props.tiles.map((tile) => /* @__PURE__ */ u2("button", {
13925      type: "button",
13926      /*	A tile may declare itself the primary action for what is selected — the widget
13927      settings do, because after clicking a countdown that is the only thing anybody
13928      came for. */
13929      class: "le-tile" + (tile.primary ? " le-tile-primary" : ""),
13930      title: tile.tooltip ?? tile.sub,
13931      onClick: () => props.onTool(tile.tool),
13932      /*	A widget tile can also be DRAGGED onto the page, which is how somebody says
13933      						"this one, there" in one gesture instead of selecting first and inserting
13934      						second. Only the widgets: every other tile acts on the element already
13935      						selected and has nowhere to be dropped.
13936      
13937      						POINTER EVENTS, NOT HTML5 DRAG-AND-DROP. The tile lives in a shadow root
13938      						over somebody else's page, and native dragging there means a drag image,
13939      						a `dragover` that has to be preventDefault-ed on every element underneath,
13940      						and behaviour that differs across browsers and iframes. It is also
13941      						untestable from outside the browser: Chrome drives native drags through its
13942      						own controller, so nothing a script dispatches reaches the handler - which
13943      						is how this shipped once unverified. A pointer drag is the same gesture,
13944      						works the same over any page, and can be exercised. */
13945      "data-drag": !!props.onDragTool && String(tile.tool).startsWith("insert-widget-") ? "1" : void 0,
13946      onMouseDown: (ev) => {
13947        if (!props.onDragTool || !String(tile.tool).startsWith("insert-widget-")) {
13948          return;
13949        }
13950        props.onDragTool(tile.tool, {
13951          x: ev.clientX,
13952          y: ev.clientY
13953        });
13954      },
13955      children: [
13956        /* @__PURE__ */ u2("div", {
13957          class: "le-tile-ico",
13958          children: li[tile.icon]
13959        }),
13960        /* @__PURE__ */ u2("div", {
13961          class: "le-tile-txt",
13962          children: [
13963            /* @__PURE__ */ u2("h4", {
13964              children: tile.title
13965            }),
13966            /* @__PURE__ */ u2("p", {
13967              children: tile.sub
13968            })
13969          ]
13970        }),
13971        tile.badge ? /* @__PURE__ */ u2("span", {
13972          class: "le-tile-badge",
13973          children: tile.badge
13974        }) : tile.key ? /* @__PURE__ */ u2("kbd", {
13975          children: tile.key
13976        }) : null
13977      ]
13978    }, tile.tool))
13979  });
13980}
13981
13982// src/components/live/liveScope.tsx
13983var SCOPE_OPTIONS = [
13984  {
13985    id: "page",
13986    label: "This page only",
13987    short: "This page",
13988    hint: "Only this address, among the pages the campaign targets. Ad parameters in the link are ignored."
13989  },
13990  {
13991    id: "shape",
13992    label: "Pages that look the same",
13993    short: "Same layout",
13994    hint: "Pages the campaign targets where this element sits in the same structure. Text is never compared."
13995  },
13996  {
13997    id: "any",
13998    label: "Anywhere it matches",
13999    short: "Anywhere",
14000    hint: "Every page the campaign targets where the saved selector finds something."
14001  }
14002];
14003var SCOPE_TIP = "The campaign still decides where it runs - its page groups, audience and A/B split come first, and this does not change them. It only narrows where this one change lands inside that.";
14004var SCOPE_ICON = {
14005  page: "scopePage",
14006  shape: "scopeShape",
14007  any: "scopeAny"
14008};
14009function ScopeLabel(props) {
14010  return /* @__PURE__ */ u2("label", {
14011    for: props.for,
14012    class: "le-scope-lbl",
14013    "data-tooltip": SCOPE_TIP,
14014    "data-tooltip-pos": "top",
14015    tabIndex: 0,
14016    children: [
14017      "Apply on ",
14018      li.info
14019    ]
14020  });
14021}
14022function ScopeSeg(props) {
14023  return /* @__PURE__ */ u2("div", {
14024    class: "le-seg",
14025    role: "group",
14026    "aria-label": "Apply on",
14027    children: SCOPE_OPTIONS.map((v3) => /* @__PURE__ */ u2("button", {
14028      type: "button",
14029      class: "le-seg-btn" + (props.value == v3.id ? " le-on" : ""),
14030      "data-tooltip": v3.label + " - " + v3.hint,
14031      "data-tooltip-pos": "top",
14032      "aria-pressed": props.value == v3.id,
14033      onClick: () => props.onChange(v3.id),
14034      children: [
14035        li[SCOPE_ICON[v3.id]],
14036        v3.short
14037      ]
14038    }, v3.id))
14039  });
14040}
14041var SHOW_ON_OPTIONS = [
14042  {
14043    id: "",
14044    label: "All devices",
14045    icon: "allDevices",
14046    hint: "Every screen size."
14047  },
14048  {
14049    id: "desktop",
14050    label: "Desktop & tablet",
14051    icon: "desktop",
14052    hint: "Screens wider than 767px - desktops, laptops and tablets."
14053  },
14054  {
14055    id: "mobile",
14056    label: "Phones only",
14057    icon: "mobile",
14058    hint: "Screens up to 767px wide."
14059  }
14060];
14061var SHOW_ON_TIP = "Which screens this change is for. A different headline on phones is two changes: one for desktop & tablet, one for phones only. Decided when the page loads.";
14062function ShowOnLabel(props) {
14063  return /* @__PURE__ */ u2("label", {
14064    for: props.for,
14065    class: "le-scope-lbl",
14066    "data-tooltip": SHOW_ON_TIP,
14067    "data-tooltip-pos": "top",
14068    tabIndex: 0,
14069    children: [
14070      "Show on ",
14071      li.info
14072    ]
14073  });
14074}
14075function DeviceSeg(props) {
14076  return /* @__PURE__ */ u2("div", {
14077    class: "le-seg",
14078    role: "group",
14079    "aria-label": "Show on",
14080    children: SHOW_ON_OPTIONS.map((v3) => /* @__PURE__ */ u2("button", {
14081      type: "button",
14082      class: "le-seg-btn" + ((props.value ?? "") == v3.id ? " le-on" : ""),
14083      "data-tooltip": v3.hint,
14084      "data-tooltip-pos": "top",
14085      "aria-pressed": (props.value ?? "") == v3.id,
14086      onClick: () => props.onChange(v3.id),
14087      children: [
14088        li[v3.icon],
14089        v3.label
14090      ]
14091    }, v3.id || "all"))
14092  });
14093}
14094
14095// src/components/live/clickCount.ts
14096var CLICKABLE = "a[href], button, input[type=submit], input[type=button], input[type=image], [role=button], [role=link]";
14097function isClickable(elem) {
14098  return !!elem?.closest?.(CLICKABLE);
14099}
14100var NO_CLICK_TOOLS = /* @__PURE__ */ new Set([
14101  "visibility",
14102  "onclick",
14103  "custom-event",
14104  "scroll-to",
14105  "delay"
14106]);
14107function toolOffersClicks(tool) {
14108  return !!tool && !NO_CLICK_TOOLS.has(tool);
14109}
14110function widgetIsClickable(widgetId) {
14111  const html = WIDGET_CATALOGUE.find((w3) => w3.id == widgetId)?.html ?? "";
14112  return /data-personyze-click-target|<a\s|<button[\s>]/i.test(html);
14113}
14114function insertCountsByDefault(kind, widgetId) {
14115  return kind == "link" || kind == "button" || kind == "widget" && widgetIsClickable(widgetId);
14116}
14117function actionHasForm(changes) {
14118  return changes.some((c3) => JSON.stringify(c3.e ?? "").includes('"data-pz-widget":"form"'));
14119}
14120function clicksAlreadyCounted(elem) {
14121  if (!elem) {
14122    return false;
14123  }
14124  const insert = insertOf(elem);
14125  if (insert && countsClicks(insert)) {
14126    return true;
14127  }
14128  return getElemPrevChanges(elem).some((p3) => !p3.change.e && countsClicks(p3.change));
14129}
14130function changeOffersClicks(change) {
14131  if (change.rm || change.rm_shallow || change.delay) {
14132    return false;
14133  }
14134  const edits = !!(change.inner_html || change.text || change.attrs || change.e || change.e_shallow || change.dup || change.mv);
14135  return edits || countsClicks(change);
14136}
14137
14138// src/components/live/changeSummary.ts
14139function splicesToText(splices) {
14140  let text = "";
14141  for (const splice of splices) {
14142    text += typeof splice == "string" ? splice : "\u2026";
14143  }
14144  return text.replace(/\s+/g, " ").trim();
14145}
14146var RE_COLOR = /(#[0-9A-F]{3,8}\b|rgba?\([^)]*\))/gi;
14147function summarizeChange(change) {
14148  const target = qelemIdToText(change.qelem_id) || "the page";
14149  if (change.rm || change.rm_shallow) {
14150    return {
14151      kind: "delete",
14152      title: change.rm_shallow ? "Removed the element, kept its contents" : "Removed the element",
14153      target
14154    };
14155  }
14156  if (change.dup) {
14157    const nCopies = Number(change.dup) || 0;
14158    return {
14159      kind: "insert",
14160      title: nCopies == 1 ? "Duplicated the element" : `Made ${nCopies + 1} copies of the element`,
14161      target
14162    };
14163  }
14164  if (change.mv) {
14165    return {
14166      kind: "move",
14167      title: `Moved the element ${change.mv} ${qelemIdToText(change.mv_qelem_id)}`,
14168      target
14169    };
14170  }
14171  if (change.e || change.e_shallow) {
14172    const spec = change.e ?? change.e_shallow;
14173    const tag = typeof spec == "string" ? "text" : "<" + spec[0] + ">";
14174    const where = change.e ? change.where ?? "inside" : "around";
14175    return {
14176      kind: "insert",
14177      title: `Added ${tag} ${where} the element`,
14178      target
14179    };
14180  }
14181  if (change.events && !(change.inner_html || change.text || change.attrs)) {
14182    const parts = describeEvents(change.events);
14183    return {
14184      kind: "behavior",
14185      title: parts || "Added an event tracker",
14186      target
14187    };
14188  }
14189  if (change.delay) {
14190    const ms = change.delay.ms || 0;
14191    const reqText = change.delay.req_text;
14192    let title = "Later changes wait for this element";
14193    if (reqText) {
14194      title += ` to say "${reqText}"`;
14195    }
14196    if (ms) {
14197      title += `, then ${ms} ms`;
14198    }
14199    return {
14200      kind: "behavior",
14201      title,
14202      target
14203    };
14204  }
14205  if (change.inner_html || change.text) {
14206    const style = change.attrs?.style;
14207    const after = splicesToText(change.inner_html ?? change.text);
14208    return {
14209      kind: "text",
14210      title: change.inner_html ? "Changed the content" : "Changed the text",
14211      target,
14212      after: style == void 0 ? after : after + (after ? "; " : "") + "style: " + style,
14213      colors: colorsIn(style ?? "")
14214    };
14215  }
14216  if (change.attrs) {
14217    const names = Object.keys(change.attrs);
14218    const style = change.attrs.style;
14219    if (names.length == 1 && names[0] == "style") {
14220      return {
14221        kind: "style",
14222        title: "Restyled the element",
14223        target,
14224        after: style ?? "(cleared)",
14225        colors: colorsIn(style ?? "")
14226      };
14227    }
14228    const shown = names.filter((v3) => v3 != "style");
14229    const after = shown.map((name) => name + ": " + (change.attrs[name] ?? "(removed)")).join("; ");
14230    return {
14231      kind: style != void 0 ? "style" : "other",
14232      title: shown.length == 1 ? `Changed ${shown[0]}` : `Changed ${shown.length} attributes`,
14233      target,
14234      after: style == void 0 ? after : after + (after ? "; " : "") + "style: " + style,
14235      colors: colorsIn(style ?? "")
14236    };
14237  }
14238  return {
14239    kind: "other",
14240    title: "Edited the element",
14241    target
14242  };
14243}
14244function describeEvents(events) {
14245  return !Array.isArray(events) ? "" : events.map((v3) => describeEvent(v3 ?? {})).filter((v3) => v3).join("; ");
14246}
14247function describeEvent(event) {
14248  const on = "On " + (event.onwhat ?? "click");
14249  if (Number(event.action_id) == -1 && event.action_state == "extra") {
14250    return on + ": count as a click on this action";
14251  }
14252  if (event.fire_action != void 0) {
14253    return on + ": execute action " + event.fire_action;
14254  }
14255  if (event.custom_event != void 0) {
14256    return on + ": trigger custom event " + event.custom_event + (event.value ? " = " + event.value : "");
14257  }
14258  if (event.rm_elem) {
14259    return on + ": remove " + event.rm_elem;
14260  }
14261  if (event.js) {
14262    return on + ": run Javascript";
14263  }
14264  return on;
14265}
14266function colorsIn(text) {
14267  const colors = text.match(RE_COLOR);
14268  return colors ? colors.slice(0, 4) : [];
14269}
14270
14271// src/components/live/ChangesSidebar.tsx
14272var PAGE_AI_CHIPS = [
14273  "Rewrite this page for enterprise buyers",
14274  "Make the calls to action more urgent",
14275  "Tighten every paragraph"
14276];
14277var SCOPE_CHIP = {
14278  page: {
14279    label: "this page",
14280    hint: "Applies only on the address it was recorded on"
14281  },
14282  shape: {
14283    label: "same layout",
14284    hint: "Applies on pages where this element sits in the same structure"
14285  },
14286  any: {
14287    label: "anywhere",
14288    hint: "Applies on every page where the saved selector matches"
14289  }
14290};
14291function widgetTypeOf(change) {
14292  const spec = change.e;
14293  const attrs = Array.isArray(spec) && typeof spec[0] == "string" ? spec[1] : null;
14294  return attrs && typeof attrs == "object" ? String(attrs["data-pz-widget"] ?? "") : "";
14295}
14296var SCOPE_PICKER = [
14297  [
14298    "page",
14299    "This page only"
14300  ],
14301  [
14302    "shape",
14303    "Pages that look the same"
14304  ],
14305  [
14306    "any",
14307    "Anywhere it matches"
14308  ]
14309];
14310function ClicksToggle(props) {
14311  const what = props.isInsert ? "the added element" : "this element";
14312  const title = props.blocked ? props.on ? "This action has a form, so this click is reported as a form SUBMISSION. Click to stop counting it." : "This action has a form, so its Clicks number counts form submissions - clicks are not counted here." : props.on ? `Clicks on ${what} count toward this action's Clicks. Click to stop counting them.` : `Count clicks on ${what} toward this action's Clicks.`;
14313  return /* @__PURE__ */ u2("button", {
14314    type: "button",
14315    class: "le-clicks" + (props.on ? props.blocked ? " le-warn" : " le-on" : ""),
14316    "aria-pressed": props.on,
14317    disabled: props.disabled || !props.on && props.blocked,
14318    "data-tooltip": title,
14319    "data-tooltip-pos": "left",
14320    onClick: (ev) => {
14321      ev.stopPropagation();
14322      props.onToggle(!props.on);
14323    },
14324    children: [
14325      li.cursor,
14326      props.on ? "Counts clicks" : "Count clicks"
14327    ]
14328  });
14329}
14330function ChangesSidebar(props) {
14331  const [href, setHref] = p2(props.href);
14332  const [hrefEdited, setHrefEdited] = p2(false);
14333  const [confirming, setConfirming] = p2(-1);
14334  const [isDiscarding, setIsDiscarding] = p2(false);
14335  const [isTipShown, setIsTipShown] = p2(true);
14336  const bodyRef = F2(null);
14337  _2(() => {
14338    bodyRef.current?.querySelector(".le-change.le-on")?.scrollIntoView({
14339      block: "nearest"
14340    });
14341  }, [
14342    props.current
14343  ]);
14344  const shownHref = hrefEdited ? href : props.href;
14345  const pausedTip = props.isPeeking ? "Put the changes back first - editing is paused while you compare" : "";
14346  return /* @__PURE__ */ u2("aside", {
14347    class: "le-root le-side" + (props.isLeft ? " le-left" : "") + (props.isCollapsed ? " le-collapsed" : ""),
14348    children: [
14349      /* @__PURE__ */ u2("div", {
14350        class: "le-peek",
14351        "data-tooltip": "Show or hide this panel",
14352        "data-tooltip-pos": "left",
14353        onClick: () => props.onCollapse(!props.isCollapsed),
14354        children: props.isCollapsed == props.isLeft ? li.chevronLeft : li.chevronRight
14355      }),
14356      /* @__PURE__ */ u2("div", {
14357        class: "le-side-top",
14358        children: [
14359          props.logoSrc && /* @__PURE__ */ u2("span", {
14360            class: "le-logo",
14361            children: /* @__PURE__ */ u2("img", {
14362              src: props.logoSrc,
14363              alt: "Personyze"
14364            })
14365          }),
14366          /* @__PURE__ */ u2("div", {
14367            class: "le-side-title",
14368            children: [
14369              /* @__PURE__ */ u2("b", {
14370                children: "Live Editor"
14371              }),
14372              /* @__PURE__ */ u2("small", {
14373                class: "le-ctx",
14374                "data-tooltip": "You're editing inside this campaign and action - 'Done editing' returns you to Personyze",
14375                "data-tooltip-pos": "left",
14376                children: [
14377                  props.campaignName && /* @__PURE__ */ u2(k, {
14378                    children: [
14379                      /* @__PURE__ */ u2("span", {
14380                        class: "le-ctx-campaign",
14381                        children: props.campaignName
14382                      }),
14383                      /* @__PURE__ */ u2("span", {
14384                        children: "\u203A"
14385                      })
14386                    ]
14387                  }),
14388                  /* @__PURE__ */ u2("span", {
14389                    class: "le-ctx-action",
14390                    children: props.actionName || "this action"
14391                  })
14392                ]
14393              })
14394            ]
14395          }),
14396          /* @__PURE__ */ u2("button", {
14397            type: "button",
14398            class: "le-icon",
14399            "data-tooltip": "Move this panel to the other side",
14400            "data-tooltip-pos": "left",
14401            onClick: () => props.onDock(!props.isLeft),
14402            children: li.dock
14403          })
14404        ]
14405      }),
14406      /* @__PURE__ */ u2("div", {
14407        class: "le-urlbar-wrap",
14408        children: /* @__PURE__ */ u2("div", {
14409          class: "le-urlbar",
14410          children: [
14411            /* @__PURE__ */ u2("input", {
14412              value: shownHref,
14413              "aria-label": "Page being edited",
14414              onInput: (ev) => {
14415                setHrefEdited(true);
14416                setHref(ev.target.value);
14417              },
14418              onKeyDown: (ev) => {
14419                if (ev.key == "Enter") {
14420                  props.onGo(shownHref);
14421                }
14422              }
14423            }),
14424            /* @__PURE__ */ u2("button", {
14425              type: "button",
14426              class: "le-icon",
14427              "data-tooltip": "Reload the page",
14428              "data-tooltip-pos": "left",
14429              onClick: props.onReload,
14430              children: li.reload
14431            }),
14432            /* @__PURE__ */ u2("button", {
14433              type: "button",
14434              class: "le-go",
14435              title: "Load this URL in the editor",
14436              onClick: () => props.onGo(shownHref),
14437              children: "Go"
14438            })
14439          ]
14440        })
14441      }),
14442      props.hasAi && !props.isPeeking && /* @__PURE__ */ u2(PageAiCard, {
14443        onSend: props.onPageAi
14444      }),
14445      /* @__PURE__ */ u2("div", {
14446        class: "le-changes-head",
14447        children: [
14448          /* @__PURE__ */ u2("b", {
14449            children: "Changes"
14450          }),
14451          /* @__PURE__ */ u2("span", {
14452            class: "le-count",
14453            children: !props.changes.length ? "0" : `${Math.min(props.current + 1, props.changes.length)} of ${props.changes.length}`
14454          }),
14455          /* @__PURE__ */ u2("div", {
14456            class: "le-nav-row",
14457            children: [
14458              /* @__PURE__ */ u2("button", {
14459                type: "button",
14460                class: "le-icon",
14461                "data-tooltip": "Previous change  K",
14462                "data-tooltip-pos": "left",
14463                disabled: !props.changes.length,
14464                onClick: () => props.onCurrent((props.current - 1 + props.changes.length) % props.changes.length),
14465                children: li.chevronLeft
14466              }),
14467              /* @__PURE__ */ u2("button", {
14468                type: "button",
14469                class: "le-icon",
14470                "data-tooltip": "Next change  J",
14471                "data-tooltip-pos": "left",
14472                disabled: !props.changes.length,
14473                onClick: () => props.onCurrent((props.current + 1) % props.changes.length),
14474                children: li.chevronRight
14475              })
14476            ]
14477          })
14478        ]
14479      }),
14480      props.changes.length >
14480 0 && (isDiscarding ? /* @__PURE__ */ u2("div", {
14481        class: "le-discardrow",
14482        children: [
14483          /* @__PURE__ */ u2("span", {
14484            children: [
14485              "Undo all ",
14486              props.changes.length,
14487              " and put the page back?"
14488            ]
14489          }),
14490          /* @__PURE__ */ u2("button", {
14491            type: "button",
14492            class: "le-btn",
14493            onClick: () => setIsDiscarding(false),
14494            children: "Keep them"
14495          }),
14496          /* @__PURE__ */ u2("button", {
14497            type: "button",
14498            class: "le-btn le-danger",
14499            onClick: () => {
14500              setIsDiscarding(false);
14501              props.onDiscardAll();
14502            },
14503            children: "Discard all"
14504          })
14505        ]
14506      }) : /* @__PURE__ */ u2("div", {
14507        class: "le-discardrow",
14508        children: [
14509          /* @__PURE__ */ u2("button", {
14510            type: "button",
14511            class: "le-btn" + (props.isPeeking ? " le-primary" : ""),
14512            title: props.isPeeking ? "Put your changes back on the page" : "Roll every change back so you can see the page as it arrived. Nothing is lost - press again to put them back.",
14513            onClick: props.onPeek,
14514            children: [
14515              li.eye,
14516              props.isPeeking ? "Put changes back" : "Show original"
14517            ]
14518          }),
14519          /* @__PURE__ */ u2("button", {
14520            type: "button",
14521            class: "le-btn",
14522            disabled: props.isPeeking,
14523            title: "Undo every change and empty this list - the page goes back to how it arrived. Nothing is stored until you press Done editing, so this cannot lose anything already saved.",
14524            onClick: () => setIsDiscarding(true),
14525            children: [
14526              li.trash,
14527              "Discard all"
14528            ]
14529          })
14530        ]
14531      })),
14532      props.changes.length > 1 && /* @__PURE__ */ u2("div", {
14533        class: "le-side-all",
14534        children: [
14535          /* @__PURE__ */ u2("label", {
14536            for: "le-scope-all",
14537            children: "Apply all on"
14538          }),
14539          /* @__PURE__ */ u2("select", {
14540            id: "le-scope-all",
14541            class: "le-scope-pick",
14542            title: "Set where EVERY change in this action is allowed to land",
14543            onChange: (ev) => {
14544              const sel = ev.currentTarget;
14545              const next = sel.value;
14546              sel.value = "";
14547              if (next) {
14548                props.onScopeAll(next);
14549              }
14550            },
14551            children: [
14552              /* @__PURE__ */ u2("option", {
14553                value: "",
14554                children: "Choose\u2026"
14555              }),
14556              SCOPE_PICKER.map(([id, label]) => /* @__PURE__ */ u2("option", {
14557                value: id,
14558                children: label
14559              }))
14560            ]
14561          })
14562        ]
14563      }),
14564      /* @__PURE__ */ u2("div", {
14565        class: "le-side-body",
14566        ref: bodyRef,
14567        children: props.changes.length == 0 ? /* @__PURE__ */ u2("div", {
14568          class: "le-empty",
14569          children: [
14570            /* @__PURE__ */ u2("h4", {
14571              children: "No changes yet"
14572            }),
14573            /* @__PURE__ */ u2("p", {
14574              children: [
14575                "Click any element on the page to start editing.",
14576                /* @__PURE__ */ u2("br", {}),
14577                "What you change shows up here as a reviewable list."
14578              ]
14579            })
14580          ]
14581        }) : props.changes.map((change, i4) => {
14582          const summary = summarizeChange(change);
14583          const isUndone = i4 >= props.nChanges;
14584          return /* @__PURE__ */ u2("div", {
14585            class: "le-change" + (i4 == props.current ? " le-on" : "") + (isUndone ? " le-undone" : ""),
14586            onClick: () => props.onCurrent(i4),
14587            onMouseEnter: () => props.onHover(change),
14588            onMouseLeave: () => props.onHover(null),
14589            children: [
14590              /* @__PURE__ */ u2("div", {
14591                class: "le-change-top",
14592                children: [
14593                  /* @__PURE__ */ u2("span", {
14594                    class: "le-tag le-t-" + summary.kind,
14595                    title: "Change type: " + summary.kind,
14596                    children: summary.kind
14597                  }),
14598                  /* @__PURE__ */ u2("div", {
14599                    class: "le-change-desc",
14600                    children: summary.title
14601                  })
14602                ]
14603              }),
14604              /* @__PURE__ */ u2("code", {
14605                class: "le-selector",
14606                title: summary.target,
14607                children: summary.target
14608              }),
14609              /* @__PURE__ */ u2("span", {
14610                class: "le-scope-row",
14611                children: [
14612                  /* @__PURE__ */ u2("select", {
14613                    class: "le-scope-pick",
14614                    title: "Where this change is allowed to land",
14615                    value: String(change.scope ?? ""),
14616                    onClick: (ev) => ev.stopPropagation(),
14617                    onChange: (ev) => {
14618                      ev.stopPropagation();
14619                      props.onScope(i4, ev.currentTarget.value);
14620                    },
14621                    children: [
14622                      !SCOPE_CHIP[String(change.scope)] && /* @__PURE__ */ u2("option", {
14623                        value: "",
14624                        children: "Apply on: not set"
14625                      }),
14626                      SCOPE_PICKER.map(([id, label]) => /* @__PURE__ */ u2("option", {
14627                        value: id,
14628                        children: "Apply on: " + label
14629                      }))
14630                    ]
14631                  }),
14632                  /* @__PURE__ */ u2("select", {
14633                    class: "le-scope-pick",
14634                    "aria-label": "Show on",
14635                    "data-tooltip": "Which screens this change is for - phones are up to 767px wide",
14636                    "data-tooltip-pos": "left",
14637                    value: String(change.device ?? ""),
14638                    onClick: (ev) => ev.stopPropagation(),
14639                    onChange: (ev) => {
14640                      ev.stopPropagation();
14641                      props.onDevice(i4, ev.currentTarget.value);
14642                    },
14643                    children: SHOW_ON_OPTIONS.map((v3) => /* @__PURE__ */ u2("option", {
14644                      value: v3.id,
14645                      children: v3.label
14646                    }))
14647                  }),
14648                  changeOffersClicks(change) && /* @__PURE__ */ u2(ClicksToggle, {
14649                    on: countsClicks(change),
14650                    isInsert: !!change.e,
14651                    blocked: props.clicksBlocked,
14652                    disabled: isUndone,
14653                    onToggle: (on) => props.onClicks(i4, on)
14654                  }),
14655                  !deviceAllows(change.device, isPhoneScreen()) && /* @__PURE__ */ u2("span", {
14656                    class: "le-offpage",
14657                    title: "The editor shows every change. A visitor at this screen width would not get this one.",
14658                    children: change.device == "mobile" ? "phones only - not at this width" : "not on phones - not at this width"
14659                  }),
14660                  !changeAppliesHere(change) && /* @__PURE__ */ u2("span", {
14661                    class: "le-offpage",
14662                    title: (change.scope == "page" ? "Recorded for " + String(change.scope_url ?? "another address") + "." : "This page is not built the same way as the one it was recorded on.") + " You can see it here, but a visitor on this page would not.",
14663                    children: "visitors here won't see this"
14664                  })
14665                ]
14666              }),
14667              summary.after != void 0 && /* @__PURE__ */ u2("div", {
14668                class: "le-diff",
14669                children: [
14670                  /* @__PURE__ */ u2("div", {
14671                    class: "le-lbl",
14672                    children: "NOW"
14673                  }),
14674                  /* @__PURE__ */ u2("div", {
14675                    class: "le-after",
14676                    children: [
14677                      summary.colors?.map((color) => /* @__PURE__ */ u2("i", {
14678                        style: "display:inline-block; width:0.9em; height:0.9em; margin-right:0.3em; vertical-align:-0.1em; border:1px solid rgba(255,255,255,.15); border-radius:0.2em; background:" + color
14679                      }, color)),
14680                      summary.after.length > 200 ? summary.after.slice(0, 200) + "\u2026" : summary.after
14681                    ]
14682                  })
14683                ]
14684              }),
14685              isUndone && /* @__PURE__ */ u2("div", {
14686                class: "le-hint",
14687                style: "margin:0",
14688                children: "Undone - it will not be saved."
14689              }),
14690              confirming == i4 ? /* @__PURE__ */ u2("div", {
14691                class: "le-change-actions",
14692                children: [
14693                  /* @__PURE__ */ u2("span", {
14694                    class: "le-hint",
14695                    style: "margin:0; flex:10000 1 0",
14696                    children: "Undo everything back to here?"
14697                  }),
14698                  /* @__PURE__ */ u2("button", {
14699                    type: "button",
14700                    class: "le-btn",
14701                    onClick: (ev) => {
14702                      ev.stopPropagation();
14703                      setConfirming(-1);
14704                    },
14705                    children: "Cancel"
14706                  }),
14707                  /* @__PURE__ */ u2("button", {
14708                    type: "button",
14709                    class: "le-btn le-danger",
14710                    onClick: (ev) => {
14711                      ev.stopPropagation();
14712                      setConfirming(-1);
14713                      props.onJump(i4);
14714                    },
14715                    children: "Undo"
14716                  })
14717                ]
14718              }) : /* @__PURE__ */ u2("div", {
14719                class: "le-change-actions",
14720                children: [
14721                  widgetTypeOf(change) && /* @__PURE__ */ u2("button", {
14722                    type: "button",
14723                    class: "le-btn le-wkey",
14724                    title: "Open this widget's settings",
14725                    onClick: (ev) => {
14726                      ev.stopPropagation();
14727                      props.onWidgetSettings(change);
14728                    },
14729                    children: "Widget settings"
14730                  }),
14731                  /* @__PURE__ */ u2("button", {
14732                    type: "button",
14733                    class: "le-btn",
14734                    title: "Scroll to it and highlight it",
14735                    onClick: (ev) => {
14736                      ev.stopPropagation();
14737                      props.onFocus(change);
14738                    },
14739                    children: "Focus on page"
14740                  }),
14741                  !isUndone ? /* @__PURE__ */ u2("button", {
14742                    type: "button",
14743                    class: "le-btn le-danger",
14744                    disabled: props.isPeeking,
14745                    title: pausedTip || "Undo this change and everything after it",
14746                    onClick: (ev) => {
14747                      ev.stopPropagation();
14748                      setConfirming(i4);
14749                    },
14750                    children: "Undo"
14751                  }) : /* @__PURE__ */ u2("button", {
14752                    type: "button",
14753                    class: "le-btn",
14754                    disabled: props.isPeeking,
14755                    title: pausedTip || "Redo this change",
14756                    onClick: (ev) => {
14757                      ev.stopPropagation();
14758                      props.onJump(i4 + 1);
14759                    },
14760                    children: "Redo"
14761                  })
14762                ]
14763              })
14764            ]
14765          }, i4);
14766        })
14767      }),
14768      isTipShown && /* @__PURE__ */ u2("div", {
14769        class: "le-tip",
14770        children: [
14771          /* @__PURE__ */ u2("span", {
14772            children: li.pencil
14773          }),
14774          /* @__PURE__ */ u2("div", {
14775            children: [
14776              /* @__PURE__ */ u2("b", {
14777                children: "Tip:"
14778              }),
14779              " hover an element to highlight it, click to select. ",
14780              /* @__PURE__ */ u2("kbd", {
14781                children: "N"
14782              }),
14783              " toggles Navigate mode, ",
14784              /* @__PURE__ */ u2("kbd", {
14785                children: "Esc"
14786              }),
14787              " comes back."
14788            ]
14789          }),
14790          /* @__PURE__ */ u2("button", {
14791            type: "button",
14792            title: "Dismiss this tip",
14793            onClick: () => setIsTipShown(false),
14794            children: "\xD7"
14795          })
14796        ]
14797      }),
14798      /* @__PURE__ */ u2("div", {
14799        class: "le-side-foot",
14800        children: /* @__PURE__ */ u2("button", {
14801          type: "button",
14802          class: "le-btn le-primary",
14803          onClick: props.onDone,
14804          children: "Done editing - back to Personyze"
14805        })
14806      })
14807    ]
14808  });
14809}
14810function PageAiCard(props) {
14811  const [isOpen, setIsOpen] = p2(false);
14812  const [prompt, setPrompt] = p2("");
14813  const boxRef = F2(null);
14814  function send() {
14815    const text = prompt.trim();
14816    if (text) {
14817      setPrompt("");
14818      setIsOpen(false);
14819      props.onSend(text);
14820    }
14821  }
14822  return /* @__PURE__ */ u2("div", {
14823    class: "le-pageai",
14824    children: [
14825      /* @__PURE__ */ u2("button", {
14826        type: "button",
14827        class: "le-pageai-head",
14828        "aria-expanded": isOpen,
14829        onClick: () => {
14830          setIsOpen((v3) => !v3);
14831          setTimeout(() => boxRef.current?.focus(), 0);
14832        },
14833        children: [
14834          /* @__PURE__ */ u2("span", {
14835            class: "le-pageai-ico",
14836            children: li.sparkle
14837          }),
14838          /* @__PURE__ */ u2("span", {
14839            class: "le-pageai-txt",
14840            children: [
14841              /* @__PURE__ */ u2("b", {
14842                children: "Page-level AI"
14843              }),
14844              /* @__PURE__ */ u2("small", {
14845                children: "Ask for changes across the whole page"
14846              })
14847            ]
14848          }),
14849          isOpen ? li.chevronUp : li.chevronDown
14850        ]
14851      }),
14852      isOpen && /* @__PURE__ */ u2("div", {
14853        class: "le-pageai-body",
14854        children: [
14855          /* @__PURE__ */ u2("textarea", {
14856            ref: boxRef,
14857            class: "le-area",
14858            rows: 2,
14859            value: prompt,
14860            placeholder: "e.g. 'rewrite for enterprise buyers and make the CTAs more urgent'",
14861            onInput: (ev) => setPrompt(ev.target.value),
14862            onKeyDown: (ev) => {
14863              if (ev.key == "Enter" && !ev.shiftKey) {
14864                ev.preventDefault();
14865                send();
14866              }
14867            }
14868          }),
14869          /* @__PURE__ */ u2("div", {
14870            class: "le-chips",
14871            children: PAGE_AI_CHIPS.map((chip) => /* @__PURE__ */ u2("button", {
14872              type: "button",
14873              class: "le-chip",
14874              onClick: () => {
14875                setPrompt(chip);
14876                boxRef.current?.focus();
14877              },
14878              children: chip
14879            }, chip))
14880          }),
14881          /* @__PURE__ */ u2("div", {
14882            class: "le-btn-bar",
14883            children: [
14884              /* @__PURE__ */ u2("button", {
14885                type: "button",
14886                class: "le-btn",
14887                onClick: () => setIsOpen(false),
14888                children: "Cancel"
14889              }),
14890              /* @__PURE__ */ u2("button", {
14891                type: "button",
14892                class: "le-btn le-ai",
14893                disabled: !prompt.trim(),
14894                onClick: send,
14895                children: [
14896                  li.sparkle,
14897                  "Suggest edits"
14898                ]
14899              })
14900            ]
14901          })
14902        ]
14903      })
14904    ]
14905  });
14906}
14907
14908// src/components/live/DevicePicker.tsx
14909var DEVICE_WIDTHS = {
14910  desktop: 0,
14911  tablet: 768,
14912  mobile: 375
14913};
14914function DevicePicker(props) {
14915  const devices = [
14916    [
14917      "desktop",
14918      "Desktop",
14919      ""
14920    ],
14921    [
14922      "tablet",
14923      "Tablet",
14924      "768"
14925    ],
14926    [
14927      "mobile",
14928      "Mobile",
14929      "375"
14930    ]
14931  ];
14932  return /* @__PURE__ */ u2("div", {
14933    class: "le-root le-devices",
14934    role: "group",
14935    "aria-label": "Preview device",
14936    children: devices.map(([id, label, size]) => /* @__PURE__ */ u2("button", {
14937      type: "button",
14938      class: "le-device" + (id == props.device ? " le-on" : ""),
14939      "data-tooltip": id == "desktop" ? "Back to the window size you started with" : "Resize the window to " + size + "px wide",
14940      "data-tooltip-pos": "top",
14941      onClick: () => props.onDevice(id),
14942      children: [
14943        li[id],
14944        label,
14945        size && /* @__PURE__ */ u2("span", {
14946          class: "le-size",
14947          children: size
14948        })
14949      ]
14950    }, id))
14951  });
14952}
14953
14954// src/components/live/liveTooltip.tsx
14955var DELAY_MS = 350;
14956var GAP = 8;
14957var MARGIN = 8;
14958function placeTip(r3, tip, prefer, vw, vh) {
14959  const clampX = (x2) => Math.max(MARGIN, Math.min(x2, vw - MARGIN - tip.width));
14960  const clampY = (y3) => Math.max(MARGIN, Math.min(y3, vh - MARGIN - tip.height));
14961  if (prefer == "left") {
14962    const left = r3.left - GAP - tip.width;
14963    if (left >= MARGIN) {
14964      return {
14965        left,
14966        top: clampY(r3.top + r3.height / 2 - tip.height / 2),
14967        side: "left"
14968      };
14969    }
14970  }
14971  const below = r3.bottom + GAP;
14972  const above = r3.top - GAP - tip.height;
14973  const fitsBelow = below + tip.height <= vh - MARGIN;
14974  const fitsAbove = above >= MARGIN;
14975  const side = prefer == "top" ? fitsAbove || !fitsBelow ? "top" : "bottom" : fitsBelow || !fitsAbove ? "bottom" : "top";
14976  return {
14977    left: clampX(r3.left + r3.width / 2 - tip.width / 2),
14978    top: clampY(side == "top" ? above : below),
14979    side
14980  };
14981}
14982function TipLayer() {
14983  const ref = F2(null);
14984  _2(() => {
14985    const tip = ref.current;
14986    if (!tip) {
14987      return;
14988    }
14989    const root = tip.getRootNode();
14990    let timer = 0;
14991    let target = null;
14992    const hide = () => {
14993      clearTimeout(timer);
14994      target = null;
14995      tip.classList.remove("le-on");
14996    };
14997    const place = (el) => {
14998      tip.textContent = el.getAttribute("data-tooltip") ?? "";
14999      tip.style.left = "0px";
15000      tip.style.top = "0px";
15001      const p3 = placeTip(el.getBoundingClientRect(), tip.getBoundingClientRect(), el.getAttribute("data-tooltip-pos") ?? "bottom", globalThis.innerWidth, globalThis.innerHeight);
15002      tip.style.left = Math.round(p3.left) + "px";
15003      tip.style.top = Math.round(p3.top) + "px";
15004      tip.setAttribute("data-side", p3.side);
15005      tip.classList.add("le-on");
15006    };
15007    const arm = (el, delay) => {
15008      if (el === target) {
15009        return;
15010      }
15011      hide();
15012      if (!el?.getAttribute("data-tooltip")) {
15013        return;
15014      }
15015      target = el;
15016      timer = setTimeout(() => {
15017        if (target === el && el.isConnected) place(el);
15018      }, delay);
15019    };
15020    const tipOf = (ev) => ev.target?.closest?.("[data-tooltip]") ?? null;
15021    const onOver = (ev) => arm(tipOf(ev), DELAY_MS);
15022    const onOut = (ev) => {
15023      const to = ev.relatedTarget;
15024      if (!(target && to && target.contains(to))) {
15025        hide();
15026      }
15027    };
15028    const onFocus = (ev) => arm(tipOf(ev), 0);
15029    const onKey = (ev) => {
15030      if (ev.key == "Escape") hide();
15031    };
15032    root.addEventListener("pointerover", onOver);
15033    root.addEventListener("pointerout", onOut);
15034    root.addEventListener("focusin", onFocus);
15035    root.addEventListener("focusout", hide);
15036    root.addEventListener("pointerdown", hide);
15037    root.addEventListener("keydown", onKey);
15038    globalThis.addEventListener("scroll", hide, true);
15039    return () => {
15040      hide();
15041      root.removeEventListener("pointerover", onOver);
15042      root.removeEventListener("pointerout", onOut);
15043      root.removeEventListener("focusin", onFocus);
15044      root.removeEventListener("focusout", hide);
15045      root.removeEventListener("pointerdown", hide);
15046      root.removeEventListener("keydown", onKey);
15047      globalThis.removeEventListener("scroll", hide, true);
15048    };
15049  }, []);
15050  return /* @__PURE__ */ u2("div", {
15051    ref,
15052    class: "le-root le-tooltip",
15053    role: "tooltip",
15054    "aria-hidden": "true"
15055  });
15056}
15057
15058// src/components/live/panels/caretOwner.ts
15059var PAGE_GESTURES = [
15060  "mousedown",
15061  "keydown"
15062];
15063function installPageTakeover(elem, onPageTook) {
15064  for (const type of PAGE_GESTURES) {
15065    elem.addEventListener(type, onPageTook);
15066  }
15067  return () => {
15068    for (const type of PAGE_GESTURES) {
15069      elem.removeEventListener(type, onPageTook);
15070    }
15071  };
15072}
15073function installSavedSelection(doc, elem, opts) {
15074  function handler() {
15075    if (opts.skip?.()) {
15076      return;
15077    }
15078    const sel = doc.getSelection();
15079    if (!sel) {
15080      return;
15081    }
15082    const cur = [];
15083    for (let i4 = 0; i4 < sel.rangeCount; i4++) {
15084      const r3 = sel.getRangeAt(i4);
15085      const container = r3.commonAncestorContainer;
15086      if (container === elem || elem.contains(container)) {
15087        cur.push(r3.cloneRange());
15088      }
15089    }
15090    if (cur.length) {
15091      opts.onSaved(cur);
15092    }
15093  }
15094  doc.addEventListener("selectionchange", handler);
15095  return () => doc.removeEventListener("selectionchange", handler);
15096}
15097
15098// src/modules/vartoken/private/vartoken.ts
15099var RE_TOKEN = /^\$\{(\w+)\s*((?:[^'"}]|'[^']*'|"[^"]*")*)\}$/;
15100var RE_PATH_STEP = /^-(?:>|&gt;)\s*(\w+|(?:'[^']*')+|(?:"[^"]*")+)\s*/;
15101var RE_SUBST_FUNC = /^:(\w+)(?:\(((?:'[^']*'|"[^"]*"|[^'"\)])*)\))?\s*/;
15102var RE_FUNC_ARGS = /\s*(?:'[^']*')+\s*|\s*(?:"[^"]*")+\s*|[^'"\),]+/g;
15103var DIRECTIVES = [
15104  "foreach",
15105  "if",
15106  "else",
15107  "case",
15108  "when",
15109  "end",
15110  "endforeach",
15111  "endif",
15112  "endcase",
15113  "menu"
15114];
15115function unquote(s3) {
15116  if (s3.length >= 2 && s3[0] == "'" && s3[s3.length - 1] == "'") {
15117    return s3.slice(1, -1).replaceAll("''", "'");
15118  }
15119  if (s3.length >= 2 && s3[0] == '"' && s3[s3.length - 1] == '"') {
15120    return s3.slice(1, -1).replaceAll('""', '"');
15121  }
15122  return s3;
15123}
15124function quoteStr(s3) {
15125  return "'" + s3.replaceAll("'", "''") + "'";
15126}
15127function argToLiteral(arg) {
15128  return /^[+-]?\d+$/.test(arg) ? arg : quoteStr(arg);
15129}
15130function splitFuncArgs(argsStr) {
15131  if (!argsStr) {
15132    return [];
15133  }
15134  const out = [];
15135  for (const m2 of argsStr.matchAll(RE_FUNC_ARGS)) {
15136    const tok = m2[0].trim();
15137    if (tok) {
15138      out.push(unquote(tok));
15139    }
15140  }
15141  return out;
15142}
15143function pathToString(path) {
15144  let out = path[0] ?? "";
15145  for (let i4 = 1; i4 < path.length; i4++) {
15146    const step = path[i4];
15147    out += "->" + (/^[a-z_]\w*$/i.test(step) ? step : quoteStr(step));
15148  }
15149  return out;
15150}
15151function funcsToString(funcs) {
15152  let out = "";
15153  for (const f4 of funcs) {
15154    out += ":" + f4.name + (f4.args.length ? "(" + f4.args.map(argToLiteral).join(", ") + ")" : "");
15155  }
15156  return out;
15157}
15158function parseVarToken(text) {
15159  const m2 = RE_TOKEN.exec(text.trim());
15160  if (!m2) {
15161    return void 0;
15162  }
15163  const ident = m2[1].toLowerCase();
15164  let rest = m2[2] ?? "";
15165  if (DIRECTIVES.includes(ident) || rest.startsWith("(")) {
15166    return void 0;
15167  }
15168  const path = [
15169    ident
15170  ];
15171  while (true) {
15172    const pm = RE_PATH_STEP.exec(rest);
15173    if (!pm) {
15174      break;
15175    }
15176    const tok = pm[1];
15177    path.push(tok[0] == "'" || tok[0] == '"' ? unquote(tok) : tok.toLowerCase());
15178    rest = rest.slice(pm[0].length);
15179  }
15180  let htmlEncoded = false;
15181  let jsonEncoded = false;
15182  let defaultValue;
15183  const funcs = [];
15184  while (true) {
15185    const fm = RE_SUBST_FUNC.exec(rest);
15186    if (!fm) {
15187      break;
15188    }
15189    const name = fm[1].toLowerCase();
15190    if (name == "html") {
15191      htmlEncoded = true;
15192    } else if (name == "json") {
15193      jsonEncoded = true;
15194    } else if (name == "default") {
15195      defaultValue = splitFuncArgs(fm[2])[0] ?? "";
15196    } else {
15197      funcs.push({
15198        name,
15199        args: splitFuncArgs(fm[2])
15200      });
15201    }
15202    rest = rest.slice(fm[0].length);
15203  }
15204  if (rest.trim() != "") {
15205    return void 0;
15206  }
15207  return {
15208    path,
15209    funcs,
15210    defaultValue,
15211    htmlEncoded,
15212    jsonEncoded
15213  };
15214}
15215function varExpr(path, funcs, flags = {}) {
15216  let body = pathToString(path) + funcsToString(funcs.filter((f4) => f4.name != "default"));
15217  if (flags.defaultValue != null && flags.defaultValue !== "") {
15218    body += ":default(" + quoteStr(flags.defaultValue) + ")";
15219  }
15220  if (flags.jsonEncoded) {
15221    body += ":json";
15222  }
15223  if (flags.htmlEncoded) {
15224    body += ":html";
15225  }
15226  return "${" + body + "}";
15227}
15228var RE_ANY_TOKEN = /\$\{[^}]*\}/g;
15229function tokenAtCaret(text, sel) {
15230  RE_ANY_TOKEN.lastIndex = 0;
15231  let m2;
15232  while (m2 = RE_ANY_TOKEN.exec(text)) {
15233    const start2 = m2.index;
15234    const end = start2 + m2[0].length;
15235    if (sel >= start2 && sel <= end) {
15236      return {
15237        start: start2,
15238        end,
15239        text: m2[0]
15240      };
15241    }
15242  }
15243  return void 0;
15244}
15245var KNOWN_FUNCS = [
15246  {
15247    name: "ellipsis",
15248    title: 'Cut long text, and show "\u2026" at the end, if was cut',
15249    args: [
15250      {
15251        title: "Show at most letters",
15252        inputType: "number",
15253        defaultValue: "50"
15254      }
15255    ]
15256  },
15257  {
15258    name: "replace",
15259    title: "Replace text",
15260    args: [
15261      {
15262        title: "Find"
15263      },
15264      {
15265        title: "Replace with"
15266      }
15267    ]
15268  },
15269  // Kept in step with the panel's catalog. The default separator is not
15270  // decoration: `st_sud__text_before(x, '')` returns '' (Locate('', x) is 1),
15271  // so an empty box would blank the value the moment the row is added.
15272  {
15273    name: "text_before",
15274    title: "Keep only the text before a word",
15275    args: [
15276      {
15277        title: "Word to cut at",
15278        defaultValue: " - "
15279      }
15280    ]
15281  },
15282  {
15283    name: "text_after",
15284    title: "Keep the text from a word on",
15285    args: [
15286      {
15287        title: "Word to start at",
15288        defaultValue: " - "
15289      }
15290    ]
15291  },
15292  {
15293    name: "lc",
15294    title: "Convert text to lower case",
15295    args: []
15296  },
15297  {
15298    name: "uc",
15299    title: "Convert text to upper case",
15300    args: []
15301  },
15302  {
15303    name: "price",
15304    title: "Format price",
15305    args: [
15306      {
15307        title: "Price format",
15308        inputType: "price"
15309      }
15310    ]
15311  },
15312  {
15313    name: "ceil",
15314    title: "Round number towards infinity",
15315    args: []
15316  },
15317  {
15318    name: "floor",
15319    title: "Round number towards minus infinity",
15320    args: []
15321  },
15322  {
15323    name: "round",
15324    title: "Round number",
15325    args: []
15326  }
15327];
15328function funcTitle(func) {
15329  return KNOWN_FUNCS.find((d3) => d3.name == func.name.toLowerCase())?.title ?? func.name;
15330}
15331function funcArgRows(func) {
15332  const def = KNOWN_FUNCS.find((d3) => d3.name == func.name.toLowerCase());
15333  if (def) {
15334    return def.args;
15335  }
15336  return func.args.map((v3, i4) => ({
15337    title: `Argument ${i4 + 1}`,
15338    defaultValue: v3
15339  }));
15340}
15341function newFunc(name) {
15342  const def = KNOWN_FUNCS.find((d3) => d3.name == name.toLowerCase());
15343  return {
15344    name,
15345    args: (def?.args ?? []).map((a3) => a3.defaultValue ?? "")
15346  };
15347}
15348
15349// src/components/live/VarPicker.tsx
15350var SESSION_VARS = [
15351  {
15352    name: "vid",
15353    label: "Page view ID"
15354  },
15355  {
15356    name: "1_vid",
15357    label: "First page view ID in session"
15358  },
15359  {
15360    name: "st",
15361    label: "Session counter"
15362  },
15363  {
15364    name: "country",
15365    label: "User country"
15366  },
15367  {
15368    name: "city",
15369    label: "User city"
15370  },
15371  {
15372    name: "ip",
15373    label: "User IP"
15374  },
15375  {
15376    name: "ur",
15377    label: "URL of current page"
15378  },
15379  {
15380    name: "rf",
15381    label: "Referrer of current page"
15382  },
15383  {
15384    name: "srch_word",
15385    label: "Search word"
15386  },
15387  {
15388    name: "1_srch_word",
15389    label: "Search word in first page view"
15390  }
15391];
15392function useUserColumns() {
15393  const [cols, setCols] = p2([]);
15394  _2(() => {
15395    if (isJsapiConfigured()) {
15396      queryRows("SELECT name, title FROM other_columns_info WHERE table_name='social' ORDER BY ordinal DESC").then((rows) => setCols(rows.map((r3) => ({
15397        path: [
15398          String(r3.name)
15399        ],
15400        label: String(r3.title || r3.name)
15401      }))), (error) => {
15402        console.error(error);
15403      });
15404    }
15405  }, []);
15406  return cols;
15407}
15408function FuncsEditor(props) {
15409  function setArg(funcI, argI, value) {
15410    props.onChange(props.funcs.map((f4, i4) => i4 != funcI ? f4 : {
15411      ...f4,
15412      args: Object.assign([
15413        ...f4.args
15414      ], {
15415        [argI]: value
15416      })
15417    }));
15418  }
15419  return /* @__PURE__ */ u2("div", {
15420    class: "le-vp-funcs",
15421    children: [
15422      /* @__PURE__ */ u2("div", {
15423        class: "le-vp-funcs-hd",
15424        children: "Convert the value"
15425      }),
15426      props.funcs.map((f4, i4) => /* @__PURE__ */ u2("div", {
15427        class: "le-vp-func",
15428        children: [
15429          /* @__PURE__ */ u2("div", {
15430            class: "le-vp-func-hd",
15431            children: [
15432              /* @__PURE__ */ u2("span", {
15433                children: funcTitle(f4)
15434              }),
15435              /* @__PURE__ */ u2("button", {
15436                type: "button",
15437                title: "Remove",
15438                onClick: () => props.onChange(props.funcs.filter((_3, k3) => k3 != i4)),
15439                children: li.cross
15440              })
15441            ]
15442          }),
15443          funcArgRows(f4).map((a3, argI) => /* @__PURE__ */ u2("label", {
15444            class: "le-vp-func-arg",
15445            children: [
15446              /* @__PURE__ */ u2("span", {
15447                children: a3.title || `Argument ${argI + 1}`
15448              }),
15449              /* @__PURE__ */ u2("input", {
15450                type: a3.inputType == "number" ? "number" : "text",
15451                class: "le-input",
15452                value: f4.args[argI] ?? a3.defaultValue ?? "",
15453                placeholder: a3.inputType == "price" ? "e.g. $#,###.##" : void 0,
15454                onInput: (ev) => setArg(i4, argI, ev.target.value)
15455              })
15456            ]
15457          }, argI))
15458        ]
15459      }, `${i4}-${f4.name}`)),
15460      /* @__PURE__ */ u2("select", {
15461        class: "le-select",
15462        value: "",
15463        onChange: (ev) => {
15464          const name = ev.target.value;
15465          ev.target.value = "";
15466          if (name) {
15467            props.onChange([
15468              ...props.funcs,
15469              newFunc(name)
15470            ]);
15471          }
15472        },
15473        children: [
15474          /* @__PURE__ */ u2("option", {
15475            value: "",
15476            children: "+ Add function"
15477          }),
15478          KNOWN_FUNCS.map((d3) => /* @__PURE__ */ u2("option", {
15479            value: d3.name,
15480            children: d3.title
15481          }, d3.name))
15482        ]
15483      })
15484    ]
15485  });
15486}
15487function VarPicker(props) {
15488  const start2 = q2(() => parseVarToken(props.initialToken ?? ""), []);
15489  const [selected, setSelected] = p2(start2?.path);
15490  const [funcs, setFuncs] = p2(start2?.funcs ?? []);
15491  const [query2, setQuery] = p2("");
15492  const searchRef = F2(null);
15493  const userCols = useUserColumns();
15494  _2(() => searchRef.current?.focus(), []);
15495  function commit() {
15496    if (selected) {
15497      props.onPick(varExpr(selected, funcs, start2 ?? {}));
15498    }
15499  }
15500  function onKeyDown(ev) {
15501    if (ev.key == "Escape") {
15502      ev.stopPropagation();
15503      props.onCancel();
15504    } else if (ev.key == "Enter" && selected) {
15505      ev.stopPropagation();
15506      ev.preventDefault();
15507      commit();
15508    }
15509  }
15510  const sections = q2(() => [
15511    {
15512      title: "User variables",
15513      entries: [
15514        {
15515          path: [
15516            "id"
15517          ],
15518          label: "User ID"
15519        },
15520        ...userCols
15521      ]
15522    },
15523    {
15524      title: "Session & context",
15525      entries: SESSION_VARS.map((v3) => ({
15526        path: [
15527          v3.name
15528        ],
15529        label: v3.label
15530      }))
15531    }
15532  ], [
15533    userCols
15534  ]);
15535  const q3 = query2.trim().toLowerCase();
15536  const shown = !q3 ? sections : sections.map((s3) => ({
15537    title: s3.title,
15538    entries: s3.entries.filter((e3) => e3.label.toLowerCase().includes(q3) || e3.path.join("->").toLowerCase().includes(q3))
15539  })).filter((s3) => s3.entries.length > 0);
15540  const selectedKey = selected?.join("->") ?? "";
15541  const unparsed = props.initialToken && !start2 ? props.initialToken : "";
15542  return (
15543    // keep the page selection, which would make this modal's inputs unfocusable.
15544    /* @__PURE__ */ u2("div", {
15545      class: "le-root le-vp-overlay",
15546      onMouseDown: (ev) => {
15547        ev.stopPropagation();
15548        if (ev.target === ev.currentTarget) {
15549          props.onCancel();
15550        }
15551      },
15552      onKeyDown,
15553      children: /* @__PURE__ */ u2("div", {
15554        class: "le-vp",
15555        role: "dialog",
15556        "aria-modal": "true",
15557        "aria-label": "Insert variable",
15558        children: [
15559          /* @__PURE__ */ u2("div", {
15560            class: "le-vp-head",
15561            children: [
15562              /* @__PURE__ */ u2("div", {
15563                children: [
15564                  /* @__PURE__ */ u2("h3", {
15565                    children: start2 ? "Edit variable" : "Insert variable"
15566                  }),
15567                  /* @__PURE__ */ u2("p", {
15568                    children: "Pick a field, and optionally convert its value."
15569                  })
15570                ]
15571              }),
15572              /* @__PURE__ */ u2("button", {
15573                type: "button",
15574                class: "le-icon",
15575                title: "Close",
15576                onClick: props.onCancel,
15577                children: li.cross
15578              })
15579            ]
15580          }),
15581          /* @__PURE__ */ u2("div", {
15582            class: "le-vp-search",
15583            children: /* @__PURE__ */ u2("input", {
15584              ref: searchRef,
15585              type: "search",
15586              class: "le-input",
15587              value: query2,
15588              placeholder: "Search variables\u2026",
15589              "aria-label": "Search variables",
15590              onInput: (ev) => setQuery(ev.target.value)
15591            })
15592          }),
15593          /* @__PURE__ */ u2("div", {
15594            class: "le-vp-body",
15595            children: [
15596              unparsed && /* @__PURE__ */ u2("div", {
15597                class: "le-vp-empty",
15598                children: [
15599                  /* @__PURE__ */ u2("code", {
15600                    children: unparsed
15601                  }),
15602                  " is a calculated value - it can't be edited here. Picking a variable below replaces it."
15603                ]
15604              }),
15605              shown.map((s3) => /* @__PURE__ */ u2("div", {
15606                class: "le-vp-sec",
15607                children: [
15608                  /* @__PURE__ */ u2("h4", {
15609                    children: s3.title
15610                  }),
15611                  /* @__PURE__ */ u2("div", {
15612                    class: "le-vp-grid",
15613                    children: s3.entries.map((e3) => {
15614                      const key = e3.path.join("->");
15615                      return /* @__PURE__ */ u2("button", {
15616                        type: "button",
15617                        class: "le-vp-var" + (key == selectedKey ? " le-on" : ""),
15618                        onClick: () => setSelected(e3.path),
15619                        onDblClick: () => props.onPick(varExpr(e3.path, funcs, start2 ?? {})),
15620                        children: [
15621                          /* @__PURE__ */ u2("span", {
15622                            class: "le-vp-tok",
15623                            children: varExpr(e3.path, [])
15624                          }),
15625                          e3.label
15626                        ]
15627                      }, key);
15628                    })
15629                  })
15630                ]
15631              }, s3.title)),
15632              shown.length == 0 && /* @__PURE__ */ u2("div", {
15633                class: "le-vp-empty",
15634                children: [
15635                  'No variable matches "',
15636                  query2,
15637                  '".'
15638                ]
15639              })
15640            ]
15641          }),
15642          selected && /* @__PURE__ */ u2("div", {
15643            class: "le-vp-picked",
15644            children: [
15645              /* @__PURE__ */ u2("div", {
15646                class: "le-vp-expr",
15647                children: varExpr(selected, funcs, start2 ?? {})
15648              }),
15649              /* @__PURE__ */ u2(FuncsEditor, {
15650                funcs,
15651                onChange: setFuncs
15652              })
15653            ]
15654          }),
15655          /* @__PURE__ */ u2("div", {
15656            class: "le-vp-foot",
15657            children: [
15658              /* @__PURE__ */ u2("span", {
15659                class: "le-vp-hint",
15660                children: selected ? "Double-click a variable to insert it right away." : "Pick a variable to insert."
15661              }),
15662              /* @__PURE__ */ u2("button", {
15663                type: "button",
15664                class: "le-btn",
15665                onClick: props.onCancel,
15666                children: "Cancel"
15667              }),
15668              /* @__PURE__ */ u2("button", {
15669                type: "button",
15670                class: "le-btn le-primary",
15671                disabled: !selected,
15672                onClick: commit,
15673                children: start2 ? "Apply" : "Insert"
15674              })
15675            ]
15676          })
15677        ]
15678      })
15679    })
15680  );
15681}
15682
15683// src/components/live/VarButton.tsx
15684function VarButton(props) {
15685  const [isOpen, setIsOpen] = p2(false);
15686  const [editing, setEditing] = p2(void 0);
15687  const atCaret = props.text != void 0 ? tokenAtCaret(props.text, props.caret ?? 0) : void 0;
15688  return /* @__PURE__ */ u2(k, {
15689    children: [
15690      /* @__PURE__ */ u2("button", {
15691        type: "button",
15692        class: "le-btn",
15693        onClick: () => {
15694          setEditing(atCaret);
15695          setIsOpen(true);
15696        },
15697        children: [
15698          li.braces,
15699          props.label ?? (atCaret ? "Edit variable" : "Insert variable")
15700        ]
15701      }),
15702      isOpen && /* @__PURE__ */ u2(VarPicker, {
15703        initialToken: editing?.text,
15704        onPick: (token) => {
15705          setIsOpen(false);
15706          if (props.onInsert && props.text != void 0) {
15707            const start2 = editing ? editing.start : props.caret ?? props.text.length;
15708            const end = editing ? editing.end : props.caret ?? props.text.length;
15709            props.onInsert(props.text.slice(0, start2) + token + props.text.slice(end), start2 + token.length);
15710          }
15711          props.onPick?.(token);
15712        },
15713        onCancel: () => setIsOpen(false)
15714      })
15715    ]
15716  });
15717}
15718
15719// src/modules/casesyntax/private/casesyntax.ts
15720var CASE_OPEN_RE = /^\$\{case\b[^}]*\}/i;
15721var CASE_END_RE = /\$\{end\}\s*$/i;
15722var BLOCK_TOKEN_RE = /\$\{(case\b[^}]*|if\b[^}]*|foreach\b[^}]*|when\b[^}]*|else|end)\}/gi;
15723var WHEN_PREFIX = "${when ";
15724var ELSE_TOKEN = "${else}";
15725var CASE_OPEN_TOKEN = "${case}";
15726var CASE_END_TOKEN = "${end}";
15727function parse(value) {
15728  const trimmed = value.trim();
15729  const openMatch = trimmed.match(CASE_OPEN_RE);
15730  if (!openMatch || !CASE_END_RE.test(trimmed)) {
15731    return null;
15732  }
15733  const expr = openMatch[0].slice("${case".length, -1).trim();
15734  if (expr) {
15735    return null;
15736  }
15737  const markers = [];
15738  let depth = 0;
15739  let closedAt = -1;
15740  let m2;
15741  BLOCK_TOKEN_RE.lastIndex = 0;
15742  while (m2 = BLOCK_TOKEN_RE.exec(trimmed)) {
15743    const word = m2[1].toLowerCase();
15744    if (word === "end") {
15745      depth--;
15746      if (depth === 0) {
15747        closedAt = m2.index;
15748        break;
15749      }
15750      if (depth < 0) {
15751        return null;
15752      }
15753    } else if (word === "else" || word.startsWith("when")) {
15754      if (depth === 1) {
15755        markers.push({
15756          start: m2.index,
15757          end: m2.index + m2[0].length,
15758          when: word === "else" ? "" : m2[0].slice(WHEN_PREFIX.length, -1).trim()
15759        });
15760      }
15761    } else {
15762      depth++;
15763    }
15764  }
15765  if (closedAt < 0 || closedAt + CASE_END_TOKEN.length !== trimmed.length || markers.length === 0) {
15766    return null;
15767  }
15768  const whens = [];
15769  for (let i4 = 0; i4 < markers.length; i4++) {
15770    const start2 = markers[i4].end;
15771    const end = i4 + 1 < markers.length ? markers[i4 + 1].start : closedAt;
15772    whens.push({
15773      when: markers[i4].when,
15774      then: trimmed.slice(start2, end)
15775    });
15776  }
15777  return {
15778    expr: "",
15779    whens
15780  };
15781}
15782function serialize(value) {
15783  if (!value.whens.length) {
15784    return "";
15785  }
15786  const parts = [
15787    CASE_OPEN_TOKEN
15788  ];
15789  for (const { when, then } of value.whens) {
15790    parts.push(when === "" ? ELSE_TOKEN : WHEN_PREFIX + when + "}");
15791    parts.push(then);
15792  }
15793  parts.push(CASE_END_TOKEN);
15794  return parts.join("");
15795}
15796function deriveCases(value) {
15797  const parsed = parse(value);
15798  if (!parsed) {
15799    return {
15800      activeCases: [],
15801      elseValue: value
15802    };
15803  }
15804  return {
15805    activeCases: parsed.whens.filter((w3) => w3.when !== ""),
15806    elseValue: parsed.whens.find((w3) => w3.when === "")?.then ?? ""
15807  };
15808}
15809function composeCases(activeCases, elseValue) {
15810  if (activeCases.length === 0) {
15811    return elseValue;
15812  }
15813  return serialize({
15814    expr: "",
15815    whens: [
15816      ...activeCases,
15817      {
15818        when: "",
15819        then: elseValue
15820      }
15821    ]
15822  });
15823}
15824
15825// src/modules/i18n/private/i18n.ts
15826function en(text) {
15827  return text;
15828}
15829
15830// src/modules/rulesyntax/private/rulesyntax.ts
15831function emptyGroup(combinator = "and") {
15832  return {
15833    type: "group",
15834    not: false,
15835    combinator,
15836    children: []
15837  };
15838}
15839var NAME_PAT = `([a-zA-Z_]\\w*(?:\\(\\))?(?:->[a-zA-Z_]\\w*)?)`;
15840var VAL_PAT = `('(?:[^']|'')*'|-?\\d+(?:\\.\\d+)?|[a-zA-Z_]\\w*(?:->[a-zA-Z_]\\w*)?)`;
15841var LIST_ITEM_PAT = `(?:'(?:[^']|'')*'|-?\\d+(?:\\.\\d+)?)`;
15842var LIST_PAT = `(${LIST_ITEM_PAT}(?:\\s*,\\s*${LIST_ITEM_PAT})*)`;
15843var LIST_RE = new RegExp(`^${LIST_PAT}$`);
15844function quote(v3) {
15845  return "'" + v3.replace(/'/g, "''") + "'";
15846}
15847function unquote2(v3) {
15848  if (v3.length >= 2 && v3[0] === "'" && v3[v3.length - 1] === "'") {
15849    return v3.slice(1, -1).replace(/''/g, "'");
15850  }
15851  return v3;
15852}
15853function classifyValue(raw) {
15854  if (raw.length >= 2 && raw[0] === "'" && raw[raw.length - 1] === "'") {
15855    return {
15856      value: unquote2(raw),
15857      valueField: false
15858    };
15859  }
15860  if (/^-?\d/.test(raw)) {
15861    return {
15862      value: raw,
15863      valueField: false
15864    };
15865  }
15866  return {
15867    value: raw,
15868    valueField: true
15869  };
15870}
15871var NAME_FIRST = (m2) => ({
15872  name: m2[1],
15873  ...classifyValue(m2[2] ?? "")
15874});
15875var VALUE_FIRST = (m2) => ({
15876  name: m2[2],
15877  ...classifyValue(m2[1] ?? "")
15878});
15879var NAME_ONLY = (m2) => ({
15880  name: m2[1],
15881  value: "",
15882  valueField: false
15883});
15884var NAME_AND_LIST = (m2) => ({
15885  name: m2[1],
15886  value: m2[2] ?? "",
15887  valueField: false
15888});
15889function normalizeInList(list) {
15890  const trimmed = list.trim();
15891  if (LIST_RE.test(trimmed)) return trimmed;
15892  return trimmed.split(",").map((s3) => s3.trim()).filter((s3) => s3.length > 0).map((s3) => /^-?\d+(?:\.\d+)?$/.test(s3) ? s3 : quote(s3)).join(",");
15893}
15894function serializeIn(name, list, negated) {
15895  const items = normalizeInList(list);
15896  return items ? `${name} ${negated ? "not in" : "in"} (${items})` : "";
15897}
15898var OPERATIONS = [
15899  {
15900    value: "equals",
15901    label: en("Exactly matches"),
15902    category: "string",
15903    hasValue: true,
15904    serialize: (n2, v3) => `${n2}=${v3}`,
15905    matchRegex: new RegExp(`^${NAME_PAT}\\s*=\\s*${VAL_PAT}$`),
15906    extract: NAME_FIRST
15907  },
15908  {
15909    value: "not_equals",
15910    label: en("Doesn't match"),
15911    category: "string",
15912    hasValue: true,
15913    serialize: (n2, v3) => `${n2}!=${v3}`,
15914    matchRegex: new RegExp(`^${NAME_PAT}\\s*!=\\s*${VAL_PAT}$`),
15915    extract: NAME_FIRST
15916  },
15917  {
15918    value: "contains",
15919    label: en("Contains"),
15920    category: "string",
15921    hasValue: true,
15922    serialize: (n2, v3) => `Locate(${v3}, ${n2})`,
15923    matchRegex: new RegExp(`^Locate\\(\\s*${VAL_PAT}\\s*,\\s*${NAME_PAT}\\s*\\)$`),
15924    extract: VALUE_FIRST
15925  },
15926  {
15927    value: "not_contains",
15928    label: en("Doesn't contain"),
15929    category: "string",
15930    hasValue: true,
15931    serialize: (n2, v3) => `Locate(${v3}, ${n2})=0`,
15932    matchRegex: new RegExp(`^Locate\\(\\s*${VAL_PAT}\\s*,\\s*${NAME_PAT}\\s*\\)=0$`),
15933    extract: VALUE_FIRST
15934  },
15935  {
15936    value: "starts",
15937    label: en("Starts with"),
15938    category: "string",
15939    hasValue: true,
15940    serialize: (n2, v3) => `Locate(${v3}, ${n2})=1`,
15941    matchRegex: new RegExp(`^Locate\\(\\s*${VAL_PAT}\\s*,\\s*${NAME_PAT}\\s*\\)=1$`),
15942    extract: VALUE_FIRST
15943  },
15944  {
15945    value: "ends",
15946    label: en("Ends with"),
15947    category: "string",
15948    hasValue: true,
15949    serialize: (n2, v3) => `Right(${n2}, Char_length(${v3}))=${v3}`,
15950    matchRegex: new RegExp(`^Right\\(\\s*${NAME_PAT}\\s*,\\s*Char_length\\(\\s*${VAL_PAT}\\s*\\)\\s*\\)=${VAL_PAT}$`),
15951    extract: NAME_FIRST
15952  },
15953  {
15954    value: "regexp",
15955    label: en("Matches regexp"),
15956    category: "string",
15957    hasValue: true,
15958    serialize: (n2, v3) => `${n2} REGEXP ${v3}`,
15959    matchRegex: new RegExp(`^${NAME_PAT}\\s+REGEXP\\s+${VAL_PAT}$`, "i"),
15960    extract: NAME_FIRST
15961  },
15962  {
15963    value: "empty",
15964    label: en("Is empty"),
15965    category: "string",
15966    hasValue: false,
15967    serialize: (n2) => `(${n2} IS NULL OR ${n2}='')`,
15968    // The `\s*` around the comparison is not cosmetic: actions in the field store this
15969    // fragment spaced out (`first_name != ''`), and without it such a rule fails to parse
15970    // and the whole condition drops to the raw-SQL fallback in every builder that reads 
15970it.
15971    matchRegex: new RegExp(`^\\(\\s*${NAME_PAT}\\s+IS\\s+NULL\\s+OR\\s+\\w+(?:->\\w+)?\\s*=\\s*''\\s*\\)$`, "i"),
15972    extract: NAME_ONLY
15973  },
15974  {
15975    value: "not_empty",
15976    label: en("Is not empty"),
15977    category: "string",
15978    hasValue: false,
15979    serialize: (n2) => `(${n2} IS NOT NULL AND ${n2}!='')`,
15980    matchRegex: new RegExp(`^\\(\\s*${NAME_PAT}\\s+IS\\s+NOT\\s+NULL\\s+AND\\s+\\w+(?:->\\w+)?\\s*!=\\s*''\\s*\\)$`, "i"),
15981    extract: NAME_ONLY
15982  },
15983  // Numeric operators
15984  {
15985    value: "gt",
15986    label: en("> greater than"),
15987    category: "numeric",
15988    hasValue: true,
15989    serialize: (n2, v3) => `${n2}>${v3}`,
15990    matchRegex: new RegExp(`^${NAME_PAT}\\s*>\\s*${VAL_PAT}$`),
15991    extract: NAME_FIRST
15992  },
15993  {
15994    value: "gte",
15995    label: en("\u2265 greater than or equal"),
15996    category: "numeric",
15997    hasValue: true,
15998    serialize: (n2, v3) => `${n2}>=${v3}`,
15999    matchRegex: new RegExp(`^${NAME_PAT}\\s*>=\\s*${VAL_PAT}$`),
16000    extract: NAME_FIRST
16001  },
16002  {
16003    value: "lt",
16004    label: en("< less than"),
16005    category: "numeric",
16006    hasValue: true,
16007    serialize: (n2, v3) => `${n2}<${v3}`,
16008    matchRegex: new RegExp(`^${NAME_PAT}\\s*<\\s*${VAL_PAT}$`),
16009    extract: NAME_FIRST
16010  },
16011  {
16012    value: "lte",
16013    label: en("\u2264 less than or equal"),
16014    category: "numeric",
16015    hasValue: true,
16016    serialize: (n2, v3) => `${n2}<=${v3}`,
16017    matchRegex: new RegExp(`^${NAME_PAT}\\s*<=\\s*${VAL_PAT}$`),
16018    extract: NAME_FIRST
16019  },
16020  // Date operators
16021  {
16022    value: "days_since_gt",
16023    label: en("days ago >"),
16024    category: "date",
16025    hasValue: true,
16026    serialize: (n2, v3) => `(Unix_timestamp()-${n2})/(24*60*60) > ${v3}`,
16027    matchRegex: new RegExp(`^\\(Unix_timestamp\\(\\)-${NAME_PAT}\\)/\\(24\\*60\\*60\\)\\s*>\\s*${VAL_PAT}$`),
16028    extract: NAME_FIRST
16029  },
16030  {
16031    value: "days_since_gte",
16032    label: en("days ago \u2265"),
16033    category: "date",
16034    hasValue: true,
16035    serialize: (n2, v3) => `(Unix_timestamp()-${n2})/(24*60*60) >= ${v3}`,
16036    matchRegex: new RegExp(`^\\(Unix_timestamp\\(\\)-${NAME_PAT}\\)/\\(24\\*60\\*60\\)\\s*>=\\s*${VAL_PAT}$`),
16037    extract: NAME_FIRST
16038  },
16039  {
16040    value: "days_since_lt",
16041    label: en("days ago <"),
16042    category: "date",
16043    hasValue: true,
16044    serialize: (n2, v3) => `(Unix_timestamp()-${n2})/(24*60*60) < ${v3}`,
16045    matchRegex: new RegExp(`^\\(Unix_timestamp\\(\\)-${NAME_PAT}\\)/\\(24\\*60\\*60\\)\\s*<\\s*${VAL_PAT}$`),
16046    extract: NAME_FIRST
16047  },
16048  {
16049    value: "days_since_lte",
16050    label: en("days ago \u2264"),
16051    category: "date",
16052    hasValue: true,
16053    serialize: (n2, v3) => `(Unix_timestamp()-${n2})/(24*60*60) <= ${v3}`,
16054    matchRegex: new RegExp(`^\\(Unix_timestamp\\(\\)-${NAME_PAT}\\)/\\(24\\*60\\*60\\)\\s*<=\\s*${VAL_PAT}$`),
16055    extract: NAME_FIRST
16056  },
16057  {
16058    value: "days_till_gt",
16059    label: en("days till >"),
16060    category: "date",
16061    hasValue: true,
16062    serialize: (n2, v3) => `(${n2}-Unix_timestamp())/(24*60*60) > ${v3}`,
16063    matchRegex: new RegExp(`^\\(${NAME_PAT}-Unix_timestamp\\(\\)\\)/\\(24\\*60\\*60\\)\\s*>\\s*${VAL_PAT}$`),
16064    extract: NAME_FIRST
16065  },
16066  {
16067    value: "days_till_gte",
16068    label: en("days till \u2265"),
16069    category: "date",
16070    hasValue: true,
16071    serialize: (n2, v3) => `(${n2}-Unix_timestamp())/(24*60*60) >= ${v3}`,
16072    matchRegex: new RegExp(`^\\(${NAME_PAT}-Unix_timestamp\\(\\)\\)/\\(24\\*60\\*60\\)\\s*>=\\s*${VAL_PAT}$`),
16073    extract: NAME_FIRST
16074  },
16075  {
16076    value: "days_till_lt",
16077    label: en("days till <"),
16078    category: "date",
16079    hasValue: true,
16080    serialize: (n2, v3) => `(${n2}-Unix_timestamp())/(24*60*60) < ${v3}`,
16081    matchRegex: new RegExp(`^\\(${NAME_PAT}-Unix_timestamp\\(\\)\\)/\\(24\\*60\\*60\\)\\s*<\\s*${VAL_PAT}$`),
16082    extract: NAME_FIRST
16083  },
16084  {
16085    value: "days_till_lte",
16086    label: en("days till \u2264"),
16087    category: "date",
16088    hasValue: true,
16089    serialize: (n2, v3) => `(${n2}-Unix_timestamp())/(24*60*60) <= ${v3}`,
16090    matchRegex: new RegExp(`^\\(${NAME_PAT}-Unix_timestamp\\(\\)\\)/\\(24\\*60\\*60\\)\\s*<=\\s*${VAL_PAT}$`),
16091    extract: NAME_FIRST
16092  },
16093  // Unix-timestamp column vs. now minus an offset in seconds, e.g.
16094  // `interaction_time > unix_timestamp()-1814400` (interacted < 21 days ago).
16095  // The value is raw seconds embedded in the arithmetic, so it is emitted bare
16096  // (rawValue) and matched as digits — mirrors the catalog's own interaction-
16097  // time window (recomCatalog.ts) and the legacy `unix_timestamp()-N` fragments.
16098  // Value names follow the SQL comparator; labels read as elapsed "… ago".
16099  {
16100    value: "unixts_gt",
16101    label: en("less than (sec) ago"),
16102    category: "date",
16103    hasValue: true,
16104    rawValue: true,
16105    serialize: (n2, v3) => `${n2} > unix_timestamp()-${v3}`,
16106    matchRegex: new RegExp(`^${NAME_PAT}\\s*>\\s*unix_timestamp\\(\\)\\s*-\\s*(-?\\d+)$`, "i"),
16107    extract: (m2) => ({
16108      name: m2[1],
16109      value: m2[2],
16110      valueField: false
16111    })
16112  },
16113  {
16114    value: "unixts_gte",
16115    label: en("at most (sec) ago"),
16116    category: "date",
16117    hasValue: true,
16118    rawValue: true,
16119    serialize: (n2, v3) => `${n2} >= unix_timestamp()-${v3}`,
16120    matchRegex: new RegExp(`^${NAME_PAT}\\s*>=\\s*unix_timestamp\\(\\)\\s*-\\s*(-?\\d+)$`, "i"),
16121    extract: (m2) => ({
16122      name: m2[1],
16123      value: m2[2],
16124      valueField: false
16125    })
16126  },
16127  {
16128    value: "unixts_lt",
16129    label: en("more than (sec) ago"),
16130    category: "date",
16131    hasValue: true,
16132    rawValue: true,
16133    serialize: (n2, v3) => `${n2} < unix_timestamp()-${v3}`,
16134    matchRegex: new RegExp(`^${NAME_PAT}\\s*<\\s*unix_timestamp\\(\\)\\s*-\\s*(-?\\d+)$`, "i"),
16135    extract: (m2) => ({
16136      name: m2[1],
16137      value: m2[2],
16138      valueField: false
16139    })
16140  },
16141  {
16142    value: "unixts_lte",
16143    label: en("at least (sec) ago"),
16144    category: "date",
16145    hasValue: true,
16146    rawValue: true,
16147    serialize: (n2, v3) => `${n2} <= unix_timestamp()-${v3}`,
16148    matchRegex: new RegExp(`^${NAME_PAT}\\s*<=\\s*unix_timestamp\\(\\)\\s*-\\s*(-?\\d+)$`, "i"),
16149    extract: (m2) => ({
16150      name: m2[1],
16151      value: m2[2],
16152      valueField: false
16153    })
16154  },
16155  // Distance (point-typed columns)
16156  {
16157    value: "distance_gt",
16158    label: en("distance from user >"),
16159    category: "distance",
16160    hasValue: true,
16161    serialize: (n2, v3) => `distance_to_point(${n2}) > ${v3}`,
16162    matchRegex: new RegExp(`^distance_to_point\\(${NAME_PAT}\\)\\s*>\\s*${VAL_PAT}$`),
16163    extract: NAME_FIRST
16164  },
16165  {
16166    value: "distance_gte",
16167    label: en("distance from user \u2265"),
16168    category: "distance",
16169    hasValue: true,
16170    serialize: (n2, v3) => `distance_to_point(${n2}) >= ${v3}`,
16171    matchRegex: new RegExp(`^distance_to_point\\(${NAME_PAT}\\)\\s*>=\\s*${VAL_PAT}$`),
16172    extract: NAME_FIRST
16173  },
16174  {
16175    value: "distance_lt",
16176    label: en("distance from user <"),
16177    category: "distance",
16178    hasValue: true,
16179    serialize: (n2, v3) => `distance_to_point(${n2}) < ${v3}`,
16180    matchRegex: new RegExp(`^distance_to_point\\(${NAME_PAT}\\)\\s*<\\s*${VAL_PAT}$`),
16181    extract: NAME_FIRST
16182  },
16183  {
16184    value: "distance_lte",
16185    label: en("distance from user \u2264"),
16186    category: "distance",
16187    hasValue: true,
16188    serialize: (n2, v3) => `distance_to_point(${n2}) <= ${v3}`,
16189    matchRegex: new RegExp(`^distance_to_point\\(${NAME_PAT}\\)\\s*<=\\s*${VAL_PAT}$`),
16190    extract: NAME_FIRST
16191  },
16192  // Set membership. The legacy `$input_recommendations` widget wrote these from
16193  // its "Based on" controls — `p->interaction_status in ('extra')` for a
16194  // recommendation seeded from the cart, `p->for_period in ('day','week')` for a
16195  // popularity window (input_recommendations.php:380) — so EVERY widget built that
16196  // way carried a fragment with no operator here to match, and the whole rule
16197  // builder dropped to raw SQL with "…an expression the rule builder doesn't
16198  // recognize yet". Nothing else in this table matches an `in (`, and [[parseOne]]
16199  // runs after [[parseLocation]] and [[parseExpr]], so these two entries can only
16200  // ever claim fragments that used to parse as nothing at all: a geo
16201  // `country_code in (…)` and a `weekday in (0,1,2)` are both taken before the
16202  // operator table is consulted.
16203  //
16204  // `rawValue` because the captured list is re-emitted verbatim — see
16205  // [[normalizeInList]] for why anything cleverer rewrites stored SQL.
16206  {
16207    value: "in",
16208    label: en("Is one of"),
16209    category: "string",
16210    hasValue: true,
16211    rawValue: true,
16212    serialize: (n2, v3) => serializeIn(n2, v3, false),
16213    matchRegex: new RegExp(`^${NAME_PAT}\\s+in\\s*\\(\\s*${LIST_PAT}\\s*\\)$`, "i"),
16214    extract: NAME_AND_LIST
16215  },
16216  {
16217    value: "not_in",
16218    label: en("Is not one of"),
16219    category: "string",
16220    hasValue: true,
16221    rawValue: true,
16222    serialize: (n2, v3) => serializeIn(n2, v3, true),
16223    matchRegex: new RegExp(`^${NAME_PAT}\\s+not\\s+in\\s*\\(\\s*${LIST_PAT}\\s*\\)$`, "i"),
16224    extract: NAME_AND_LIST
16225  }
16226];
16227var OPS_BY_VALUE = new Map(OPERATIONS.map((o3) => [
16228  o3.value,
16229  o3
16230]));
16231function getOperator(value) {
16232  return OPS_BY_VALUE.get(value);
16233}
16234function splitTopLevelByOp(sql, op) {
16235  const sep = " " + op + " ";
16236  const parts = [];
16237  let depth = 0;
16238  let inQuote = false;
16239  let last = 0;
16240  let i4 = 0;
16241  let pendingBetween = 0;
16242  while (i4 < sql.length) {
16243    const ch = sql[i4];
16244    if (inQuote) {
16245      if (ch === "'") {
16246        if (sql[i4 + 1] === "'") {
16247          i4 += 2;
16248          continue;
16249        }
16250        inQuote = false;
16251      }
16252      i4++;
16253      continue;
16254    }
16255    if (ch === "'") {
16256      inQuote = true;
16257      i4++;
16258      continue;
16259    }
16260    if (ch === "(") {
16261      depth++;
16262      i4++;
16263      continue;
16264    }
16265    if (ch === ")") {
16266      depth--;
16267      i4++;
16268      continue;
16269    }
16270    if (op === "and" && depth === 0 && sql.slice(i4, i4 + 9).toLowerCase() === " between ") {
16271      pendingBetween++;
16272      i4 += 9;
16273      continue;
16274    }
16275    if (depth === 0 && sql.slice(i4, i4 + sep.length).toLowerCase() === sep) {
16276      if (op === "and" && pendingBetween > 0) {
16277        pendingBetween--;
16278        i4 += sep.length;
16279        continue;
16280      }
16281      parts.push(sql.slice(last, i4).trim());
16282      i4 += sep.length;
16283      last = i4;
16284      continue;
16285    }
16286    i4++;
16287  }
16288  if (inQuote || depth !== 0) return null;
16289  parts.push(sql.slice(last).trim());
16290  return parts;
16291}
16292function isWholeWrap(s3) {
16293  if (s3[0] !== "(" || s3[s3.length - 1] !== ")") return false;
16294  let depth = 0;
16295  let inQuote = false;
16296  for (let i4 = 0; i4 < s3.length; i4++) {
16297    const ch = s3[i4];
16298    if (inQuote) {
16299      if (ch === "'") {
16300        if (s3[i4 + 1] === "'") {
16301          i4++;
16302          continue;
16303        }
16304        inQuote = false;
16305      }
16306      continue;
16307    }
16308    if (ch === "'") {
16309      inQuote = true;
16310      continue;
16311    }
16312    if (ch === "(") {
16313      depth++;
16314    } else if (ch === ")") {
16315      depth--;
16316      if (depth === 0 && i4 !== s3.length - 1) return false;
16317    }
16318  }
16319  return depth === 0;
16320}
16321function parseOne(fragment) {
16322  for (const op of OPERATIONS) {
16323    const m2 = fragment.match(op.matchRegex);
16324    if (m2) {
16325      const { name, value, valueField } = op.extract(m2);
16326      return {
16327        kind: "field",
16328        field: name,
16329        operator: op.value,
16330        value,
16331        valueField
16332      };
16333    }
16334  }
16335  return null;
16336}
16337var COUNTRY_LIST_RE = /^country_code\s+(?:(not)\s+)?in\s*\(\s*((?:'[A-Z]{2}'\s*,\s*)*'[A-Z]{2}')\s*\)$/i;
16338var REGION_LIST_RE = /^\(\s*country_code\s*,\s*region_code\s*\)\s+(?:(not)\s+)?in\s*\(\s*((?:\(\s*'[A-Z]{2}'\s*,\s*'(?:[^']|'')+'\s*\)\s*,\s*)*\(\s*'[A-Z]{2}'\s*,\s*'(?:[^']|'')+'\s*\))\s*\)$/i;
16339var CITY_LIST_RE = /^\(\s*country_code\s*,\s*region_code\s*,\s*city\s*\)\s+(?:(not)\s+)?in\s*\(\s*((?:\(\s*'[A-Z]{2}'\s*,\s*'(?:[^']|'')+'\s*,\s*'(?:[^']|'')*'\s*\)\s*,\s*)*\(\s*'[A-Z]{2}'\s*,\s*'(?:[^']|'')+'\s*,\s*'(?:[^']|'')*'\s*\))\s*\)$/i;
16340function parseSingleGeo(s3) {
16341  const c3 = s3.match(COUNTRY_LIST_RE);
16342  if (c3) {
16343    const items = c3[2].split(/\s*,\s*/).map((v3) => unquote2(v3));
16344    return {
16345      sub: "countries",
16346      not: !!c3[1],
16347      items
16348    };
16349  }
16350  const r3 = s3.match(REGION_LIST_RE);
16351  if (r3) {
16352    const inner = r3[2];
16353    const tuples = [];
16354    const tupleRe = /\(\s*'([A-Z]{2})'\s*,\s*'((?:[^']|'')+)'\s*\)/gi;
16355    let m2;
16356    while (m2 = tupleRe.exec(inner)) tuples.push(`${m2[1]}-${m2[2]}`);
16357    return {
16358      sub: "regions",
16359      not: !!r3[1],
16360      items: tuples
16361    };
16362  }
16363  const ci = s3.match(CITY_LIST_RE);
16364  if (ci) {
16365    const inner = ci[2];
16366    const tuples = [];
16367    const tupleRe = /\(\s*'([A-Z]{2})'\s*,\s*'((?:[^']|'')+)'\s*,\s*'((?:[^']|'')*)'\s*\)/gi;
16368    let m2;
16369    while (m2 = tupleRe.exec(inner)) tuples.push(`${m2[1]}-${m2[2]}-${unquote2("'" + m2[3] + "'")}`);
16370    return {
16371      sub: "cities",
16372      not: !!ci[1],
16373      items: tuples
16374    };
16375  }
16376  return null;
16377}
16378function emptyLocation(positive) {
16379  return {
16380    kind: "location",
16381    positive,
16382    countries: [],
16383    regions: [],
16384    cities: [],
16385    t_countries: [],
16386    t_regions: [],
16387    t_cities: []
16388  };
16389}
16390function addGeoFragment(loc, f4) {
16391  return {
16392    ...loc,
16393    [f4.sub]: [
16394      ...loc[f4.sub],
16395      ...f4.items
16396    ],
16397    ["t_" + f4.sub]: [
16398      ...loc["t_" + f4.sub],
16399      ...f4.items
16400    ]
16401  };
16402}
16403function parseLocation(sql) {
16404  const trimmed = sql.trim();
16405  if (!/\bcountry_code\b/i.test(trimmed)) return null;
16406  if (trimmed.startsWith("(") && trimmed.endsWith(")")) {
16407    const inner = trimmed.slice(1, -1).trim();
16408    const parts = splitTopLevelByOp(inner, "or");
16409    if (parts && parts.length > 1) {
16410      let loc = emptyLocation(true);
16411      for (const p3 of parts) {
16412        const f4 = parseSingleGeo(p3);
16413        if (!f4 || f4.not) return null;
16414        loc = addGeoFragment(loc, f4);
16415      }
16416      return loc;
16417    }
16418  }
16419  const andParts = splitTopLevelByOp(trimmed, "and");
16420  if (andParts && andParts.length > 1) {
16421    let loc = emptyLocation(false);
16422    for (const p3 of andParts) {
16423      const f4 = parseSingleGeo(p3);
16424      if (!f4 || !f4.not) return null;
16425      loc = addGeoFragment(loc, f4);
16426    }
16427    return loc;
16428  }
16429  const single = parseSingleGeo(trimmed);
16430  if (single) {
16431    return addGeoFragment(emptyLocation(!single.not), single);
16432  }
16433  return null;
16434}
16435function sqlQuote(v3) {
16436  return "'" + v3.replace(/'/g, "''") + "'";
16437}
16438function serializeLocation(loc) {
16439  const parts = [];
16440  const opIn = loc.positive ? "in" : "not in";
16441  if (loc.countries.length > 0) {
16442    parts.push(`country_code ${opIn} (${loc.countries.map(sqlQuote).join(", ")})`);
16443  }
16444  if (loc.regions.length > 0) {
16445    const tuples = loc.regions.map((v3) => {
16446      const [cc, rc] = v3.split("-");
16447      return `(${sqlQuote(cc)}, ${sqlQuote(rc)})`;
16448    });
16449    parts.push(`(country_code, region_code) ${opIn} (${tuples.join(", ")})`);
16450  }
16451  if (loc.cities.length > 0) {
16452    const tuples = loc.cities.map((v3) => {
16453      const cc = v3.slice(0, 2);
16454      const rest = v3.slice(3);
16455      const dash = rest.indexOf("-");
16456      const rc = dash >= 0 ? rest.slice(0, dash) : rest;
16457      const cn = dash >= 0 ? rest.slice(dash + 1) : "";
16458      return `(${sqlQuote(cc)}, ${sqlQuote(rc)}, ${sqlQuote(cn)})`;
16459    });
16460    parts.push(`(country_code, region_code, city) ${opIn} (${tuples.join(", ")})`);
16461  }
16462  if (parts.length === 0) return "";
16463  if (loc.positive) {
16464    return parts.length === 1 ? parts[0] : "(" + parts.join(" or ") + ")";
16465  }
16466  return parts.join(" and ");
16467}
16468function serializeExpr(r3) {
16469  switch (r3.sub) {
16470    case "weekdays":
16471      return r3.values.length ? `weekday in (${r3.values.join(",")})` : "";
16472    case "monthdays":
16473      return r3.values.length ? `day in (${r3.values.map((v3) => v3 > 0 ? String(v3) : `last_month_day-${-v3}`).join(",")})` : "";
16474    case "dates":
16475      return r3.from && r3.to ? `ss between ${r3.from} and ${r3.to}` : "";
16476    case "time":
16477      return r3.from && r3.to ? `daytime between ${r3.from} and ${r3.to}` : "";
16478  }
16479}
16480function parseExpr(fragment) {
16481  let m2 = fragment.match(/^weekday\s+in\s*\(\s*([\d\s,]+?)\s*\)$/i);
16482  if (m2) {
16483    const values = m2[1].split(",").map((s3) => parseInt(s3.trim(), 10)).filter((n2) => Number.isFinite(n2));
16484    return {
16485      kind: "expr",
16486      sub: "weekdays",
16487      values,
16488      from: "",
16489      to: ""
16490    };
16491  }
16492  m2 = fragment.match(/^day\s+in\s*\(\s*(.+?)\s*\)$/i);
16493  if (m2) {
16494    const values = [];
16495    for (const raw of m2[1].split(",")) {
16496      const it = raw.trim();
16497      if (/^\d+$/.test(it)) values.push(parseInt(it, 10));
16498      else {
16499        const mm = it.match(/^last_month_day(?:\s*-\s*(\d+))?$/i);
16500        if (!mm) return null;
16501        values.push(-(mm[1] ? parseInt(mm[1], 10) : 0));
16502      }
16503    }
16504    return {
16505      kind: "expr",
16506      sub: "monthdays",
16507      values,
16508      from: "",
16509      to: ""
16510    };
16511  }
16512  m2 = fragment.match(/^ss\s+between\s+(\d+)\s+and\s+(\d+)$/i);
16513  if (m2) return {
16514    kind: "expr",
16515    sub: "dates",
16516    values: [],
16517    from: m2[1],
16518    to: m2[2]
16519  };
16520  m2 = fragment.match(/^daytime\s+between\s+(\d+)\s+and\s+(\d+)$/i);
16521  if (m2) return {
16522    kind: "expr",
16523    sub: "time",
16524    values: [],
16525    from: m2[1],
16526    to: m2[2]
16527  };
16528  return null;
16529}
16530function parseRuleFragment(fragment) {
16531  const loc = parseLocation(fragment);
16532  if (loc) return loc;
16533  const expr = parseExpr(fragment);
16534  if (expr) return expr;
16535  return parseOne(fragment);
16536}
16537function serializeRule(r3) {
16538  if (r3.kind === "location") return serializeLocation(r3);
16539  if (r3.kind === "expr") return serializeExpr(r3);
16540  const op = OPS_BY_VALUE.get(r3.operator);
16541  if (!op) return "";
16542  const valueToken = r3.valueField ? r3.value : op.rawValue ? r3.value : quote(r3.value);
16543  return op.serialize(r3.field, valueToken);
16544}
16545var PRECEDENCE = [
16546  "or",
16547  "xor",
16548  "and"
16549];
16550var RANK = {
16551  or: 0,
16552  xor: 1,
16553  and: 2
16554};
16555function serializeNode(node, parentCombinator) {
16556  if (node.type === "leaf") {
16557    const sql = serializeRule(node.rule);
16558    if (!sql) return "";
16559    return node.not ? `not (${sql})` : sql;
16560  }
16561  const parts = [];
16562  for (const child of node.children) {
16563    const s3 = serializeNode(child, node.combinator);
16564    if (s3) parts.push(s3);
16565  }
16566  if (parts.length === 0) return "";
16567  const body = parts.length === 1 ? parts[0] : parts.join(" " + node.combinator + " ");
16568  if (node.not) return `not (${body})`;
16569  if (parts.length > 1 && parentCombinator !== null && RANK[node.combinator] < RANK[parentCombinator]) {
16570    return `(${body})`;
16571  }
16572  return body;
16573}
16574function serialize2(root) {
16575  return serializeNode(root, null);
16576}
16577function negate(node) {
16578  return {
16579    ...node,
16580    not: !node.not
16581  };
16582}
16583function parseNode(sql) {
16584  const trimmed = sql.trim();
16585  if (!trimmed) return null;
16586  const notMatch = /^not\b\s*/i.exec(trimmed);
16587  if (notMatch) {
16588    const rest = trimmed.slice(notMatch[0].length).trim();
16589    if (isWholeWrap(rest)) {
16590      const inner = parseNode(rest.slice(1, -1).trim());
16591      if (inner) return negate(inner);
16592    } else {
16593      const r3 = parseRuleFragment(rest);
16594      if (r3) return {
16595        type: "leaf",
16596        not: true,
16597        rule: r3
16598      };
16599    }
16600  }
16601  const whole = parseRuleFragment(trimmed);
16602  if (whole) return {
16603    type: "leaf",
16604    not: false,
16605    rule: whole
16606  };
16607  for (const op of PRECEDENCE) {
16608    const parts = splitTopLevelByOp(trimmed, op);
16609    if (parts === null) return null;
16610    if (parts.length > 1) {
16611      const children = [];
16612      for (const p3 of parts) {
16613        const child = parseNode(p3);
16614        if (!child) return null;
16615        children.push(child);
16616      }
16617      return {
16618        type: "group",
16619        not: false,
16620        combinator: op,
16621        children
16622      };
16623    }
16624  }
16625  if (isWholeWrap(trimmed)) return parseNode(trimmed.slice(1, -1).trim());
16626  return null;
16627}
16628function parse2(sql) {
16629  if (!sql.trim()) return emptyGroup("and");
16630  const node = parseNode(sql);
16631  if (!node) return null;
16632  return node.type === "group" ? node : {
16633    type: "group",
16634    not: false,
16635    combinator: "and",
16636    children: [
16637      node
16638    ]
16639  };
16640}
16641
16642// src/components/live/RuleBuilder.tsx
16643var FIELD_CATALOG = [
16644  {
16645    value: "url_host_port",
16646    label: "URL domain",
16647    group: "Current page",
16648    kind: "text"
16649  },
16650  {
16651    value: "url_path",
16652    label: "URL path",
16653    group: "Current page",
16654    kind: "text"
16655  },
16656  {
16657    value: "ur",
16658    label: "Whole URL",
16659    group: "Current page",
16660    kind: "text"
16661  },
16662  {
16663    value: "country",
16664    label: "Country",
16665    group: "Visitor",
16666    kind: "text"
16667  },
16668  {
16669    value: "city",
16670    label: "City",
16671    group: "Visitor",
16672    kind: "text"
16673  },
16674  {
16675    value: "lang_0",
16676    label: "Primary browser language",
16677    group: "Visitor",
16678    kind: "language"
16679  },
16680  {
16681    value: "known_device_type",
16682    label: "Device type",
16683    group: "Visitor",
16684    kind: "device"
16685  },
16686  {
16687    value: "is_returning_user",
16688    label: "Visit type",
16689    group: "Visitor",
16690    kind: "visit"
16691  },
16692  {
16693    value: "ip",
16694    label: "IP",
16695    group: "Visitor",
16696    kind: "text"
16697  },
16698  {
16699    value: "landing_url",
16700    label: "Landing page - Full URL",
16701    group: "Landing page",
16702    kind: "text"
16703  },
16704  {
16705    value: "landing_host_port",
16706    label: "Landing page - Host name",
16707    group: "Landing page",
16708    kind: "text"
16709  },
16710  {
16711    value: "landing_path",
16712    label: "Landing page - Path",
16713    group: "Landing page",
16714    kind: "text"
16715  },
16716  {
16717    value: "part_of_day()",
16718    label: "Part of day",
16719    group: "Date & time",
16720    kind: "partofday"
16721  }
16722];
16723var DEVICE_OPTIONS = [
16724  [
16725    "regular",
16726    "Desktop"
16727  ],
16728  [
16729    "tablet",
16730    "Tablet"
16731  ],
16732  [
16733    "phone",
16734    "Phone"
16735  ],
16736  [
16737    "tablet-sdk",
16738    "Tablet (SDK)"
16739  ],
16740  [
16741    "phone-sdk",
16742    "Phone (SDK)"
16743  ]
16744];
16745var VISIT_OPTIONS = [
16746  [
16747    "0",
16748    "New user"
16749  ],
16750  [
16751    "1",
16752    "Returning user"
16753  ]
16754];
16755var PART_OF_DAY_OPTIONS = [
16756  [
16757    "morning",
16758    "Morning"
16759  ],
16760  [
16761    "afternoon",
16762    "Afternoon"
16763  ],
16764  [
16765    "evening",
16766    "Evening"
16767  ],
16768  [
16769    "night",
16770    "Night"
16771  ]
16772];
16773var LANGUAGE_OPTIONS = [
16774  [
16775    "en",
16776    "English"
16777  ],
16778  [
16779    "es",
16780    "Spanish"
16781  ],
16782  [
16783    "fr",
16784    "French"
16785  ],
16786  [
16787    "de",
16788    "German"
16789  ],
16790  [
16791    "it",
16792    "Italian"
16793  ],
16794  [
16795    "pt",
16796    "Portuguese"
16797  ],
16798  [
16799    "nl",
16800    "Dutch"
16801  ],
16802  [
16803    "ru",
16804    "Russian"
16805  ],
16806  [
16807    "pl",
16808    "Polish"
16809  ],
16810  [
16811    "tr",
16812    "Turkish"
16813  ],
16814  [
16815    "ar",
16816    "Arabic"
16817  ],
16818  [
16819    "he",
16820    "Hebrew"
16821  ],
16822  [
16823    "zh",
16824    "Chinese"
16825  ],
16826  [
16827    "ja",
16828    "Japanese"
16829  ],
16830  [
16831    "ko",
16832    "Korean"
16833  ]
16834];
16835var EQUALITY_ONLY = [
16836  "equals",
16837  "not_equals"
16838];
16839var TEXT_OPERATORS = [
16840  "equals",
16841  "not_equals",
16842  "contains",
16843  "not_contains",
16844  "starts",
16845  "ends",
16846  "regexp",
16847  "empty",
16848  "not_empty"
16849];
16850function optionsFor(kind) {
16851  switch (kind) {
16852    case "language":
16853      return LANGUAGE_OPTIONS;
16854    case "device":
16855      return DEVICE_OPTIONS;
16856    case "visit":
16857      return VISIT_OPTIONS;
16858    case "partofday":
16859      return PART_OF_DAY_OPTIONS;
16860  }
16861  return null;
16862}
16863function toFlat(sql) {
16864  if (!sql.trim()) {
16865    return {
16866      combinator: "and",
16867      rules: []
16868    };
16869  }
16870  const root = parse2(sql);
16871  if (!root || root.not) {
16872    return void 0;
16873  }
16874  const rules = [];
16875  for (const child of root.children) {
16876    if (child.type != "leaf" || child.not || child.rule.kind != "field") {
16877      return void 0;
16878    }
16879    rules.push(child.rule);
16880  }
16881  return {
16882    combinator: root.combinator,
16883    rules
16884  };
16885}
16886function fromFlat(flat) {
16887  const root = {
16888    type: "group",
16889    not: false,
16890    combinator: flat.combinator,
16891    children: flat.rules.map((rule) => ({
16892      type: "leaf",
16893      not: false,
16894      rule
16895    }))
16896  };
16897  return serialize2(root);
16898}
16899function ModeToggle(props) {
16900  return /* @__PURE__ */ u2("div", {
16901    class: "le-rb-modes",
16902    children: [
16903      /* @__PURE__ */ u2("button", {
16904        type: "button",
16905        class: props.isRaw ? void 0 : "le-on",
16906        disabled: !props.canRules,
16907        title: props.canRules ? "Edit this condition as rules" : "This condition has a shape the rule editor can't draw, so it stays as SQL",
16908        onClick: () => props.onMode(false),
16909        children: "Edit as rules"
16910      }),
16911      /* @__PURE__ */ u2("button", {
16912        type: "button",
16913        class: props.isRaw ? "le-on" : void 0,
16914        title: "Edit this condition as SQL",
16915        onClick: () => props.onMode(true),
16916        children: "Edit as SQL"
16917      })
16918    ]
16919  });
16920}
16921function RuleBuilder(props) {
16922  const flat = toFlat(props.value);
16923  const [isRaw, setIsRaw] = p2(flat == void 0);
16924  const [isSqlOffered] = p2(flat == void 0);
16925  const fields = q2(() => FIELD_CATALOG.concat(props.userColumns.map((c3) => ({
16926    value: c3.value,
16927    label: c3.label,
16928    group: "Visitor data",
16929    kind: "text"
16930  }))), [
16931    props.userColumns
16932  ]);
16933  if (isRaw || !flat) {
16934    return /* @__PURE__ */ u2("div", {
16935      class: "le-rb",
16936      children: [
16937        /* @__PURE__ */ u2("textarea", {
16938          class: "le-area le-mono",
16939          rows: 2,
16940          value: props.value,
16941          placeholder: "country='FR' and city='Paris'",
16942          onInput: (ev) => props.onChange(ev.target.value)
16943        }),
16944        /* @__PURE__ */ u2("div", {
16945          class: "le-rb-foot",
16946          children: [
16947            /* @__PURE__ */ u2("span", {
16948              class: "le-rb-hint",
16949              children: "Condition as SQL, the way the panel's rule builder writes it."
16950            }),
16951            /* @__PURE__ */ u2(ModeToggle, {
16952              isRaw: true,
16953              canRules: toFlat(props.value) != void 0,
16954              onMode: setIsRaw
16955            })
16956          ]
16957        })
16958      ]
16959    });
16960  }
16961  function update(next) {
16962    props.onChange(fromFlat(next));
16963  }
16964  function setRule(i4, patch) {
16965    update({
16966      ...flat,
16967      rules: flat.rules.map((r3, k3) => k3 != i4 ? r3 : {
16968        ...r3,
16969        ...patch
16970      })
16971    });
16972  }
16973  return /* @__PURE__ */ u2("div", {
16974    class: "le-rb",
16975    children: [
16976      flat.rules.map((rule, i4) => {
16977        const field = fields.find((f4) => f4.value == rule.field);
16978        const choices = field ? optionsFor(field.kind) : null;
16979        const operators = choices ? EQUALITY_ONLY : TEXT_OPERATORS;
16980        const hasValue = getOperator(rule.operator)?.hasValue ?? true;
16981        return /* @__PURE__ */ u2("div", {
16982          class: "le-rb-row",
16983          children: [
16984            i4 > 0 && /* @__PURE__ */ u2("select", {
16985              class: "le-select le-rb-comb",
16986              value: flat.combinator,
16987              onChange: (ev) => update({
16988                ...flat,
16989                combinator: ev.target.value
16990              }),
16991              children: [
16992                /* @__PURE__ */ u2("option", {
16993                  value: "and",
16994                  children: "and"
16995                }),
16996                /* @__PURE__ */ u2("option", {
16997                  value: "or",
16998                  children: "or"
16999                })
17000              ]
17001            }),
17002            i4 == 0 && /* @__PURE__ */ u2("span", {
17003              class: "le-rb-comb le-rb-if",
17004              children: "if"
17005            }),
17006            /* @__PURE__ */ u2("select", {
17007              class: "le-select le-rb-field",
17008              value: rule.field,
17009              onChange: (ev) => {
17010                const next = ev.target.value;
17011                const nextField = fields.find((f4) => f4.value == next);
17012                const nextChoices = nextField ? optionsFor(nextField.kind) : null;
17013                setRule(i4, nextChoices ? {
17014                  field: next,
17015                  operator: "equals",
17016                  value: nextChoices[0][0]
17017                } : {
17018                  field: next
17019                });
17020              },
17021              children: [
17022                ...new Set(fields.map((f4) => f4.group))
17023              ].map((group) => /* @__PURE__ */ u2("optgroup", {
17024                label: group,
17025                children: fields.filter((f4) => f4.group == group).map((f4) => /* @__PURE__ */ u2("option", {
17026                  value: f4.value,
17027                  children: f4.label
17028                }, f4.value))
17029              }, group))
17030            }),
17031            /* @__PURE__ */ u2("select", {
17032              class: "le-select le-rb-op",
17033              value: rule.operator,
17034              onChange: (ev) => setRule(i4, {
17035                operator: ev.target.value
17036              }),
17037              children: operators.map((op) => {
17038                const def = OPERATIONS.find((d3) => d3.value == op);
17039                return def && /* @__PURE__ */ u2("option", {
17040                  value: op,
17041                  children: def.label
17042                }, op);
17043              })
17044            }),
17045            hasValue && (choices ? /* @__PURE__ */ u2("select", {
17046              class: "le-select le-rb-val",
17047              value: rule.value,
17048              onChange: (ev) => setRule(i4, {
17049                value: ev.target.value
17050              }),
17051              children: choices.map(([v3, label]) => /* @__PURE__ */ u2("option", {
17052                value: v3,
17053                children: label
17054              }, v3))
17055            }) : /* @__PURE__ */ u2("input", {
17056              class: "le-input le-rb-val",
17057              type: "text",
17058              value: rule.value,
17059              placeholder: "value",
17060              onInput: (ev) => setRule(i4, {
17061                value: ev.target.value
17062              })
17063            })),
17064            /* @__PURE__ */ u2("button", {
17065              type: "button",
17066              class: "le-icon",
17067              title: "Remove this condition",
17068              onClick: () => update({
17069                ...flat,
17070                rules: flat.rules.filter((_3, k3) => k3 != i4)
17071              }),
17072              children: li.cross
17073            })
17074          ]
17075        }, i4);
17076      }),
17077      /* @__PURE__ */ u2("div", {
17078        class: "le-rb-foot",
17079        children: [
17080          /* @__PURE__ */ u2("button", {
17081            type: "button",
17082            class: "le-btn",
17083            onClick: () => update({
17084              ...flat,
17085              rules: [
17086                ...flat.rules,
17087                {
17088                  kind: "field",
17089                  field: fields[0].value,
17090                  operator: "equals",
17091                  value: ""
17092                }
17093              ]
17094            }),
17095            children: "+ Add condition"
17096          }),
17097          /* @__PURE__ */ u2("span", {
17098            class: "le-rb-hint",
17099            children: flat.rules.length == 0 ? "No condition yet - this case would match everyone." : ""
17100          }),
17101          isSqlOffered && /* @__PURE__ */ u2(ModeToggle, {
17102            isRaw: false,
17103            canRules: true,
17104            onMode: setIsRaw
17105          })
17106        ]
17107      })
17108    ]
17109  });
17110}
17111
17112// src/components/live/AiSuggestCard.tsx
17113function AiSuggestCard(props) {
17114  const [isOpen, setIsOpen] = p2(!props.collapsible);
17115  const [prompt, setPrompt] = p2("");
17116  const [result, setResult] = p2("");
17117  const [error, setError] = p2("");
17118  const [isBusy, setIsBusy] = p2(false);
17119  async function generate(instruction) {
17120    const text = instruction.trim();
17121    if (!text || isBusy) {
17122      return;
17123    }
17124    setIsBusy(true);
17125    setError("");
17126    setResult("");
17127    try {
17128      setResult(await aiSuggest({
17129        kind: props.kind,
17130        current: props.getCurrent(),
17131        context: props.getContext?.(),
17132        instruction: text,
17133        elem: props.elem,
17134        actionId: props.actionId
17135      }));
17136    } catch (e3) {
17137      console.error(e3);
17138      setError(e3 instanceof Error ? e3.message : "The AI request failed.");
17139    }
17140    setIsBusy(false);
17141  }
17142  if (!isOpen) {
17143    return /* @__PURE__ */ u2("button", {
17144      type: "button",
17145      class: "le-aicard-open",
17146      onClick: () => setIsOpen(true),
17147      children: [
17148        /* @__PURE__ */ u2("span", {
17149          class: "le-aicard-ico",
17150          children: li.sparkle
17151        }),
17152        /* @__PURE__ */ u2("span", {
17153          children: props.title
17154        }),
17155        li.chevronDown
17156      ]
17157    });
17158  }
17159  return /* @__PURE__ */ u2("div", {
17160    class: "le-aicard",
17161    children: [
17162      /* @__PURE__ */ u2("div", {
17163        class: "le-aicard-head",
17164        children: [
17165          /* @__PURE__ */ u2("span", {
17166            class: "le-aicard-ico",
17167            children: li.sparkle
17168          }),
17169          /* @__PURE__ */ u2("h5", {
17170            children: props.title
17171          })
17172        ]
17173      }),
17174      /* @__PURE__ */ u2("textarea", {
17175        class: "le-area",
17176        rows: 2,
17177        value: prompt,
17178        placeholder: props.placeholder,
17179        disabled: isBusy,
17180        onInput: (ev) => setPrompt(ev.target.value),
17181        onKeyDown: (ev) => {
17182          if (ev.key == "Enter" && !ev.shiftKey) {
17183            ev.preventDefault();
17184            void generate(prompt);
17185          }
17186        }
17187      }),
17188      /* @__PURE__ */ u2("div", {
17189        class: "le-chips",
17190        children: props.chips.map((chip) => /* @__PURE__ */ u2("button", {
17191          type: "button",
17192          class: "le-chip",
17193          title: "Ask for: " + chip,
17194          disabled: isBusy,
17195          onClick: () => {
17196            setPrompt(chip);
17197            void generate(chip);
17198          },
17199          children: chip
17200        }, chip))
17201      }),
17202      /* @__PURE__ */ u2("button", {
17203        type: "button",
17204        class: "le-btn le-ai",
17205        disabled: isBusy || !prompt.trim(),
17206        onClick: () => void generate(prompt),
17207        children: [
17208          li.sparkle,
17209          isBusy ? "Thinking..." : "Generate"
17210        ]
17211      }),
17212      error && /* @__PURE__ */ u2("div", {
17213        class: "le-notice le-warn",
17214        children: [
17215          li.warn,
17216          /* @__PURE__ */ u2("div", {
17217            children: error
17218          })
17219        ]
17220      }),
17221      result && /* @__PURE__ */ u2(Result, {
17222        kind: props.kind,
17223        result,
17224        onDiscard: () => setResult(""),
17225        onApply: () => {
17226          props.onApply(result);
17227          setResult("");
17228        }
17229      })
17230    ]
17231  });
17232}
17233function Result(props) {
17234  const declarations = props.kind == "style" ? parseDeclarations(props.result) : [];
17235  const isUnusable = props.kind == "style" && declarations.length == 0;
17236  return /* @__PURE__ */ u2("div", {
17237    class: "le-aicard-result",
17238    children: [
17239      /* @__PURE__ */ u2("div", {
17240        class: "le-aicard-label",
17241        children: props.kind == "style" ? "Properties to set" : props.kind == "html" ? "Suggested markup" : "Suggested rewrite"
17242      }),
17243      props.kind == "style" ? isUnusable ? /* @__PURE__ */ u2("div", {
17244        class: "le-aicard-text le-mono",
17245        children: props.result
17246      }) : /* @__PURE__ */ u2("div", {
17247        class: "le-decls",
17248        children: declarations.map(([name, value]) => /* @__PURE__ */ u2("div", {
17249          class: "le-decl",
17250          children: [
17251            /* @__PURE__ */ u2("code", {
17252              children: name
17253            }),
17254            /* @__PURE__ */ u2("span", {
17255              children: value
17256            }),
17257            /^(#|rgb|hsl)/i.test(value) && /* @__PURE__ */ u2("i", {
17258              class: "le-decl-swatch",
17259              style: "background:" + value
17260            })
17261          ]
17262        }, name))
17263      }) : /* @__PURE__ */ u2("div", {
17264        class: "le-aicard-text" + (props.kind == "html" ? " le-mono" : ""),
17265        children: props.result
17266      }),
17267      isUnusable && /* @__PURE__ */ u2("p", {
17268        class: "le-hint",
17269        style: "margin:0.5em 0 0",
17270        children: "No CSS declarations could be read out of that. Discard it and rephrase."
17271      }),
17272      /* @__PURE__ */ u2("div", {
17273        class: "le-btn-row",
17274        children: [
17275          /* @__PURE__ */ u2("button", {
17276            type: "button",
17277            class: "le-btn",
17278            onClick: props.onDiscard,
17279            children: "Discard"
17280          }),
17281          /* @__PURE__ */ u2("button", {
17282            type: "button",
17283            class: "le-btn le-primary",
17284            disabled: isUnusable,
17285            onClick: props.onApply,
17286            children: "Apply"
17287          })
17288        ]
17289      })
17290    ]
17291  });
17292}
17293
17294// src/components/live/VariationsDialog.tsx
17295var CASE_CHIPS = [
17296  "Address this audience directly",
17297  "Shorter",
17298  "Benefit-led",
17299  "More urgent"
17300];
17301function ContentBox(props) {
17302  const [caret, setCaret] = p2(0);
17303  function capture(ev) {
17304    setCaret(ev.target.selectionStart ?? 0);
17305  }
17306  const common = {
17307    value: props.value,
17308    placeholder: props.placeholder,
17309    onInput: (ev) => props.onChange(ev.target.value),
17310    onSelect: capture,
17311    onClick: capture,
17312    onKeyUp: capture,
17313    onFocus: capture
17314  };
17315  return /* @__PURE__ */ u2("div", {
17316    class: "le-vd-content",
17317    children: [
17318      props.multiline ? /* @__PURE__ */ u2("textarea", {
17319        class: "le-area",
17320        rows: 2,
17321        ...common
17322      }) : /* @__PURE__ */ u2("input", {
17323        class: "le-input",
17324        type: "text",
17325        ...common
17326      }),
17327      /* @__PURE__ */ u2(VarButton, {
17328        text: props.value,
17329        caret,
17330        onInsert: (next, pos) => {
17331          props.onChange(next);
17332          setCaret(pos);
17333        }
17334      })
17335    ]
17336  });
17337}
17338function CaseAi(props) {
17339  const [isOpen, setIsOpen] = p2(false);
17340  const rule = props.when.trim();
17341  if (!isOpen) {
17342    return /* @__PURE__ */ u2("div", {
17343      class: "le-btn-bar",
17344      children: /* @__PURE__ */ u2("button", {
17345        type: "button",
17346        class: "le-btn le-ai",
17347        style: "width:auto",
17348        title: rule ? "Write this variation with AI, using the rule above as the audience" : "Set a condition first - the AI writes for the audience it describes",
17349        disabled: !rule,
17350        onClick: () => setIsOpen(true),
17351        children: [
17352          li.sparkle,
17353          "Generate for this case"
17354        ]
17355      })
17356    });
17357  }
17358  return /* @__PURE__ */ u2(AiSuggestCard, {
17359    title: "Generate for this case",
17360    kind: "text",
17361    getCurrent: () => props.then,
17362    getContext: () => "This variation is shown only to visitors matching: " + rule,
17363    elem: props.elem,
17364    actionId: props.actionId,
17365    placeholder: "What should this audience read? e.g. 'address CFOs, emphasize ROI, under 10 words'",
17366    chips: CASE_CHIPS,
17367    onApply: props.onApply
17368  });
17369}
17370function VariationsDialog(props) {
17371  const start2 = deriveCases(props.value);
17372  const [cases, setCases] = p2(start2.activeCases.length ? start2.activeCases : [
17373    {
17374      when: "",
17375      then: ""
17376    }
17377  ]);
17378  const [elseValue, setElseValue] = p2(start2.elseValue);
17379  const userColumns = useUserColumns().filter((c3) => c3.path.length == 1).map((c3) => ({
17380    value: c3.path[0],
17381    label: c3.label
17382  }));
17383  _2(() => {
17384    function onKey(ev) {
17385      if (ev.key == "Escape") {
17386        ev.stopPropagation();
17387        props.onCancel();
17388      }
17389    }
17390    document.addEventListener("keydown", onKey);
17391    return () => document.removeEventListener("keydown", onKey);
17392  }, []);
17393  function save() {
17394    props.onSave(composeCases(cases.filter((c3) => c3.when.trim() != ""), elseValue));
17395  }
17396  function setCase(i4, patch) {
17397    setCases(cases.map((c3, k3) => k3 != i4 ? c3 : {
17398      ...c3,
17399      ...patch
17400    }));
17401  }
17402  return /* @__PURE__ */ u2("div", {
17403    class: "le-root le-vd-overlay",
17404    onMouseDown: (ev) => {
17405      ev.stopPropagation();
17406      if (ev.target === ev.currentTarget) {
17407        props.onCancel();
17408      }
17409    },
17410    children: /* @__PURE__ */ u2("div", {
17411      class: "le-vd",
17412      role: "dialog",
17413      "aria-modal": "true",
17414      "aria-label": "Variations",
17415      children: [
17416        /* @__PURE__ */ u2("div", {
17417          class: "le-vd-head",
17418          children: [
17419            /* @__PURE__ */ u2("div", {
17420              children: [
17421                /* @__PURE__ */ u2("h3", {
17422                  children: [
17423                    li.bolt,
17424                    " Variations ",
17425                    /* @__PURE__ */ u2("span", {
17426                      class: "le-vd-tag",
17427                      children: "(audience-based)"
17428                    })
17429                  ]
17430                }),
17431                /* @__PURE__ */ u2("p", {
17432                  children: "The first matching case wins; everyone else gets the default."
17433                })
17434              ]
17435            }),
17436            /* @__PURE__ */ u2("button", {
17437              type: "button",
17438              class: "le-icon",
17439              title: "Close",
17440              onClick: props.onCancel,
17441              children: li.cross
17442            })
17443          ]
17444        }),
17445        /* @__PURE__ */ u2("div", {
17446          class: "le-vd-body",
17447          children: [
17448            cases.map((c3, i4) => /* @__PURE__ */ u2("div", {
17449              class: "le-vd-case",
17450              children: [
17451                /* @__PURE__ */ u2("div", {
17452                  class: "le-vd-case-head",
17453                  children: [
17454                    /* @__PURE__ */ u2("b", {
17455                      children: [
17456                        "Case ",
17457                        i4 + 1
17458                      ]
17459                    }),
17460                    /* @__PURE__ */ u2("div", {
17461                      class: "le-spacer"
17462                    }),
17463                    /* @__PURE__ */ u2("button", {
17464                      type: "button",
17465                      class: "le-icon",
17466                      title: "Remove this case",
17467                      onClick: () => setCases(cases.filter((_3, k3) => k3 != i4)),
17468                      children: li.cross
17469                    })
17470                  ]
17471                }),
17472                /* @__PURE__ */ u2("label", {
17473                  class: "le-vd-label",
17474                  children: "If visitor is:"
17475                }),
17476                /* @__PURE__ */ u2(RuleBuilder, {
17477                  value: c3.when,
17478                  userColumns,
17479                  onChange: (when) => setCase(i4, {
17480                    when
17481                  })
17482                }),
17483                /* @__PURE__ */ u2("label", {
17484                  class: "le-vd-label",
17485                  children: "Then show:"
17486                }),
17487                /* @__PURE__ */ u2(ContentBox, {
17488                  value: c3.then,
17489                  multiline: props.multiline,
17490                  placeholder: "Content for this audience",
17491                  onChange: (then) => setCase(i4, {
17492                    then
17493                  })
17494                }),
17495                isJsapiConfigured() && /* @__PURE__ */ u2(CaseAi, {
17496                  when: c3.when,
17497                  then: c3.then,
17498                  actionId: props.actionId ?? 0,
17499                  elem: props.elem ?? null,
17500                  onApply: (then) => setCase(i4, {
17501                    then
17502                  })
17503                })
17504              ]
17505            }, i4)),
17506            /* @__PURE__ */ u2("div", {
17507              class: "le-vd-default",
17508              children: [
17509                /* @__PURE__ */ u2("label", {
17510                  class: "le-vd-label",
17511                  children: "Default - when no case matches:"
17512                }),
17513                /* @__PURE__ */ u2(ContentBox, {
17514                  value: elseValue,
17515                  multiline: props.multiline,
17516                  placeholder: "Content everyone else sees",
17517                  onChange: setElseValue
17518                })
17519              ]
17520            })
17521          ]
17522        }),
17523        /* @__PURE__ */ u2("div", {
17524          class: "le-vd-foot",
17525          children: [
17526            /* @__PURE__ */ u2("button", {
17527              type: "button",
17528              class: "le-btn",
17529              onClick: () => setCases([
17530                ...cases,
17531                {
17532                  when: "",
17533                  then: ""
17534                }
17535              ]),
17536              children: "+ Add case"
17537            }),
17538            /* @__PURE__ */ u2("div", {
17539              class: "le-spacer"
17540            }),
17541            /* @__PURE__ */ u2("button", {
17542              type: "button",
17543              class: "le-btn",
17544              onClick: props.onCancel,
17545              children: "Cancel"
17546            }),
17547            /* @__PURE__ */ u2("button", {
17548              type: "button",
17549              class: "le-btn le-primary",
17550              onClick: save,
17551              children: "Done"
17552            })
17553          ]
17554        })
17555      ]
17556    })
17557  });
17558}
17559
17560// src/components/live/panels/TextPanels.tsx
17561var TEXT_CHIPS = [
17562  "Make it punchier",
17563  "Shorter",
17564  "Benefit-focused",
17565  "More urgent",
17566  "Match brand voice"
17567];
17568var HTML_CHIPS = [
17569  "Simplify the markup",
17570  "Make the wording punchier",
17571  "Shorter"
17572];
17573function useContentEditable(props, onInput) {
17574  const { session } = props;
17575  const [text, setText] = p2(() => session.elem.textContent ?? "");
17576  _2(() => {
17577    const elem = session.elem;
17578    elem.contentEditable = "true";
17579    try {
17580      elem.focus();
17581    } catch {
17582    }
17583    const handler = () => {
17584      setText(elem.textContent ?? "");
17585      onInput?.();
17586      session.onPreview();
17587    };
17588    elem.addEventListener("input", handler);
17589    elem.addEventListener("keyup", handler);
17590    elem.addEventListener("mouseup", handler);
17591    return () => {
17592      elem.removeEventListener("input", handler);
17593      elem.removeEventListener("keyup", handler);
17594      elem.removeEventListener("mouseup", handler);
17595      elem.contentEditable = "false";
17596      elem.removeAttribute("contenteditable");
17597    };
17598  }, [
17599    session.elem
17600  ]);
17601  return [
17602    text,
17603    setText
17604  ];
17605}
17606function useSavedPageSelection(elem, onOwn, skip) {
17607  const ranges = F2([]);
17608  const onOwnRef = F2(onOwn);
17609  onOwnRef.current = onOwn;
17610  const skipRef = F2(skip);
17611  skipRef.current = skip;
17612  _2(() => {
17613    ranges.current = [];
17614    return installSavedSelection(document, elem, {
17615      skip: () => skipRef.current?.() ?? false,
17616      onSaved: (cur) => {
17617        ranges.current = cur;
17618        onOwnRef.current?.();
17619      }
17620    });
17621  }, [
17622    elem
17623  ]);
17624  return ranges;
17625}
17626function insertTextOnPage(elem, savedRanges, text) {
17627  const sel = document.getSelection();
17628  if (sel && savedRanges.length) {
17629    try {
17630      elem.focus();
17631    } catch {
17632    }
17633    sel.removeAllRanges();
17634    for (const r3 of savedRanges) {
17635      sel.addRange(r3);
17636    }
17637    let inserted = false;
17638    try {
17639      inserted = document.execCommand("insertText", false, text);
17640    } catch {
17641      inserted = false;
17642    }
17643    if (!inserted) {
17644      const r3 = savedRanges[savedRanges.length - 1];
17645      r3.deleteContents();
17646      const node = document.createTextNode(text);
17647      r3.insertNode(node);
17648      r3.setStartAfter(node);
17649      r3.collapse(true);
17650    }
17651  } else {
17652    elem.appendChild(document.createTextNode(text));
17653  }
17654}
17655function useBoxCaret() {
17656  const [caret, setCaret] = p2(void 0);
17657  function capture(ev) {
17658    const el = ev.target;
17659    setCaret({
17660      start: el.selectionStart ?? 0,
17661      end: el.selectionEnd ?? 0
17662    });
17663  }
17664  return [
17665    caret,
17666    setCaret,
17667    capture
17668  ];
17669}
17670function VariationsButton(props) {
17671  const [isOpen, setIsOpen] = p2(false);
17672  const n2 = deriveCases(props.value).activeCases.length;
17673  return /* @__PURE__ */ u2(k, {
17674    children: [
17675      /* @__PURE__ */ u2("button", {
17676        type: "button",
17677        class: "le-btn",
17678        title: "Show different content to different audiences",
17679        onClick: () => setIsOpen(true),
17680        children: [
17681          li.bolt,
17682          "Variations",
17683          n2 > 0 && /* @__PURE__ */ u2("span", {
17684            class: "le-count",
17685            children: n2
17686          })
17687        ]
17688      }),
17689      isOpen && /* @__PURE__ */ u2(VariationsDialog, {
17690        value: props.value,
17691        multiline: props.multiline,
17692        actionId: props.actionId,
17693        elem: props.elem,
17694        onSave: (value) => {
17695          setIsOpen(false);
17696          props.onSave(value);
17697        },
17698        onCancel: () => setIsOpen(false)
17699      })
17700    ]
17701  });
17702}
17703function restoreBoxCaret(el, pos) {
17704  setTimeout(() => {
17705    if (el) {
17706      el.focus();
17707      el.setSelectionRange(pos, pos);
17708    }
17709  }, 0);
17710}
17711function TextPanel(props) {
17712  const { session } = props;
17713  const isInput = editHtmlKind(session.elem) == 2;
17714  if (isInput) {
17715    return /* @__PURE__ */ u2(InputValuePanel, {
17716      ...props
17717    });
17718  }
17719  return /* @__PURE__ */ u2(InPlacePanel, {
17720    ...props
17721  });
17722}
17723function InPlacePanel(props) {
17724  const { session } = props;
17725  const [text, setText] = useContentEditable(props);
17726  const mirrorRef = F2(null);
17727  const [mirrorCaret, setMirrorCaret, captureMirrorCaret] = useBoxCaret();
17728  const pageRanges = useSavedPageSelection(session.elem, void 0, () => mirrorRef.current?.matches(":focus") ?? false);
17729  _2(() => installPageTakeover(session.elem, () => setMirrorCaret(void 0)), [
17730    session.elem
17731  ]);
17732  function apply(next) {
17733    setText(next);
17734    session.elem.textContent = next;
17735    session.onPreview();
17736  }
17737  return /* @__PURE__ */ u2(k, {
17738    children: [
17739      /* @__PURE__ */ u2("div", {
17740        class: "le-notice",
17741        children: [
17742          li.pencil,
17743          /* @__PURE__ */ u2("div", {
17744            children: "Type straight on the page - the element is editable while this panel is open. The box below mirrors it, and replaces the whole text when you edit it here."
17745          })
17746        ]
17747      }),
17748      isJsapiConfigured() && /* @__PURE__ */ u2(AiSuggestCard, {
17749        title: "Edit text with AI",
17750        kind: "text",
17751        getCurrent: () => text,
17752        elem: session.elem,
17753        actionId: props.actionId,
17754        placeholder: "Tell AI what to change... e.g. 'make this headline punchier and under 8 words'",
17755        chips: TEXT_CHIPS,
17756        onApply: apply
17757      }),
17758      /* @__PURE__ */ u2(Section, {
17759        title: "Content",
17760        children: [
17761          /* @__PURE__ */ u2("textarea", {
17762            ref: mirrorRef,
17763            class: "le-area",
17764            rows: 3,
17765            value: text,
17766            onInput: (ev) => apply(ev.target.value),
17767            onSelect: captureMirrorCaret,
17768            onClick: captureMirrorCaret,
17769            onKeyUp: captureMirrorCaret,
17770            onFocus: captureMirrorCaret
17771          }),
17772          /* @__PURE__ */ u2("div", {
17773            class: "le-btn-bar",
17774            children: [
17775              mirrorCaret ? /* @__PURE__ */ u2(VarButton, {
17776                text,
17777                caret: mirrorCaret.start,
17778                onInsert: (next, pos) => {
17779                  apply(next);
17780                  setMirrorCaret({
17781                    start: pos,
17782                    end: pos
17783                  });
17784                  restoreBoxCaret(mirrorRef.current, pos);
17785                }
17786              }) : /* @__PURE__ */ u2(VarButton, {
17787                onPick: (token) => {
17788                  insertTextOnPage(session.elem, pageRanges.current, token);
17789                  setText(session.elem.textContent ?? "");
17790                  session.onPreview();
17791                }
17792              }),
17793              /* @__PURE__ */ u2(VariationsButton, {
17794                value: text,
17795                multiline: true,
17796                actionId: props.actionId,
17797                elem: session.elem,
17798                onSave: apply
17799              })
17800            ]
17801          })
17802        ]
17803      })
17804    ]
17805  });
17806}
17807function InputValuePanel(props) {
17808  const { session } = props;
17809  const input = session.elem;
17810  const [value, setValue] = p2(() => input.value ?? "");
17811  const boxRef = F2(null);
17812  const [caret, setCaret, captureCaret] = useBoxCaret();
17813  function apply(next) {
17814    setValue(next);
17815    input.value = next;
17816    input.setAttribute("value", next);
17817    session.onPreview();
17818  }
17819  return /* @__PURE__ */ u2(k, {
17820    children: [
17821      isJsapiConfigured() && /* @__PURE__ */ u2(AiSuggestCard, {
17822        title: "Edit this value with AI",
17823        kind: "text",
17824        getCurrent: () => value,
17825        elem: session.elem,
17826        actionId: props.actionId,
17827        placeholder: "Tell AI what to change... e.g. 'a friendlier placeholder'",
17828        chips: TEXT_CHIPS,
17829        onApply: apply
17830      }),
17831      /* @__PURE__ */ u2(Section, {
17832        title: "Value",
17833        children: [
17834          /* @__PURE__ */ u2("input", {
17835            ref: boxRef,
17836            class: "le-input",
17837            type: "text",
17838            value,
17839            placeholder: "Value of the field",
17840            onInput: (ev) => apply(ev.target.value),
17841            onSelect: captureCaret,
17842            onClick: captureCaret,
17843            onKeyUp: captureCaret,
17844            onFocus: captureCaret
17845          }),
17846          /* @__PURE__ */ u2("div", {
17847            class: "le-btn-bar",
17848            children: [
17849              /* @__PURE__ */ u2(VarButton, {
17850                text: value,
17851                caret: caret?.start ?? value.length,
17852                onInsert: (next, pos) => {
17853                  apply(next);
17854                  setCaret({
17855                    start: pos,
17856                    end: pos
17857                  });
17858                  restoreBoxCaret(boxRef.current, pos);
17859                }
17860              }),
17861              /* @__PURE__ */ u2(VariationsButton, {
17862                value,
17863                actionId: props.actionId,
17864                elem: session.elem,
17865                onSave: apply
17866              })
17867            ]
17868          })
17869        ]
17870      })
17871    ]
17872  });
17873}
17874var FORMAT_BLOCKS = [
17875  [
17876    "",
17877    "Formatting..."
17878  ],
17879  [
17880    "P",
17881    "Paragraph"
17882  ],
17883  [
17884    "PRE",
17885    "Preserve whitespace"
17886  ],
17887  [
17888    "ADDRESS",
17889    "Address"
17890  ],
17891  [
17892    "H1",
17893    "Heading, level 1"
17894  ],
17895  [
17896    "H2",
17897    "Heading, level 2"
17898  ],
17899  [
17900    "H3",
17901    "Heading, level 3"
17902  ],
17903  [
17904    "H4",
17905    "Heading, level 4"
17906  ],
17907  [
17908    "H5",
17909    "Heading, level 5"
17910  ],
17911  [
17912    "H6",
17913    "Heading, level 6"
17914  ]
17915];
17916function RichPanel(props) {
17917  const { session } = props;
17918  useContentEditable(props);
17919  const [linkUrl, setLinkUrl] = p2("");
17920  const [isLinkOpen, setIsLinkOpen] = p2(false);
17921  const [selectedText, setSelectedText] = p2("");
17922  const lastSelection = F2([]);
17923  const pageRanges = useSavedPageSelection(session.elem, () => {
17924    const sel = document.getSelection();
17925    const text = sel?.toString() ?? "";
17926    if (text && sel) {
17927      const ranges = new Array();
17928      for (let i4 = 0; i4 < sel.rangeCount; i4++) {
17929        ranges.push(sel.getRangeAt(i4).cloneRange());
17930      }
17931      lastSelection.current = ranges;
17932      setSelectedText(text);
17933    }
17934  });
17935  function restoreSelection() {
17936    const sel = document.getSelection();
17937    const saved = lastSelection.current;
17938    if (!sel || !saved.length) {
17939      return false;
17940    }
17941    try {
17942      session.elem.focus();
17943    } catch {
17944    }
17945    sel.removeAllRanges();
17946    for (const r3 of saved) {
17947      sel.addRange(r3);
17948    }
17949    return true;
17950  }
17951  function command(name, value, useCss = false) {
17952    try {
17953      restoreSelection();
17954      try {
17955        document.execCommand("styleWithCSS", false, useCss ? "true" : "false");
17956      } catch {
17957      }
17958      document.execCommand(name, false, value ?? void 0);
17959      session.onPreview();
17960    } catch (e3) {
17961      console.error(e3);
17962    }
17963  }
17964  function keepSelection(ev) {
17965    ev.preventDefault();
17966  }
17967  return /* @__PURE__ */ u2(k, {
17968    children: [
17969      /* @__PURE__ */ u2("div", {
17970        class: "le-notice",
17971        children: [
17972          li.lines,
17973          selectedText.trim() ? /* @__PURE__ */ u2("div", {
17974            children: [
17975              "These controls apply to ",
17976              /* @__PURE__ */ u2("b", {
17977                children: [
17978                  '"',
17979                  selectedText.length > 60 ? selectedText.slice(0, 59) + "\u2026" : selectedText,
17980                  '"'
17981                ]
17982              }),
17983              " - the last text you selected on the page. Select something else to move them."
17984            ]
17985          }) : /* @__PURE__ */ u2("div", {
17986            children: [
17987              "Select some text ",
17988              /* @__PURE__ */ u2("b", {
17989                children: "on the page"
17990              }),
17991              " first - these controls act on a selection, and do nothing without one."
17992            ]
17993          })
17994        ]
17995      }),
17996      /* @__PURE__ */ u2(Section, {
17997        title: "Inline",
17998        children: /* @__PURE__ */ u2("div", {
17999          class: "le-rt",
18000          onMouseDown: keepSelection,
18001          children: [
18002            /* @__PURE__ */ u2("button", {
18003              type: "button",
18004              title: "Bold",
18005              style: "font-weight:900",
18006              onClick: () => command("Bold"),
18007              children: "B"
18008            }),
18009            /* @__PURE__ */ u2("button", {
18010              type: "button",
18011              title: "Italic",
18012              style: "font-style:italic",
18013              onClick: () => command("Italic"),
18014              children: "I"
18015            }),
18016            /* @__PURE__ */ u2("button", {
18017              type: "button",
18018              title: "Underline",
18019              style: "text-decoration:underline",
18020              onClick: () => command("Underline"),
18021              children: "U"
18022            }),
18023            /* @__PURE__ */ u2("button", {
18024              type: "button",
18025              title: "Strike through",
18026              style: "text-decoration:line-through",
18027              onClick: () => command("StrikeThrough"),
18028              children: "S"
18029            }),
18030            /* @__PURE__ */ u2("button", {
18031              type: "button",
18032              title: "Subscript",
18033              onClick: () => command("Subscript"),
18034              children: "x\xB2"
18035            }),
18036            /* @__PURE__ */ u2("button", {
18037              type: "button",
18038              title: "Superscript",
18039              onClick: () => command("Superscript"),
18040              children: "x\u2082"
18041            }),
18042            /* @__PURE__ */ u2("button", {
18043              type: "button",
18044              title: "Bigger",
18045              onClick: () => command("IncreaseFontSize"),
18046              children: "F+"
18047            }),
18048            /* @__PURE__ */ u2("button", {
18049              type: "button",
18050              title: "Smaller",
18051              onClick: () => command("DecreaseFontSize"),
18052              children: "F-"
18053            }),
18054            /* @__PURE__ */ u2("button", {
18055              type: "button",
18056              title: "Remove formatting",
18057              onClick: () => {
18058                command("RemoveFormat");
18059                command("Unlink");
18060              },
18061              children: "\u232B"
18062            })
18063          ]
18064        })
18065      }),
18066      /* @__PURE__ */ u2(Section, {
18067        title: "Color",
18068        children: /* @__PURE__ */ u2("div", {
18069          class: "le-field-group",
18070          children: [
18071            /* @__PURE__ */ u2("label", {
18072              class: "le-btn",
18073              title: "Color of the selected text",
18074              children: [
18075                "Text",
18076                /* @__PURE__ */ u2("input", {
18077                  type: "color",
18078                  style: "width:1.5em; height:1.5em; padding:0; border:none; background:transparent",
18079                  onInput: (ev) => command("ForeColor", ev.target.value, true)
18080                })
18081              ]
18082            }),
18083            /* @__PURE__ */ u2("label", {
18084              class: "le-btn",
18085              title: "Highlight behind the selected text",
18086              children: [
18087                "Highlight",
18088                /* @__PURE__ */ u2("input", {
18089                  type: "color",
18090                  style: "width:1.5em; height:1.5em; padding:0; border:none; background:transparent",
18091                  onInput: (ev) => {
18092                    const color = ev.target.value;
18093                    command("HiliteColor", color, true);
18094                    if (!document.queryCommandValue?.("hiliteColor")) {
18095                      command("BackColor", color, true);
18096                    }
18097                  }
18098                })
18099              ]
18100            })
18101          ]
18102        })
18103      }),
18104      /* @__PURE__ */ u2(Section, {
18105        title: "Block",
18106        children: [
18107          /* @__PURE__ */ u2(Field, {
18108            label: "Type",
18109            children: /* @__PURE__ */ u2(SelectInput, {
18110              value: "",
18111              options: FORMAT_BLOCKS,
18112              onChange: (value) => value && command("FormatBlock", value)
18113            })
18114          }),
18115          /* @__PURE__ */ u2("div", {
18116            class: "le-rt",
18117            onMouseDown: keepSelection,
18118            children: [
18119              /* @__PURE__ */ u2("button", {
18120                type: "button",
18121                title: "Align left",
18122                onClick: () => command("JustifyLeft"),
18123                children: "\u25E7"
18124              }),
18125              /* @__PURE__ */ u2("button", {
18126                type: "button",
18127                title: "Align center",
18128                onClick: () => command("JustifyCenter"),
18129                children: "\u25A3"
18130              }),
18131              /* @__PURE__ */ u2("button", {
18132                type: "button",
18133                title: "Align right",
18134                onClick: () => command("JustifyRight"),
18135                children: "\u25E8"
18136              }),
18137              /* @__PURE__ */ u2("button", {
18138                type: "button",
18139                title: "Justify",
18140                onClick: () => command("JustifyFull"),
18141                children: "\u25A4"
18142              }),
18143              /* @__PURE__ */ u2("button", {
18144                type: "button",
18145                title: "Bulleted list",
18146                onClick: () => command("InsertUnorderedList"),
18147                children: "\u2022\u2261"
18148              }),
18149              /* @__PURE__ */ u2("button", {
18150                type: "button",
18151                title: "Numbered list",
18152                onClick: () => command("InsertOrderedList"),
18153                children: "1\u2261"
18154              }),
18155              /* @__PURE__ */ u2("button", {
18156                type: "button",
18157                title: "Outdent",
18158                onClick: () => command("Outdent"),
18159                children: "\u21E4"
18160              }),
18161              /* @__PURE__ */ u2("button", {
18162                type: "button",
18163                title: "Indent",
18164                onClick: () => command("Indent"),
18165                children: "\u21E5"
18166              }),
18167              /* @__PURE__ */ u2("button", {
18168                type: "button",
18169                title: "Horizontal rule",
18170                onClick: () => command("InsertHorizontalRule"),
18171                children: "\u2015"
18172              }),
18173              /* @__PURE__ */ u2("button", {
18174                type: "button",
18175                title: "Paragraph",
18176                onClick: () => command("InsertParagraph"),
18177                children: "\xB6"
18178              })
18179            ]
18180          })
18181        ]
18182      }),
18183      /* @__PURE__ */ u2(Section, {
18184        title: "Dynamic value",
18185        children: /* @__PURE__ */ u2("div", {
18186          class: "le-btn-bar",
18187          onMouseDown: keepSelection,
18188          children: [
18189            /* @__PURE__ */ u2(VarButton, {
18190              onPick: (token) => {
18191                insertTextOnPage(session.elem, pageRanges.current, token);
18192                session.onPreview();
18193              }
18194            }),
18195            /* @__PURE__ */ u2(VariationsButton, {
18196              value: session.elem.innerHTML,
18197              multiline: true,
18198              actionId: props.actionId,
18199              elem: session.elem,
18200              onSave: (value) => {
18201                session.elem.innerHTML = value;
18202                session.onPreview();
18203              }
18204            })
18205          ]
18206        })
18207      }),
18208      /* @__PURE__ */ u2(Section, {
18209        title: "Link",
18210        children: !isLinkOpen ? /* @__PURE__ */ u2("div", {
18211          class: "le-btn-bar",
18212          onMouseDown: keepSelection,
18213          children: [
18214            /* @__PURE__ */ u2("button", {
18215              type: "button",
18216              class: "le-btn",
18217              disabled: !selectedText.trim(),
18218              title: selectedText.trim() ? "Turn the selected text into a link" : "Select the text to link first",
18219              onClick: () => setIsLinkOpen(true),
18220              children: [
18221                li.link,
18222                " Link the selection"
18223              ]
18224            }),
18225            /* @__PURE__ */ u2("button", {
18226              type: "button",
18227              class: "le-btn",
18228              title: "Remove the link from the selection",
18229              onClick: () => command("Unlink"),
18230              children: "Unlink"
18231            })
18232          ]
18233        }) : /* @__PURE__ */ u2(k, {
18234          children: [
18235            /* @__PURE__ */ u2("div", {
18236              class: "le-field-group",
18237              children: /* @__PURE__ */ u2(TextInput, {
18238                value: linkUrl,
18239                placeholder: "https://...",
18240                onChange: setLinkUrl
18241              })
18242            }),
18243            /* @__PURE__ */ u2("div", {
18244              class: "le-btn-bar",
18245              children: [
18246                /* @__PURE__ */ u2("button", {
18247                  type: "button",
18248                  class: "le-btn",
18249                  onClick: () => {
18250                    setIsLinkOpen(false);
18251                    setLinkUrl("");
18252                  },
18253                  children: "Cancel"
18254                }),
18255                /* @__PURE__ */ u2("button", {
18256                  type: "button",
18257                  class: "le-btn le-primary",
18258                  disabled: !linkUrl.trim(),
18259                  onClick: () => {
18260                    command("CreateLink", linkUrl.trim());
18261                    setIsLinkOpen(false);
18262                    setLinkUrl("");
18263                  },
18264                  children: "Apply"
18265                })
18266              ]
18267            })
18268          ]
18269        })
18270      })
18271    ]
18272  });
18273}
18274function SourcePanel(props) {
18275  const { session } = props;
18276  const [html, setHtml] = p2(() => session.elem.innerHTML);
18277  const boxRef = F2(null);
18278  const [caret, setCaret, captureCaret] = useBoxCaret();
18279  const errorRef = F2("");
18280  const [error, setError] = p2("");
18281  function apply(value) {
18282    setHtml(value);
18283    try {
18284      session.elem.innerHTML = value;
18285      if (errorRef.current) {
18286        errorRef.current = "";
18287        setError("");
18288      }
18289    } catch (e3) {
18290      const message = e3 instanceof Error ? e3.message : String(e3);
18291      errorRef.current = message;
18292      setError(message);
18293    }
18294    session.onPreview();
18295  }
18296  return /* @__PURE__ */ u2(k, {
18297    children: [
18298      /* @__PURE__ */ u2("div", {
18299        class: "le-notice",
18300        children: [
18301          li.code,
18302          /* @__PURE__ */ u2("div", {
18303            children: [
18304              "Everything inside the element. The outer tag and its attributes are edited in ",
18305              /* @__PURE__ */ u2("b", {
18306                children: "Properties"
18307              }),
18308              "."
18309            ]
18310          })
18311        ]
18312      }),
18313      error && /* @__PURE__ */ u2("div", {
18314        class: "le-notice le-warn",
18315        children: [
18316          li.warn,
18317          /* @__PURE__ */ u2("div", {
18318            children: error
18319          })
18320        ]
18321      }),
18322      isJsapiConfigured() && /* @__PURE__ */ u2(AiSuggestCard, {
18323        title: "Edit the markup with AI",
18324        kind: "html",
18325        getCurrent: () => html,
18326        elem: session.elem,
18327        actionId: props.actionId,
18328        placeholder: "Describe the markup you want... e.g. 'wrap each line in its own paragraph'",
18329        chips: HTML_CHIPS,
18330        onApply: apply
18331      }),
18332      /* @__PURE__ */ u2(Section, {
18333        title: "Inner HTML",
18334        children: [
18335          /* @__PURE__ */ u2("textarea", {
18336            ref: boxRef,
18337            class: "le-area le-mono",
18338            rows: 10,
18339            value: html,
18340            onInput: (ev) => apply(ev.target.value),
18341            onSelect: captureCaret,
18342            onClick: captureCaret,
18343            onKeyUp: captureCaret,
18344            onFocus: captureCaret
18345          }),
18346          /* @__PURE__ */ u2("div", {
18347            class: "le-btn-bar",
18348            children: [
18349              /* @__PURE__ */ u2(VarButton, {
18350                text: html,
18351                caret: caret?.start ?? html.length,
18352                onInsert: (next, pos) => {
18353                  apply(next);
18354                  setCaret({
18355                    start: pos,
18356                    end: pos
18357                  });
18358                  restoreBoxCaret(boxRef.current, pos);
18359                }
18360              }),
18361              /* @__PURE__ */ u2(VariationsButton, {
18362                value: html,
18363                multiline: true,
18364                actionId: props.actionId,
18365                elem: session.elem,
18366                onSave: apply
18367              })
18368            ]
18369          })
18370        ]
18371      })
18372    ]
18373  });
18374}
18375
18376// src/components/live/panels/StylePanels.tsx
18377function useStyleEditor(session) {
18378  const [, setVersion] = p2(0);
18379  const style = session.elem.style;
18380  function get(name) {
18381    const own2 = style[name];
18382    if (own2) {
18383      return own2;
18384    }
18385    try {
18386      return getComputedStyle(session.elem)[name] || "";
18387    } catch {
18388      return "";
18389    }
18390  }
18391  function own(name) {
18392    return style[name] ?? "";
18393  }
18394  function getNum(name, useComputed = true) {
18395    const value = useComputed ? get(name) : own(name);
18396    const n2 = parseFloat(value);
18397    return isNaN(n2) ? "" : String(Math.round(n2 * 1e3) / 1e3);
18398  }
18399  function set(name, value) {
18400    style[name] = value;
18401    setVersion((v3) => v3 + 1);
18402    session.onPreview();
18403  }
18404  function setNum(name, value, unit = "px") {
18405    set(name, value === "" ? "" : value + unit);
18406  }
18407  function setMany(values) {
18408    for (const name in values) {
18409      style[name] = values[name];
18410    }
18411    setVersion((v3) => v3 + 1);
18412    session.onPreview();
18413  }
18414  function setDeclarations(declarations) {
18415    const css = session.elem.style;
18416    for (const [name, value] of declarations) {
18417      try {
18418        css.setProperty(name, value);
18419      } catch (e3) {
18420        console.error(e3);
18421      }
18422    }
18423    setVersion((v3) => v3 + 1);
18424    session.onPreview();
18425  }
18426  function describeComputed(names) {
18427    const computed = getComputedStyle(session.elem);
18428    return names.map((name) => name + ": " + (computed.getPropertyValue(name) || "(unset)")).join("\n");
18429  }
18430  return {
18431    get,
18432    own,
18433    getNum,
18434    set,
18435    setNum,
18436    setMany,
18437    setDeclarations,
18438    describeComputed
18439  };
18440}
18441var FONT_WEIGHTS = [
18442  [
18443    "",
18444    "Inherit"
18445  ],
18446  [
18447    "400",
18448    "400 - Regular"
18449  ],
18450  [
18451    "500",
18452    "500 - Medium"
18453  ],
18454  [
18455    "600",
18456    "600 - Semibold"
18457  ],
18458  [
18459    "700",
18460    "700 - Bold"
18461  ],
18462  [
18463    "800",
18464    "800 - Extra-bold"
18465  ],
18466  [
18467    "900",
18468    "900 - Black"
18469  ]
18470];
18471var BORDER_STYLES = [
18472  [
18473    "",
18474    "Inherit"
18475  ],
18476  [
18477    "none",
18478    "None"
18479  ],
18480  [
18481    "solid",
18482    "Solid"
18483  ],
18484  [
18485    "dashed",
18486    "Dashed"
18487  ],
18488  [
18489    "dotted",
18490    "Dotted"
18491  ]
18492];
18493var ALIGN_CHOICES = [
18494  {
18495    value: "left",
18496    title: "Left",
18497    icon: li.alignLeft
18498  },
18499  {
18500    value: "center",
18501    title: "Center",
18502    icon: li.alignCenter
18503  },
18504  {
18505    value: "right",
18506    title: "Right",
18507    icon: li.alignRight
18508  },
18509  {
18510    value: "justify",
18511    title: "Justify",
18512    icon: li.alignJustify
18513  }
18514];
18515var PRESETS = [
18516  {
18517    label: "Heading 1",
18518    title: "36px, bold, line-height 1.2",
18519    style: {
18520      fontSize: "36px",
18521      fontWeight: "700",
18522      lineHeight: "1.2"
18523    }
18524  },
18525  {
18526    label: "Heading 2",
18527    title: "28px, semibold, line-height 1.3",
18528    style: {
18529      fontSize: "28px",
18530      fontWeight: "600",
18531      lineHeight: "1.3"
18532    }
18533  },
18534  {
18535    label: "Body",
18536    title: "16px, regular, line-height 1.5",
18537    style: {
18538      fontSize: "16px",
18539      fontWeight: "400",
18540      lineHeight: "1.5"
18541    }
18542  },
18543  {
18544    label: "Caption",
18545    title: "13px, regular",
18546    style: {
18547      fontSize: "13px",
18548      fontWeight: "400",
18549      lineHeight: "1.4"
18550    }
18551  },
18552  {
18553    label: "Muted",
18554    title: "Current size at 50% opacity",
18555    style: {
18556      opacity: "0.5"
18557    }
18558  },
18559  {
18560    label: "Brand accent",
18561    title: "Personyze green #12B886",
18562    style: {
18563      color: "#12B886"
18564    }
18565  }
18566];
18567var AI_CONTEXT_PROPS = [
18568  "color",
18569  "background-color",
18570  "font-family",
18571  "font-size",
18572  "font-weight",
18573  "line-height",
18574  "letter-spacing",
18575  "text-align",
18576  "text-transform",
18577  "border",
18578  "border-radius",
18579  "opacity",
18580  "padding",
18581  "margin",
18582  "display"
18583];
18584var STYLE_CHIPS = [
18585  "Make it bolder",
18586  "More contrast",
18587  "Softer and rounded",
18588  "Match the brand green",
18589  "Bigger and tighter"
18590];
18591function StylePanel(props) {
18592  const { session } = props;
18593  const style = useStyleEditor(session);
18594  const pageCssRules = q2(() => matchingRules(session.elem), [
18595    session.elem
18596  ]);
18597  const [tab, setTab] = p2("text");
18598  _2(() => setTab("text"), [
18599    session.elem
18600  ]);
18601  const tabs = [
18602    {
18603      id: "text",
18604      label: "Text",
18605      title: "Colour, size, weight, spacing, alignment"
18606    },
18607    {
18608      id: "bg",
18609      label: "Background",
18610      title: "Background colour and image"
18611    },
18612    {
18613      id: "box",
18614      label: "Box",
18615      title: "Border, corner radius, opacity"
18616    },
18617    {
18618      id: "presets",
18619      label: "Presets",
18620      title: "Ready-made looks"
18621    },
18622    ...pageCssRules.length ? [
18623      {
18624        id: "css",
18625        label: "Page CSS",
18626        title: "Rules from the site's own stylesheets that match this element"
18627      }
18628    ] : []
18629  ];
18630  const cur = tabs.some((v3) => v3.id == tab) ? tab : "text";
18631  return /* @__PURE__ */ u2(k, {
18632    children: [
18633      /* @__PURE__ */ u2("p", {
18634        class: "le-hint le-style-note",
18635        title: "A box left empty leaves that property to the page",
18636        children: [
18637          "Written as ",
18638          /* @__PURE__ */ u2("b", {
18639            children: "inline style"
18640          }),
18641          ", so it beats the site's own stylesheet."
18642        ]
18643      }),
18644      isJsapiConfigured() && /* @__PURE__ */ u2(AiSuggestCard, {
18645        title: "Edit style with AI",
18646        kind: "style",
18647        collapsible: true,
18648        // The declarations already ON the element, which is what the AI is amending.
18649        getCurrent: () => session.elem.getAttribute("style") ?? "",
18650        getContext: () => style.describeComputed(AI_CONTEXT_PROPS),
18651        elem: session.elem,
18652        actionId: props.actionId,
18653        placeholder: "Describe the look you want... e.g. 'bolder and more premium' or 'match the hero above'",
18654        chips: STYLE_CHIPS,
18655        onApply: (text) => style.setDeclarations(parseDeclarations(text))
18656      }),
18657      /* @__PURE__ */ u2(PanelTabs, {
18658        tabs,
18659        value: cur,
18660        onChange: setTab
18661      }),
18662      cur == "text" && /* @__PURE__ */ u2(Section, {
18663        children: [
18664          /* @__PURE__ */ u2(Field, {
18665            label: "Color",
18666            children: /* @__PURE__ */ u2(ColorInput, {
18667              value: style.get("color"),
18668              onChange: (value) => style.set("color", value)
18669            })
18670          }),
18671          /* @__PURE__ */ u2(Field, {
18672            label: "Size",
18673            children: [
18674              /* @__PURE__ */ u2(NumInput, {
18675                value: style.getNum("fontSize"),
18676                unit: "px",
18677                min: 4,
18678                max: 400,
18679                onChange: (value) => style.setNum("fontSize", value)
18680              }),
18681              /* @__PURE__ */ u2(SelectInput, {
18682                value: style.own("fontWeight"),
18683                options: FONT_WEIGHTS,
18684                onChange: (value) => style.set("fontWeight", value)
18685              })
18686            ]
18687          }),
18688          /* @__PURE__ */ u2(Field, {
18689            label: "Line height",
18690            children: /* @__PURE__ */ u2(NumInput, {
18691              value: style.getNum("lineHeight"),
18692              unit: "px",
18693              step: 1,
18694              onChange: (value) => style.setNum("lineHeight", value)
18695            })
18696          }),
18697          /* @__PURE__ */ u2(Field, {
18698            label: "Letter space",
18699            children: /* @__PURE__ */ u2(NumInput, {
18700              value: style.getNum("letterSpacing", false),
18701              unit: "px",
18702              step: 0.1,
18703              onChange: (value) => style.setNum("letterSpacing", value)
18704            })
18705          }),
18706          /* @__PURE__ */ u2(Field, {
18707            label: "Align",
18708            children: /* @__PURE__ */ u2(IconGroup, {
18709              value: style.get("textAlign"),
18710              choices: ALIGN_CHOICES,
18711              onChange: (value) => style.set("textAlign", value)
18712            })
18713          }),
18714          /* @__PURE__ */ u2(Field, {
18715            label: "Transform",
18716            children: /* @__PURE__ */ u2(SelectInput, {
18717              value: style.own("textTransform"),
18718              options: [
18719                [
18720                  "",
18721                  "Inherit"
18722                ],
18723                [
18724                  "none",
18725                  "None"
18726                ],
18727                [
18728                  "uppercase",
18729                  "UPPERCASE"
18730                ],
18731                [
18732                  "lowercase",
18733                  "lowercase"
18734                ],
18735                [
18736                  "capitalize",
18737                  "Capitalize"
18738                ]
18739              ],
18740              onChange: (value) => style.set("textTransform", value)
18741            })
18742          })
18743        ]
18744      }),
18745      cur == "bg" && /* @__PURE__ */ u2(Section, {
18746        children: [
18747          /* @__PURE__ */ u2(Field, {
18748            label: "Color",
18749            children: /* @__PURE__ */ u2(ColorInput, {
18750              value: style.own("backgroundColor"),
18751              onChange: (value) => style.set("backgroundColor", value)
18752            })
18753          }),
18754          /* @__PURE__ */ u2(Field, {
18755            label: "Image",
18756            children: /* @__PURE__ */ u2(MediaUrlInput, {
18757              value: (style.own("backgroundImage").match(/^url\(["']?([^"')]*)/i) ?? [
18758                "",
18759                ""
18760              ])[1],
18761              placeholder: "https://... (empty for none)",
18762              accept: "image",
18763              onChange: (value) => style.set("backgroundImage", value ? "url(" + encodeURI(value) + ")" : "")
18764            })
18765          }),
18766          /* @__PURE__ */ u2(Field, {
18767            label: "Fit",
18768            children: /* @__PURE__ */ u2(SelectInput, {
18769              value: style.own("backgroundSize"),
18770              options: [
18771                [
18772                  "",
18773                  "Inherit"
18774                ],
18775                [
18776                  "auto",
18777                  "Auto"
18778                ],
18779                [
18780                  "cover",
18781                  "Cover"
18782                ],
18783                [
18784                  "contain",
18785                  "Contain"
18786                ]
18787              ],
18788              onChange: (value) => style.set("backgroundSize", value)
18789            })
18790          })
18791        ]
18792      }),
18793      cur == "box" && /* @__PURE__ */ u2(Section, {
18794        children: [
18795          /* @__PURE__ */ u2(Field, {
18796            label: "Width",
18797            children: [
18798              /* @__PURE__ */ u2(NumInput, {
18799                value: style.getNum("borderWidth", false),
18800                unit: "px",
18801                min: 0,
18802                onChange: (value) => style.setNum("borderWidth", value)
18803              }),
18804              /* @__PURE__ */ u2(SelectInput, {
18805                value: style.own("borderStyle"),
18806                options: BORDER_STYLES,
18807                onChange: (value) => style.set("borderStyle", value)
18808              })
18809            ]
18810          }),
18811          /* @__PURE__ */ u2(Field, {
18812            label: "Color",
18813            children: /* @__PURE__ */ u2(ColorInput, {
18814              value: style.own("borderColor"),
18815              onChange: (value) => style.set("borderColor", value)
18816            })
18817          }),
18818          /* @__PURE__ */ u2(Field, {
18819            label: "Radius",
18820            children: /* @__PURE__ */ u2(NumInput, {
18821              value: style.getNum("borderRadius", false),
18822              unit: "px",
18823              min: 0,
18824              onChange: (value) => style.setNum("borderRadius", value)
18825            })
18826          }),
18827          /* @__PURE__ */ u2(Field, {
18828            label: "Opacity",
18829            children: /* @__PURE__ */ u2(NumInput, {
18830              value: style.getNum("opacity", false),
18831              unit: "",
18832              min: 0,
18833              max: 1,
18834              step: 0.05,
18835              onChange: (value) => style.set("opacity", value)
18836            })
18837          })
18838        ]
18839      }),
18840      cur == "presets" && /* @__PURE__ */ u2(Section, {
18841        children: [
18842          /* @__PURE__ */ u2("p", {
18843            class: "le-hint",
18844            children: "One tap sets several properties at once."
18845          }),
18846          /* @__PURE__ */ u2("div", {
18847            class: "le-presets",
18848            children: PRESETS.map((preset) => /* @__PURE__ */ u2("button", {
18849              type: "button",
18850              class: "le-preset",
18851              title: preset.title,
18852              onClick: () => style.setMany(preset.style),
18853              children: preset.label
18854            }, preset.label))
18855          })
18856        ]
18857      }),
18858      cur == "css" && /* @__PURE__ */ u2(PageCssSection, {
18859        session,
18860        initialRules: pageCssRules
18861      })
18862    ]
18863  });
18864}
18865function SpacingPanel(props) {
18866  const style = useStyleEditor(props.session);
18867  const sides = [
18868    [
18869      "Top",
18870      "Top"
18871    ],
18872    [
18873      "Right",
18874      "Right"
18875    ],
18876    [
18877      "Bottom",
18878      "Bottom"
18879    ],
18880    [
18881      "Left",
18882      "Left"
18883    ]
18884  ];
18885  return /* @__PURE__ */ u2(k, {
18886    children: [
18887      /* @__PURE__ */ u2("p", {
18888        class: "le-hint",
18889        children: "Leave a box empty to keep whatever the site's own stylesheet says."
18890      }),
18891      /* @__PURE__ */ u2(Section, {
18892        title: "Margin",
18893        children: sides.map(([side, label]) => /* @__PURE__ */ u2(Field, {
18894          label,
18895          children: /* @__PURE__ */ u2(NumInput, {
18896            value: style.getNum("margin" + side, false),
18897            unit: "px",
18898            step: 1,
18899            onChange: (value) => style.setNum("margin" + side, value)
18900          })
18901        }, "m" + side))
18902      }),
18903      /* @__PURE__ */ u2(Section, {
18904        title: "Padding",
18905        children: sides.map(([side, label]) => /* @__PURE__ */ u2(Field, {
18906          label,
18907          children: /* @__PURE__ */ u2(NumInput, {
18908            value: style.getNum("padding" + side, false),
18909            unit: "px",
18910            min: 0,
18911            step: 1,
18912            onChange: (value) => style.setNum("padding" + side, value)
18913          })
18914        }, "p" + side))
18915      })
18916    ]
18917  });
18918}
18919function AlignmentPanel(props) {
18920  const style = useStyleEditor(props.session);
18921  const display = style.get("display");
18922  const isFlexish = display.indexOf("flex") != -1 || display.indexOf("grid") != -1;
18923  return /* @__PURE__ */ u2(k, {
18924    children: [
18925      /* @__PURE__ */ u2(Section, {
18926        title: "Text",
18927        children: [
18928          /* @__PURE__ */ u2(Field, {
18929            label: "Align",
18930            children: /* @__PURE__ */ u2(IconGroup, {
18931              value: style.get("textAlign"),
18932              choices: ALIGN_CHOICES,
18933              onChange: (value) => style.set("textAlign", value)
18934            })
18935          }),
18936          /* @__PURE__ */ u2(Field, {
18937            label: "Vertical",
18938            children: /* @__PURE__ */ u2(SelectInput, {
18939              value: style.own("verticalAlign"),
18940              options: [
18941                [
18942                  "",
18943                  "Inherit"
18944                ],
18945                [
18946                  "baseline",
18947                  "Baseline"
18948                ],
18949                [
18950                  "top",
18951                  "Top"
18952                ],
18953                [
18954                  "middle",
18955                  "Middle"
18956                ],
18957                [
18958                  "bottom",
18959                  "Bottom"
18960                ]
18961              ],
18962              onChange: (value) => style.set("verticalAlign", value)
18963            })
18964          })
18965        ]
18966      }),
18967      /* @__PURE__ */ u2(Section, {
18968        title: "Layout",
18969        children: [
18970          /* @__PURE__ */ u2(Field, {
18971            label: "Display",
18972            children: /* @__PURE__ */ u2(SelectInput, {
18973              value: style.own("display"),
18974              options: [
18975                [
18976                  "",
18977                  "Inherit (" + display + ")"
18978                ],
18979                [
18980                  "block",
18981                  "Block"
18982                ],
18983                [
18984                  "inline-block",
18985                  "Inline block"
18986                ],
18987                [
18988                  "inline",
18989                  "Inline"
18990                ],
18991                [
18992                  "flex",
18993                  "Flex"
18994                ],
18995                [
18996                  "inline-flex",
18997                  "Inline flex"
18998                ],
18999                [
19000                  "grid",
19001                  "Grid"
19002                ],
19003                [
19004                  "none",
19005                  "Hidden"
19006                ]
19007              ],
19008              onChange: (value) => style.set("display", value)
19009            })
19010          }),
19011          isFlexish && /* @__PURE__ */ u2(k, {
19012            children: [
19013              /* @__PURE__ */ u2(Field, {
19014                label: "Direction",
19015                children: /* @__PURE__ */ u2(SelectInput, {
19016                  value: style.own("flexDirection"),
19017                  options: [
19018                    [
19019                      "",
19020                      "Inherit"
19021                    ],
19022                    [
19023                      "row",
19024                      "Row"
19025                    ],
19026                    [
19027                      "row-reverse",
19028                      "Row reversed"
19029                    ],
19030                    [
19031                      "column",
19032                      "Column"
19033                    ],
19034                    [
19035                      "column-reverse",
19036                      "Column reversed"
19037                    ]
19038                  ],
19039                  onChange: (value) => style.set("flexDirection", value)
19040                })
19041              }),
19042              /* @__PURE__ */ u2(Field, {
19043                label: "Justify",
19044                children: /* @__PURE__ */ u2(SelectInput, {
19045                  value: style.own("justifyContent"),
19046                  options: [
19047                    [
19048                      "",
19049                      "Inherit"
19050                    ],
19051                    [
19052                      "flex-start",
19053                      "Start"
19054                    ],
19055                    [
19056                      "center",
19057                      "Center"
19058                    ],
19059                    [
19060                      "flex-end",
19061                      "End"
19062                    ],
19063                    [
19064                      "space-between",
19065                      "Space between"
19066                    ],
19067                    [
19068                      "space-around",
19069                      "Space around"
19070                    ]
19071                  ],
19072                  onChange: (value) => style.set("justifyContent", value)
19073                })
19074              }),
19075              /* @__PURE__ */ u2(Field, {
19076                label: "Items",
19077                children: /* @__PURE__ */ u2(SelectInput, {
19078                  value: style.own("alignItems"),
19079                  options: [
19080                    [
19081                      "",
19082                      "Inherit"
19083                    ],
19084                    [
19085                      "stretch",
19086                      "Stretch"
19087                    ],
19088                    [
19089                      "flex-start",
19090                      "Start"
19091                    ],
19092                    [
19093                      "center",
19094                      "Center"
19095                    ],
19096                    [
19097                      "flex-end",
19098                      "End"
19099                    ],
19100                    [
19101                      "baseline",
19102                      "Baseline"
19103                    ]
19104                  ],
19105                  onChange: (value) => style.set("alignItems", value)
19106                })
19107              }),
19108              /* @__PURE__ */ u2(Field, {
19109                label: "Gap",
19110                children: /* @__PURE__ */ u2(NumInput, {
19111                  value: style.getNum("gap", false),
19112                  unit: "px",
19113                  min: 0,
19114                  onChange: (value) => style.setNum("gap", value)
19115                })
19116              })
19117            ]
19118          })
19119        ]
19120      })
19121    ]
19122  });
19123}
19124function PositionPanel(props) {
19125  const style = useStyleEditor(props.session);
19126  const position = style.own("position") || style.get("position");
19127  const isPositioned = position && position != "static";
19128  return /* @__PURE__ */ u2(k, {
19129    children: /* @__PURE__ */ u2(Section, {
19130      title: "Position",
19131      children: [
19132        /* @__PURE__ */ u2(Field, {
19133          label: "Mode",
19134          children: /* @__PURE__ */ u2(SelectInput, {
19135            value: style.own("position"),
19136            options: [
19137              [
19138                "",
19139                "Inherit (" + style.get("position") + ")"
19140              ],
19141              [
19142                "static",
19143                "Static"
19144              ],
19145              [
19146                "relative",
19147                "Relative"
19148              ],
19149              [
19150                "absolute",
19151                "Absolute"
19152              ],
19153              [
19154                "fixed",
19155                "Fixed"
19156              ],
19157              [
19158                "sticky",
19159                "Sticky"
19160              ]
19161            ],
19162            onChange: (value) => style.set("position", value)
19163          })
19164        }),
19165        isPositioned && /* @__PURE__ */ u2(k, {
19166          children: [
19167            /* @__PURE__ */ u2(Field, {
19168              label: "Top",
19169              children: /* @__PURE__ */ u2(NumInput, {
19170                value: style.getNum("top", false),
19171                unit: "px",
19172                onChange: (value) => style.setNum("top", value)
19173              })
19174            }),
19175            /* @__PURE__ */ u2(Field, {
19176              label: "Left",
19177              children: /* @__PURE__ */ u2(NumInput, {
19178                value: style.getNum("left", false),
19179                unit: "px",
19180                onChange: (value) => style.setNum("left", value)
19181              })
19182            }),
19183            /* @__PURE__ */ u2(Field, {
19184              label: "Z-index",
19185              children: /* @__PURE__ */ u2(NumInput, {
19186                value: style.own("zIndex"),
19187                unit: "",
19188                onChange: (value) => style.set("zIndex", value)
19189              })
19190            })
19191          ]
19192        }),
19193        /* @__PURE__ */ u2(Field, {
19194          label: "Float",
19195          children: /* @__PURE__ */ u2(SelectInput, {
19196            value: style.own("cssFloat"),
19197            options: [
19198              [
19199                "",
19200                "Inherit"
19201              ],
19202              [
19203                "none",
19204                "None"
19205              ],
19206              [
19207                "left",
19208                "Left"
19209              ],
19210              [
19211                "right",
19212                "Right"
19213              ]
19214            ],
19215            onChange: (value) => style.set("cssFloat", value)
19216          })
19217        }),
19218        /* @__PURE__ */ u2(Field, {
19219          label: "Overflow",
19220          children: /* @__PURE__ */ u2(SelectInput, {
19221            value: style.own("overflow"),
19222            options: [
19223              [
19224                "",
19225                "Inherit"
19226              ],
19227              [
19228                "visible",
19229                "Visible"
19230              ],
19231              [
19232                "hidden",
19233                "Hidden"
19234              ],
19235              [
19236                "auto",
19237                "Auto"
19238              ],
19239              [
19240                "scroll",
19241                "Scroll"
19242              ]
19243            ],
19244            onChange: (value) => style.set("overflow", value)
19245          })
19246        })
19247      ]
19248    })
19249  });
19250}
19251var DEVICE_BANDS = [
19252  {
19253    id: "mobile",
19254    label: "Phones",
19255    hint: "up to 767px",
19256    media: "(max-width: 767px)"
19257  },
19258  {
19259    id: "tablet",
19260    label: "Tablets",
19261    hint: "768 - 1023px",
19262    media: "(min-width: 768px) and (max-width: 1023px)"
19263  },
19264  {
19265    id: "desktop",
19266    label: "Desktops",
19267    hint: "1024px and wider",
19268    media: "(min-width: 1024px)"
19269  }
19270];
19271function VisibilityPanel(props) {
19272  const { session } = props;
19273  const [hidden, setHidden] = p2([]);
19274  const selector = session.targetSelector || elemToSelector(session.elem);
19275  const styleElemRef = F2(null);
19276  const changesRef = F2([]);
19277  _2(() => () => {
19278    styleElemRef.current?.remove();
19279    styleElemRef.current = null;
19280  }, []);
19281  function commit(next) {
19282    setHidden(next);
19283    let css = "";
19284    for (const band of DEVICE_BANDS) {
19285      if (next.indexOf(band.id) != -1) {
19286        css += "@media " + band.media + "\n{	" + selector + " { display: none !important }\n}\n";
19287      }
19288    }
19289    styleElemRef.current?.remove();
19290    styleElemRef.current = null;
19291    if (css) {
19292      const styleElem = document.createElement("style");
19293      styleElem.setAttribute("data-s-t-css", "1");
19294      styleElem.textContent = css;
19295      document.head.appendChild(styleElem);
19296      styleElemRef.current = styleElem;
19297    }
19298    for (const change of changesRef.current) {
19299      const i4 = session.extraChanges.indexOf(change);
19300      if (i4 != -1) {
19301        session.extraChanges.splice(i4, 1);
19302      }
19303    }
19304    changesRef.current = !css ? [] : [
19305      {
19306        qelem_id: "head",
19307        where: "inside",
19308        e: [
19309          "style",
19310          {
19311            "data-s-t-css": "1"
19312          },
19313          [
19314            css
19315          ]
19316        ]
19317      },
19318      {
19319        qelem_id: "html",
19320        attrs: {
19321          "data-s-t-css": "1"
19322        }
19323      }
19324    ];
19325    session.extraChanges.push(...changesRef.current);
19326    session.onPreview();
19327  }
19328  return /* @__PURE__ */ u2(k, {
19329    children: [
19330      /* @__PURE__ */ u2("div", {
19331        class: "le-notice",
19332        children: [
19333          li.eye,
19334          /* @__PURE__ */ u2("div", {
19335            children: [
19336              "Hiding is per screen width, so it needs a media query rather than a style on the element - it is written into the page's stylesheet and applies to",
19337              /* @__PURE__ */ u2("code", {
19338                class: "le-selector",
19339                style: "margin:0 0.3em",
19340                children: selector
19341              }),
19342              ", anything else that selector matches included."
19343            ]
19344          })
19345        ]
19346      }),
19347      /* @__PURE__ */ u2(Section, {
19348        title: "Show this element on",
19349        children: DEVICE_BANDS.map((band) => {
19350          const isHidden = hidden.indexOf(band.id) != -1;
19351          return /* @__PURE__ */ u2(Field, {
19352            label: band.label,
19353            children: [
19354              /* @__PURE__ */ u2("label", {
19355                class: "le-btn" + (isHidden ? "" : " le-primary"),
19356                children: [
19357                  /* @__PURE__ */ u2("input", {
19358                    type: "checkbox",
19359                    checked: !isHidden,
19360                    onChange: (ev) => commit(ev.target.checked ? hidden.filter((v3) => v3 != band.id) : hidden.concat(band.id))
19361                  }),
19362                  isHidden ? "Hidden" : "Shown"
19363                ]
19364              }),
19365              /* @__PURE__ */ u2("span", {
19366                class: "le-hint",
19367                style: "margin:0",
19368                children: band.hint
19369              })
19370            ]
19371          }, band.id);
19372        })
19373      }),
19374      hidden.length == DEVICE_BANDS.length && /* @__PURE__ */ u2("div", {
19375        class: "le-notice le-warn",
19376        children: [
19377          li.warn,
19378          /* @__PURE__ */ u2("div", {
19379            children: [
19380              "Hidden on every width - no visitor will see it. ",
19381              /* @__PURE__ */ u2("b", {
19382                children: "Delete"
19383              }),
19384              " says that more plainly."
19385            ]
19386          })
19387        ]
19388      })
19389    ]
19390  });
19391}
19392function PageCssSection(props) {
19393  const { session } = props;
19394  const [rules, setRules] = p2(props.initialRules);
19395  const styleElemRef = F2(null);
19396  const changesRef = F2([]);
19397  _2(() => {
19398    setRules(props.initialRules);
19399    return () => {
19400      styleElemRef.current?.remove();
19401      styleElemRef.current = null;
19402    };
19403  }, [
19404    session.elem
19405  ]);
19406  function commit(next) {
19407    setRules(next);
19408    let css = "";
19409    for (const rule of next) {
19410      const diff = diffDeclarations(rule.orig, rule.text);
19411      if (diff.length) {
19412        css += rule.selectorText + "\n{	" + diff.join(";\n	") + "\n}\n";
19413      }
19414    }
19415    styleElemRef.current?.remove();
19416    styleElemRef.current = null;
19417    if (css) {
19418      const styleElem = document.createElement("style");
19419      styleElem.setAttribute("data-s-t-css", "1");
19420      styleElem.textContent = css;
19421      document.head.appendChild(styleElem);
19422      styleElemRef.current = styleElem;
19423    }
19424    for (const change of changesRef.current) {
19425      const i4 = session.extraChanges.indexOf(change);
19426      if (i4 != -1) {
19427        session.extraChanges.splice(i4, 1);
19428      }
19429    }
19430    changesRef.current = !css ? [] : [
19431      // "inside" anyway, but the editor's own applier reads it as "next to", which would have
19432      // previewed the stylesheet outside <head>.
19433      {
19434        qelem_id: "head",
19435        where: "inside",
19436        e: [
19437          "style",
19438          {
19439            "data-s-t-css": "1"
19440          },
19441          [
19442            css
19443          ]
19444        ]
19445      },
19446      {
19447        qelem_id: "html",
19448        attrs: {
19449          "data-s-t-css": "1"
19450        }
19451      }
19452    ];
19453    session.extraChanges.push(...changesRef.current);
19454    session.onPreview();
19455  }
19456  if (rules.length == 0) {
19457    return null;
19458  }
19459  return /* @__PURE__ */ u2(Section, {
19460    title: "Page CSS rules",
19461    children: [
19462      /* @__PURE__ */ u2("p", {
19463        class: "le-hint",
19464        children: "Rules from the site's own stylesheets that match this element. Editing one here adds an override for the whole page - it can affect other elements the rule matches."
19465      }),
19466      rules.map((rule, i4) => /* @__PURE__ */ u2("div", {
19467        style: "margin-bottom:0.6em",
19468        children: [
19469          /* @__PURE__ */ u2("code", {
19470            class: "le-selector",
19471            title: rule.selectorText,
19472            children: rule.selectorText
19473          }),
19474          /* @__PURE__ */ u2("textarea", {
19475            class: "le-area le-mono",
19476            rows: Math.min(8, rule.text.split(";").length + 1),
19477            value: rule.text,
19478            onInput: (ev) => {
19479              const next = rules.slice();
19480              next[i4] = {
19481                ...rule,
19482                text: ev.target.value
19483              };
19484              commit(next);
19485            }
19486          })
19487        ]
19488      }, rule.selectorText))
19489    ]
19490  });
19491}
19492function matchingRules(elem) {
19493  const found = /* @__PURE__ */ new Map();
19494  for (const sheet of document.styleSheets) {
19495    try {
19496      for (const rule of sheet.cssRules) {
19497        const styleRule = rule;
19498        if (styleRule.selectorText && elem.matches(styleRule.selectorText)) {
19499          const selectorText = styleRule.selectorText.trim();
19500          const list = found.get(selectorText);
19501          if (list) {
19502            list.push(styleRule.style.cssText);
19503          } else {
19504            found.set(selectorText, [
19505              styleRule.style.cssText
19506            ]);
19507          }
19508        }
19509      }
19510    } catch {
19511    }
19512  }
19513  const rules = new Array();
19514  for (const [selectorText, list] of found) {
19515    const text = list.join(";\n");
19516    rules.push({
19517      selectorText,
19518      orig: text,
19519      text
19520    });
19521  }
19522  return rules;
19523}
19524function diffDeclarations(orig, text) {
19525  const before = parseStyleAttr(orig);
19526  const after = parseStyleAttr(text);
19527  const diff = new Array();
19528  for (const name in after) {
19529    if (before[name] != after[name]) {
19530      diff.push(name + ":" + after[name]);
19531    }
19532  }
19533  return diff;
19534}
19535function resetInlineStyle(session) {
19536  session.elem.removeAttribute("style");
19537  session.onPreview();
19538}
19539
19540// src/components/live/panels/PropsPanels.tsx
19541var COMMON_ATTRS = [
19542  {
19543    name: "title",
19544    label: "Tooltip",
19545    placeholder: "Shown on hover"
19546  },
19547  {
19548    name: "id",
19549    label: "Id"
19550  },
19551  {
19552    name: "class",
19553    label: "Classes"
19554  }
19555];
19556var ATTRS_BY_TAG = {
19557  a: [
19558    {
19559      name: "href",
19560      label: "URL (href)",
19561      placeholder: "https://...",
19562      url: true
19563    },
19564    {
19565      name: "target",
19566      label: "Opens in",
19567      options: [
19568        [
19569          "",
19570          "Same tab"
19571        ],
19572        [
19573          "_blank",
19574          "New tab"
19575        ],
19576        [
19577          "_parent",
19578          "Parent frame"
19579        ],
19580        [
19581          "_top",
19582          "Top frame"
19583        ]
19584      ]
19585    },
19586    {
19587      name: "rel",
19588      label: "Rel",
19589      placeholder: "nofollow, noopener..."
19590    }
19591  ],
19592  img: [
19593    {
19594      name: "src",
19595      label: "URL",
19596      placeholder: "https://...",
19597      url: true
19598    },
19599    {
19600      name: "alt",
19601      label: "Alt text",
19602      placeholder: "What the image shows"
19603    },
19604    {
19605      name: "width",
19606      label: "Width"
19607    },
19608    {
19609      name: "height",
19610      label: "Height"
19611    },
19612    {
19613      name: "loading",
19614      label: "Loading",
19615      options: [
19616        [
19617          "",
19618          "Default"
19619        ],
19620        [
19621          "lazy",
19622          "Lazy"
19623        ],
19624        [
19625          "eager",
19626          "Eager"
19627        ]
19628      ]
19629    }
19630  ],
19631  input: [
19632    {
19633      name: "name",
19634      label: "Name"
19635    },
19636    {
19637      name: "placeholder",
19638      label: "Placeholder"
19639    },
19640    {
19641      name: "required",
19642      label: "Required",
19643      boolean: true
19644    },
19645    {
19646      name: "disabled",
19647      label: "Disabled",
19648      boolean: true
19649    }
19650  ],
19651  textarea: [
19652    {
19653      name: "name",
19654      label: "Name"
19655    },
19656    {
19657      name: "placeholder",
19658      label: "Placeholder"
19659    },
19660    {
19661      name: "required",
19662      label: "Required",
19663      boolean: true
19664    },
19665    {
19666      name: "disabled",
19667      label: "Disabled",
19668      boolean: true
19669    }
19670  ],
19671  select: [
19672    {
19673      name: "name",
19674      label: "Name"
19675    },
19676    {
19677      name: "required",
19678      label: "Required",
19679      boolean: true
19680    },
19681    {
19682      name: "disabled",
19683      label: "Disabled",
19684      boolean: true
19685    }
19686  ],
19687  button: [
19688    {
19689      name: "type",
19690      label: "Type",
19691      options: [
19692        [
19693          "",
19694          "Default"
19695        ],
19696        [
19697          "button",
19698          "Button"
19699        ],
19700        [
19701          "submit",
19702          "Submit"
19703        ],
19704        [
19705          "reset",
19706          "Reset"
19707        ]
19708      ]
19709    },
19710    {
19711      name: "disabled",
19712      label: "Disabled",
19713      boolean: true
19714    }
19715  ],
19716  form: [
19717    {
19718      name: "action",
19719      label: "Posts to",
19720      placeholder: "https://...",
19721      url: true
19722    },
19723    {
19724      name: "method",
19725      label: "Method",
19726      options: [
19727        [
19728          "",
19729          "Default"
19730        ],
19731        [
19732          "get",
19733          "GET"
19734        ],
19735        [
19736          "post",
19737          "POST"
19738        ]
19739      ]
19740    },
19741    {
19742      name: "target",
19743      label: "Opens in",
19744      options: [
19745        [
19746          "",
19747          "Same tab"
19748        ],
19749        [
19750          "_blank",
19751          "New tab"
19752        ]
19753      ]
19754    }
19755  ],
19756  iframe: [
19757    {
19758      name: "src",
19759      label: "URL",
19760      placeholder: "https://...",
19761      url: true
19762    },
19763    {
19764      name: "width",
19765      label: "Width"
19766    },
19767    {
19768      name: "height",
19769      label: "Height"
19770    }
19771  ],
19772  video: [
19773    {
19774      name: "src",
19775      label: "URL",
19776      placeholder: "https://..."
19777    },
19778    {
19779      name: "poster",
19780      label: "Poster",
19781      placeholder: "https://..."
19782    },
19783    {
19784      name: "autoplay",
19785      label: "Autoplay",
19786      boolean: true
19787    },
19788    {
19789      name: "controls",
19790      label: "Controls",
19791      boolean: true
19792    },
19793    {
19794      name: "loop",
19795      label: "Loop",
19796      boolean: true
19797    },
19798    {
19799      name: "muted",
19800      label: "Muted",
19801      boolean: true
19802    }
19803  ],
19804  audio: [
19805    {
19806      name: "src",
19807      label: "URL",
19808      placeholder: "https://..."
19809    },
19810    {
19811      name: "autoplay",
19812      label: "Autoplay",
19813      boolean: true
19814    },
19815    {
19816      name: "controls",
19817      label: "Controls",
19818      boolean: true
19819    },
19820    {
19821      name: "loop",
19822      label: "Loop",
19823      boolean: true
19824    }
19825  ]
19826};
19827function PropsPanel(props) {
19828  const { session } = props;
19829  const [, setVersion] = p2(0);
19830  const elem = session.elem;
19831  const specs = [
19832    ...ATTRS_BY_TAG[elem.localName] ?? [],
19833    ...COMMON_ATTRS
19834  ];
19835  function setAttr(name, value) {
19836    if (!value) {
19837      elem.removeAttribute(name);
19838    } else {
19839      try {
19840        elem.setAttribute(name, value);
19841      } catch (e3) {
19842        console.error(e3);
19843      }
19844    }
19845    setVersion((v3) => v3 + 1);
19846    session.onPreview();
19847  }
19848  const known = new Set(specs.map((v3) => v3.name));
19849  const others = Array.from(elem.attributes).map((v3) => v3.name).filter((name) => !known.has(name) && name != "style" && name != "contenteditable");
19850  return /* @__PURE__ */ u2(k, {
19851    children: [
19852      /* @__PURE__ */ u2(Section, {
19853        title: elem.localName.toUpperCase() + " attributes",
19854        children: specs.map((spec) => /* @__PURE__ */ u2(Field, {
19855          label: spec.label,
19856          children: spec.boolean ? /* @__PURE__ */ u2("label", {
19857            class: "le-btn",
19858            children: [
19859              /* @__PURE__ */ u2("input", {
19860                type: "checkbox",
19861                checked: elem.hasAttribute(spec.name),
19862                onChange: (ev) => setAttr(spec.name, ev.target.checked ? spec.name : "")
19863              }),
19864              elem.hasAttribute(spec.name) ? "Yes" : "No"
19865            ]
19866          }) : spec.options ? /* @__PURE__ */ u2(SelectInput, {
19867            value: elem.getAttribute(spec.name) ?? "",
19868            options: spec.options,
19869            onChange: (value) => setAttr(spec.name, value)
19870          }) : /* @__PURE__ */ u2(TextInput, {
19871            value: elem.getAttribute(spec.name) ?? "",
19872            placeholder: spec.placeholder,
19873            onChange: (value) => setAttr(spec.name, spec.url ? withScheme(value) : value)
19874          })
19875        }, spec.name))
19876      }),
19877      others.length > 0 && /* @__PURE__ */ u2(Section, {
19878        title: "Other attributes",
19879        children: others.map((name) => /* @__PURE__ */ u2(Field, {
19880          label: name,
19881          children: /* @__PURE__ */ u2(TextInput, {
19882            value: elem.getAttribute(name) ?? "",
19883            onChange: (value) => setAttr(name, value)
19884          })
19885        }, name))
19886      })
19887    ]
19888  });
19889}
19890function ReplaceImagePanel(props) {
19891  const { session } = props;
19892  const elem = session.elem;
19893  const [url, setUrl] = p2(() => {
19894    const current = imageUrlOf(elem);
19895    return current && getTracker()?.url_from_proxy?.(current) || current;
19896  });
19897  return /* @__PURE__ */ u2(k, {
19898    children: [
19899      /* @__PURE__ */ u2("div", {
19900        class: "le-notice",
19901        children: [
19902          li.image,
19903          /* @__PURE__ */ u2("div", {
19904            children: "Paste the address of the new image, or pick one from this account's media library. Uploading new files is done in the Personyze panel."
19905          })
19906        ]
19907      }),
19908      /* @__PURE__ */ u2(Section, {
19909        title: "Image URL",
19910        children: /* @__PURE__ */ u2(MediaUrlInput, {
19911          value: url,
19912          placeholder: "https://...",
19913          accept: "image",
19914          onChange: (value) => {
19915            setUrl(value);
19916            if (elem.localName == "img") {
19917              elem.setAttribute("src", value);
19918            } else {
19919              elem.style.backgroundImage = value ? "url(" + encodeURI(value) + ")" : "";
19920            }
19921            session.onPreview();
19922          }
19923        })
19924      }),
19925      url && /* @__PURE__ */ u2(Section, {
19926        title: "Preview",
19927        children: /* @__PURE__ */ u2("img", {
19928          src: url,
19929          alt: "",
19930          style: "max-width:100%; max-height:10em; border-radius:0.45em"
19931        })
19932      })
19933    ]
19934  });
19935}
19936
19937// src/components/live/panels/MovePanel.tsx
19938var RE_ROTATE = /(^|,)\s*rotate\(([+-]?(?:\d+(?:\.\d*)?|\.\d+))([a-z]+)\)/i;
19939function parseAngle(transform) {
19940  const m2 = (transform || "").match(RE_ROTATE);
19941  return m2 ? parseFloat(m2[2]) : 0;
19942}
19943function withAngle(transform, angle) {
19944  const rotate = "rotate(" + angle + "deg)";
19945  const text = (transform || "").trim();
19946  const m2 = text.match(RE_ROTATE);
19947  if (m2) {
19948    return text.slice(0, m2.index) + m2[1] + rotate + text.slice(m2.index + m2[0].length);
19949  }
19950  return !text ? rotate : text + "," + rotate;
19951}
19952function MovePanel(props) {
19953  const { session } = props;
19954  const elem = session.elem;
19955  const { canResize } = dragCapability(elem);
19956  const [, setVersion] = p2(0);
19957  const [moveTo, setMoveTo] = p2(null);
19958  const gripRef = F2(null);
19959  _2(() => {
19960    const origCursor = elem.style.cursor;
19961    const origPosition = elem.style.position;
19962    elem.style.cursor = "move";
19963    if (getComputedStyle(elem).position == "static") {
19964      elem.style.position = "relative";
19965    }
19966    let drag = null;
19967    const onDown = (ev) => {
19968      if (ev.button != 0) {
19969        return;
19970      }
19971      ev.preventDefault();
19972      ev.stopPropagation();
19973      drag = {
19974        startX: ev.clientX,
19975        startY: ev.clientY,
19976        left: parseFloat(getComputedStyle(elem).left) || 0,
19977        top: parseFloat(getComputedStyle(elem).top) || 0
19978      };
19979    };
19980    const onMove = (ev) => {
19981      if (drag) {
19982        ev.preventDefault();
19983        elem.style.left = drag.left + ev.clientX - drag.startX + "px";
19984        elem.style.top = drag.top + ev.clientY - drag.startY + "px";
19985        setVersion((v3) => v3 + 1);
19986        session.onPreview();
19987      }
19988    };
19989    const onUp = () => {
19990      drag = null;
19991    };
19992    elem.addEventListener("mousedown", onDown, true);
19993    document.addEventListener("mousemove", onMove, true);
19994    document.addEventListener("mouseup", onUp, true);
19995    return () => {
19996      elem.removeEventListener("mousedown", onDown, true);
19997      document.removeEventListener("mousemove", onMove, true);
19998      document.removeEventListener("mouseup", onUp, true);
19999      elem.style.cursor = origCursor;
20000      if (!elem.style.left && !elem.style.top && origPosition != elem.style.position) {
20001        elem.style.position = origPosition;
20002      }
20003    };
20004  }, [
20005    elem
20006  ]);
20007  _2(() => {
20008    if (!canResize) {
20009      return;
20010    }
20011    const grip = document.createElement("div");
20012    grip.style.cssText = "position:absolute; z-index:2147483645; width:14px; height:14px; box-sizing:border-box; border:2px solid #12B886; border-radius:3px; background-color:#0B1220; cursor:nwse-resize";
20013    document.body.appendChild(grip);
20014    gripRef.current = grip;
20015    const place = () => {
20016      const rect = elem.getBoundingClientRect();
20017      grip.style.left = rect.right + globalThis.scrollX - 7 + "px";
20018      grip.style.top = rect.bottom + globalThis.scrollY - 7 + "px";
20019    };
20020    place();
20021    let drag = null;
20022    const onDown = (ev) => {
20023      ev.preventDefault();
20024      ev.stopPropagation();
20025      const rect = elem.getBoundingClientRect();
20026      drag = {
20027        startX: ev.clientX,
20028        startY: ev.clientY,
20029        width: rect.width,
20030        height: rect.height
20031      };
20032    };
20033    const onMove = (ev) => {
20034      if (drag) {
20035        ev.preventDefault();
20036        elem.style.width = Math.max(1, drag.width + ev.clientX - drag.startX) + "px";
20037        elem.style.height = Math.max(1, drag.height + ev.clientY - drag.startY) + "px";
20038        place();
20039        setVersion((v3) => v3 + 1);
20040        session.onPreview();
20041      }
20042    };
20043    const onUp = () => {
20044      drag = null;
20045    };
20046    grip.addEventListener("mousedown", onDown, true);
20047    document.addEventListener("mousemove", onMove, true);
20048    document.addEventListener("mouseup", onUp, true);
20049    const timer = setInterval(place, 300);
20050    globalThis.addEventListener("scroll", place, true);
20051    globalThis.addEventListener("resize", place);
20052    return () => {
20053      clearInterval(timer);
20054      globalThis.removeEventListener("scroll", place, true);
20055      globalThis.removeEventListener("resize", place);
20056      document.removeEventListener("mousemove", onMove, true);
20057      document.removeEventListener("mouseup", onUp, true);
20058      grip.remove();
20059      gripRef.current = null;
20060    };
20061  }, [
20062    elem,
20063    canResize
20064  ]);
20065  function setStyle(name, value) {
20066    elem.style[name] = value;
20067    setVersion((v3) => v3 + 1);
20068    session.onPreview();
20069  }
20070  function num(name) {
20071    const value = elem.style[name];
20072    const n2 = parseFloat(value);
20073    return isNaN(n2) ? "" : String(Math.round(n2 * 100) / 100);
20074  }
20075  function chooseMoveTo(target, where) {
20076    session.extraChanges.length = 0;
20077    if (!target) {
20078      setMoveTo(null);
20079      return;
20080    }
20081    setMoveTo({
20082      elem: target,
20083      where
20084    });
20085    session.extraChanges.push({
20086      mv: where,
20087      mv_qelem_id: elemToSelector(target)
20088    });
20089  }
20090  return /* @__PURE__ */ u2(k, {
20091    children: [
20092      /* @__PURE__ */ u2("div", {
20093        class: "le-notice",
20094        children: [
20095          li.move,
20096          /* @__PURE__ */ u2("div", {
20097            children: [
20098              "Drag the element on the page",
20099              canResize && ", or pull the green corner grip to resize it",
20100              ". The numbers below follow along."
20101            ]
20102          })
20103        ]
20104      }),
20105      /* @__PURE__ */ u2(Section, {
20106        title: "Offset",
20107        children: [
20108          /* @__PURE__ */ u2(Field, {
20109            label: "Left",
20110            children: /* @__PURE__ */ u2(NumInput, {
20111              value: num("left"),
20112              unit: "px",
20113              onChange: (value) => setStyle("left", value === "" ? "" : value + "px")
20114            })
20115          }),
20116          /* @__PURE__ */ u2(Field, {
20117            label: "Top",
20118            children: /* @__PURE__ */ u2(NumInput, {
20119              value: num("top"),
20120              unit: "px",
20121              onChange: (value) => setStyle("top", value === "" ? "" : value + "px")
20122            })
20123          }),
20124          /* @__PURE__ */ u2(Field, {
20125            label: "Position",
20126            children: /* @__PURE__ */ u2(SelectInput, {
20127              value: elem.style.position || "",
20128              options: [
20129                [
20130                  "",
20131                  "Inherit"
20132                ],
20133                [
20134                  "relative",
20135                  "Relative"
20136                ],
20137                [
20138                  "absolute",
20139                  "Absolute"
20140                ],
20141                [
20142                  "fixed",
20143                  "Fixed"
20144                ]
20145              ],
20146              onChange: (value) => setStyle("position", value)
20147            })
20148          })
20149        ]
20150      }),
20151      canResize && /* @__PURE__ */ u2(Section, {
20152        title: "Size",
20153        children: [
20154          /* @__PURE__ */ u2(Field, {
20155            label: "Width",
20156            children: /* @__PURE__ */ u2(NumInput, {
20157              value: num("width"),
20158              unit: "px",
20159              min: 0,
20160              onChange: (value) => setStyle("width", value === "" ? "" : value + "px")
20161            })
20162          }),
20163          /* @__PURE__ */ u2(Field, {
20164            label: "Height",
20165            children: /* @__PURE__ */ u2(NumInput, {
20166              value: num("height"),
20167              unit: "px",
20168              min: 0,
20169              onChange: (value) => setStyle("height", value === "" ? "" : value + "px")
20170            })
20171          })
20172        ]
20173      }),
20174      /* @__PURE__ */ u2(Section, {
20175        title: "Rotate",
20176        children: /* @__PURE__ */ u2(Field, {
20177          label: "Angle",
20178          children: /* @__PURE__ */ u2(NumInput, {
20179            value: String(parseAngle(elem.style.transform)),
20180            unit: "deg",
20181            min: -360,
20182            max: 360,
20183            onChange: (value) => setStyle("transform", withAngle(elem.style.transform, parseFloat(value) || 0))
20184          })
20185        })
20186      }),
20187      /* @__PURE__ */ u2(Section, {
20188        title: "Move somewhere else",
20189        children: [
20190          /* @__PURE__ */ u2("p", {
20191            class: "le-hint",
20192            children: "Puts the element in another place in the page. This is recorded as a move, so it happens for the visitor too - not just here."
20193          }),
20194          !moveTo ? /* @__PURE__ */ u2("button", {
20195            type: "button",
20196            class: "le-btn",
20197            onClick: () => props.onPickElement((target) => chooseMoveTo(target, "inside")),
20198            children: [
20199              li.cursor,
20200              " Pick the destination on the page"
20201            ]
20202          }) : /* @__PURE__ */ u2(k, {
20203            children: [
20204              /* @__PURE__ */ u2("div", {
20205                class: "le-target",
20206                children: /* @__PURE__ */ u2("code", {
20207                  title: describeElem(moveTo.elem),
20208                  children: describeElem(moveTo.elem)
20209                })
20210              }),
20211              /* @__PURE__ */ u2(Field, {
20212                label: "Put",
20213                children: /* @__PURE__ */ u2(SelectInput, {
20214                  value: moveTo.where,
20215                  options: [
20216                    [
20217                      "inside",
20218                      "Inside it"
20219                    ],
20220                    [
20221                      "before",
20222                      "Before it"
20223                    ],
20224                    [
20225                      "after",
20226                      "After it"
20227                    ]
20228                  ],
20229                  onChange: (value) => chooseMoveTo(moveTo.elem, value)
20230                })
20231              }),
20232              /* @__PURE__ */ u2("button", {
20233                type: "button",
20234                class: "le-btn le-danger",
20235                onClick: () => chooseMoveTo(null, "inside"),
20236                children: "Cancel the move"
20237              })
20238            ]
20239          })
20240        ]
20241      })
20242    ]
20243  });
20244}
20245
20246// src/components/live/panels/InsertPanel.tsx
20247function autoMediaType(src) {
20248  const m2 = src.match(/.\.(\w{1,8})$/);
20249  switch (m2?.[1]?.toLowerCase()) {
20250    case "jpg":
20251    case "jpeg":
20252    case "png":
20253    case "gif":
20254    case "webp":
20255    case "svg":
20256    case "tiff":
20257      return "img";
20258    case "mpg":
20259    case "mp4":
20260    case "webm":
20261    case "ogv":
20262      return "video";
20263    case "mp3":
20264    case "ogg":
20265    case "wav":
20266      return "audio";
20267    case "swf":
20268      return "embed";
20269  }
20270  return null;
20271}
20272var MEDIA_TYPES = [
20273  [
20274    "auto",
20275    "Detect from the URL"
20276  ],
20277  [
20278    "img",
20279    "Image"
20280  ],
20281  [
20282    "video",
20283    "Video"
20284  ],
20285  [
20286    "audio",
20287    "Audio"
20288  ],
20289  [
20290    "embed",
20291    "Embedded plugin"
20292  ]
20293];
20294function InsertPanel(props) {
20295  const { session, kind } = props;
20296  const canPut = canPutElem("span", session.elem);
20297  const wheres = [];
20298  if (canPut.aside) {
20299    wheres.push([
20300      "before",
20301      "Before",
20302      "Above the element you picked, as its sibling"
20303    ]);
20304  }
20305  if (canPut.inside) {
20306    wheres.push([
20307      "inside",
20308      "Inside",
20309      "Within the element you picked, at its end"
20310    ]);
20311  }
20312  if (canPut.aside) {
20313    wheres.push([
20314      "after",
20315      "After",
20316      "Below the element you picked, as its sibling"
20317    ]);
20318  }
20319  const [where, setWhere] = p2(wheres[0]?.[0] ?? "inside");
20320  const [src, setSrc] = p2("");
20321  const [href, setHref] = p2("");
20322  const [text, setText] = p2(kind == "link" ? "Click here" : kind == "button" ? "Click here" : "");
20323  const [html, setHtml] = p2("");
20324  const [width, setWidth] = p2("");
20325  const [height, setHeight] = p2("");
20326  const [mediaType, setMediaType] = p2("auto");
20327  const [isMessageBox, setIsMessageBox] = p2(false);
20328  const entry = WIDGET_CATALOGUE.find((e3) => e3.id == props.widgetId);
20329  const [widgetProps, setWidgetProps] = p2(() => ({
20330    ...entry?.props ?? {}
20331  }));
20332  const [widgetHtml, setWidgetHtml] = p2(entry?.html ?? "");
20333  const resolvedMedia = mediaType != "auto" ? mediaType : autoMediaType(src);
20334  _2(() => {
20335    session.extraChanges.length = 0;
20336    const spec = buildSpec();
20337    if (spec) {
20338      session.extraChanges.push({
20339        where,
20340        e: spec
20341      });
20342    }
20343  });
20344  function buildSpec() {
20345    switch (kind) {
20346      case "link":
20347        return !href && !text ? null : [
20348          "a",
20349          {
20350            href
20351          },
20352          [
20353            text
20354          ]
20355        ];
20356      case "button":
20357        return !text ? null : [
20358          "button",
20359          {
20360            type: "button"
20361          },
20362          [
20363            text
20364          ]
20365        ];
20366      case "block":
20367        if (!html) {
20368          return null;
20369        }
20370        return isMessageBox ? [
20371          "div",
20372          {
20373            "class": "$personyze_message_box"
20374          },
20375          null,
20376          html + '<div><button class="message-box-close">Close</button></div>'
20377        ] : [
20378          "div",
20379          null,
20380          null,
20381          html
20382        ];
20383      case "html":
20384        return !html ? null : [
20385          "div",
20386          null,
20387          null,
20388          html
20389        ];
20390      case "widget": {
20391        const w3 = entry;
20392        return !w3 ? null : [
20393          "div",
20394          {
20395            "data-pz-widget": w3.id,
20396            "data-pz-props": JSON.stringify(widgetProps)
20397          },
20398          null,
20399          widgetHtml || w3.html
20400        ];
20401      }
20402      case "image":
20403      case "video": {
20404        if (!src) {
20405          return null;
20406        }
20407        const type = resolvedMedia || (kind == "image" ? "img" : "video");
20408        const attrs = {
20409          src
20410        };
20411        if (parseInt(width)) {
20412          attrs.width = String(parseInt(width));
20413        }
20414        if (parseInt(height)) {
20415          attrs.height = String(parseInt(height));
20416        }
20417        if (type == "embed") {
20418          attrs.type = "application/x-shockwave-flash";
20419        }
20420        if (type == "img" && href) {
20421          attrs.href = href;
20422        }
20423        return [
20424          type,
20425          attrs
20426        ];
20427      }
20428    }
20429    return null;
20430  }
20431  const whereSection = /* @__PURE__ */ u2(Section, {
20432    title: "Where",
20433    children: [
20434      /* @__PURE__ */ u2(Field, {
20435        label: "Put it",
20436        children: /* @__PURE__ */ u2("div", {
20437          class: "le-seg",
20438          children: wheres.map(([id, label, hint]) => /* @__PURE__ */ u2("button", {
20439            type: "button",
20440            class: "le-seg-btn" + (where == id ? " le-on" : ""),
20441            "data-tooltip": hint,
20442            "data-tooltip-pos": "top",
20443            "aria-pressed": where == id,
20444            onClick: () => setWhere(id),
20445            children: [
20446              li[id == "before" ? "putBefore" : id == "after" ? "putAfter" : "putInside"],
20447              label
20448            ]
20449          }, id))
20450        })
20451      }),
20452      /* @__PURE__ */ u2("div", {
20453        class: "le-field le-where-scope",
20454        children: [
20455          /* @__PURE__ */ u2(ScopeLabel, {}),
20456          /* @__PURE__ */ u2("div", {
20457            class: "le-field-group",
20458            children: /* @__PURE__ */ u2(ScopeSeg, {
20459              value: props.scope,
20460              onChange: props.onScope
20461            })
20462          })
20463        ]
20464      }),
20465      /* @__PURE__ */ u2("div", {
20466        class: "le-field le-where-scope",
20467        children: [
20468          /* @__PURE__ */ u2(ShowOnLabel, {}),
20469          /* @__PURE__ */ u2("div", {
20470            class: "le-field-group",
20471            children: /* @__PURE__ */ u2(DeviceSeg, {
20472              value: props.device,
20473              onChange: props.onDevice
20474            })
20475          })
20476        ]
20477      })
20478    ]
20479  });
20480  if (wheres.length == 0) {
20481    return /* @__PURE__ */ u2("div", {
20482      class: "le-notice le-warn",
20483      children: [
20484        li.warn,
20485        /* @__PURE__ */ u2("div", {
20486          children: "Nothing can be added next to this element - HTML does not allow it here. Select its parent and try again."
20487        })
20488      ]
20489    });
20490  }
20491  return /* @__PURE__ */ u2(k, {
20492    children: [
20493      kind != "widget" && whereSection,
20494      (kind == "image" || kind == "video") && /* @__PURE__ */ u2(Section, {
20495        title: kind == "image" ? "Image" : "Video",
20496        children: [
20497          /* @__PURE__ */ u2(Field, {
20498            label: "URL",
20499            children: /* @__PURE__ */ u2(MediaUrlInput, {
20500              value: src,
20501              placeholder: "https://...",
20502              accept: kind == "image" ? "image" : "any",
20503              onChange: setSrc
20504            })
20505          }),
20506          /* @__PURE__ */ u2(Field, {
20507            label: "Type",
20508            children: /* @__PURE__ */ u2(SelectInput, {
20509              value: mediaType,
20510              options: MEDIA_TYPES,
20511              onChange: setMediaType
20512            })
20513          }),
20514          mediaType == "auto" && /* @__PURE__ */ u2("p", {
20515            class: "le-hint",
20516            children: resolvedMedia ? "Detected: " + (MEDIA_TYPES.find((v3) => v3[0] == resolvedMedia)?.[1] ?? resolvedMedia) : "Couldn't tell the type from the URL - please choose it."
20517          }),
20518          /* @__PURE__ */ u2(Field, {
20519            label: "Width",
20520            children: /* @__PURE__ */ u2(TextInput, {
20521              value: width,
20522              placeholder: "auto",
20523              onChange: setWidth
20524            })
20525          }),
20526          /* @__PURE__ */ u2(Field, {
20527            label: "Height",
20528            children: /* @__PURE__ */ u2(TextInput, {
20529              value: height,
20530              placeholder: resolvedMedia == "img" ? "auto" : "please specify",
20531              onChange: setHeight
20532            })
20533          }),
20534          resolvedMedia == "img" && /* @__PURE__ */ u2(Field, {
20535            label: "Links to",
20536            children: /* @__PURE__ */ u2(TextInput, {
20537              value: href,
20538              placeholder: "https://... (optional)",
20539              onChange: setHref
20540            })
20541          })
20542        ]
20543      }),
20544      kind == "widget" && entry && /* @__PURE__ */ u2(Section, {
20545        title: entry.label,
20546        children: [
20547          /* @__PURE__ */ u2(WidgetPanel, {
20548            entry,
20549            values: widgetProps,
20550            onChange: (key, value) => {
20551              const next = {
20552                ...widgetProps,
20553                [key]: value
20554              };
20555              setWidgetProps(next);
20556              if (props.session.report) {
20557                void requestWidgetHtml(props.session.report, entry.id, next).then((html2) => {
20558                  if (html2 != null) {
20559                    setWidgetHtml(html2);
20560                  }
20561                });
20562              }
20563            }
20564          }),
20565          widgetHtml === "" && /* @__PURE__ */ u2("div", {
20566            class: "le-wl-note",
20567            children: "Nothing to show yet - this one needs content before it appears on the page."
20568          }),
20569          !props.session.report && /* @__PURE__ */ u2("div", {
20570            class: "le-wl-note",
20571            children: "The Personyze window is not reachable, so these settings cannot be redrawn. Reopen the editor from the panel to change them."
20572          })
20573        ]
20574      }),
20575      kind == "link" && /* @__PURE__ */ u2(Section, {
20576        title: "Link",
20577        children: [
20578          /* @__PURE__ */ u2(Field, {
20579            label: "Text",
20580            children: /* @__PURE__ */ u2(TextInput, {
20581              value: text,
20582              onChange: setText
20583            })
20584          }),
20585          /* @__PURE__ */ u2(Field, {
20586            label: "URL",
20587            children: /* @__PURE__ */ u2(TextInput, {
20588              value: href,
20589              placeholder: "https://...",
20590              onChange: setHref
20591            })
20592          })
20593        ]
20594      }),
20595      kind == "button" && /* @__PURE__ */ u2(Section, {
20596        title: "Button",
20597        children: [
20598          /* @__PURE__ */ u2(Field, {
20599            label: "Label",
20600            children: /* @__PURE__ */ u2(TextInput, {
20601              value: text,
20602              onChange: setText
20603            })
20604          }),
20605          /* @__PURE__ */ u2("p", {
20606            class: "le-hint",
20607            children: [
20608              "What the button does is set up afterwards, with ",
20609              /* @__PURE__ */ u2("b", {
20610                children: "On click"
20611              }),
20612              "."
20613            ]
20614          })
20615        ]
20616      }),
20617      (kind == "block" || kind == "html") && /* @__PURE__ */ u2(Section, {
20618        title: kind == "block" ? "Block" : "HTML",
20619        children: [
20620          kind == "block" && /* @__PURE__ */ u2(Field, {
20621            label: "Kind",
20622            children: /* @__PURE__ */ u2(SelectInput, {
20623              value: isMessageBox ? "message_box" : "div",
20624              options: [
20625                [
20626                  "div",
20627                  "Plain block"
20628                ],
20629                [
20630                  "message_box",
20631                  "Message box (with a Close button)"
20632                ]
20633              ],
20634              onChange: (value) => setIsMessageBox(value == "message_box")
20635            })
20636          }),
20637          /* @__PURE__ */ u2("textarea", {
20638            class: "le-area le-mono",
20639            rows: 6,
20640            value: html,
20641            placeholder: kind == "block" ? "Text or HTML to show" : "<div>...</div>",
20642            onInput: (ev) => setHtml(ev.target.value)
20643          })
20644        ]
20645      }),
20646      kind == "widget" && whereSection,
20647      /* @__PURE__ */ u2("div", {
20648        class: "le-notice",
20649        children: [
20650          li.block,
20651          /* @__PURE__ */ u2("div", {
20652            children: "The new element appears when you save - it is added as a change, so nothing on the page moves while you fill this in."
20653          })
20654        ]
20655      })
20656    ]
20657  });
20658}
20659
20660// src/components/live/panels/BehaviorPanels.tsx
20661function eventsAvailable(elem) {
20662  const events = [
20663    [
20664      "click",
20665      "Click"
20666    ],
20667    [
20668      "mouseover",
20669      "Mouse over"
20670    ],
20671    [
20672      "mouseout",
20673      "Mouse out"
20674    ]
20675  ];
20676  switch (elem.localName) {
20677    case "select":
20678    case "a":
20679      events.push([
20680        "focus",
20681        "Focus in"
20682      ], [
20683        "blur",
20684        "Focus out"
20685      ]);
20686      break;
20687    case "input":
20688    case "textarea":
20689      events.push([
20690        "focus",
20691        "Focus in"
20692      ], [
20693        "blur",
20694        "Focus out"
20695      ], [
20696        "keypress",
20697        "Key press"
20698      ]);
20699      break;
20700    default:
20701      if (elem.tabIndex != null && elem.tabIndex != -1) {
20702        events.push([
20703          "focus",
20704          "Focus in"
20705        ], [
20706          "blur",
20707          "Focus out"
20708        ]);
20709      }
20710  }
20711  return events;
20712}
20713var TRACK_TYPES = [
20714  [
20715    "js",
20716    "Execute custom Javascript"
20717  ],
20718  [
20719    "fire_action",
20720    "Execute another action"
20721  ],
20722  [
20723    "custom_event",
20724    "Trigger a custom event or goal"
20725  ],
20726  [
20727    "action_extra_this",
20728    'Set "click" for this action CTR'
20729  ],
20730  [
20731    "rm_elem",
20732    "Dismiss an element from the page"
20733  ]
20734];
20735var TRACK_TYPE_HINTS = {
20736  js: "When the event happens, this Javascript runs on the page.",
20737  fire_action: "When the event happens, another action of this account is executed.",
20738  custom_event: `When the event happens, a "custom event" (defined in the account's containers) is sent to Personyze.`,
20739  action_extra_this: "Counts the interaction as a click on this action, which is what the campaign's CTR is measured from.",
20740  rm_elem: "When the event happens, the element you choose is removed from the page."
20741};
20742function trackTypeOf(event) {
20743  if (event.custom_event != void 0) return "custom_event";
20744  if (event.fire_action != void 0) return "fire_action";
20745  if (event.action_id == -1 && event.action_state == "extra") return "action_extra_this";
20746  if (event.rm_elem) return "rm_elem";
20747  return "js";
20748}
20749function draftFrom(event, defaultOnwhat) {
20750  return {
20751    onwhat: event?.onwhat ?? defaultOnwhat,
20752    type: event ? trackTypeOf(event) : "js",
20753    js: event?.js ?? "",
20754    once: !!event?.once,
20755    fireAction: event?.fire_action == void 0 ? "" : String(event.fire_action),
20756    containerId: event?.custom_event == void 0 ? "" : String(event.custom_event),
20757    customValue: event?.value ?? "",
20758    rmElem: typeof event?.rm_elem == "string" ? event.rm_elem : ""
20759  };
20760}
20761function draftToEvent(draft) {
20762  switch (draft.type) {
20763    case "custom_event":
20764      return !draft.containerId ? null : {
20765        onwhat: draft.onwhat,
20766        custom_event: draft.containerId,
20767        value: draft.customValue
20768      };
20769    case "fire_action":
20770      return !draft.fireAction ? null : {
20771        onwhat: draft.onwhat,
20772        fire_action: draft.fireAction
20773      };
20774    case "action_extra_this":
20775      return {
20776        onwhat: draft.onwhat,
20777        action_id: -1,
20778        action_state: "extra"
20779      };
20780    case "js": {
20781      const js = draft.js.trim();
20782      return !js ? null : {
20783        onwhat: draft.onwhat,
20784        js,
20785        once: draft.once ? 1 : 0
20786      };
20787    }
20788    case "rm_elem":
20789      return !draft.rmElem ? null : {
20790        onwhat: draft.onwhat,
20791        rm_elem: draft.rmElem
20792      };
20793  }
20794}
20795function OnClickPanel(props) {
20796  const { session, actionId } = props;
20797  const events = eventsAvailable(session.elem);
20798  const existing = collectExisting(session.elem);
20799  const [drafts, setDrafts] = p2(() => existing.map((v3) => draftFrom(v3.event, events[0][0])));
20800  const [fresh, setFresh] = p2(() => {
20801    const draft = draftFrom(null, events[0][0]);
20802    return props.initialType ? {
20803      ...draft,
20804      type: props.initialType
20805    } : draft;
20806  });
20807  _2(() => {
20808    session.extraChanges.length = 0;
20809    const newEvent = draftToEvent(fresh);
20810    if (newEvent) {
20811      session.extraChanges.push({
20812        events: [
20813          newEvent
20814        ]
20815      });
20816    }
20817    const rewrites = new Array();
20818    for (let i4 = 0; i4 < existing.length; i4++) {
20819      const edited = draftToEvent(drafts[i4]);
20820      if (edited && JSON.stringify(edited) != JSON.stringify(existing[i4].event)) {
20821        const change = structuredClone(existing[i4].change);
20822        const list = change.events;
20823        if (list) {
20824          list[existing[i4].eventIndex] = edited;
20825          rewrites.push({
20826            n: existing[i4].n,
20827            change
20828          });
20829        }
20830      }
20831    }
20832    session.prevChanges = rewrites.length ? rewrites : void 0;
20833  });
20834  return /* @__PURE__ */ u2(k, {
20835    children: [
20836      existing.map((item, i4) => /* @__PURE__ */ u2(Section, {
20837        title: "Change #" + (item.n + 1) + (item.eventIndex ? "." + (item.eventIndex + 1) : ""),
20838        children: /* @__PURE__ */ u2(EventFields, {
20839          draft: drafts[i4],
20840          events,
20841          actionId,
20842          session,
20843          onPickElement: props.onPickElement,
20844          onError: props.onError,
20845          onChange: (next) => setDrafts((list) => list.map((v3, j3) => j3 == i4 ? next : v3))
20846        })
20847      }, "e" + i4)),
20848      /* @__PURE__ */ u2(Section, {
20849        title: existing.length ? "New event" : "When the visitor...",
20850        children: /* @__PURE__ */ u2(EventFields, {
20851          draft: fresh,
20852          events,
20853          actionId,
20854          session,
20855          onPickElement: props.onPickElement,
20856          onError: props.onError,
20857          onChange: setFresh
20858        })
20859      })
20860    ]
20861  });
20862}
20863function collectExisting(elem) {
20864  const items = new Array();
20865  for (const prev of getElemPrevChanges(elem)) {
20866    const list = prev.change.e ? void 0 : prev.change.events;
20867    if (list) {
20868      for (let j3 = 0; j3 < list.length; j3++) {
20869        items.push({
20870          n: prev.n,
20871          change: prev.change,
20872          event: list[j3],
20873          eventIndex: j3
20874        });
20875      }
20876    }
20877  }
20878  return items;
20879}
20880function EventFields(props) {
20881  const { draft, onChange } = props;
20882  const set = (patch) => onChange({
20883    ...draft,
20884    ...patch
20885  });
20886  const hasSql = isJsapiConfigured();
20887  const actions = useAsync(async () => !hasSql || draft.type != "fire_action" ? [] : await queryRows("SELECT id, If(name<>'', name, Concat('ID ', id)) AS name FROM actions WHERE id!=@action_id ORDER BY name LIMIT 500", {
20888    action_id: props.actionId
20889  }), [
20890    hasSql,
20891    draft.type,
20892    props.actionId
20893  ], props.onError);
20894  const containers = useAsync(async () => !hasSql || draft.type != "custom_event" ? [] : await queryRows("SELECT id, If(name<>'', name, Concat('ID ', id)) AS name FROM containers WHERE Find_in_set('custom event', container_types) ORDER BY name LIMIT 500"), [
20895    hasSql,
20896    draft.type
20897  ], props.onError);
20898  return /* @__PURE__ */ u2(k, {
20899    children: [
20900      /* @__PURE__ */ u2(Field, {
20901        label: "Event",
20902        children: /* @__PURE__ */ u2(SelectInput, {
20903          value: draft.onwhat,
20904          options: props.events,
20905          onChange: (value) => set({
20906            onwhat: value
20907          })
20908        })
20909      }),
20910      /* @__PURE__ */ u2(Field, {
20911        label: "Then",
20912        children: /* @__PURE__ */ u2(SelectInput, {
20913          value: draft.type,
20914          options: TRACK_TYPES,
20915          onChange: (value) => set({
20916            type: value
20917          })
20918        })
20919      }),
20920      /* @__PURE__ */ u2("p", {
20921        class: "le-hint",
20922        children: TRACK_TYPE_HINTS[draft.type]
20923      }),
20924      draft.type == "js" && /* @__PURE__ */ u2(k, {
20925        children: [
20926          /* @__PURE__ */ u2("textarea", {
20927            class: "le-area le-mono",
20928            rows: 6,
20929            value: draft.js,
20930            placeholder: "Javascript to run",
20931            onInput: (ev) => set({
20932              js: ev.target.value
20933            })
20934          }),
20935          /* @__PURE__ */ u2(Field, {
20936            label: "Once",
20937            children: /* @__PURE__ */ u2("label", {
20938              class: "le-btn",
20939              children: [
20940                /* @__PURE__ */ u2("input", {
20941                  type: "checkbox",
20942                  checked: draft.once,
20943                  onChange: (ev) => set({
20944                    once: ev.target.checked
20945                  })
20946                }),
20947                "Only the first time on this page"
20948              ]
20949            })
20950          })
20951        ]
20952      }),
20953      draft.type == "fire_action" && /* @__PURE__ */ u2(Field, {
20954        label: "Action",
20955        children: !hasSql ? /* @__PURE__ */ u2(TextInput, {
20956          value: draft.fireAction,
20957          placeholder: "Action id",
20958          onChange: (value) => set({
20959            fireAction: value
20960          })
20961        }) : /* @__PURE__ */ u2(SelectInput, {
20962          value: draft.fireAction,
20963          options: [
20964            [
20965              "",
20966              actions.isBusy ? "Loading..." : "Choose..."
20967            ],
20968            ...(actions.data ?? []).map((row) => [
20969              String(row.id),
20970              String(row.name)
20971            ])
20972          ],
20973          onChange: (value) => set({
20974            fireAction: value
20975          })
20976        })
20977      }),
20978      draft.type == "custom_event" && /* @__PURE__ */ u2(k, {
20979        children: [
20980          /* @__PURE__ */ u2(Field, {
20981            label: "Container",
20982            children: !hasSql ? /* @__PURE__ */ u2(TextInput, {
20983              value: draft.containerId,
20984              placeholder: "Container id",
20985              onChange: (value) => set({
20986                containerId: value
20987              })
20988            }) : /* @__PURE__ */ u2(SelectInput, {
20989              value: draft.containerId,
20990              options: [
20991                [
20992                  "",
20993                  containers.isBusy ? "Loading..." : "Choose..."
20994                ],
20995                ...(containers.data ?? []).map((row) => [
20996                  String(row.id),
20997                  String(row.name)
20998                ])
20999              ],
21000              onChange: (value) => set({
21001                containerId: value
21002              })
21003            })
21004          }),
21005          /* @__PURE__ */ u2(Field, {
21006            label: "Value",
21007            children: /* @__PURE__ */ u2(TextInput, {
21008              value: draft.customValue,
21009              placeholder: "Optional",
21010              onChange: (value) => set({
21011                customValue: value
21012              })
21013            })
21014          })
21015        ]
21016      }),
21017      draft.type == "rm_elem" && /* @__PURE__ */ u2(Field, {
21018        label: "Element",
21019        children: [
21020          /* @__PURE__ */ u2(TextInput, {
21021            value: draft.rmElem,
21022            placeholder: "CSS selector",
21023            onChange: (value) => set({
21024              rmElem: value
21025            })
21026          }),
21027          /* @__PURE__ */ u2("button", {
21028            type: "button",
21029            class: "le-btn",
21030            onClick: () => props.onPickElement((elem) => elem && set({
21031              rmElem: elemToSelector(elem)
21032            })),
21033            children: [
21034              li.cursor,
21035              " Choose"
21036            ]
21037          })
21038        ]
21039      })
21040    ]
21041  });
21042}
21043var SCROLL_BLOCKS = [
21044  [
21045    "start",
21046    "Top of the screen"
21047  ],
21048  [
21049    "center",
21050    "Middle of the screen"
21051  ],
21052  [
21053    "end",
21054    "Bottom of the screen"
21055  ]
21056];
21057function ScrollToPanel(props) {
21058  const { session } = props;
21059  const events = eventsAvailable(session.elem);
21060  const [onwhat, setOnwhat] = p2(events[0][0]);
21061  const [target, setTarget] = p2("");
21062  const [block, setBlock] = p2("start");
21063  const [isSmooth, setIsSmooth] = p2(true);
21064  const [offset, setOffset] = p2("0");
21065  _2(() => {
21066    session.extraChanges.length = 0;
21067    if (target.trim()) {
21068      session.extraChanges.push({
21069        events: [
21070          {
21071            onwhat,
21072            js: scrollJs(target.trim(), block, isSmooth, parseInt(offset) || 0)
21073          }
21074        ]
21075      });
21076    }
21077  });
21078  return /* @__PURE__ */ u2(k, {
21079    children: [
21080      /* @__PURE__ */ u2("div", {
21081        class: "le-notice",
21082        children: [
21083          li.scroll,
21084          /* @__PURE__ */ u2("div", {
21085            children: [
21086              "Point at where the page should scroll to. It is recorded as an on-click script, so it works on any element - the target needs no ",
21087              /* @__PURE__ */ u2("code", {
21088                children: "id"
21089              }),
21090              " and no anchor link."
21091            ]
21092          })
21093        ]
21094      }),
21095      /* @__PURE__ */ u2(Section, {
21096        title: "Destination",
21097        children: [
21098          /* @__PURE__ */ u2(Field, {
21099            label: "Element",
21100            children: /* @__PURE__ */ u2(TextInput, {
21101              value: target,
21102              placeholder: "CSS selector, or pick it on the page",
21103              onChange: setTarget
21104            })
21105          }),
21106          /* @__PURE__ */ u2("div", {
21107            class: "le-btn-bar",
21108            children: [
21109              /* @__PURE__ */ u2("button", {
21110                type: "button",
21111                class: "le-btn",
21112                onClick: () => props.onPickElement((elem) => {
21113                  if (elem) setTarget(elemToSelector(elem));
21114                }),
21115                children: [
21116                  li.cursor,
21117                  "Pick on the page"
21118                ]
21119              }),
21120              target && /* @__PURE__ */ u2("span", {
21121                class: "le-hint",
21122                style: "margin:0",
21123                children: describeElem(document.querySelector(safeSelector(target)) ?? session.elem)
21124              })
21125            ]
21126          })
21127        ]
21128      }),
21129      /* @__PURE__ */ u2(Section, {
21130        title: "How",
21131        children: [
21132          /* @__PURE__ */ u2(Field, {
21133            label: "On",
21134            children: /* @__PURE__ */ u2(SelectInput, {
21135              value: onwhat,
21136              options: events,
21137              onChange: setOnwhat
21138            })
21139          }),
21140          /* @__PURE__ */ u2(Field, {
21141            label: "Align to",
21142            children: /* @__PURE__ */ u2(SelectInput, {
21143              value: block,
21144              options: SCROLL_BLOCKS,
21145              onChange: setBlock
21146            })
21147          }),
21148          /* @__PURE__ */ u2(Field, {
21149            label: "Offset",
21150            children: [
21151              /* @__PURE__ */ u2(TextInput, {
21152                value: offset,
21153                placeholder: "0",
21154                onChange: setOffset
21155              }),
21156              /* @__PURE__ */ u2("span", {
21157                class: "le-hint",
21158                style: "margin:0",
21159                children: "px, for a sticky header"
21160              })
21161            ]
21162          }),
21163          /* @__PURE__ */ u2(Field, {
21164            label: "Motion",
21165            children: /* @__PURE__ */ u2("label", {
21166              class: "le-btn",
21167              children: [
21168                /* @__PURE__ */ u2("input", {
21169                  type: "checkbox",
21170                  checked: isSmooth,
21171                  onChange: (ev) => setIsSmooth(ev.target.checked)
21172                }),
21173                "Smooth"
21174              ]
21175            })
21176          })
21177        ]
21178      })
21179    ]
21180  });
21181}
21182function safeSelector(selector) {
21183  try {
21184    document.querySelector(selector);
21185    return selector;
21186  } catch {
21187    return ":root";
21188  }
21189}
21190function scrollJs(selector, block, isSmooth, offset) {
21191  const behavior = isSmooth ? "smooth" : "auto";
21192  const target = JSON.stringify(selector);
21193  if (!offset) {
21194    return `var t=document.querySelector(${target}); if(t) t.scrollIntoView({behavior:${JSON.stringify(behavior)},block:${JSON.stringify(block)}});`;
21195  }
21196  return `var t=document.querySelector(${target}); if(t) scrollTo({top:t.getBoundingClientRect().top+scrollY-${offset},behavior:${JSON.stringify(behavior)}});`;
21197}
21198function DelayPanel(props) {
21199  const { session } = props;
21200  const [ms, setMs] = p2("0");
21201  const [hasReqText, setHasReqText] = p2(false);
21202  const [reqText, setReqText] = p2("");
21203  _2(() => {
21204    const delay = {
21205      ms: parseInt(ms) || 0
21206    };
21207    if (hasReqText) {
21208      delay.req_text = reqText;
21209    }
21210    session.extraChanges.length = 0;
21211    session.extraChanges.push({
21212      delay
21213    });
21214  });
21215  return /* @__PURE__ */ u2(k, {
21216    children: [
21217      /* @__PURE__ */ u2("div", {
21218        class: "le-notice",
21219        children: [
21220          li.clock,
21221          /* @__PURE__ */ u2("div", {
21222            children: [
21223              "Every change recorded ",
21224              /* @__PURE__ */ u2("b", {
21225                children: "after"
21226              }),
21227              " this one waits until ",
21228              /* @__PURE__ */ u2("b", {
21229                children: describeElem(session.elem)
21230              }),
21231              " is on the page. Use it when the part you edited is rendered by the site's own script."
21232            ]
21233          })
21234        ]
21235      }),
21236      /* @__PURE__ */ u2(Section, {
21237        title: "Wait for",
21238        children: [
21239          /* @__PURE__ */ u2(Field, {
21240            label: "Text",
21241            children: /* @__PURE__ */ u2("label", {
21242              class: "le-btn",
21243              children: [
21244                /* @__PURE__ */ u2("input", {
21245                  type: "checkbox",
21246                  checked: hasReqText,
21247                  onChange: (ev) => setHasReqText(ev.target.checked)
21248                }),
21249                "Must contain text"
21250              ]
21251            })
21252          }),
21253          hasReqText && /* @__PURE__ */ u2(Field, {
21254            label: "The text",
21255            children: /* @__PURE__ */ u2(TextInput, {
21256              value: reqText,
21257              placeholder: "Text the element must show",
21258              onChange: setReqText
21259            })
21260          }),
21261          /* @__PURE__ */ u2(Field, {
21262            label: "And then",
21263            children: [
21264              /* @__PURE__ */ u2(TextInput, {
21265                value: ms,
21266                placeholder: "0",
21267                onChange: setMs
21268              }),
21269              /* @__PURE__ */ u2("span", {
21270                class: "le-hint",
21271                style: "margin:0",
21272                children: "ms of extra delay"
21273              })
21274            ]
21275          })
21276        ]
21277      })
21278    ]
21279  });
21280}
21281
21282// src/components/live/panels/AiPanel.tsx
21283function AiPanel(props) {
21284  const { session, selected } = props;
21285  const inputRef = F2(null);
21286  const logRef = F2(null);
21287  _2(() => {
21288    const log = logRef.current;
21289    if (log) {
21290      log.scrollTop = log.scrollHeight;
21291    }
21292  }, [
21293    session.transcript.length,
21294    session.isBusy
21295  ]);
21296  function send() {
21297    const input = inputRef.current;
21298    const prompt = input?.value.trim() ?? "";
21299    if (prompt && !session.isBusy) {
21300      if (input) {
21301        input.value = "";
21302      }
21303      void session.send(prompt);
21304    }
21305  }
21306  return /* @__PURE__ */ u2(k, {
21307    children: [
21308      /* @__PURE__ */ u2("div", {
21309        class: "le-sub-head",
21310        children: [
21311          /* @__PURE__ */ u2("button", {
21312            type: "button",
21313            class: "le-back",
21314            onClick: props.onBack,
21315            title: "Back to all actions",
21316            children: [
21317              li.chevronLeft,
21318              "Back"
21319            ]
21320          }),
21321          /* @__PURE__ */ u2("b", {
21322            children: "AI assistant"
21323          }),
21324          /* @__PURE__ */ u2("div", {
21325            class: "le-spacer"
21326          }),
21327          session.isBusy && /* @__PURE__ */ u2("button", {
21328            type: "button",
21329            class: "le-btn",
21330            title: "Stop after the current step",
21331            onClick: () => session.stop(),
21332            children: [
21333              li.stop,
21334              "Stop"
21335            ]
21336          })
21337        ]
21338      }),
21339      /* @__PURE__ */ u2("div", {
21340        class: "le-target",
21341        children: selected ? /* @__PURE__ */ u2(k, {
21342          children: [
21343            "Selected ",
21344            /* @__PURE__ */ u2("code", {
21345              title: describeElem(selected),
21346              children: describeElem(selected)
21347            }),
21348            ' \u2014 "this" in your request means it.'
21349          ]
21350        }) : /* @__PURE__ */ u2(k, {
21351          children: "Nothing selected \u2014 describe the elements in words, or click one first."
21352        })
21353      }),
21354      /* @__PURE__ */ u2("div", {
21355        class: "le-ai-log",
21356        ref: logRef,
21357        children: [
21358          session.transcript.length == 0 && /* @__PURE__ */ u2("div", {
21359            class: "le-ai-hint",
21360            children: "Describe an edit in plain words, and the assistant will make it on this page \u2014 move or restyle elements, insert new content, add behavior. The result appears in the change list like any other edit, and every step can be undone."
21361          }),
21362          session.transcript.map((entry, i4) => /* @__PURE__ */ u2(Entry, {
21363            entry
21364          }, i4)),
21365          session.isBusy && /* @__PURE__ */ u2("div", {
21366            class: "le-ai-busy",
21367            children: [
21368              /* @__PURE__ */ u2("span", {
21369                class: "le-ai-pulse"
21370              }),
21371              "Working\u2026"
21372            ]
21373          })
21374        ]
21375      }),
21376      /* @__PURE__ */ u2("div", {
21377        class: "le-ai-inputrow",
21378        children: [
21379          /* @__PURE__ */ u2("textarea", {
21380            ref: inputRef,
21381            class: "le-ai-input",
21382            rows: 2,
21383            placeholder: "E.g.: move this banner under the menu",
21384            disabled: session.isBusy,
21385            onKeyDown: (ev) => {
21386              if (ev.key == "Enter" && !ev.shiftKey) {
21387                ev.preventDefault();
21388                send();
21389              }
21390            }
21391          }),
21392          /* @__PURE__ */ u2("button", {
21393            type: "button",
21394            class: "le-btn le-primary",
21395            disabled: session.isBusy,
21396            title: "Send (Enter)",
21397            onClick: send,
21398            children: li.send
21399          })
21400        ]
21401      }),
21402      /* @__PURE__ */ u2(AiBudgetLine, {
21403        session
21404      })
21405    ]
21406  });
21407}
21408function AiBudgetLine(props) {
21409  const budget = props.session.budget;
21410  if (!budget || budget.unlimited || budget.actionsTotal <= 0) {
21411    return null;
21412  }
21413  const isOut = budget.actionsLeft <= 0;
21414  if (isOut) {
21415    return /* @__PURE__ */ u2("div", {
21416      class: "le-ai-budget le-ai-budget-out",
21417      children: budget.canOverage ? "Monthly AI allowance used \u2014 further AI is added to your invoice." : "You've used this month's AI allowance. Add AI credits in Billing to keep going."
21418    });
21419  }
21420  return /* @__PURE__ */ u2("div", {
21421    class: "le-ai-budget",
21422    children: [
21423      budget.actionsLeft,
21424      " AI ",
21425      budget.actionsLeft == 1 ? "action" : "actions",
21426      " left this month"
21427    ]
21428  });
21429}
21430function Entry(props) {
21431  const { entry } = props;
21432  switch (entry.role) {
21433    case "user":
21434      return /* @__PURE__ */ u2("div", {
21435        class: "le-ai-msg le-ai-user",
21436        children: entry.text
21437      });
21438    case "assistant":
21439      return /* @__PURE__ */ u2("div", {
21440        class: "le-ai-msg le-ai-assistant",
21441        children: entry.text
21442      });
21443    case "error":
21444      return /* @__PURE__ */ u2("div", {
21445        class: "le-ai-status le-ai-error",
21446        children: [
21447          li.warn,
21448          /* @__PURE__ */ u2("span", {
21449            children: entry.text
21450          })
21451        ]
21452      });
21453  }
21454  return /* @__PURE__ */ u2("div", {
21455    class: "le-ai-status",
21456    children: entry.text
21457  });
21458}
21459
21460// src/components/live/LiveTool.tsx
21461var ADD_ONLY_TOOLS = /* @__PURE__ */ new Set([
21462  "insert-image",
21463  "insert-link",
21464  "insert-button",
21465  "insert-block",
21466  "insert-video",
21467  "insert-html",
21468  "onclick",
21469  "scroll-to",
21470  "custom-event",
21471  "delay",
21472  "visibility"
21473]);
21474var INSERT_KINDS = {
21475  "insert-image": "image",
21476  "insert-link": "link",
21477  "insert-button": "button",
21478  "insert-block": "block",
21479  "insert-video": "video",
21480  "insert-html": "html"
21481};
21482function widgetOfTool(tool) {
21483  const m2 = String(tool ?? "").match(/^insert-widget-(.+)$/);
21484  return m2 ? m2[1] : "";
21485}
21486function inheritedDevice(elem) {
21487  for (let node = elem; node; node = node.parentElement) {
21488    const insert = insertOf(node);
21489    if (insert) {
21490      return insert.device ?? "";
21491    }
21492  }
21493  return "";
21494}
21495function isAddOnly(tool) {
21496  return ADD_ONLY_TOOLS.has(tool) || !!widgetOfTool(tool);
21497}
21498function insertKindOf(tool) {
21499  return !tool ? void 0 : widgetOfTool(tool) ? "widget" : INSERT_KINDS[tool];
21500}
21501var SCOPE_KEY = "pz_live_scope";
21502function loadScope() {
21503  try {
21504    const saved = localStorage.getItem(SCOPE_KEY);
21505    if (saved == "page" || saved == "shape" || saved == "any") {
21506      return saved;
21507    }
21508  } catch {
21509  }
21510  return "shape";
21511}
21512function saveScope(scope) {
21513  try {
21514    localStorage.setItem(SCOPE_KEY, scope);
21515  } catch {
21516  }
21517}
21518function unreachableReason(elem) {
21519  const root = elem.getRootNode?.();
21520  if (root && root !== document && root.host) {
21521    const host = root.host;
21522    return `This is inside <${host.localName}>, a component that keeps its own content sealed off from the page. Changes to it cannot be found again on a visitor's page, so it cannot be edited here \u2014 try the element around it.`;
21523  }
21524  if (!elemToSelector(elem)) {
21525    return "This element cannot be addressed on a visitor's page, so a change to it would never be found again. Try the element around it.";
21526  }
21527  return "";
21528}
21529var DRAG_SLOP = 8;
21530function LiveTool(props) {
21531  const [selected, setSelected] = p2(null);
21532  const [tab, setTab] = p2("basics");
21533  const [tool, setTool] = p2(null);
21534  const [isNavigate, setIsNavigate] = p2(false);
21535  const [isCollapsed, setIsCollapsed] = p2(false);
21536  const [toolbarPos, setToolbarPos] = p2(() => ({
21537    left: 18,
21538    top: Math.max(16, Math.min(360, globalThis.innerHeight - 640))
21539  }));
21540  const [changes, setChanges] = p2([]);
21541  const [nChanges, setNChanges] = p2(0);
21542  const [current, setCurrent] = p2(0);
21543  const [href, setHref] = p2(() => getPageUrl());
21544  const [pending2, setPending] = p2(null);
21545  const [allow, setAllow] = p2({});
21546  const [dragFrom, setDragFrom] = p2(null);
21547  const [scope, setScope] = p2(loadScope);
21548  const scopeRef = F2(scope);
21549  scopeRef.current = scope;
21550  const pickScope = (next) => {
21551    setScope(next);
21552    saveScope(next);
21553  };
21554  const [showOn, setShowOn] = p2("");
21555  const showOnRef = F2("");
21556  showOnRef.current = showOn;
21557  const pickShowOn = (next) => {
21558    showOnRef.current = next;
21559    setShowOn(next);
21560  };
21561  const [clicks, setClicks] = p2({
21562    on: false,
21563    counted: false,
21564    touched: false
21565  });
21566  const [error, setError] = p2("");
21567  const [device, setDevice] = p2("desktop");
21568  const [isSideCollapsed, setIsSideCollapsed] = p2(false);
21569  const [isSideLeft, setIsSideLeft] = p2(false);
21570  const [isStopped, setIsStopped] = p2(false);
21571  const [isHidden, setIsHidden] = p2(false);
21572  const [isPicking, setIsPicking] = p2(false);
21573  const [isAiOpen, setIsAiOpen] = p2(false);
21574  const [isSearchOpen, setIsSearchOpen] = p2(false);
21575  const [isPeeking, setIsPeeking] = p2(false);
21576  const [, setAiTick] = p2(0);
21577  const pickerRef = F2(null);
21578  const editorRef = F2(null);
21579  const aiRef = F2(null);
21580  const selectedRef = F2(null);
21581  const bridgeRef = F2(null);
21582  const sessionRef = F2(null);
21583  const actionIdRef = F2(0);
21584  const [names, setNames] = p2({
21585    campaign: "",
21586    action: ""
21587  });
21588  const [isGroup, setIsGroup] = p2(false);
21589  const isGroupRef = F2(false);
21590  isGroupRef.current = isGroup;
21591  const isSettingValueRef = F2(false);
21592  const isGotValueRef = F2(false);
21593  const isPageLoadedRef = F2(true);
21594  const isInitedRef = F2(false);
21595  const origSizeRef = F2(null);
21596  const pickOnceRef = F2(null);
21597  const previewTimerRef = F2(0);
21598  const peekDepthRef = F2(0);
21599  const isPeekingRef = F2(false);
21600  const isOwnWindow = typeof globalThis.opener == "object" && !!globalThis.opener;
21601  function postStatus(withChanges, isPeekStarting = false) {
21602    const editor = editorRef.current;
21603    const bridge = bridgeRef.current;
21604    if (!editor || !bridge || isSettingValueRef.current || isPeekingRef.current) {
21605      return;
21606    }
21607    const isReady = isPageLoadedRef.current && isGotValueRef.current && document.readyState == "complete";
21608    const message = {
21609      page_is_loaded: isReady,
21610      href: getPageUrl(),
21611      n_changes: editor.getNChanges(),
21612      // Whether the AI is reachable at all. The panel's column hides its Page-level AI card
21613      // otherwise, rather than offering a box that answers nothing.
21614      has_ai: isJsapiConfigured() && !!aiRef.current,
21615      // False on every ordinary status, because the guard above means this only runs with the
21616      // peek OFF - which is what takes the panel out of "Showing the original" in the very
21617      // message that brings the real counts back. True only for the one status [[togglePeek]]
21618      // sends on its way IN, before the flag goes up. See there for why it is that status
21619      // rather than a message of its own.
21620      is_peeking: isPeekStarting
21621    };
21622    if (withChanges && isGotValueRef.current) {
21623      message.html_changes = editor.getChanges(true);
21624    }
21625    void bridge.reportMessage(message);
21626    if (!isInitedRef.current && isReady && !isPeekStarting) {
21627      isInitedRef.current = true;
21628      beginPicking();
21629    }
21630  }
21631  function refreshChanges() {
21632    const editor = editorRef.current;
21633    if (editor) {
21634      setChanges(editor.getChanges(true));
21635      setNChanges(editor.getNChanges());
21636      setHref(getPageUrl());
21637      postStatus(true);
21638    }
21639  }
21640  function groupSelectorOf(elem) {
21641    const selector = elemToGroupSelector(elem);
21642    return selector && findMatches(selector, document).length > 1 ? selector : "";
21643  }
21644  function beginPicking() {
21645    const picker = pickerRef.current;
21646    if (!picker || isNavigate || sessionRef.current || isStopped) {
21647      return;
21648    }
21649    setIsPicking(true);
21650    picker.selectBegin({
21651      ignore: [
21652        props.hostElem
21653      ],
21654      onSelect: (elem) => {
21655        const once = pickOnceRef.current;
21656        if (once) {
21657          pickOnceRef.current = null;
21658          picker.selectEnd();
21659          setIsPicking(false);
21660          once(elem);
21661          return;
21662        }
21663        if (elem && elem != document.body && elem != document.documentElement) {
21664          const why = unreachableReason(elem);
21665          if (why) {
21666            setError(why);
21667            return;
21668          }
21669          setError("");
21670          setSelected(elem);
21671          picker.highlight([
21672            elem
21673          ]);
21674        }
21675      }
21676    });
21677  }
21678  function endPicking() {
21679    pickerRef.current?.selectEnd();
21680    setIsPicking(false);
21681  }
21682  function pickElement(onPicked) {
21683    const picker = pickerRef.current;
21684    if (picker) {
21685      pickOnceRef.current = onPicked;
21686      setIsPicking(true);
21687      picker.selectBegin({
21688        ignore: [
21689          props.hostElem
21690        ],
21691        onSelect: (elem) => {
21692          pickOnceRef.current = null;
21693          picker.selectEnd();
21694          setIsPicking(false);
21695          onPicked(elem);
21696          if (sessionRef.current) {
21697            picker.highlight([
21698              sessionRef.current.elem
21699            ]);
21700          }
21701        }
21702      });
21703    }
21704  }
21705  _2(() => {
21706    const picker = new Picker();
21707    pickerRef.current = picker;
21708    const editor = new PageEditor({
21709      onChange: refreshChanges,
21710      onPending: setPending,
21711      getScope: () => scopeRef.current,
21712      getDevice: () => showOnRef.current
21713    });
21714    editorRef.current = editor;
21715    if (isJsapiConfigured()) {
21716      aiRef.current = new AiLiveSession({
21717        editor,
21718        hostElem: props.hostElem,
21719        getActionId: () => actionIdRef.current,
21720        getSelected: () => selectedRef.current,
21721        onUpdate: () => setAiTick((tick) => tick + 1)
21722      });
21723    }
21724    const bridge = new ToolBridge({
21725      bufferKey: props.bufferKey,
21726      guiOrigin: props.guiOrigin,
21727      onValue: (value) => void applyValue(value),
21728      onMessageData: (data) => onCommand(data)
21729    });
21730    bridgeRef.current = bridge;
21731    void bridge.start();
21732    const onLoad = () => postStatus(false);
21733    const onBeforeUnload = () => {
21734      isPageLoadedRef.current = false;
21735      postStatus(false);
21736      setTimeout(() => {
21737        isPageLoadedRef.current = true;
21738        postStatus(false);
21739      }, 2e3);
21740    };
21741    globalThis.addEventListener("load", onLoad);
21742    globalThis.addEventListener("beforeunload", onBeforeUnload);
21743    postStatus(false);
21744    return () => {
21745      globalThis.removeEventListener("load", onLoad);
21746      globalThis.removeEventListener("beforeunload", onBeforeUnload);
21747      clearTimeout(previewTimerRef.current);
21748      bridge.destroy();
21749      picker.destroy();
21750    };
21751  }, []);
21752  async function applyValue(value) {
21753    const editor = editorRef.current;
21754    if (!editor) {
21755      return;
21756    }
21757    if (value.action_id) {
21758      actionIdRef.current = Number(value.action_id) | 0;
21759    }
21760    if (value.campaign_name != void 0 || value.action_name != void 0) {
21761      setNames({
21762        campaign: String(value.campaign_name ?? ""),
21763        action: String(value.action_name ?? "")
21764      });
21765    }
21766    if (value.allow_form != void 0 || value.allow_chat != void 0 || value.allow_meetings != void 0) {
21767      setAllow({
21768        form: !!value.allow_form,
21769        chat: !!value.allow_chat,
21770        meetings: !!value.allow_meetings
21771      });
21772    }
21773    const htmlChanges = value.html_changes;
21774    const wanted = value.n_changes;
21775    if (htmlChanges && wanted != null) {
21776      try {
21777        isSettingValueRef.current = true;
21778        closeSession(false);
21779        isPeekingRef.current = false;
21780        peekDepthRef.current = 0;
21781        setIsPeeking(false);
21782        editor.reset();
21783        await editor.addChanges(htmlChanges);
21784        setAppliedTo(wanted);
21785      } catch (e3) {
21786        console.error(e3);
21787        setError(e3 instanceof Error ? e3.message : "Operation failed");
21788        void bridgeRef.current?.reportMessage({
21789          error: "Operation failed"
21790        });
21791      } finally {
21792        isSettingValueRef.current = false;
21793        isGotValueRef.current = true;
21794      }
21795      refreshChanges();
21796    }
21797  }
21798  function onCommand(data) {
21799    const editor = editorRef.current;
21800    if (!editor) {
21801      return;
21802    }
21803    try {
21804      switch (data.command) {
21805        case "widget_html":
21806          resolveWidgetHtml(data.req_id, data.html);
21807          break;
21808        case "undo":
21809          undoOne();
21810          break;
21811        case "redo":
21812          redoOne();
21813          break;
21814        case "highlight_elems":
21815          if (data.qelem_id == void 0) {
21816            previewChange(null);
21817          } else {
21818            pickerRef.current?.highlight(resolveQelem(data.qelem_id, true));
21819          }
21820          break;
21821        case "focus_elems":
21822          focusQelem(data.qelem_id);
21823          break;
21824        case "peek": {
21825          const wanted = data.on == void 0 ? !isPeekingRef.current : !!data.on;
21826          if (wanted != isPeekingRef.current) {
21827            togglePeek();
21828          }
21829          break;
21830        }
21831        case "discard_all":
21832          discardAll();
21833          break;
21834        case "page_ai": {
21835          const prompt = String(data.prompt ?? "").trim();
21836          if (prompt && aiRef.current && !isEditingPaused()) {
21837            closeSession(false);
21838            setSelected(null);
21839            setIsAiOpen(true);
21840            void aiRef.current.send(prompt);
21841          }
21842          break;
21843        }
21844        case "stop":
21845          closeSession(false);
21846          endPicking();
21847          setIsStopped(true);
21848          break;
21849      }
21850    } catch (e3) {
21851      console.error(e3);
21852      void bridgeRef.current?.reportMessage({
21853        error: "Operation failed"
21854      });
21855    }
21856  }
21857  function isEditingPaused() {
21858    return isPeekingRef.current;
21859  }
21860  function undoOne() {
21861    if (!isEditingPaused()) {
21862      editorRef.current?.undo(true);
21863    }
21864  }
21865  function redoOne() {
21866    if (!isEditingPaused()) {
21867      editorRef.current?.redo(true);
21868    }
21869  }
21870  function openAi() {
21871    if (!isEditingPaused()) {
21872      closeSession(false);
21873      setIsAiOpen(true);
21874    }
21875  }
21876  function openTool(next, on) {
21877    const editor = editorRef.current;
21878    const selected2 = on ?? selectedRef.current;
21879    if (!editor || !selected2 || isEditingPaused()) {
21880      return;
21881    }
21882    const qelemId = (isGroupRef.current ? groupSelectorOf(selected2) : "") || elemToSelector(selected2);
21883    pickShowOn(inheritedDevice(selected2));
21884    if (INSTANT_TOOLS.indexOf(next) != -1) {
21885      try {
21886        editor.moveCursor(selected2, qelemId);
21887        switch (next) {
21888          case "duplicate":
21889            editor.changeDup(1);
21890            break;
21891          case "delete":
21892            editor.changeRm();
21893            break;
21894          case "action-click":
21895            if (actionHasForm(editor.getChanges())) {
21896              throw new Error("This action has a form, so its Clicks number counts form submissions - a click here would be reported as a submission.");
21897            }
21898            if (clicksAlreadyCounted(selected2)) {
21899              throw new Error("Clicks on this element are already counted - see the list of changes.");
21900            }
21901            editor.changeCustom({
21902              events: [
21903                {
21904                  ...CLICK_EVENT
21905                }
21906              ]
21907            });
21908            break;
21909        }
21910        editor.saveAtCursor();
21911      } catch (e3) {
21912        console.error(e3);
21913        setError(e3 instanceof Error ? e3.message : "Operation failed");
21914        editor.discardAtCursor();
21915      }
21916      setSelected(resolveQelem(qelemId));
21917      return;
21918    }
21919    endPicking();
21920    const working = editor.moveCursor(selected2, qelemId);
21921    if (!(working instanceof Element)) {
21922      editor.discardAtCursor();
21923      return;
21924    }
21925    sessionRef.current = {
21926      elem: working,
21927      qelemId,
21928      targetSelector: qelemId,
21929      editor,
21930      extraChanges: [],
21931      subjElemEdited: !isAddOnly(next),
21932      onPreview: () => {
21933        pickerRef.current?.highlight([
21934          sessionRef.current?.elem ?? working
21935        ]);
21936      },
21937      /*	The way a panel asks the Personyze window for work this bundle must not do — today
21938      that is compiling a widget's markup. Absent when there is no bridge, and the panel
21939      that needs it says so rather than silently doing nothing. */
21940      report: (data) => void bridgeRef.current?.reportMessage(data)
21941    };
21942    const insertKind = insertKindOf(next);
21943    const counted = !insertKind && clicksAlreadyCounted(working);
21944    setClicks({
21945      counted,
21946      on: counted || (insertKind ? insertCountsByDefault(insertKind, widgetOfTool(next)) : isClickable(working)),
21947      touched: false
21948    });
21949    setSelected(working);
21950    setTool(next);
21951    pickerRef.current?.highlight([
21952      working
21953    ]);
21954  }
21955  function closeSession(withSave) {
21956    const session2 = sessionRef.current;
21957    const editor = editorRef.current;
21958    sessionRef.current = null;
21959    if (!session2 || !editor) {
21960      setTool(null);
21961      return;
21962    }
21963    try {
21964      if (withSave) {
21965        const addClicks = clicks.on && !clicks.counted && toolOffersClicks(tool) && !actionHasForm([
21966          ...editor.getChanges(),
21967          ...session2.extraChanges
21968        ]);
21969        const nBefore = editor.getNChanges();
21970        let edited;
21971        if (session2.subjElemEdited) {
21972          editor.saveAtCursor();
21973          if (editor.getNChanges() > nBefore) {
21974            edited = editor.getChanges()[editor.getNChanges() - 1];
21975          }
21976        } else {
21977          editor.discardAtCursor();
21978        }
21979        if (session2.prevChanges?.length) {
21980          editor.setElemPrevChangesChanges(session2.elem, session2.prevChanges);
21981        }
21982        const extra = session2.extraChanges.filter((v3) => v3);
21983        for (const change of extra) {
21984          if (change.qelem_id == void 0) {
21985            change.qelem_id = session2.qelemId;
21986          }
21987        }
21988        const insert = extra.find((c3) => c3.e);
21989        const carrier = insert ?? (edited ? void 0 : extra.find((c3) => c3.mv));
21990        if (addClicks && carrier) {
21991          carrier.events = [
21992            ...carrier.events ?? [],
21993            {
21994              ...CLICK_EVENT
21995            }
21996          ];
21997        } else if (addClicks && !edited && clicks.touched && !insertKindOf(tool)) {
21998          extra.push({
21999            qelem_id: session2.qelemId,
22000            events: [
22001              {
22002                ...CLICK_EVENT
22003              }
22004            ]
22005          });
22006        }
22007        if (extra.length) {
22008          editor.addChangesSync(extra, true);
22009        }
22010        if (addClicks && edited && !carrier) {
22011          const at = editor.getChanges().indexOf(edited);
22012          if (at != -1) {
22013            editor.setChangeClicks(at, true);
22014          }
22015        }
22016      } else {
22017        editor.discardAtCursor();
22018      }
22019    } catch (e3) {
22020      console.error(e3);
22021      setError(e3 instanceof Error ? e3.message : "Operation failed");
22022    }
22023    setTool(null);
22024    setSelected(resolveQelem(session2.qelemId));
22025  }
22026  _2(() => {
22027    if (isStopped || isHidden || isNavigate || tool || isPeeking) {
22028      endPicking();
22029    } else if (isInitedRef.current) {
22030      beginPicking();
22031    }
22032  }, [
22033    isNavigate,
22034    tool,
22035    isStopped,
22036    isPeeking,
22037    isHidden
22038  ]);
22039  _2(() => isStopped || isNavigate ? void 0 : installPageFreeze(document, props.hostElem), [
22040    isNavigate,
22041    isStopped
22042  ]);
22043  _2(() => {
22044    if (!isOwnWindow) {
22045      return;
22046    }
22047    function avoidSide2() {
22048      const root = props.hostElem.shadowRoot;
22049      const side = root?.querySelector(".le-side");
22050      const toolbar = root?.querySelector(".le-toolbar");
22051      if (!side || !toolbar) {
22052        return;
22053      }
22054      const rect = side.getBoundingClientRect();
22055      const width = toolbar.offsetWidth;
22056      setToolbarPos((pos) => {
22057        if (pos.left + width <= rect.left || pos.left >= rect.right) {
22058          return pos;
22059        }
22060        const left = isSideLeft ? Math.min(globalThis.innerWidth - width - 8, rect.right + 12) : Math.max(8, rect.left - width - 12);
22061        return left == pos.left ? pos : {
22062          ...pos,
22063          left
22064        };
22065      });
22066    }
22067    avoidSide2();
22068    globalThis.addEventListener("resize", avoidSide2);
22069    return () => globalThis.removeEventListener("resize", avoidSide2);
22070  }, [
22071    isSideLeft,
22072    isSideCollapsed,
22073    isOwnWindow
22074  ]);
22075  _2(() => {
22076    const drag = dragFrom;
22077    if (!drag) {
22078      return;
22079    }
22080    let moved = false;
22081    const targetAt = (x2, y3) => {
22082      const at = document.elementFromPoint(x2, y3);
22083      return !at || at == props.hostElem || props.hostElem.contains(at) || at == document.documentElement ? null : at;
22084    };
22085    const onMove = (ev) => {
22086      if (!moved && Math.abs(ev.clientX - drag.from.x) + Math.abs(ev.clientY - drag.from.y) < DRAG_SLOP) {
22087        return;
22088      }
22089      moved = true;
22090      ev.preventDefault();
22091      const at = targetAt(ev.clientX, ev.clientY);
22092      pickerRef.current?.highlight(at ? [
22093        at
22094      ] : [], "#F59E0B");
22095    };
22096    const onUp = (ev) => {
22097      setDragFrom(null);
22098      const at = moved ? targetAt(ev.clientX, ev.clientY) : null;
22099      if (!at) {
22100        pickerRef.current?.highlight(selectedRef.current ? [
22101          selectedRef.current
22102        ] : []);
22103        return;
22104      }
22105      const why = unreachableReason(at);
22106      if (why) {
22107        setError(why);
22108        pickerRef.current?.highlight(selectedRef.current ? [
22109          selectedRef.current
22110        ] : []);
22111        return;
22112      }
22113      setError("");
22114      setSelected(at);
22115      pickerRef.current?.highlight([
22116        at
22117      ]);
22118      openTool(drag.tool, at);
22119    };
22120    document.addEventListener("mousemove", onMove, true);
22121    document.addEventListener("mouseup", onUp, true);
22122    return () => {
22123      document.removeEventListener("mousemove", onMove, true);
22124      document.removeEventListener("mouseup", onUp, true);
22125    };
22126  }, [
22127    dragFrom
22128  ]);
22129  _2(() => {
22130    const root = props.hostElem.shadowRoot?.querySelector(".le-toolbar");
22131    if (!selected || !root || tool) {
22132      return;
22133    }
22134    const gap = 12;
22135    const panel = root.getBoundingClientRect();
22136    const elem = selected.getBoundingClientRect();
22137    const overlaps = panel.left < elem.right + gap && panel.right + gap > elem.left && panel.top < elem.bottom + gap && panel.bottom + gap > elem.top;
22138    if (!overlaps || !elem.width || !elem.height) {
22139      return;
22140    }
22141    const maxLeft = globalThis.innerWidth - panel.width - 8;
22142    const maxTop = globalThis.innerHeight - panel.height - 8;
22143    const candidates = [
22144      {
22145        left: elem.right + gap,
22146        top: toolbarPos.top
22147      },
22148      {
22149        left: elem.left - panel.width - gap,
22150        top: toolbarPos.top
22151      },
22152      {
22153        left: toolbarPos.left,
22154        top: elem.bottom + gap
22155      },
22156      {
22157        left: toolbarPos.left,
22158        top: elem.top - panel.height - gap
22159      }
22160    ];
22161    const fits = candidates.find((p3) => p3.left >= 8 && p3.left <= maxLeft && p3.top >
22161= 8 && p3.top <= maxTop && !(p3.left < elem.right + gap && p3.left + panel.width + gap > elem.left && p3.top < elem.bottom + gap && p3.top + panel.height + gap > elem.top));
22162    if (fits) {
22163      setToolbarPos(fits);
22164    }
22165  }, [
22166    selected,
22167    tool
22168  ]);
22169  _2(() => {
22170    if (selected && !isPicking && !tool) {
22171      pickerRef.current?.highlight([
22172        selected
22173      ]);
22174    }
22175  }, [
22176    selected,
22177    isPicking,
22178    tool
22179  ]);
22180  _2(() => {
22181    selectedRef.current = selected;
22182  }, [
22183    selected
22184  ]);
22185  _2(() => {
22186    function onKeyDown(ev) {
22187      const target2 = ev.composedPath()[0];
22188      const isTyping = !!target2 && (target2.matches?.("input, textarea, select") || target2.isContentEditable);
22189      if ((ev.metaKey || ev.ctrlKey) && ev.key.toLowerCase() == "k") {
22190        ev.preventDefault();
22191        setIsSearchOpen((v3) => !v3);
22192        return;
22193      }
22194      if (ev.key == "Escape") {
22195        if (isSearchOpen) {
22196          setIsSearchOpen(false);
22197        } else if (tool) {
22198          closeSession(false);
22199        } else if (isAiOpen) {
22200          setIsAiOpen(false);
22201        } else if (isNavigate) {
22202          setIsNavigate(false);
22203        }
22204        return;
22205      }
22206      if (isTyping || isSearchOpen) {
22207        return;
22208      }
22209      if ((ev.metaKey || ev.ctrlKey) && ev.key.toLowerCase() == "z") {
22210        ev.preventDefault();
22211        if (ev.shiftKey) {
22212          redoOne();
22213        } else {
22214          undoOne();
22215        }
22216        return;
22217      }
22218      if ((ev.metaKey || ev.ctrlKey) && ev.key == "\\") {
22219        ev.preventDefault();
22220        setIsCollapsed((v3) => !v3);
22221        return;
22222      }
22223      if (ev.metaKey || ev.ctrlKey || ev.altKey) {
22224        return;
22225      }
22226      if ((ev.key == "Backspace" || ev.key == "Delete") && !tool && !isAiOpen && selected) {
22227        ev.preventDefault();
22228        openTool("delete");
22229        return;
22230      }
22231      const key = ev.key.toUpperCase();
22232      if (key == "N") {
22233        ev.preventDefault();
22234        setIsNavigate((v3) => !v3);
22235        return;
22236      }
22237      if ((key == "J" || key == "K") && isOwnWindow && changes.length) {
22238        ev.preventDefault();
22239        setCurrent((v3) => (v3 + (key == "J" ? 1 : changes.length - 1)) % changes.length);
22240        return;
22241      }
22242      if (!tool && !isAiOpen && selected) {
22243        const tile = TILES.find((v3) => v3.key == key);
22244        if (tile) {
22245          ev.preventDefault();
22246          openTool(tile.tool);
22247        }
22248      }
22249    }
22250    document.addEventListener("keydown", onKeyDown, true);
22251    return () => document.removeEventListener("keydown", onKeyDown, true);
22252  }, [
22253    tool,
22254    selected,
22255    isNavigate,
22256    isAiOpen,
22257    isSearchOpen,
22258    changes.length
22259  ]);
22260  function onDevice(next) {
22261    setDevice(next);
22262    if (!origSizeRef.current) {
22263      origSizeRef.current = {
22264        width: globalThis.outerWidth,
22265        height: globalThis.outerHeight
22266      };
22267    }
22268    const width = DEVICE_WIDTHS[next];
22269    try {
22270      if (!width) {
22271        globalThis.resizeTo(origSizeRef.current.width, origSizeRef.current.height);
22272      } else {
22273        globalThis.resizeTo(width + (globalThis.outerWidth - globalThis.innerWidth), globalThis.outerHeight);
22274      }
22275    } catch (e3) {
22276      console.error(e3);
22277      setError("This browser would not let the editor resize the window.");
22278    }
22279  }
22280  function setAppliedTo(n2, withScroll) {
22281    const editor = editorRef.current;
22282    if (!editor) {
22283      return;
22284    }
22285    for (let prev = -1; editor.getNChanges() != n2 && editor.getNChanges() != prev; ) {
22286      prev = editor.getNChanges();
22287      if (prev > n2) {
22288        editor.undo(withScroll);
22289      } else {
22290        editor.redo(withScroll);
22291      }
22292    }
22293  }
22294  function jumpTo(n2) {
22295    setAppliedTo(n2, true);
22296  }
22297  function discardAll() {
22298    closeSession(false);
22299    setCurrent(0);
22300    isPeekingRef.current = false;
22301    peekDepthRef.current = 0;
22302    setIsPeeking(false);
22303    try {
22304      editorRef.current?.reset();
22305    } catch (e3) {
22306      console.error(e3);
22307      setError(e3 instanceof Error ? e3.message : "Could not discard the changes");
22308    }
22309    setSelected(null);
22310  }
22311  function togglePeek() {
22312    const editor = editorRef.current;
22313    if (!editor) {
22314      return;
22315    }
22316    if (!isPeekingRef.current) {
22317      closeSession(false);
22318      endPicking();
22319      pickerRef.current?.highlight([]);
22320      peekDepthRef.current = editor.getNChanges();
22321      postStatus(true, true);
22322      isPeekingRef.current = true;
22323      setIsPeeking(true);
22324      setAppliedTo(0);
22325    } else {
22326      setAppliedTo(peekDepthRef.current);
22327      isPeekingRef.current = false;
22328      setIsPeeking(false);
22329      postStatus(true);
22330    }
22331  }
22332  function focusQelem(qelemId) {
22333    closeSession(false);
22334    const elems = resolveQelem(qelemId, true);
22335    pickerRef.current?.highlight(elems, "#F59E0B");
22336    elems[0]?.scrollIntoView({
22337      block: "center",
22338      behavior: "smooth"
22339    });
22340    if (elems[0]) {
22341      setSelected(elems[0]);
22342    }
22343  }
22344  function setScopeOn(indices, scope2) {
22345    const editor = editorRef.current;
22346    if (!editor) {
22347      return;
22348    }
22349    const refused = indices.filter((i4) => !editor.canSetChangeScope(i4, scope2));
22350    if (refused.length) {
22351      setError(indices.length == 1 ? "That change was made on another page, so there is no layout here to compare it with. Open the page it belongs to and set it there." : "Some of these changes were made on another page, so there is no layout here to compare them with. Open that page to set them together.");
22352      return;
22353    }
22354    let done2 = false;
22355    for (const i4 of indices) {
22356      done2 = editor.setChangeScope(i4, scope2) || done2;
22357    }
22358    if (done2) {
22359      setError("");
22360      refreshChanges();
22361    }
22362  }
22363  function setClicksOn(index, on) {
22364    const editor = editorRef.current;
22365    if (!editor) {
22366      return;
22367    }
22368    const applied = editor.getChanges();
22369    const change = applied[index];
22370    if (on && change) {
22371      if (actionHasForm(applied)) {
22372        setError("This action has a form, so its Clicks number counts form submissions - a click here would be reported as a submission.");
22373        return;
22374      }
22375      const twin = !change.e && applied.some((c3, i4) => i4 != index && !c3.e && countsClicks(c3) && JSON.stringify(c3.qelem_id) == JSON.stringify(change.qelem_id));
22376      if (twin) {
22377        setError("Clicks on this element are already counted by another change in the list.");
22378        return;
22379      }
22380    }
22381    if (editor.setChangeClicks(index, on)) {
22382      setError("");
22383      refreshChanges();
22384    }
22385  }
22386  function setDeviceOn(index, device2) {
22387    const editor = editorRef.current;
22388    if (!editor) {
22389      return;
22390    }
22391    let done2 = false;
22392    for (const i4 of relatedChanges(changes, index)) {
22393      done2 = editor.setChangeDevice(i4, device2) || done2;
22394    }
22395    if (done2) {
22396      setError("");
22397      refreshChanges();
22398    }
22399  }
22400  function focusChange(change) {
22401    focusQelem(change.qelem_id);
22402  }
22403  function openWidgetSettings(change) {
22404    const type = String((Array.isArray(change.e) && typeof change.e[0] == "string" ? change.e[1] : null)?.["data-pz-widget"] ?? "");
22405    if (!type) {
22406      return;
22407    }
22408    const want = `[data-pz-widget="${type.replace(/"/g, "")}"]`;
22409    const anchor = resolveQelem(change.qelem_id);
22410    const near = anchor?.nodeType == 1 ? anchor : null;
22411    let host = null;
22412    for (let at = near; at && !host; at = at.parentElement) {
22413      host = at.matches?.(want) ? at : at.querySelector(want);
22414    }
22415    host ??= document.querySelector(want);
22416    if (!host) {
22417      setError("That widget is not on this page any more.");
22418      return;
22419    }
22420    closeSession(false);
22421    setSelected(host);
22422    pickerRef.current?.highlight([
22423      host
22424    ]);
22425    host.scrollIntoView({
22426      block: "center",
22427      behavior: "smooth"
22428    });
22429    openTool("widget-edit", host);
22430  }
22431  function previewChange(change) {
22432    clearTimeout(previewTimerRef.current);
22433    const picker = pickerRef.current;
22434    if (!change) {
22435      picker?.highlight(selected ? [
22436        selected
22437      ] : []);
22438      return;
22439    }
22440    const elems = resolveQelem(change.qelem_id, true);
22441    picker?.highlight(elems, "#F59E0B");
22442    previewTimerRef.current = setTimeout(() => {
22443      const elem = elems[0];
22444      const rect = elem?.getBoundingClientRect();
22445      if (rect && (rect.top < 0 || rect.bottom > globalThis.innerHeight)) {
22446        elem.scrollIntoView({
22447          block: "center",
22448          behavior: "smooth"
22449        });
22450      }
22451    }, 120);
22452  }
22453  function selectBySelector(selector) {
22454    let elem = null;
22455    try {
22456      elem = document.querySelector(selector);
22457    } catch {
22458      return "That is not a valid CSS selector.";
22459    }
22460    if (!elem) {
22461      return "Nothing on this page matches it.";
22462    }
22463    if (elem == props.hostElem || props.hostElem.contains(elem)) {
22464      return "That is the editor's own toolbar, not the page.";
22465    }
22466    closeSession(false);
22467    setSelected(elem);
22468    pickerRef.current?.highlight([
22469      elem
22470    ]);
22471    elem.scrollIntoView({
22472      block: "center",
22473      behavior: "smooth"
22474    });
22475    return "";
22476  }
22477  function buildCommands() {
22478    const commands = [];
22479    commands.push({
22480      group: "Editor",
22481      label: isNavigate ? "Turn Navigate mode off" : "Turn Navigate mode on",
22482      hint: "Click through the page like a visitor",
22483      shortcut: "N",
22484      run: () => setIsNavigate(!isNavigate)
22485    }, {
22486      group: "Editor",
22487      label: "Undo the last change",
22488      hint: "Reverse the most recent edit",
22489      shortcut: "\u2318Z",
22490      run: undoOne
22491    }, {
22492      group: "Editor",
22493      label: "Redo",
22494      hint: "Reapply the last undone change",
22495      shortcut: "\u2318\u21E7Z",
22496      run: redoOne
22497    }, {
22498      group: "Editor",
22499      label: isCollapsed ? "Expand the toolbar" : "Collapse the toolbar",
22500      hint: "Shrink it to one line, out of the way",
22501      shortcut: "\u2318\\",
22502      run: () => setIsCollapsed(!isCollapsed)
22503    });
22504    if (changes.length) {
22505      commands.push({
22506        group: "Editor",
22507        label: isPeeking ? "Put the changes back" : "Show the original page",
22508        hint: "Roll every change back to compare, without giving them up",
22509        run: togglePeek
22510      }, {
22511        group: "Editor",
22512        label: "Discard all changes",
22513        hint: "Undo everything - the page goes back to how it arrived",
22514        run: discardAll
22515      });
22516    }
22517    if (isJsapiConfigured()) {
22518      commands.push({
22519        group: "Editor",
22520        label: "AI assistant",
22521        hint: "Describe an edit in plain words",
22522        run: openAi
22523      });
22524    }
22525    if (!tool) {
22526      for (const tile of availableTiles(selected)) {
22527        commands.push({
22528          group: "This element",
22529          label: tile.title,
22530          hint: tile.sub,
22531          shortcut: tile.key,
22532          run: () => openTool(tile.tool)
22533        });
22534      }
22535    }
22536    if (selected) {
22537      for (const node of ancestorChain(selected).slice(0, -1).reverse()) {
22538        commands.push({
22539          group: "Select a parent",
22540          label: describeElem(node),
22541          hint: tagHumanName(node),
22542          run: () => {
22543            closeSession(false);
22544            setSelected(node);
22545          }
22546        });
22547      }
22548    }
22549    if (isOwnWindow) {
22550      commands.push({
22551        group: "Preview",
22552        label: "Desktop preview",
22553        hint: "The window size you started with",
22554        run: () => onDevice("desktop")
22555      }, {
22556        group: "Preview",
22557        label: "Tablet preview",
22558        hint: "768px wide",
22559        run: () => onDevice("tablet")
22560      }, {
22561        group: "Preview",
22562        label: "Mobile preview",
22563        hint: "375px wide",
22564        run: () => onDevice("mobile")
22565      }, {
22566        group: "Preview",
22567        label: isSideLeft ? "Dock the panel to the right" : "Dock the panel to the left",
22568        hint: "Move the change list to the other side",
22569        run: () => setIsSideLeft(!isSideLeft)
22570      }, {
22571        group: "Preview",
22572        label: "Done editing - back to Personyze",
22573        hint: "Save and return",
22574        run: done
22575      });
22576    }
22577    for (const [i4, change] of changes.entries()) {
22578      const summary = summarizeChange(change);
22579      commands.push({
22580        group: "Changes",
22581        label: summary.title,
22582        hint: summary.target + (i4 >= nChanges ? " - undone" : ""),
22583        run: () => {
22584          setCurrent(i4);
22585          focusChange(change);
22586        }
22587      });
22588    }
22589    return commands;
22590  }
22591  function done() {
22592    const bridge = bridgeRef.current;
22593    const editor = editorRef.current;
22594    if (isPeekingRef.current) {
22595      togglePeek();
22596    }
22597    closeSession(true);
22598    const finish = () => {
22599      try {
22600        getPanelWindow()?.focus();
22601        globalThis.close();
22602      } catch (e3) {
22603        console.error(e3);
22604      }
22605    };
22606    if (bridge && editor) {
22607      bridge.reportMessage({
22608        page_is_loaded: true,
22609        href: getPageUrl(),
22610        n_changes: editor.getNChanges(),
22611        html_changes: editor.getChanges(true)
22612      }).then(finish, finish);
22613    } else {
22614      finish();
22615    }
22616  }
22617  if (isStopped) {
22618    return null;
22619  }
22620  if (isHidden) {
22621    const logoSrc = props.counterUrl ? props.counterUrl + "/images/logo-icon.svg" : "";
22622    return /* @__PURE__ */ u2("div", {
22623      class: "le-root le-reopen",
22624      children: /* @__PURE__ */ u2("button", {
22625        type: "button",
22626        class: "le-reopen-btn",
22627        title: "Bring the editor back and carry on where you left off",
22628        onClick: () => setIsHidden(false),
22629        children: [
22630          logoSrc ? /* @__PURE__ */ u2("img", {
22631            src: logoSrc,
22632            alt: "",
22633            onError: (ev) => {
22634              ev.target.style.display = "none";
22635            }
22636          }) : li.pencil,
22637          /* @__PURE__ */ u2("span", {
22638            children: "Reopen the Personyze editor"
22639          }),
22640          nChanges > 0 && /* @__PURE__ */ u2("span", {
22641            class: "le-reopen-n",
22642            children: [
22643              nChanges,
22644              " change",
22645              nChanges == 1 ? "" : "s"
22646            ]
22647          })
22648        ]
22649      })
22650    });
22651  }
22652  const session = sessionRef.current;
22653  const target = selected ? describeElem(selected) : "";
22654  const groupInfo = (() => {
22655    const selector = !selected || tool ? "" : groupSelectorOf(selected);
22656    return selector ? {
22657      selector,
22658      count: findMatches(selector, document).length
22659    } : null;
22660  })();
22661  const avoidSide = !isOwnWindow || isSideCollapsed ? "" : isSideLeft ? " le-avoid-left" : " le-avoid-right";
22662  return /* @__PURE__ */ u2(k, {
22663    children: [
22664      /* @__PURE__ */ u2(Toolbar, {
22665        selected,
22666        tab,
22667        isNavigate,
22668        isCollapsed,
22669        canUndo: nChanges > 0,
22670        canRedo: nChanges < changes.length,
22671        hasAi: isJsapiConfigured(),
22672        allow,
22673        onDragTool: (tool2, from) => setDragFrom({
22674          tool: tool2,
22675          from
22676        }),
22677        notice: tool ? "" : error,
22678        onDismissNotice: () => setError(""),
22679        isAiOpen,
22680        position: toolbarPos,
22681        group: groupInfo,
22682        isGroup,
22683        onGroup: (next) => {
22684          setIsGroup(next);
22685          pickerRef.current?.highlight(next && groupInfo ? findMatches(groupInfo.selector, document) : selected ? [
22686            selected
22687          ] : []);
22688        },
22689        onSelect: (elem) => {
22690          closeSession(false);
22691          setSelected(elem);
22692        },
22693        onSelectBySelector: selectBySelector,
22694        onHoverCrumb: (elem) => pickerRef.current?.highlight(elem ? [
22695          elem
22696        ] : selected ? [
22697          selected
22698        ] : []),
22699        onTab: setTab,
22700        onTool: openTool,
22701        onNavigate: setIsNavigate,
22702        onAi: (next) => {
22703          if (!next) setIsAiOpen(false);
22704          else openAi();
22705        },
22706        onSearch: () => setIsSearchOpen(true),
22707        onCollapse: setIsCollapsed,
22708        onUndo: undoOne,
22709        onRedo: redoOne,
22710        onClose: () => {
22711          closeSession(false);
22712          endPicking();
22713          setIsHidden(true);
22714        },
22715        onMove: setToolbarPos,
22716        subpanelFoot: !(tool && session) ? null : (
22717          /*	ONE ROW. "Apply on" sits with the buttons because it is part of committing
22718          						the edit, not a property of the element - and because a full-width control
22719          						with a paragraph under it made the footer taller than the panel it belongs
22720          						to (Jacob, 26 Aug 26). What it means lives in the tooltip on the label.
22721          
22722          						EXCEPT WHEN THE PANEL ASKS "WHERE" ITSELF. An insert opens on a Where section -
22723          						before, inside, after - and "Apply on" is the other half of that same question,
22724          						so there it sits under "Put it" as the same three buttons and the footer is
22725          						just Cancel and Save (Jacob, 28 Sep 26). One place per panel, never two. */
22726          /* @__PURE__ */ u2("div", {
22727            class: "le-foot",
22728            children: [
22729              !insertKindOf(tool) && /* @__PURE__ */ u2("div", {
22730                class: "le-foot-scope",
22731                children: [
22732                  /* @__PURE__ */ u2(ScopeLabel, {
22733                    for: "le-scope-sel"
22734                  }),
22735                  /* @__PURE__ */ u2("select", {
22736                    id: "le-scope-sel",
22737                    class: "le-scope-pick le-scope-foot",
22738                    "data-tooltip": ((o3) => o3 ? o3.label + " - " + o3.hint : "")(SCOPE_OPTIONS.find((v3) => v3.id == scope)),
22739                    "data-tooltip-pos": "top",
22740                    value: scope,
22741                    onChange: (e3) => pickScope(e3.currentTarget.value),
22742                    children: SCOPE_OPTIONS.map((v3) => /* @__PURE__ */ u2("option", {
22743                      value: v3.id,
22744                      title: v3.hint,
22745                      children: v3.short
22746                    }))
22747                  })
22748                ]
22749              }),
22750              !insertKindOf(tool) && /* @__PURE__ */ u2("div", {
22751                class: "le-foot-scope",
22752                children: /* @__PURE__ */ u2("select", {
22753                  id: "le-device-sel",
22754                  class: "le-scope-pick le-scope-foot",
22755                  "aria-label": "Show on",
22756                  "data-tooltip": "Show on: " + SHOW_ON_TIP,
22757                  "data-tooltip-pos": "top",
22758                  value: showOn,
22759                  onChange: (e3) => pickShowOn(e3.currentTarget.value),
22760                  children: SHOW_ON_OPTIONS.map((v3) => /* @__PURE__ */ u2("option", {
22761                    value: v3.id,
22762                    title: v3.hint,
22763                    children: v3.label
22764                  }))
22765                })
22766              }),
22767              /* @__PURE__ */ u2("div", {
22768                class: "le-foot-save",
22769                children: [
22770                  toolOffersClicks(tool) && widgetOfTool(tool) != "form" && !actionHasForm(changes.slice(0, nChanges)) && /* @__PURE__ */ u2(CountClicksCheck, {
22771                    on: clicks.on,
22772                    counted: clicks.counted,
22773                    isInsert: !!insertKindOf(tool),
22774                    onChange: (on) => setClicks({
22775                      on,
22776                      counted: false,
22777                      touched: true
22778                    })
22779                  }),
22780                  /* @__PURE__ */ u2("button", {
22781                    type: "button",
22782                    class: "le-btn",
22783                    "data-tooltip": "Discard what you changed here and go back to the tiles",
22784                    "data-tooltip-pos": "top",
22785                    onClick: () => closeSession(false),
22786                    children: "Cancel"
22787                  }),
22788                  /* @__PURE__ */ u2("button", {
22789                    type: "button",
22790                    class: "le-btn le-primary",
22791                    "data-tooltip": "Keep this edit and go back to the tiles",
22792                    "data-tooltip-pos": "top",
22793                    onClick: () => closeSession(true),
22794                    children: "Save"
22795                  })
22796                ]
22797              })
22798            ]
22799          })
22800        ),
22801        subpanel: !(tool && session) ? !isAiOpen || !aiRef.current ? null : /* @__PURE__ */ u2(AiPanel, {
22802          session: aiRef.current,
22803          selected,
22804          onBack: () => setIsAiOpen(false)
22805        }) : /* @__PURE__ */ u2(k, {
22806          children: [
22807            /* @__PURE__ */ u2(SubPanelHead, {
22808              title: TILES.find((v3) => v3.tool == tool)?.title ?? "",
22809              target: target || tagHumanName(session.elem),
22810              onBack: () => closeSession(false),
22811              extra: tool == "style" || tool == "spacing" || tool == "alignment" || tool == "position" ? /* @__PURE__ */ u2("button", {
22812                type: "button",
22813                class: "le-btn",
22814                title: "Drop every inline style on this element - all tabs, not just this one",
22815                onClick: () => resetInlineStyle(session),
22816                children: "Reset all"
22817              }) : void 0
22818            }),
22819            error && /* @__PURE__ */ u2("div", {
22820              class: "le-notice le-warn",
22821              children: [
22822                li.warn,
22823                /* @__PURE__ */ u2("div", {
22824                  children: error
22825                }),
22826                /* @__PURE__ */ u2("button", {
22827                  type: "button",
22828                  class: "le-icon",
22829                  title: "Dismiss",
22830                  onClick: () => setError(""),
22831                  children: li.cross
22832                })
22833              ]
22834            }),
22835            /* @__PURE__ */ u2(ToolBody, {
22836              tool,
22837              session,
22838              actionId: actionIdRef.current,
22839              scope,
22840              onScope: pickScope,
22841              device: showOn,
22842              onDevice: pickShowOn,
22843              onPickElement: pickElement,
22844              onError: (e3) => setError(e3 instanceof Error ? e3.message : String(e3))
22845            })
22846          ]
22847        })
22848      }),
22849      isSearchOpen && /* @__PURE__ */ u2(CommandPalette, {
22850        commands: buildCommands(),
22851        onClose: () => setIsSearchOpen(false)
22852      }),
22853      pending2 && /* @__PURE__ */ u2("div", {
22854        class: "le-root le-navbanner" + avoidSide,
22855        style: "top:auto; bottom:6em",
22856        children: [
22857          li.warn,
22858          /* @__PURE__ */ u2("span", {
22859            children: [
22860              "An earlier edit points at ",
22861              /* @__PURE__ */ u2("b", {
22862                children: pending2.target
22863              }),
22864              ", which is not on this page (yet)."
22865            ]
22866          }),
22867          /* @__PURE__ */ u2("button", {
22868            type: "button",
22869            class: "le-btn",
22870            onClick: () => editorRef.current?.bypassPending(),
22871            children: "Skip it"
22872          })
22873        ]
22874      }),
22875      isPeeking && /* @__PURE__ */ u2("div", {
22876        class: "le-root le-navbanner" + avoidSide,
22877        children: [
22878          /* @__PURE__ */ u2("span", {
22879            children: "\u{1F441}"
22880          }),
22881          /* @__PURE__ */ u2("span", {
22882            children: [
22883              /* @__PURE__ */ u2("b", {
22884                children: "Showing the original"
22885              }),
22886              " - your ",
22887              changes.length,
22888              " ",
22889              changes.length == 1 ? "change is" : "changes are",
22890              " kept, and editing is paused."
22891            ]
22892          }),
22893          /* @__PURE__ */ u2("button", {
22894            type: "button",
22895            class: "le-btn",
22896            onClick: togglePeek,
22897            children: "Put them back"
22898          })
22899        ]
22900      }),
22901      isNavigate && !isPeeking && /* @__PURE__ */ u2("div", {
22902        class: "le-root le-navbanner" + avoidSide,
22903        children: [
22904          /* @__PURE__ */ u2("span", {
22905            children: "\u{1F446}"
22906          }),
22907          /* @__PURE__ */ u2("span", {
22908            children: [
22909              /* @__PURE__ */ u2("b", {
22910                children: "Navigate mode"
22911              }),
22912              " - clicks go to the page. ",
22913              /* @__PURE__ */ u2("kbd", {
22914                children: "Esc"
22915              }),
22916              " to edit."
22917            ]
22918          })
22919        ]
22920      }),
22921      isOwnWindow && /* @__PURE__ */ u2(k, {
22922        children: [
22923          /* @__PURE__ */ u2(ChangesSidebar, {
22924            changes,
22925            nChanges,
22926            current,
22927            href,
22928            campaignName: names.campaign,
22929            actionName: names.action || (actionIdRef.current > 0 ? "Action " + actionIdRef.current : "New action"),
22930            logoSrc: props.counterUrl ? props.counterUrl + "/images/logo-icon.svg" : "",
22931            isCollapsed: isSideCollapsed,
22932            isLeft: isSideLeft,
22933            onCurrent: setCurrent,
22934            onWidgetSettings: openWidgetSettings,
22935            onScope: (index, next) => setScopeOn(relatedChanges(changes, index), next),
22936            onScopeAll: (next) => setScopeOn(changes.map((_3, i4) => i4), next),
22937            onClicks: setClicksOn,
22938            onDevice: setDeviceOn,
22939            clicksBlocked: actionHasForm(changes.slice(0, nChanges)),
22940            onJump: jumpTo,
22941            onFocus: focusChange,
22942            onHover: previewChange,
22943            onGo: (url) => {
22944              globalThis.location.href = url;
22945            },
22946            onReload: () => globalThis.location.reload(),
22947            onDone: done,
22948            onDiscardAll: discardAll,
22949            isPeeking,
22950            onPeek: togglePeek,
22951            onCollapse: setIsSideCollapsed,
22952            onDock: setIsSideLeft,
22953            hasAi: isJsapiConfigured() && !!aiRef.current,
22954            onPageAi: (prompt) => {
22955              if (isEditingPaused()) {
22956                return;
22957              }
22958              closeSession(false);
22959              setSelected(null);
22960              setIsAiOpen(true);
22961              void aiRef.current?.send(prompt);
22962            }
22963          }),
22964          /* @__PURE__ */ u2(DevicePicker, {
22965            device,
22966            onDevice
22967          })
22968        ]
22969      }),
22970      /* @__PURE__ */ u2(TipLayer, {})
22971    ]
22972  });
22973}
22974function ToolBody(props) {
22975  const { tool, session } = props;
22976  const insertKind = insertKindOf(tool);
22977  if (insertKind) {
22978    return /* @__PURE__ */ u2(InsertPanel, {
22979      session,
22980      kind: insertKind,
22981      widgetId: widgetOfTool(tool),
22982      scope: props.scope,
22983      onScope: props.onScope,
22984      device: props.device,
22985      onDevice: props.onDevice
22986    });
22987  }
22988  switch (tool) {
22989    case "widget-edit":
22990      return /* @__PURE__ */ u2(WidgetEditPanel, {
22991        session
22992      });
22993    case "text":
22994      return /* @__PURE__ */ u2(TextPanel, {
22995        session,
22996        actionId: props.actionId
22997      });
22998    case "rich":
22999      return /* @__PURE__ */ u2(RichPanel, {
23000        session,
23001        actionId: props.actionId
23002      });
23003    case "source":
23004      return /* @__PURE__ */ u2(SourcePanel, {
23005        session,
23006        actionId: props.actionId
23007      });
23008    case "style":
23009      return /* @__PURE__ */ u2(StylePanel, {
23010        session,
23011        actionId: props.actionId
23012      });
23013    case "spacing":
23014      return /* @__PURE__ */ u2(SpacingPanel, {
23015        session
23016      });
23017    case "alignment":
23018      return /* @__PURE__ */ u2(AlignmentPanel, {
23019        session
23020      });
23021    case "position":
23022      return /* @__PURE__ */ u2(PositionPanel, {
23023        session
23024      });
23025    case "visibility":
23026      return /* @__PURE__ */ u2(VisibilityPanel, {
23027        session
23028      });
23029    case "props":
23030      return /* @__PURE__ */ u2(PropsPanel, {
23031        session
23032      });
23033    case "replace-image":
23034      return /* @__PURE__ */ u2(ReplaceImagePanel, {
23035        session
23036      });
23037    case "move":
23038      return /* @__PURE__ */ u2(MovePanel, {
23039        session,
23040        onPickElement: props.onPickElement
23041      });
23042    case "onclick":
23043      return /* @__PURE__ */ u2(OnClickPanel, {
23044        session,
23045        actionId: props.actionId,
23046        onPickElement: props.onPickElement,
23047        onError: props.onError
23048      });
23049    case "custom-event":
23050      return /* @__PURE__ */ u2(OnClickPanel, {
23051        session,
23052        actionId: props.actionId,
23053        initialType: "custom_event",
23054        onPickElement: props.onPickElement,
23055        onError: props.onError
23056      });
23057    case "scroll-to":
23058      return /* @__PURE__ */ u2(ScrollToPanel, {
23059        session,
23060        onPickElement: props.onPickElement
23061      });
23062    case "delay":
23063      return /* @__PURE__ */ u2(DelayPanel, {
23064        session
23065      });
23066  }
23067  return null;
23068}
23069
23070// src/modules/toplayer/mod.ts
23071function raiseToTopLayer(elem) {
23072  if (typeof elem.showPopover != "function") {
23073    return false;
23074  }
23075  try {
23076    elem.setAttribute("popover", "manual");
23077    elem.showPopover();
23078    return true;
23079  } catch {
23080    elem.removeAttribute("popover");
23081    return false;
23082  }
23083}
23084
23085// src/apps/simulator-live.ts
23086var HOST_STYLE = "all: initial !important; position: fixed !important; z-index: 2147483647 !important; top: 0 !important; left: 0 !important; width: 0 !important; height: 0 !important";
23087function start(options) {
23088  if (options.jsapi_url) {
23089    configureJsapi({
23090      url: options.jsapi_url,
23091      serverId: options.server_id || 0
23092    });
23093  }
23094  const api = globalScope.personyzeSimulatorLive;
23095  if (api) {
23096    api.session = {
23097      jsapi_url: options.jsapi_url ?? "",
23098      server_id: options.server_id ?? 0,
23099      buffer_key: options.buffer_key
23100    };
23101  }
23102  const hostElem = document.createElement("div");
23103  hostElem.setAttribute("style", HOST_STYLE);
23104  document.body.appendChild(hostElem);
23105  raiseToTopLayer(hostElem);
23106  const shadow = hostElem.attachShadow({
23107    mode: "open"
23108  });
23109  const style = document.createElement("style");
23110  style.textContent = LIVE_STYLE;
23111  shadow.appendChild(style);
23112  const mount = document.createElement("div");
23113  shadow.appendChild(mount);
23114  B(_(LiveTool, {
23115    bufferKey: options.buffer_key,
23116    guiOrigin: options.gui_origin || "*",
23117    counterUrl: options.counter_url || "",
23118    hostElem
23119  }), mount);
23120  return {
23121    destroy() {
23122      B(null, mount);
23123      hostElem.remove();
23124    }
23125  };
23126}
23127var globalScope = globalThis;
23128if (globalScope.personyzeSimulatorLive == void 0) {
23129  globalScope.personyzeSimulatorLive = {
23130    version: 1,
23131    start
23132  };
23133}
23134})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.