open-chat-studio-widget 0.9.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/cjs/{index-fFSp-Z_h.js → index-DMXmZhVD.js} +3 -3
  2. package/dist/cjs/index-DMXmZhVD.js.map +1 -0
  3. package/dist/cjs/loader.cjs.js +2 -2
  4. package/dist/cjs/open-chat-studio-widget.cjs.entry.js +1251 -314
  5. package/dist/cjs/open-chat-studio-widget.cjs.entry.js.map +1 -1
  6. package/dist/cjs/open-chat-studio-widget.cjs.js +2 -2
  7. package/dist/cjs/open-chat-studio-widget.entry.cjs.js.map +1 -1
  8. package/dist/collection/components/ocs-chat/ocs-chat.js +90 -5
  9. package/dist/collection/components/ocs-chat/ocs-chat.js.map +1 -1
  10. package/dist/collection/services/chat-session-service.js +5 -0
  11. package/dist/collection/services/chat-session-service.js.map +1 -1
  12. package/dist/components/open-chat-studio-widget.js +1251 -313
  13. package/dist/components/open-chat-studio-widget.js.map +1 -1
  14. package/dist/esm/{index-ythTKHg-.js → index-JDApwJx_.js} +3 -3
  15. package/dist/esm/index-JDApwJx_.js.map +1 -0
  16. package/dist/esm/loader.js +3 -3
  17. package/dist/esm/open-chat-studio-widget.entry.js +1251 -314
  18. package/dist/esm/open-chat-studio-widget.entry.js.map +1 -1
  19. package/dist/esm/open-chat-studio-widget.js +3 -3
  20. package/dist/open-chat-studio-widget/open-chat-studio-widget.entry.esm.js.map +1 -1
  21. package/dist/open-chat-studio-widget/open-chat-studio-widget.esm.js +1 -1
  22. package/dist/open-chat-studio-widget/{p-ythTKHg-.js → p-JDApwJx_.js} +2 -2
  23. package/dist/open-chat-studio-widget/p-JDApwJx_.js.map +1 -0
  24. package/dist/open-chat-studio-widget/p-c2d3b2d1.entry.js +4 -0
  25. package/dist/open-chat-studio-widget/p-c2d3b2d1.entry.js.map +1 -0
  26. package/dist/types/components/ocs-chat/ocs-chat.d.ts +15 -0
  27. package/dist/types/services/chat-session-service.d.ts +2 -0
  28. package/package.json +2 -2
  29. package/dist/cjs/index-fFSp-Z_h.js.map +0 -1
  30. package/dist/esm/index-ythTKHg-.js.map +0 -1
  31. package/dist/open-chat-studio-widget/p-2d31a15c.entry.js +0 -4
  32. package/dist/open-chat-studio-widget/p-2d31a15c.entry.js.map +0 -1
  33. package/dist/open-chat-studio-widget/p-ythTKHg-.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { h, r as registerInstance, E as Env, H as Host, g as getElement } from './index-ythTKHg-.js';
1
+ import { h, r as registerInstance, E as Env, H as Host, g as getElement } from './index-JDApwJx_.js';
2
2
 
3
3
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
4
4
  const OcsWidgetAvatar = () => {
@@ -2942,24 +2942,65 @@ marked.Slugger = Slugger;
2942
2942
  marked.Hooks = Hooks;
2943
2943
  marked.parse = marked;
2944
2944
 
2945
- /*! @license DOMPurify 3.4.0 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.0/LICENSE */
2946
-
2947
- const {
2948
- entries,
2949
- setPrototypeOf,
2950
- isFrozen,
2951
- getPrototypeOf,
2952
- getOwnPropertyDescriptor
2953
- } = Object;
2954
- let {
2955
- freeze,
2956
- seal,
2957
- create
2958
- } = Object; // eslint-disable-line import/no-mutable-exports
2959
- let {
2960
- apply,
2961
- construct
2962
- } = typeof Reflect !== 'undefined' && Reflect;
2945
+ /*! @license DOMPurify 3.4.11 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.11/LICENSE */
2946
+
2947
+ function _arrayLikeToArray(r, a) {
2948
+ (null == a || a > r.length) && (a = r.length);
2949
+ for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
2950
+ return n;
2951
+ }
2952
+ function _arrayWithHoles(r) {
2953
+ if (Array.isArray(r)) return r;
2954
+ }
2955
+ function _iterableToArrayLimit(r, l) {
2956
+ var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
2957
+ if (null != t) {
2958
+ var e,
2959
+ n,
2960
+ i,
2961
+ u,
2962
+ a = [],
2963
+ f = true,
2964
+ o = false;
2965
+ try {
2966
+ if (i = (t = t.call(r)).next, 0 === l) ; else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = true);
2967
+ } catch (r) {
2968
+ o = true, n = r;
2969
+ } finally {
2970
+ try {
2971
+ if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
2972
+ } finally {
2973
+ if (o) throw n;
2974
+ }
2975
+ }
2976
+ return a;
2977
+ }
2978
+ }
2979
+ function _nonIterableRest() {
2980
+ throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
2981
+ }
2982
+ function _slicedToArray(r, e) {
2983
+ return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
2984
+ }
2985
+ function _unsupportedIterableToArray(r, a) {
2986
+ if (r) {
2987
+ if ("string" == typeof r) return _arrayLikeToArray(r, a);
2988
+ var t = {}.toString.call(r).slice(8, -1);
2989
+ return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
2990
+ }
2991
+ }
2992
+
2993
+ const entries = Object.entries,
2994
+ setPrototypeOf = Object.setPrototypeOf,
2995
+ isFrozen = Object.isFrozen,
2996
+ getPrototypeOf = Object.getPrototypeOf,
2997
+ getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
2998
+ let freeze = Object.freeze,
2999
+ seal = Object.seal,
3000
+ create = Object.create; // eslint-disable-line import/no-mutable-exports
3001
+ let _ref = typeof Reflect !== 'undefined' && Reflect,
3002
+ apply = _ref.apply,
3003
+ construct = _ref.construct;
2963
3004
  if (!freeze) {
2964
3005
  freeze = function freeze(x) {
2965
3006
  return x;
@@ -2991,13 +3032,19 @@ const arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);
2991
3032
  const arrayPop = unapply(Array.prototype.pop);
2992
3033
  const arrayPush = unapply(Array.prototype.push);
2993
3034
  const arraySplice = unapply(Array.prototype.splice);
3035
+ const arrayIsArray = Array.isArray;
2994
3036
  const stringToLowerCase = unapply(String.prototype.toLowerCase);
2995
3037
  const stringToString = unapply(String.prototype.toString);
2996
3038
  const stringMatch = unapply(String.prototype.match);
2997
3039
  const stringReplace = unapply(String.prototype.replace);
2998
3040
  const stringIndexOf = unapply(String.prototype.indexOf);
2999
3041
  const stringTrim = unapply(String.prototype.trim);
3042
+ const numberToString = unapply(Number.prototype.toString);
3043
+ const booleanToString = unapply(Boolean.prototype.toString);
3044
+ const bigintToString = typeof BigInt === 'undefined' ? null : unapply(BigInt.prototype.toString);
3045
+ const symbolToString = typeof Symbol === 'undefined' ? null : unapply(Symbol.prototype.toString);
3000
3046
  const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
3047
+ const objectToString = unapply(Object.prototype.toString);
3001
3048
  const regExpTest = unapply(RegExp.prototype.test);
3002
3049
  const typeErrorCreate = unconstruct(TypeError);
3003
3050
  /**
@@ -3047,6 +3094,9 @@ function addToSet(set, array) {
3047
3094
  // Prevent prototype setters from intercepting set as a this value.
3048
3095
  setPrototypeOf(set, null);
3049
3096
  }
3097
+ if (!arrayIsArray(array)) {
3098
+ return set;
3099
+ }
3050
3100
  let l = array.length;
3051
3101
  while (l--) {
3052
3102
  let element = array[l];
@@ -3087,10 +3137,13 @@ function cleanArray(array) {
3087
3137
  */
3088
3138
  function clone(object) {
3089
3139
  const newObject = create(null);
3090
- for (const [property, value] of entries(object)) {
3140
+ for (const _ref2 of entries(object)) {
3141
+ var _ref3 = _slicedToArray(_ref2, 2);
3142
+ const property = _ref3[0];
3143
+ const value = _ref3[1];
3091
3144
  const isPropertyExist = objectHasOwnProperty(object, property);
3092
3145
  if (isPropertyExist) {
3093
- if (Array.isArray(value)) {
3146
+ if (arrayIsArray(value)) {
3094
3147
  newObject[property] = cleanArray(value);
3095
3148
  } else if (value && typeof value === 'object' && value.constructor === Object) {
3096
3149
  newObject[property] = clone(value);
@@ -3101,6 +3154,58 @@ function clone(object) {
3101
3154
  }
3102
3155
  return newObject;
3103
3156
  }
3157
+ /**
3158
+ * Convert non-node values into strings without depending on direct property access.
3159
+ *
3160
+ * @param value - The value to stringify.
3161
+ * @returns A string representation of the provided value.
3162
+ */
3163
+ function stringifyValue(value) {
3164
+ switch (typeof value) {
3165
+ case 'string':
3166
+ {
3167
+ return value;
3168
+ }
3169
+ case 'number':
3170
+ {
3171
+ return numberToString(value);
3172
+ }
3173
+ case 'boolean':
3174
+ {
3175
+ return booleanToString(value);
3176
+ }
3177
+ case 'bigint':
3178
+ {
3179
+ return bigintToString ? bigintToString(value) : '0';
3180
+ }
3181
+ case 'symbol':
3182
+ {
3183
+ return symbolToString ? symbolToString(value) : 'Symbol()';
3184
+ }
3185
+ case 'undefined':
3186
+ {
3187
+ return objectToString(value);
3188
+ }
3189
+ case 'function':
3190
+ case 'object':
3191
+ {
3192
+ if (value === null) {
3193
+ return objectToString(value);
3194
+ }
3195
+ const valueAsRecord = value;
3196
+ const valueToString = lookupGetter(valueAsRecord, 'toString');
3197
+ if (typeof valueToString === 'function') {
3198
+ const stringified = valueToString(valueAsRecord);
3199
+ return typeof stringified === 'string' ? stringified : objectToString(stringified);
3200
+ }
3201
+ return objectToString(value);
3202
+ }
3203
+ default:
3204
+ {
3205
+ return objectToString(value);
3206
+ }
3207
+ }
3208
+ }
3104
3209
  /**
3105
3210
  * This method automatically checks if the prop is function or getter and behaves accordingly.
3106
3211
  *
@@ -3126,6 +3231,14 @@ function lookupGetter(object, prop) {
3126
3231
  }
3127
3232
  return fallbackValue;
3128
3233
  }
3234
+ function isRegex(value) {
3235
+ try {
3236
+ regExpTest(value, '');
3237
+ return true;
3238
+ } catch (_unused) {
3239
+ return false;
3240
+ }
3241
+ }
3129
3242
 
3130
3243
  const html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'search', 'section', 'select', 'shadow', 'slot', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']);
3131
3244
  const svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'enterkeyhint', 'exportparts', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'inputmode', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'part', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);
@@ -3141,15 +3254,14 @@ const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mgly
3141
3254
  const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
3142
3255
  const text = freeze(['#text']);
3143
3256
 
3144
- const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'exportparts', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inert', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'part', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'slot', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns', 'slot']);
3257
+ const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'command', 'commandfor', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'exportparts', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inert', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'part', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'slot', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns']);
3145
3258
  const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'mask-type', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);
3146
3259
  const mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnalign', 'columnlines', 'columnspacing', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lquote', 'lspace', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']);
3147
3260
  const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
3148
3261
 
3149
- // eslint-disable-next-line unicorn/better-regex
3150
- const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode
3151
- const ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm);
3152
- const TMPLIT_EXPR = seal(/\$\{[\w\W]*/gm); // eslint-disable-line unicorn/better-regex
3262
+ const MUSTACHE_EXPR = seal(/{{[\w\W]*|^[\w\W]*}}/g);
3263
+ const ERB_EXPR = seal(/<%[\w\W]*|^[\w\W]*%>/g);
3264
+ const TMPLIT_EXPR = seal(/\${[\w\W]*/g);
3153
3265
  const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape
3154
3266
  const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
3155
3267
  const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
@@ -3159,30 +3271,23 @@ const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205
3159
3271
  );
3160
3272
  const DOCTYPE_NAME = seal(/^html$/i);
3161
3273
  const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
3274
+ // Markup-significant character probes used by _sanitizeElements.
3275
+ // Shared module-level instances are safe despite the sticky /g flags:
3276
+ // unapply() resets lastIndex for RegExp receivers before every call.
3277
+ const ELEMENT_MARKUP_PROBE = seal(/<[/\w!]/g);
3278
+ const COMMENT_MARKUP_PROBE = seal(/<[/\w]/g);
3279
+ const FALLBACK_TAG_CLOSE = seal(/<\/no(script|embed|frames)/i);
3280
+ const SELF_CLOSING_TAG = seal(/\/>/i);
3162
3281
 
3163
- var EXPRESSIONS = /*#__PURE__*/Object.freeze({
3164
- __proto__: null,
3165
- ARIA_ATTR: ARIA_ATTR,
3166
- ATTR_WHITESPACE: ATTR_WHITESPACE,
3167
- CUSTOM_ELEMENT: CUSTOM_ELEMENT,
3168
- DATA_ATTR: DATA_ATTR,
3169
- DOCTYPE_NAME: DOCTYPE_NAME,
3170
- ERB_EXPR: ERB_EXPR,
3171
- IS_ALLOWED_URI: IS_ALLOWED_URI,
3172
- IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
3173
- MUSTACHE_EXPR: MUSTACHE_EXPR,
3174
- TMPLIT_EXPR: TMPLIT_EXPR
3175
- });
3176
-
3177
- /* eslint-disable @typescript-eslint/indent */
3178
3282
  // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
3179
3283
  const NODE_TYPE = {
3180
3284
  element: 1,
3181
3285
  text: 3,
3182
3286
  // Deprecated
3183
- progressingInstruction: 7,
3287
+ processingInstruction: 7,
3184
3288
  comment: 8,
3185
- document: 9};
3289
+ document: 9,
3290
+ documentFragment: 11};
3186
3291
  const getGlobal = function getGlobal() {
3187
3292
  return typeof window === 'undefined' ? null : window;
3188
3293
  };
@@ -3237,10 +3342,25 @@ const _createHooksMap = function _createHooksMap() {
3237
3342
  uponSanitizeShadowNode: []
3238
3343
  };
3239
3344
  };
3345
+ /**
3346
+ * Resolve a set-valued configuration option: a fresh set built from
3347
+ * cfg[key] when it is an own array property (seeded with a clone of
3348
+ * options.base when given, case-normalized via options.transform),
3349
+ * the fallback set otherwise.
3350
+ *
3351
+ * @param cfg the cloned, prototype-free configuration object
3352
+ * @param key the configuration property to read
3353
+ * @param fallback the set to use when the option is absent or not an array
3354
+ * @param options transform and optional base set to merge into
3355
+ * @returns the resolved set
3356
+ */
3357
+ const _resolveSetOption = function _resolveSetOption(cfg, key, fallback, options) {
3358
+ return objectHasOwnProperty(cfg, key) && arrayIsArray(cfg[key]) ? addToSet(options.base ? clone(options.base) : {}, cfg[key], options.transform) : fallback;
3359
+ };
3240
3360
  function createDOMPurify() {
3241
3361
  let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
3242
3362
  const DOMPurify = root => createDOMPurify(root);
3243
- DOMPurify.version = '3.4.0';
3363
+ DOMPurify.version = '3.4.11';
3244
3364
  DOMPurify.removed = [];
3245
3365
  if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
3246
3366
  // Not running in a browser, provide a factory function
@@ -3248,28 +3368,25 @@ function createDOMPurify() {
3248
3368
  DOMPurify.isSupported = false;
3249
3369
  return DOMPurify;
3250
3370
  }
3251
- let {
3252
- document
3253
- } = window;
3371
+ let document = window.document;
3254
3372
  const originalDocument = document;
3255
3373
  const currentScript = originalDocument.currentScript;
3256
- const {
3257
- DocumentFragment,
3258
- HTMLTemplateElement,
3259
- Node,
3260
- Element,
3261
- NodeFilter,
3262
- NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap,
3263
- HTMLFormElement,
3264
- DOMParser,
3265
- trustedTypes
3266
- } = window;
3374
+ const HTMLTemplateElement = window.HTMLTemplateElement,
3375
+ Node = window.Node,
3376
+ Element = window.Element,
3377
+ NodeFilter = window.NodeFilter;
3378
+ const DOMParser = window.DOMParser,
3379
+ trustedTypes = window.trustedTypes;
3267
3380
  const ElementPrototype = Element.prototype;
3268
3381
  const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
3269
3382
  const remove = lookupGetter(ElementPrototype, 'remove');
3270
3383
  const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
3271
3384
  const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
3272
3385
  const getParentNode = lookupGetter(ElementPrototype, 'parentNode');
3386
+ const getShadowRoot = lookupGetter(ElementPrototype, 'shadowRoot');
3387
+ const getAttributes = lookupGetter(ElementPrototype, 'attributes');
3388
+ const getNodeType = Node && Node.prototype ? lookupGetter(Node.prototype, 'nodeType') : null;
3389
+ const getNodeName = Node && Node.prototype ? lookupGetter(Node.prototype, 'nodeName') : null;
3273
3390
  // As per issue #47, the web-components registry is inherited by a
3274
3391
  // new document created via createHTMLDocument. As per the spec
3275
3392
  // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
@@ -3284,33 +3401,74 @@ function createDOMPurify() {
3284
3401
  }
3285
3402
  let trustedTypesPolicy;
3286
3403
  let emptyHTML = '';
3287
- const {
3288
- implementation,
3289
- createNodeIterator,
3290
- createDocumentFragment,
3291
- getElementsByTagName
3292
- } = document;
3293
- const {
3294
- importNode
3295
- } = originalDocument;
3404
+ // The instance's own internal Trusted Types policy. Unlike a caller-supplied
3405
+ // `TRUSTED_TYPES_POLICY`, this is created at most once — Trusted Types throws
3406
+ // on duplicate policy names — and is the only policy allowed to persist
3407
+ // across configurations and survive `clearConfig()`.
3408
+ let defaultTrustedTypesPolicy;
3409
+ let defaultTrustedTypesPolicyResolved = false;
3410
+ // Tracks whether we are already inside a call to the configured Trusted Types
3411
+ // policy (`createHTML` or `createScriptURL`). If a supplied policy callback
3412
+ // itself calls `DOMPurify.sanitize` (the cause of #1422), `sanitize` would
3413
+ // re-enter the policy and recurse until the stack overflows. We detect that
3414
+ // re-entry and throw a clear, actionable error instead. The guard is shared
3415
+ // across both callbacks, because either one re-entering `sanitize` triggers
3416
+ // the same unbounded recursion.
3417
+ let IN_TRUSTED_TYPES_POLICY = 0;
3418
+ const _assertNotInTrustedTypesPolicy = function _assertNotInTrustedTypesPolicy() {
3419
+ if (IN_TRUSTED_TYPES_POLICY > 0) {
3420
+ throw typeErrorCreate('A configured TRUSTED_TYPES_POLICY callback (createHTML or ' + 'createScriptURL) must not call DOMPurify.sanitize, as that causes ' + 'infinite recursion. Do not pass a policy whose callbacks wrap ' + 'DOMPurify as TRUSTED_TYPES_POLICY; see the "DOMPurify and Trusted ' + 'Types" section of the README.');
3421
+ }
3422
+ };
3423
+ const _createTrustedHTML = function _createTrustedHTML(html) {
3424
+ _assertNotInTrustedTypesPolicy();
3425
+ IN_TRUSTED_TYPES_POLICY++;
3426
+ try {
3427
+ return trustedTypesPolicy.createHTML(html);
3428
+ } finally {
3429
+ IN_TRUSTED_TYPES_POLICY--;
3430
+ }
3431
+ };
3432
+ const _createTrustedScriptURL = function _createTrustedScriptURL(scriptUrl) {
3433
+ _assertNotInTrustedTypesPolicy();
3434
+ IN_TRUSTED_TYPES_POLICY++;
3435
+ try {
3436
+ return trustedTypesPolicy.createScriptURL(scriptUrl);
3437
+ } finally {
3438
+ IN_TRUSTED_TYPES_POLICY--;
3439
+ }
3440
+ };
3441
+ // Lazily resolve (and cache) the instance's internal default policy.
3442
+ // Resolution is attempted at most once: a successful `createPolicy` cannot be
3443
+ // repeated (Trusted Types throws on duplicate names), and a failed or
3444
+ // unsupported attempt must not be retried on every parse.
3445
+ const _getDefaultTrustedTypesPolicy = function _getDefaultTrustedTypesPolicy() {
3446
+ if (!defaultTrustedTypesPolicyResolved) {
3447
+ defaultTrustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);
3448
+ defaultTrustedTypesPolicyResolved = true;
3449
+ }
3450
+ return defaultTrustedTypesPolicy;
3451
+ };
3452
+ const _document = document,
3453
+ implementation = _document.implementation,
3454
+ createNodeIterator = _document.createNodeIterator,
3455
+ createDocumentFragment = _document.createDocumentFragment,
3456
+ getElementsByTagName = _document.getElementsByTagName;
3457
+ const importNode = originalDocument.importNode;
3296
3458
  let hooks = _createHooksMap();
3297
3459
  /**
3298
3460
  * Expose whether this browser supports running the full DOMPurify.
3299
3461
  */
3300
3462
  DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;
3301
- const {
3302
- MUSTACHE_EXPR,
3303
- ERB_EXPR,
3304
- TMPLIT_EXPR,
3305
- DATA_ATTR,
3306
- ARIA_ATTR,
3307
- IS_SCRIPT_OR_DATA,
3308
- ATTR_WHITESPACE,
3309
- CUSTOM_ELEMENT
3310
- } = EXPRESSIONS;
3311
- let {
3312
- IS_ALLOWED_URI: IS_ALLOWED_URI$1
3313
- } = EXPRESSIONS;
3463
+ const MUSTACHE_EXPR$1 = MUSTACHE_EXPR,
3464
+ ERB_EXPR$1 = ERB_EXPR,
3465
+ TMPLIT_EXPR$1 = TMPLIT_EXPR,
3466
+ DATA_ATTR$1 = DATA_ATTR,
3467
+ ARIA_ATTR$1 = ARIA_ATTR,
3468
+ IS_SCRIPT_OR_DATA$1 = IS_SCRIPT_OR_DATA,
3469
+ ATTR_WHITESPACE$1 = ATTR_WHITESPACE,
3470
+ CUSTOM_ELEMENT$1 = CUSTOM_ELEMENT;
3471
+ let IS_ALLOWED_URI$1 = IS_ALLOWED_URI;
3314
3472
  /**
3315
3473
  * We consider the elements and attributes below to be safe. Ideally
3316
3474
  * don't add any new ones but feel free to remove unwanted ones.
@@ -3387,6 +3545,13 @@ function createDOMPurify() {
3387
3545
  let WHOLE_DOCUMENT = false;
3388
3546
  /* Track whether config is already set on this instance of DOMPurify. */
3389
3547
  let SET_CONFIG = false;
3548
+ /* Pristine allowlist bindings captured at setConfig() time. On the
3549
+ * persistent-config path sanitize() restores the sets from these before
3550
+ * the per-walk hook clone-guard, so a hook's in-call widening cannot
3551
+ * carry across calls. Null until setConfig() is called; reset by
3552
+ * clearConfig(). */
3553
+ let SET_CONFIG_ALLOWED_TAGS = null;
3554
+ let SET_CONFIG_ALLOWED_ATTR = null;
3390
3555
  /* Decide if all elements (e.g. style, script) must be children of
3391
3556
  * document.body. By default, browsers might move them to document.head */
3392
3557
  let FORCE_BODY = false;
@@ -3429,7 +3594,17 @@ function createDOMPurify() {
3429
3594
  let USE_PROFILES = {};
3430
3595
  /* Tags to ignore content of when KEEP_CONTENT is true */
3431
3596
  let FORBID_CONTENTS = null;
3432
- const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);
3597
+ const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script',
3598
+ // <selectedcontent> mirrors the selected <option>'s subtree, cloned by
3599
+ // the UA (customizable <select>) — including any on* handlers — and the
3600
+ // engine re-mirrors synchronously whenever a removal changes which
3601
+ // option/selectedcontent is current, even inside DOMPurify's inert
3602
+ // DOMParser document. Hoisting its children on removal re-inserts a fresh
3603
+ // mirror target ahead of the walk, which the engine refills, looping
3604
+ // forever (DoS) and amplifying output. Dropping its content on removal
3605
+ // (rather than hoisting) breaks that cascade; the content is a duplicate
3606
+ // of the option, which is sanitized on its own. See campaign-3 F1/F6.
3607
+ 'selectedcontent', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);
3433
3608
  /* Tags that are safe for data: URIs */
3434
3609
  let DATA_URI_TAGS = null;
3435
3610
  const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
@@ -3445,8 +3620,10 @@ function createDOMPurify() {
3445
3620
  /* Allowed XHTML+XML namespaces */
3446
3621
  let ALLOWED_NAMESPACES = null;
3447
3622
  const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);
3448
- let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
3449
- let HTML_INTEGRATION_POINTS = addToSet({}, ['annotation-xml']);
3623
+ const DEFAULT_MATHML_TEXT_INTEGRATION_POINTS = freeze(['mi', 'mo', 'mn', 'ms', 'mtext']);
3624
+ let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, DEFAULT_MATHML_TEXT_INTEGRATION_POINTS);
3625
+ const DEFAULT_HTML_INTEGRATION_POINTS = freeze(['annotation-xml']);
3626
+ let HTML_INTEGRATION_POINTS = addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS);
3450
3627
  // Certain elements are allowed in both SVG and HTML
3451
3628
  // namespace. We need to specify them explicitly
3452
3629
  // so that they don't get erroneously deleted from
@@ -3488,15 +3665,33 @@ function createDOMPurify() {
3488
3665
  // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
3489
3666
  transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
3490
3667
  /* Set configuration parameters */
3491
- ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
3492
- ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
3493
- ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
3494
- URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR, transformCaseFunc) : DEFAULT_URI_SAFE_ATTRIBUTES;
3495
- DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS, transformCaseFunc) : DEFAULT_DATA_URI_TAGS;
3496
- FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
3497
- FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : clone({});
3498
- FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : clone({});
3499
- USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES : false;
3668
+ ALLOWED_TAGS = _resolveSetOption(cfg, 'ALLOWED_TAGS', DEFAULT_ALLOWED_TAGS, {
3669
+ transform: transformCaseFunc
3670
+ });
3671
+ ALLOWED_ATTR = _resolveSetOption(cfg, 'ALLOWED_ATTR', DEFAULT_ALLOWED_ATTR, {
3672
+ transform: transformCaseFunc
3673
+ });
3674
+ ALLOWED_NAMESPACES = _resolveSetOption(cfg, 'ALLOWED_NAMESPACES', DEFAULT_ALLOWED_NAMESPACES, {
3675
+ transform: stringToString
3676
+ });
3677
+ URI_SAFE_ATTRIBUTES = _resolveSetOption(cfg, 'ADD_URI_SAFE_ATTR', DEFAULT_URI_SAFE_ATTRIBUTES, {
3678
+ transform: transformCaseFunc,
3679
+ base: DEFAULT_URI_SAFE_ATTRIBUTES
3680
+ });
3681
+ DATA_URI_TAGS = _resolveSetOption(cfg, 'ADD_DATA_URI_TAGS', DEFAULT_DATA_URI_TAGS, {
3682
+ transform: transformCaseFunc,
3683
+ base: DEFAULT_DATA_URI_TAGS
3684
+ });
3685
+ FORBID_CONTENTS = _resolveSetOption(cfg, 'FORBID_CONTENTS', DEFAULT_FORBID_CONTENTS, {
3686
+ transform: transformCaseFunc
3687
+ });
3688
+ FORBID_TAGS = _resolveSetOption(cfg, 'FORBID_TAGS', clone({}), {
3689
+ transform: transformCaseFunc
3690
+ });
3691
+ FORBID_ATTR = _resolveSetOption(cfg, 'FORBID_ATTR', clone({}), {
3692
+ transform: transformCaseFunc
3693
+ });
3694
+ USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES && typeof cfg.USE_PROFILES === 'object' ? clone(cfg.USE_PROFILES) : cfg.USE_PROFILES : false;
3500
3695
  ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
3501
3696
  ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
3502
3697
  ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
@@ -3512,20 +3707,22 @@ function createDOMPurify() {
3512
3707
  SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false
3513
3708
  KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
3514
3709
  IN_PLACE = cfg.IN_PLACE || false; // Default false
3515
- IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI;
3516
- NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
3517
- MATHML_TEXT_INTEGRATION_POINTS = cfg.MATHML_TEXT_INTEGRATION_POINTS || MATHML_TEXT_INTEGRATION_POINTS;
3518
- HTML_INTEGRATION_POINTS = cfg.HTML_INTEGRATION_POINTS || HTML_INTEGRATION_POINTS;
3519
- CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || create(null);
3520
- if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {
3521
- CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;
3522
- }
3523
- if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) {
3524
- CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;
3525
- }
3526
- if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') {
3527
- CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;
3528
- }
3710
+ IS_ALLOWED_URI$1 = isRegex(cfg.ALLOWED_URI_REGEXP) ? cfg.ALLOWED_URI_REGEXP : IS_ALLOWED_URI; // Default regexp
3711
+ NAMESPACE = typeof cfg.NAMESPACE === 'string' ? cfg.NAMESPACE : HTML_NAMESPACE; // Default HTML namespace
3712
+ MATHML_TEXT_INTEGRATION_POINTS = objectHasOwnProperty(cfg, 'MATHML_TEXT_INTEGRATION_POINTS') && cfg.MATHML_TEXT_INTEGRATION_POINTS && typeof cfg.MATHML_TEXT_INTEGRATION_POINTS === 'object' ? clone(cfg.MATHML_TEXT_INTEGRATION_POINTS) : addToSet({}, DEFAULT_MATHML_TEXT_INTEGRATION_POINTS); // Default built-in map
3713
+ HTML_INTEGRATION_POINTS = objectHasOwnProperty(cfg, 'HTML_INTEGRATION_POINTS') && cfg.HTML_INTEGRATION_POINTS && typeof cfg.HTML_INTEGRATION_POINTS === 'object' ? clone(cfg.HTML_INTEGRATION_POINTS) : addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS); // Default built-in map
3714
+ const customElementHandling = objectHasOwnProperty(cfg, 'CUSTOM_ELEMENT_HANDLING') && cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING === 'object' ? clone(cfg.CUSTOM_ELEMENT_HANDLING) : create(null);
3715
+ CUSTOM_ELEMENT_HANDLING = create(null);
3716
+ if (objectHasOwnProperty(customElementHandling, 'tagNameCheck') && isRegexOrFunction(customElementHandling.tagNameCheck)) {
3717
+ CUSTOM_ELEMENT_HANDLING.tagNameCheck = customElementHandling.tagNameCheck; // Default undefined
3718
+ }
3719
+ if (objectHasOwnProperty(customElementHandling, 'attributeNameCheck') && isRegexOrFunction(customElementHandling.attributeNameCheck)) {
3720
+ CUSTOM_ELEMENT_HANDLING.attributeNameCheck = customElementHandling.attributeNameCheck; // Default undefined
3721
+ }
3722
+ if (objectHasOwnProperty(customElementHandling, 'allowCustomizedBuiltInElements') && typeof customElementHandling.allowCustomizedBuiltInElements === 'boolean') {
3723
+ CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = customElementHandling.allowCustomizedBuiltInElements; // Default undefined
3724
+ }
3725
+ seal(CUSTOM_ELEMENT_HANDLING);
3529
3726
  if (SAFE_FOR_TEMPLATES) {
3530
3727
  ALLOW_DATA_ATTR = false;
3531
3728
  }
@@ -3561,36 +3758,36 @@ function createDOMPurify() {
3561
3758
  EXTRA_ELEMENT_HANDLING.tagCheck = null;
3562
3759
  EXTRA_ELEMENT_HANDLING.attributeCheck = null;
3563
3760
  /* Merge configuration parameters */
3564
- if (cfg.ADD_TAGS) {
3761
+ if (objectHasOwnProperty(cfg, 'ADD_TAGS')) {
3565
3762
  if (typeof cfg.ADD_TAGS === 'function') {
3566
3763
  EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS;
3567
- } else {
3764
+ } else if (arrayIsArray(cfg.ADD_TAGS)) {
3568
3765
  if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
3569
3766
  ALLOWED_TAGS = clone(ALLOWED_TAGS);
3570
3767
  }
3571
3768
  addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
3572
3769
  }
3573
3770
  }
3574
- if (cfg.ADD_ATTR) {
3771
+ if (objectHasOwnProperty(cfg, 'ADD_ATTR')) {
3575
3772
  if (typeof cfg.ADD_ATTR === 'function') {
3576
3773
  EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR;
3577
- } else {
3774
+ } else if (arrayIsArray(cfg.ADD_ATTR)) {
3578
3775
  if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
3579
3776
  ALLOWED_ATTR = clone(ALLOWED_ATTR);
3580
3777
  }
3581
3778
  addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
3582
3779
  }
3583
3780
  }
3584
- if (cfg.ADD_URI_SAFE_ATTR) {
3781
+ if (objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') && arrayIsArray(cfg.ADD_URI_SAFE_ATTR)) {
3585
3782
  addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
3586
3783
  }
3587
- if (cfg.FORBID_CONTENTS) {
3784
+ if (objectHasOwnProperty(cfg, 'FORBID_CONTENTS') && arrayIsArray(cfg.FORBID_CONTENTS)) {
3588
3785
  if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
3589
3786
  FORBID_CONTENTS = clone(FORBID_CONTENTS);
3590
3787
  }
3591
3788
  addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
3592
3789
  }
3593
- if (cfg.ADD_FORBID_CONTENTS) {
3790
+ if (objectHasOwnProperty(cfg, 'ADD_FORBID_CONTENTS') && arrayIsArray(cfg.ADD_FORBID_CONTENTS)) {
3594
3791
  if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
3595
3792
  FORBID_CONTENTS = clone(FORBID_CONTENTS);
3596
3793
  }
@@ -3609,6 +3806,13 @@ function createDOMPurify() {
3609
3806
  addToSet(ALLOWED_TAGS, ['tbody']);
3610
3807
  delete FORBID_TAGS.tbody;
3611
3808
  }
3809
+ // Re-derive the active Trusted Types policy from this configuration on
3810
+ // every parse. The active policy must never be sticky closure state that
3811
+ // outlives the config that set it: a caller-supplied policy left in place
3812
+ // after `clearConfig()` — or after a later call that supplied none, or
3813
+ // `TRUSTED_TYPES_POLICY: null` — could sign a subsequent "default"
3814
+ // `RETURN_TRUSTED_TYPE` result with a foreign, possibly unsafe policy.
3815
+ // See GHSA-vxr8-fq34-vvx9.
3612
3816
  if (cfg.TRUSTED_TYPES_POLICY) {
3613
3817
  if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') {
3614
3818
  throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');
@@ -3616,18 +3820,45 @@ function createDOMPurify() {
3616
3820
  if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {
3617
3821
  throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
3618
3822
  }
3619
- // Overwrite existing TrustedTypes policy.
3823
+ // A caller-supplied policy applies to this configuration only.
3824
+ const previousTrustedTypesPolicy = trustedTypesPolicy;
3620
3825
  trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;
3621
- // Sign local variables required by `sanitize`.
3622
- emptyHTML = trustedTypesPolicy.createHTML('');
3826
+ // Sign local variables required by `sanitize`. If the supplied policy's
3827
+ // `createHTML` is circular (i.e. it calls `DOMPurify.sanitize`), this
3828
+ // throws via the re-entrancy guard. Restore the previous policy first so
3829
+ // the instance is not left in a poisoned state. See #1422.
3830
+ try {
3831
+ emptyHTML = _createTrustedHTML('');
3832
+ } catch (error) {
3833
+ trustedTypesPolicy = previousTrustedTypesPolicy;
3834
+ throw error;
3835
+ }
3836
+ } else if (cfg.TRUSTED_TYPES_POLICY === null) {
3837
+ // Explicit opt-out for this call: perform no Trusted Types signing and
3838
+ // create nothing (so a strict `trusted-types` CSP that disallows a
3839
+ // `dompurify` policy can still call `sanitize` from inside its own
3840
+ // policy — see #1422). Resetting to `undefined` rather than a sticky
3841
+ // `null` also drops any previously retained caller policy, so it cannot
3842
+ // resurface on a later call, while still allowing the next config-less
3843
+ // call to restore the internal default policy. See GHSA-vxr8-fq34-vvx9.
3844
+ trustedTypesPolicy = undefined;
3845
+ emptyHTML = '';
3623
3846
  } else {
3624
- // Uninitialized policy, attempt to initialize the internal dompurify policy.
3847
+ // No policy supplied: keep the currently active policy if one is set — a
3848
+ // previously supplied policy is intentionally sticky across config-less
3849
+ // calls — otherwise fall back to the instance's own internal policy,
3850
+ // created at most once. (A policy supplied for a *single* call still
3851
+ // lingers by design; what must not linger is a policy whose configuration
3852
+ // has been torn down via `clearConfig()`, which restores the default.)
3625
3853
  if (trustedTypesPolicy === undefined) {
3626
- trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);
3854
+ trustedTypesPolicy = _getDefaultTrustedTypesPolicy();
3627
3855
  }
3628
- // If creating the internal policy succeeded sign internal variables.
3629
- if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') {
3630
- emptyHTML = trustedTypesPolicy.createHTML('');
3856
+ // Sign internal variables only when a policy is active. A falsy policy
3857
+ // (Trusted Types unsupported, creation failed, or an explicit opt-out)
3858
+ // leaves `emptyHTML` as a plain string, so we never call `.createHTML` on
3859
+ // a non-policy and throw. See #1422.
3860
+ if (trustedTypesPolicy && typeof emptyHTML === 'string') {
3861
+ emptyHTML = _createTrustedHTML('');
3631
3862
  }
3632
3863
  }
3633
3864
  // Prevent further manipulation of configuration.
@@ -3642,6 +3873,77 @@ function createDOMPurify() {
3642
3873
  * correctly. */
3643
3874
  const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);
3644
3875
  const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);
3876
+ /**
3877
+ * Namespace rules for an element in the SVG namespace.
3878
+ *
3879
+ * @param tagName the element's lowercase tag name
3880
+ * @param parent the (possibly simulated) parent node
3881
+ * @param parentTagName the parent's lowercase tag name
3882
+ * @returns true if a spec-compliant parser could produce this element
3883
+ */
3884
+ const _checkSvgNamespace = function _checkSvgNamespace(tagName, parent, parentTagName) {
3885
+ // The only way to switch from HTML namespace to SVG
3886
+ // is via <svg>. If it happens via any other tag, then
3887
+ // it should be killed.
3888
+ if (parent.namespaceURI === HTML_NAMESPACE) {
3889
+ return tagName === 'svg';
3890
+ }
3891
+ // The only way to switch from MathML to SVG is via <svg>
3892
+ // if the parent is either <annotation-xml> or a MathML
3893
+ // text integration point.
3894
+ if (parent.namespaceURI === MATHML_NAMESPACE) {
3895
+ return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
3896
+ }
3897
+ // We only allow elements that are defined in SVG
3898
+ // spec. All others are disallowed in SVG namespace.
3899
+ return Boolean(ALL_SVG_TAGS[tagName]);
3900
+ };
3901
+ /**
3902
+ * Namespace rules for an element in the MathML namespace.
3903
+ *
3904
+ * @param tagName the element's lowercase tag name
3905
+ * @param parent the (possibly simulated) parent node
3906
+ * @param parentTagName the parent's lowercase tag name
3907
+ * @returns true if a spec-compliant parser could produce this element
3908
+ */
3909
+ const _checkMathMlNamespace = function _checkMathMlNamespace(tagName, parent, parentTagName) {
3910
+ // The only way to switch from HTML namespace to MathML
3911
+ // is via <math>. If it happens via any other tag, then
3912
+ // it should be killed.
3913
+ if (parent.namespaceURI === HTML_NAMESPACE) {
3914
+ return tagName === 'math';
3915
+ }
3916
+ // The only way to switch from SVG to MathML is via
3917
+ // <math> and HTML integration points
3918
+ if (parent.namespaceURI === SVG_NAMESPACE) {
3919
+ return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
3920
+ }
3921
+ // We only allow elements that are defined in MathML
3922
+ // spec. All others are disallowed in MathML namespace.
3923
+ return Boolean(ALL_MATHML_TAGS[tagName]);
3924
+ };
3925
+ /**
3926
+ * Namespace rules for an element in the HTML namespace.
3927
+ *
3928
+ * @param tagName the element's lowercase tag name
3929
+ * @param parent the (possibly simulated) parent node
3930
+ * @param parentTagName the parent's lowercase tag name
3931
+ * @returns true if a spec-compliant parser could produce this element
3932
+ */
3933
+ const _checkHtmlNamespace = function _checkHtmlNamespace(tagName, parent, parentTagName) {
3934
+ // The only way to switch from SVG to HTML is via
3935
+ // HTML integration points, and from MathML to HTML
3936
+ // is via MathML text integration points
3937
+ if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
3938
+ return false;
3939
+ }
3940
+ if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
3941
+ return false;
3942
+ }
3943
+ // We disallow tags that are specific for MathML
3944
+ // or SVG and should never appear in HTML namespace
3945
+ return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
3946
+ };
3645
3947
  /**
3646
3948
  * @param element a DOM element whose namespace is being checked
3647
3949
  * @returns Return false if the element has a
@@ -3664,51 +3966,13 @@ function createDOMPurify() {
3664
3966
  return false;
3665
3967
  }
3666
3968
  if (element.namespaceURI === SVG_NAMESPACE) {
3667
- // The only way to switch from HTML namespace to SVG
3668
- // is via <svg>. If it happens via any other tag, then
3669
- // it should be killed.
3670
- if (parent.namespaceURI === HTML_NAMESPACE) {
3671
- return tagName === 'svg';
3672
- }
3673
- // The only way to switch from MathML to SVG is via`
3674
- // svg if parent is either <annotation-xml> or MathML
3675
- // text integration points.
3676
- if (parent.namespaceURI === MATHML_NAMESPACE) {
3677
- return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
3678
- }
3679
- // We only allow elements that are defined in SVG
3680
- // spec. All others are disallowed in SVG namespace.
3681
- return Boolean(ALL_SVG_TAGS[tagName]);
3969
+ return _checkSvgNamespace(tagName, parent, parentTagName);
3682
3970
  }
3683
3971
  if (element.namespaceURI === MATHML_NAMESPACE) {
3684
- // The only way to switch from HTML namespace to MathML
3685
- // is via <math>. If it happens via any other tag, then
3686
- // it should be killed.
3687
- if (parent.namespaceURI === HTML_NAMESPACE) {
3688
- return tagName === 'math';
3689
- }
3690
- // The only way to switch from SVG to MathML is via
3691
- // <math> and HTML integration points
3692
- if (parent.namespaceURI === SVG_NAMESPACE) {
3693
- return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
3694
- }
3695
- // We only allow elements that are defined in MathML
3696
- // spec. All others are disallowed in MathML namespace.
3697
- return Boolean(ALL_MATHML_TAGS[tagName]);
3972
+ return _checkMathMlNamespace(tagName, parent, parentTagName);
3698
3973
  }
3699
3974
  if (element.namespaceURI === HTML_NAMESPACE) {
3700
- // The only way to switch from SVG to HTML is via
3701
- // HTML integration points, and from MathML to HTML
3702
- // is via MathML text integration points
3703
- if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
3704
- return false;
3705
- }
3706
- if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
3707
- return false;
3708
- }
3709
- // We disallow tags that are specific for MathML
3710
- // or SVG and should never appear in HTML namespace
3711
- return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
3975
+ return _checkHtmlNamespace(tagName, parent, parentTagName);
3712
3976
  }
3713
3977
  // For XHTML and XML documents that support custom namespaces
3714
3978
  if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {
@@ -3733,7 +3997,74 @@ function createDOMPurify() {
3733
3997
  // eslint-disable-next-line unicorn/prefer-dom-node-remove
3734
3998
  getParentNode(node).removeChild(node);
3735
3999
  } catch (_) {
4000
+ /* The normal detach failed — this is reached for a parentless node
4001
+ (getParentNode() is null, so .removeChild throws). Element.prototype
4002
+ .remove() is itself a spec no-op on a parentless node, so a recorded
4003
+ "removal" would otherwise hand the caller back an intact,
4004
+ payload-bearing node (e.g. a detached IN_PLACE root the mXSS canary or
4005
+ the style-with-element-child rule decided to kill). Fail closed by
4006
+ throwing — exactly as a clobbered root does at the IN_PLACE entry —
4007
+ rather than trying to "neutralize" the node via its own methods.
4008
+ Neutralizing would mean calling getAttributeNames()/removeAttribute()
4009
+ on the node, both of which a <form> root can clobber via a named child
4010
+ (and _isClobbered does not even probe getAttributeNames), so the
4011
+ neutralize step could itself be silently defeated, leaving the payload
4012
+ intact. A throw touches only the cached, clobber-safe remove() and
4013
+ getParentNode(). Generalizes GHSA-r47g-fvhr-h676 (clobbered-form root)
4014
+ to every root-kill reason. REPORT-3.
4015
+ This lives inside the catch, so it never fires for a normally-removed
4016
+ in-tree node: those have a parent, removeChild() succeeds, and the
4017
+ catch is not entered. Only a kept (parentless) root reaches here. */
3736
4018
  remove(node);
4019
+ if (!getParentNode(node)) {
4020
+ throw typeErrorCreate('a node selected for removal could not be detached from its tree ' + 'and cannot be safely returned; refusing to sanitize in place');
4021
+ }
4022
+ }
4023
+ };
4024
+ /**
4025
+ * _neutralizeRoot
4026
+ *
4027
+ * Fail-closed teardown of an in-place root after the sanitize walk aborts
4028
+ * (campaign-3 F2). An internal throw mid-walk — e.g. a page-registered
4029
+ * custom element's reaction detaches a node so `_forceRemove`'s deliberate
4030
+ * parentless guard throws, or any other re-entrant engine mutation — would
4031
+ * otherwise leave the caller's *live* tree half-sanitized, with everything
4032
+ * after the abort point still carrying its handlers. There is no safe way
4033
+ * to resume the walk (the tree mutated under us), so we strip the root bare:
4034
+ * remove every child and every attribute, then let the caller's catch see
4035
+ * the original error. Clobber-safe (cached `remove`/`childNodes`/`attributes`
4036
+ * getters; the root was already clobber-pre-flighted at the IN_PLACE entry).
4037
+ *
4038
+ * @param root the in-place root to empty
4039
+ */
4040
+ const _neutralizeRoot = function _neutralizeRoot(root) {
4041
+ const childNodes = getChildNodes(root);
4042
+ if (childNodes) {
4043
+ const snapshot = [];
4044
+ arrayForEach(childNodes, child => {
4045
+ arrayPush(snapshot, child);
4046
+ });
4047
+ arrayForEach(snapshot, child => {
4048
+ try {
4049
+ remove(child);
4050
+ } catch (_) {
4051
+ /* Best-effort teardown; a still-attached child is handled below */
4052
+ }
4053
+ });
4054
+ }
4055
+ const attributes = getAttributes(root);
4056
+ if (attributes) {
4057
+ for (let i = attributes.length - 1; i >= 0; --i) {
4058
+ const attribute = attributes[i];
4059
+ const name = attribute && attribute.name;
4060
+ if (typeof name === 'string') {
4061
+ try {
4062
+ root.removeAttribute(name);
4063
+ } catch (_) {
4064
+ /* Clobbered removeAttribute — ignore (fail-closed best effort) */
4065
+ }
4066
+ }
4067
+ }
3737
4068
  }
3738
4069
  };
3739
4070
  /**
@@ -3768,6 +4099,72 @@ function createDOMPurify() {
3768
4099
  }
3769
4100
  }
3770
4101
  };
4102
+ /**
4103
+ * _stripDisallowedAttributes
4104
+ *
4105
+ * Removes every attribute the active configuration does not allow from a
4106
+ * single element, using the same allowlist as the main attribute pass (so
4107
+ * `on*` handlers go, but no `/^on/` blocklist is introduced). Used only to
4108
+ * neutralise nodes that are being discarded from an in-place tree.
4109
+ *
4110
+ * @param element the element to strip
4111
+ */
4112
+ const _stripDisallowedAttributes = function _stripDisallowedAttributes(element) {
4113
+ const attributes = getAttributes(element);
4114
+ if (!attributes) {
4115
+ return;
4116
+ }
4117
+ for (let i = attributes.length - 1; i >= 0; --i) {
4118
+ const attribute = attributes[i];
4119
+ const name = attribute && attribute.name;
4120
+ if (typeof name !== 'string' || ALLOWED_ATTR[transformCaseFunc(name)]) {
4121
+ continue;
4122
+ }
4123
+ try {
4124
+ element.removeAttribute(name);
4125
+ } catch (_) {
4126
+ /* Clobbered removeAttribute on a doomed node — ignore */
4127
+ }
4128
+ }
4129
+ };
4130
+ /**
4131
+ * _neutralizeSubtree
4132
+ *
4133
+ * Completes the audit-5 F1 fix across every removal path. The KEEP_CONTENT
4134
+ * move-hoist neutralises only disallowed-tag removals; clobber, mXSS-canary,
4135
+ * namespace, comment, processing-instruction and KEEP_CONTENT:false removals
4136
+ * all drop their subtree wholesale via `_forceRemove`. On the IN_PLACE path
4137
+ * those dropped nodes are detached from the caller's LIVE tree but a
4138
+ * handler-bearing original among them (an `<img onerror>`/`<video>` that was
4139
+ * loading) keeps its queued resource event, which fires in page scope after
4140
+ * sanitize returns. This walks a removed subtree and strips every attribute
4141
+ * the active configuration does not allow — so `on*` handlers are cancelled
4142
+ * through the SAME allowlist that governs kept nodes, not a separate `/^on/`
4143
+ * blocklist. Run synchronously before sanitize returns, i.e. before any
4144
+ * queued event can fire. Hook-free by design: these nodes leave the output,
4145
+ * so firing attribute hooks for them would be surprising. Clobber-safe reads;
4146
+ * a doomed clobbered node may shadow `removeAttribute` (its own attributes are
4147
+ * irrelevant — it is discarded — while its non-clobbered descendants, e.g.
4148
+ * the `<img>`, are reached and scrubbed).
4149
+ *
4150
+ * @param root the root of a removed subtree to neutralise
4151
+ */
4152
+ const _neutralizeSubtree = function _neutralizeSubtree(root) {
4153
+ const stack = [root];
4154
+ while (stack.length > 0) {
4155
+ const node = stack.pop();
4156
+ const nodeType = getNodeType ? getNodeType(node) : node.nodeType;
4157
+ if (nodeType === NODE_TYPE.element) {
4158
+ _stripDisallowedAttributes(node);
4159
+ }
4160
+ const childNodes = getChildNodes(node);
4161
+ if (childNodes) {
4162
+ for (let i = childNodes.length - 1; i >= 0; --i) {
4163
+ stack.push(childNodes[i]);
4164
+ }
4165
+ }
4166
+ }
4167
+ };
3771
4168
  /**
3772
4169
  * _initDocument
3773
4170
  *
@@ -3789,7 +4186,7 @@ function createDOMPurify() {
3789
4186
  // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
3790
4187
  dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
3791
4188
  }
3792
- const dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
4189
+ const dirtyPayload = trustedTypesPolicy ? _createTrustedHTML(dirty) : dirty;
3793
4190
  /*
3794
4191
  * Use the DOMParser API by default, fallback later if needs be
3795
4192
  * DOMParser not work for svg when has multiple root element.
@@ -3829,29 +4226,254 @@ function createDOMPurify() {
3829
4226
  // eslint-disable-next-line no-bitwise
3830
4227
  NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
3831
4228
  };
4229
+ /**
4230
+ * Replace template expression syntax (mustache, ERB, template
4231
+ * literal) with a space; shared by all SAFE_FOR_TEMPLATES scrub
4232
+ * sites. Order matters: mustache, then ERB, then template literal.
4233
+ *
4234
+ * @param value the string to scrub
4235
+ * @returns the scrubbed string
4236
+ */
4237
+ const _stripTemplateExpressions = function _stripTemplateExpressions(value) {
4238
+ value = stringReplace(value, MUSTACHE_EXPR$1, ' ');
4239
+ value = stringReplace(value, ERB_EXPR$1, ' ');
4240
+ value = stringReplace(value, TMPLIT_EXPR$1, ' ');
4241
+ return value;
4242
+ };
4243
+ /**
4244
+ * Strip template-engine expressions ({{...}}, ${...}, <%...%>) from the
4245
+ * character data of an element subtree. Used as the final safety net for
4246
+ * SAFE_FOR_TEMPLATES on every DOM-returning code path so that expressions
4247
+ * which only form after text-node normalization (e.g. fragments split across
4248
+ * stripped elements) cannot survive into a template-evaluating framework.
4249
+ *
4250
+ * Walks text/comment/CDATA/processing-instruction nodes and mutates `.data`
4251
+ * in place rather than round-tripping through innerHTML. This preserves
4252
+ * descendant node references (important for IN_PLACE callers), avoids a
4253
+ * serialize/reparse cycle, and reads literal character data — which means
4254
+ * `<%...%>` in text content matches the ERB regex against its real bytes
4255
+ * instead of the HTML-entity-escaped form innerHTML would produce.
4256
+ *
4257
+ * Attribute values are not visited here; SAFE_FOR_TEMPLATES handling for
4258
+ * attributes is performed during the per-node `_sanitizeAttributes` pass.
4259
+ *
4260
+ * @param node The root element whose character data should be scrubbed.
4261
+ */
4262
+ const _scrubTemplateExpressions2 = function _scrubTemplateExpressions(node) {
4263
+ var _node$querySelectorAl;
4264
+ node.normalize();
4265
+ const walker = createNodeIterator.call(node.ownerDocument || node, node,
4266
+ // eslint-disable-next-line no-bitwise
4267
+ NodeFilter.SHOW_TEXT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_CDATA_SECTION | NodeFilter.SHOW_PROCESSING_INSTRUCTION, null);
4268
+ let currentNode = walker.nextNode();
4269
+ while (currentNode) {
4270
+ currentNode.data = _stripTemplateExpressions(currentNode.data);
4271
+ currentNode = walker.nextNode();
4272
+ }
4273
+ // NodeIterator does not descend into <template>.content per the DOM spec,
4274
+ // so we must explicitly recurse into each template's content fragment,
4275
+ // mirroring the approach used by _sanitizeShadowDOM.
4276
+ const templates = (_node$querySelectorAl = node.querySelectorAll) === null || _node$querySelectorAl === void 0 ? void 0 : _node$querySelectorAl.call(node, 'template');
4277
+ if (templates) {
4278
+ arrayForEach(templates, tmpl => {
4279
+ if (_isDocumentFragment(tmpl.content)) {
4280
+ _scrubTemplateExpressions2(tmpl.content);
4281
+ }
4282
+ });
4283
+ }
4284
+ };
3832
4285
  /**
3833
4286
  * _isClobbered
3834
4287
  *
4288
+ * Detect DOM-clobbering on HTMLFormElement nodes. Form is the only HTML
4289
+ * interface with [LegacyOverrideBuiltIns]; a descendant element with a
4290
+ * `name` attribute matching a prototype property shadows that property
4291
+ * on direct reads. We use this check at the IN_PLACE entry-point and
4292
+ * during attribute sanitization to refuse clobbered forms.
4293
+ *
3835
4294
  * @param element element to check for clobbering attacks
3836
4295
  * @return true if clobbered, false if safe
3837
4296
  */
3838
4297
  const _isClobbered = function _isClobbered(element) {
3839
- return element instanceof HTMLFormElement && (typeof element.nodeName !== 'string' || typeof element.textContent !== 'string' || typeof element.removeChild !== 'function' || !(element.attributes instanceof NamedNodeMap) || typeof element.removeAttribute !== 'function' || typeof element.setAttribute !== 'function' || typeof element.namespaceURI !== 'string' || typeof element.insertBefore !== 'function' || typeof element.hasChildNodes !== 'function');
4298
+ // Realm-independent tag-name probe. If we can't determine the tag
4299
+ // name at all, we can't reason about clobbering — return false
4300
+ // (the caller's other defences still apply).
4301
+ const realTagName = getNodeName ? getNodeName(element) : null;
4302
+ if (typeof realTagName !== 'string') {
4303
+ return false;
4304
+ }
4305
+ if (transformCaseFunc(realTagName) !== 'form') {
4306
+ return false;
4307
+ }
4308
+ return typeof element.nodeName !== 'string' || typeof element.textContent !== 'string' || typeof element.removeChild !== 'function' ||
4309
+ // Realm-safe NamedNodeMap detection: equality against the cached
4310
+ // prototype getter. Clobbered .attributes (e.g. <input name="attributes">)
4311
+ // makes the direct read diverge from the cached read; a clean form
4312
+ // (same-realm OR foreign-realm) has both reads pointing at the same
4313
+ // canonical NamedNodeMap.
4314
+ element.attributes !== getAttributes(element) || typeof element.removeAttribute !== 'function' || typeof element.setAttribute !== 'function' || typeof element.namespaceURI !== 'string' || typeof element.insertBefore !== 'function' || typeof element.hasChildNodes !== 'function' ||
4315
+ // NodeType clobbering probe. Cached Node.prototype.nodeType getter
4316
+ // returns the integer 1 for any Element regardless of realm; direct
4317
+ // read on a clobbered form (e.g. <input name="nodeType">) returns
4318
+ // the named child element. Cheap addition — nodeType is read from
4319
+ // an internal slot, no serialization cost — and removes a residual
4320
+ // clobbering surface used by several mXSS / PI / comment branches
4321
+ // in _sanitizeElements that compare currentNode.nodeType directly.
4322
+ element.nodeType !== getNodeType(element) ||
4323
+ // HTMLFormElement has [LegacyOverrideBuiltIns]: a descendant named
4324
+ // "childNodes" shadows the prototype getter. Direct reads of
4325
+ // form.childNodes from a clobbered form return the named child
4326
+ // instead of the real NodeList, so any walk that reads it directly
4327
+ // skips the form's real children. Compare the direct read to the
4328
+ // cached Node.prototype getter — when the form's named-property
4329
+ // getter intercepts the read, the two values differ and we flag
4330
+ // the form. This catches every clobbering child type (input,
4331
+ // select, etc.) regardless of whether the named child happens to
4332
+ // carry a numeric .length, which a typeof-based probe would miss
4333
+ // (e.g. HTMLSelectElement.length is a defined unsigned-long).
4334
+ element.childNodes !== getChildNodes(element);
3840
4335
  };
3841
4336
  /**
3842
- * Checks whether the given object is a DOM node.
4337
+ * Checks whether the given value is a DocumentFragment from any realm.
4338
+ *
4339
+ * The realm-independent replacement reads `nodeType` through the cached
4340
+ * Node.prototype getter and compares to the DOCUMENT_FRAGMENT_NODE
4341
+ * constant (11). nodeType is a numeric value resolved from the node's
4342
+ * internal slot, identical across realms for the same kind of node.
4343
+ *
4344
+ * @param value object to check
4345
+ * @return true if value is a DocumentFragment-shaped node from any realm
4346
+ */
4347
+ const _isDocumentFragment = function _isDocumentFragment(value) {
4348
+ if (!getNodeType || typeof value !== 'object' || value === null) {
4349
+ return false;
4350
+ }
4351
+ try {
4352
+ return getNodeType(value) === NODE_TYPE.documentFragment;
4353
+ } catch (_) {
4354
+ return false;
4355
+ }
4356
+ };
4357
+ /**
4358
+ * Checks whether the given object is a DOM node, including nodes that
4359
+ * originate from a different window/realm (e.g. an iframe's
4360
+ * contentDocument). The previous `value instanceof Node` check was
4361
+ * realm-bound: nodes from a different window failed it, causing
4362
+ * sanitize() to silently stringify them and reset IN_PLACE to false,
4363
+ * returning the original node unsanitized. See GHSA-4w3q-35jp-p934.
3843
4364
  *
3844
4365
  * @param value object to check whether it's a DOM node
3845
- * @return true is object is a DOM node
4366
+ * @return true if value is a DOM node from any realm
3846
4367
  */
3847
4368
  const _isNode = function _isNode(value) {
3848
- return typeof Node === 'function' && value instanceof Node;
4369
+ if (!getNodeType || typeof value !== 'object' || value === null) {
4370
+ return false;
4371
+ }
4372
+ try {
4373
+ return typeof getNodeType(value) === 'number';
4374
+ } catch (_) {
4375
+ return false;
4376
+ }
3849
4377
  };
3850
4378
  function _executeHooks(hooks, currentNode, data) {
4379
+ if (hooks.length === 0) {
4380
+ return;
4381
+ }
3851
4382
  arrayForEach(hooks, hook => {
3852
4383
  hook.call(DOMPurify, currentNode, data, CONFIG);
3853
4384
  });
3854
4385
  }
4386
+ /**
4387
+ * Structural-threat checks that condemn a node regardless of the
4388
+ * allowlists: mXSS via namespace confusion, risky CSS construction,
4389
+ * processing instructions, markup-bearing comments. Pure predicate;
4390
+ * the caller removes. Check order is load-bearing.
4391
+ *
4392
+ * @param currentNode the node to inspect
4393
+ * @param tagName the node's transformCaseFunc'd tag name
4394
+ * @return true if the node must be removed
4395
+ */
4396
+ const _isUnsafeNode = function _isUnsafeNode(currentNode, tagName) {
4397
+ /* Detect mXSS attempts abusing namespace confusion */
4398
+ if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.textContent) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.innerHTML)) {
4399
+ return true;
4400
+ }
4401
+ /* Remove risky CSS construction leading to mXSS */
4402
+ if (SAFE_FOR_XML && currentNode.namespaceURI === HTML_NAMESPACE && tagName === 'style' && _isNode(currentNode.firstElementChild)) {
4403
+ return true;
4404
+ }
4405
+ /* Remove any occurrence of processing instructions */
4406
+ if (currentNode.nodeType === NODE_TYPE.processingInstruction) {
4407
+ return true;
4408
+ }
4409
+ /* Remove any kind of possibly harmful comments */
4410
+ if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(COMMENT_MARKUP_PROBE, currentNode.data)) {
4411
+ return true;
4412
+ }
4413
+ return false;
4414
+ };
4415
+ /**
4416
+ * Handle a node whose tag is forbidden or not allowlisted: keep
4417
+ * allowed custom elements (false return exits _sanitizeElements
4418
+ * early - namespace/fallback checks and the afterSanitizeElements
4419
+ * hook are intentionally skipped for kept custom elements), else
4420
+ * hoist content per KEEP_CONTENT and remove.
4421
+ *
4422
+ * @param currentNode the disallowed node
4423
+ * @param tagName the node's transformCaseFunc'd tag name
4424
+ * @return true if the node was removed, false if kept
4425
+ */
4426
+ const _sanitizeDisallowedNode = function _sanitizeDisallowedNode(currentNode, tagName) {
4427
+ /* Check if we have a custom element to handle */
4428
+ if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
4429
+ if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
4430
+ return false;
4431
+ }
4432
+ if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) {
4433
+ return false;
4434
+ }
4435
+ }
4436
+ /* Keep content except for bad-listed elements.
4437
+ Use the cached prototype getters exclusively — the previous code
4438
+ had `|| currentNode.parentNode` / `|| currentNode.childNodes`
4439
+ fallbacks, but the cached getters always return the canonical
4440
+ value (or null for a real parent-less node), so the fallback
4441
+ path was dead in safe cases and a clobbering surface in unsafe
4442
+ ones. Falsy cached results stay falsy; the `if (childNodes &&
4443
+ parentNode)` check already gates correctly. */
4444
+ if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
4445
+ const parentNode = getParentNode(currentNode);
4446
+ const childNodes = getChildNodes(currentNode);
4447
+ if (childNodes && parentNode) {
4448
+ const childCount = childNodes.length;
4449
+ /* In-place: hoist the *original* children so the iterator visits
4450
+ and sanitises them through the same allowlist pass as every other
4451
+ node. The caller built the tree in the live document, so the
4452
+ originals carry already-queued resource events (`<img onerror>`,
4453
+ `<video>`/`<audio>` error, lazy/`onload`, …); cloning would leave
4454
+ those originals detached but still armed, firing in page scope
4455
+ while the returned tree looked clean. Moving is safe in-place: the
4456
+ root is pre-validated as an allowed tag and so is never the node
4457
+ being removed, which keeps `parentNode` inside the iterator root
4458
+ and the relocated child inside the serialised tree.
4459
+ Otherwise (string / DOM-copy paths): clone. The iterator is rooted
4460
+ at — and the result serialised from — `body`, so a restrictive
4461
+ ALLOWED_TAGS that removes `body` itself must leave its content in
4462
+ place, which only cloning does; and those paths parse into an
4463
+ inert document, so their discarded originals never had a queued
4464
+ event to neutralise.
4465
+ `childNodes` is live; a tail-to-head walk keeps `childNodes[i]`
4466
+ valid whether we move (drops the trailing entry) or clone (leaves
4467
+ the list intact). */
4468
+ for (let i = childCount - 1; i >= 0; --i) {
4469
+ const hoisted = IN_PLACE ? childNodes[i] : cloneNode(childNodes[i], true);
4470
+ parentNode.insertBefore(hoisted, getNextSibling(currentNode));
4471
+ }
4472
+ }
4473
+ }
4474
+ _forceRemove(currentNode);
4475
+ return true;
4476
+ };
3855
4477
  /**
3856
4478
  * _sanitizeElements
3857
4479
  *
@@ -3862,7 +4484,6 @@ function createDOMPurify() {
3862
4484
  * @return true if node was killed, false if left alive
3863
4485
  */
3864
4486
  const _sanitizeElements = function _sanitizeElements(currentNode) {
3865
- let content = null;
3866
4487
  /* Execute a hook if present */
3867
4488
  _executeHooks(hooks.beforeSanitizeElements, currentNode, null);
3868
4489
  /* Check if element is clobbered or can clobber */
@@ -3871,76 +4492,41 @@ function createDOMPurify() {
3871
4492
  return true;
3872
4493
  }
3873
4494
  /* Now let's check the element's type and name */
3874
- const tagName = transformCaseFunc(currentNode.nodeName);
4495
+ const tagName = transformCaseFunc(getNodeName ? getNodeName(currentNode) : currentNode.nodeName);
3875
4496
  /* Execute a hook if present */
3876
4497
  _executeHooks(hooks.uponSanitizeElement, currentNode, {
3877
4498
  tagName,
3878
4499
  allowedTags: ALLOWED_TAGS
3879
4500
  });
3880
- /* Detect mXSS attempts abusing namespace confusion */
3881
- if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w!]/g, currentNode.innerHTML) && regExpTest(/<[/\w!]/g, currentNode.textContent)) {
3882
- _forceRemove(currentNode);
3883
- return true;
3884
- }
3885
- /* Remove risky CSS construction leading to mXSS */
3886
- if (SAFE_FOR_XML && currentNode.namespaceURI === HTML_NAMESPACE && tagName === 'style' && _isNode(currentNode.firstElementChild)) {
3887
- _forceRemove(currentNode);
3888
- return true;
3889
- }
3890
- /* Remove any occurrence of processing instructions */
3891
- if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {
3892
- _forceRemove(currentNode);
3893
- return true;
3894
- }
3895
- /* Remove any kind of possibly harmful comments */
3896
- if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\w]/g, currentNode.data)) {
4501
+ /* Remove mXSS vectors, processing instructions and risky comments */
4502
+ if (_isUnsafeNode(currentNode, tagName)) {
3897
4503
  _forceRemove(currentNode);
3898
4504
  return true;
3899
4505
  }
3900
4506
  /* Remove element if anything forbids its presence */
3901
4507
  if (FORBID_TAGS[tagName] || !(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && !ALLOWED_TAGS[tagName]) {
3902
- /* Check if we have a custom element to handle */
3903
- if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
3904
- if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
3905
- return false;
3906
- }
3907
- if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) {
3908
- return false;
3909
- }
3910
- }
3911
- /* Keep content except for bad-listed elements */
3912
- if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
3913
- const parentNode = getParentNode(currentNode) || currentNode.parentNode;
3914
- const childNodes = getChildNodes(currentNode) || currentNode.childNodes;
3915
- if (childNodes && parentNode) {
3916
- const childCount = childNodes.length;
3917
- for (let i = childCount - 1; i >= 0; --i) {
3918
- const childClone = cloneNode(childNodes[i], true);
3919
- childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
3920
- parentNode.insertBefore(childClone, getNextSibling(currentNode));
3921
- }
3922
- }
3923
- }
3924
- _forceRemove(currentNode);
3925
- return true;
3926
- }
3927
- /* Check whether element has a valid namespace */
3928
- if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
4508
+ return _sanitizeDisallowedNode(currentNode, tagName);
4509
+ }
4510
+ /* Check whether element has a valid namespace.
4511
+ Realm-safe check (GHSA-hpcv-96wg-7vj8): use the cached Node.prototype
4512
+ nodeType getter rather than `instanceof Element`, which is realm-
4513
+ bound and short-circuits to false for any node minted in a different
4514
+ realm — letting a foreign-realm element with a forbidden namespace
4515
+ slip past the namespace check entirely. */
4516
+ const nt = getNodeType ? getNodeType(currentNode) : currentNode.nodeType;
4517
+ if (nt === NODE_TYPE.element && !_checkValidNamespace(currentNode)) {
3929
4518
  _forceRemove(currentNode);
3930
4519
  return true;
3931
4520
  }
3932
4521
  /* Make sure that older browsers don't get fallback-tag mXSS */
3933
- if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML)) {
4522
+ if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(FALLBACK_TAG_CLOSE, currentNode.innerHTML)) {
3934
4523
  _forceRemove(currentNode);
3935
4524
  return true;
3936
4525
  }
3937
4526
  /* Sanitize element content to be template-safe */
3938
4527
  if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
3939
4528
  /* Get the element's text content */
3940
- content = currentNode.textContent;
3941
- arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
3942
- content = stringReplace(content, expr, ' ');
3943
- });
4529
+ const content = _stripTemplateExpressions(currentNode.textContent);
3944
4530
  if (currentNode.textContent !== content) {
3945
4531
  arrayPush(DOMPurify.removed, {
3946
4532
  element: currentNode.cloneNode()
@@ -3970,11 +4556,12 @@ function createDOMPurify() {
3970
4556
  if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
3971
4557
  return false;
3972
4558
  }
4559
+ const nameIsPermitted = ALLOWED_ATTR[lcName] || EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag);
3973
4560
  /* Allow valid data-* attributes: At least one character after "-"
3974
4561
  (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
3975
4562
  XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
3976
4563
  We don't need to check the value; it's always URI safe. */
3977
- if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) ; else if (EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag)) ; else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
4564
+ if (ALLOW_DATA_ATTR && regExpTest(DATA_ATTR$1, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$1, lcName)) ; else if (!nameIsPermitted) {
3978
4565
  if (
3979
4566
  // First condition does a very basic check if a) it's basically a valid custom element tagname AND
3980
4567
  // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
@@ -3986,11 +4573,15 @@ function createDOMPurify() {
3986
4573
  return false;
3987
4574
  }
3988
4575
  /* Check value is safe. First, is attr inert? If so, is safe */
3989
- } else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if (value) {
4576
+ } else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE$1, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA$1, stringReplace(value, ATTR_WHITESPACE$1, ''))) ; else if (value) {
3990
4577
  return false;
3991
4578
  } else ;
3992
4579
  return true;
3993
4580
  };
4581
+ /* Names the HTML spec reserves from valid-custom-element-name; these must
4582
+ * never be treated as basic custom elements even when a permissive
4583
+ * CUSTOM_ELEMENT_HANDLING.tagNameCheck is configured. */
4584
+ const RESERVED_CUSTOM_ELEMENT_NAMES = addToSet({}, ['annotation-xml', 'color-profile', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'missing-glyph']);
3994
4585
  /**
3995
4586
  * _isBasicCustomElement
3996
4587
  * checks if at least one dash is included in tagName, and it's not the first char
@@ -4000,7 +4591,64 @@ function createDOMPurify() {
4000
4591
  * @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
4001
4592
  */
4002
4593
  const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
4003
- return tagName !== 'annotation-xml' && stringMatch(tagName, CUSTOM_ELEMENT);
4594
+ return !RESERVED_CUSTOM_ELEMENT_NAMES[stringToLowerCase(tagName)] && regExpTest(CUSTOM_ELEMENT$1, tagName);
4595
+ };
4596
+ /**
4597
+ * Wrap an attribute value in the matching Trusted Types object when
4598
+ * the active policy requires it. Namespaced attributes pass through
4599
+ * unchanged (no TT support yet, see
4600
+ * https://bugs.chromium.org/p/chromium/issues/detail?id=1305293).
4601
+ *
4602
+ * @param lcTag lowercase tag name of the containing element
4603
+ * @param lcName lowercase attribute name
4604
+ * @param namespaceURI the attribute's namespace, if any
4605
+ * @param value the attribute value to wrap
4606
+ * @return the value, wrapped when Trusted Types demand it
4607
+ */
4608
+ const _applyTrustedTypesToAttribute = function _applyTrustedTypesToAttribute(lcTag, lcName, namespaceURI, value) {
4609
+ if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function' && !namespaceURI) {
4610
+ switch (trustedTypes.getAttributeType(lcTag, lcName)) {
4611
+ case 'TrustedHTML':
4612
+ {
4613
+ return _createTrustedHTML(value);
4614
+ }
4615
+ case 'TrustedScriptURL':
4616
+ {
4617
+ return _createTrustedScriptURL(value);
4618
+ }
4619
+ }
4620
+ }
4621
+ return value;
4622
+ };
4623
+ /**
4624
+ * Write a modified attribute value back onto the element. On
4625
+ * success, re-probe for clobbering introduced by the new value and
4626
+ * remove the element when found; otherwise pop the removal entry
4627
+ * recorded by the earlier _removeAttribute (long-standing pairing
4628
+ * with the SANITIZE_NAMED_PROPS path - do not "fix" casually). On
4629
+ * failure, remove the attribute instead.
4630
+ *
4631
+ * @param currentNode the element carrying the attribute
4632
+ * @param name the attribute name as present on the element
4633
+ * @param namespaceURI the attribute's namespace, if any
4634
+ * @param value the new attribute value
4635
+ */
4636
+ const _setAttributeValue = function _setAttributeValue(currentNode, name, namespaceURI, value) {
4637
+ try {
4638
+ if (namespaceURI) {
4639
+ currentNode.setAttributeNS(namespaceURI, name, value);
4640
+ } else {
4641
+ /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
4642
+ currentNode.setAttribute(name, value);
4643
+ }
4644
+ if (_isClobbered(currentNode)) {
4645
+ _forceRemove(currentNode);
4646
+ } else {
4647
+ arrayPop(DOMPurify.removed);
4648
+ }
4649
+ } catch (_) {
4650
+ _removeAttribute(name, currentNode);
4651
+ }
4004
4652
  };
4005
4653
  /**
4006
4654
  * _sanitizeAttributes
@@ -4015,9 +4663,7 @@ function createDOMPurify() {
4015
4663
  const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
4016
4664
  /* Execute a hook if present */
4017
4665
  _executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
4018
- const {
4019
- attributes
4020
- } = currentNode;
4666
+ const attributes = currentNode.attributes;
4021
4667
  /* Check if we have attributes; if not we might have a text node */
4022
4668
  if (!attributes || _isClobbered(currentNode)) {
4023
4669
  return;
@@ -4030,14 +4676,13 @@ function createDOMPurify() {
4030
4676
  forceKeepAttr: undefined
4031
4677
  };
4032
4678
  let l = attributes.length;
4679
+ const lcTag = transformCaseFunc(currentNode.nodeName);
4033
4680
  /* Go backwards over all attributes; safely remove bad ones */
4034
4681
  while (l--) {
4035
4682
  const attr = attributes[l];
4036
- const {
4037
- name,
4038
- namespaceURI,
4039
- value: attrValue
4040
- } = attr;
4683
+ const name = attr.name,
4684
+ namespaceURI = attr.namespaceURI,
4685
+ attrValue = attr.value;
4041
4686
  const lcName = transformCaseFunc(name);
4042
4687
  const initValue = attrValue;
4043
4688
  let value = name === 'value' ? initValue : stringTrim(initValue);
@@ -4051,12 +4696,14 @@ function createDOMPurify() {
4051
4696
  /* Full DOM Clobbering protection via namespace isolation,
4052
4697
  * Prefix id and name attributes with `user-content-`
4053
4698
  */
4054
- if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
4699
+ if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name') && stringIndexOf(value, SANITIZE_NAMED_PROPS_PREFIX) !== 0) {
4055
4700
  // Remove the attribute with this value
4056
4701
  _removeAttribute(name, currentNode);
4057
4702
  // Prefix the value and later re-create the attribute with the sanitized value
4058
4703
  value = SANITIZE_NAMED_PROPS_PREFIX + value;
4059
4704
  }
4705
+ // Else: already prefixed, leave the attribute alone — the prefix is
4706
+ // itself the clobbering protection, and re-applying it is incorrect.
4060
4707
  /* Work around a security issue with comments inside attributes */
4061
4708
  if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, value)) {
4062
4709
  _removeAttribute(name, currentNode);
@@ -4067,7 +4714,7 @@ function createDOMPurify() {
4067
4714
  _removeAttribute(name, currentNode);
4068
4715
  continue;
4069
4716
  }
4070
- /* Did the hooks approve of the attribute? */
4717
+ /* Did the hooks force-keep the attribute? */
4071
4718
  if (hookEvent.forceKeepAttr) {
4072
4719
  continue;
4073
4720
  }
@@ -4077,56 +4724,24 @@ function createDOMPurify() {
4077
4724
  continue;
4078
4725
  }
4079
4726
  /* Work around a security issue in jQuery 3.0 */
4080
- if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) {
4727
+ if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(SELF_CLOSING_TAG, value)) {
4081
4728
  _removeAttribute(name, currentNode);
4082
4729
  continue;
4083
4730
  }
4084
4731
  /* Sanitize attribute content to be template-safe */
4085
4732
  if (SAFE_FOR_TEMPLATES) {
4086
- arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
4087
- value = stringReplace(value, expr, ' ');
4088
- });
4733
+ value = _stripTemplateExpressions(value);
4089
4734
  }
4090
4735
  /* Is `value` valid for this attribute? */
4091
- const lcTag = transformCaseFunc(currentNode.nodeName);
4092
4736
  if (!_isValidAttribute(lcTag, lcName, value)) {
4093
4737
  _removeAttribute(name, currentNode);
4094
4738
  continue;
4095
4739
  }
4096
4740
  /* Handle attributes that require Trusted Types */
4097
- if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function') {
4098
- if (namespaceURI) ; else {
4099
- switch (trustedTypes.getAttributeType(lcTag, lcName)) {
4100
- case 'TrustedHTML':
4101
- {
4102
- value = trustedTypesPolicy.createHTML(value);
4103
- break;
4104
- }
4105
- case 'TrustedScriptURL':
4106
- {
4107
- value = trustedTypesPolicy.createScriptURL(value);
4108
- break;
4109
- }
4110
- }
4111
- }
4112
- }
4741
+ value = _applyTrustedTypesToAttribute(lcTag, lcName, namespaceURI, value);
4113
4742
  /* Handle invalid data-* attribute set by try-catching it */
4114
4743
  if (value !== initValue) {
4115
- try {
4116
- if (namespaceURI) {
4117
- currentNode.setAttributeNS(namespaceURI, name, value);
4118
- } else {
4119
- /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
4120
- currentNode.setAttribute(name, value);
4121
- }
4122
- if (_isClobbered(currentNode)) {
4123
- _forceRemove(currentNode);
4124
- } else {
4125
- arrayPop(DOMPurify.removed);
4126
- }
4127
- } catch (_) {
4128
- _removeAttribute(name, currentNode);
4129
- }
4744
+ _setAttributeValue(currentNode, name, namespaceURI, value);
4130
4745
  }
4131
4746
  }
4132
4747
  /* Execute a hook if present */
@@ -4149,14 +4764,133 @@ function createDOMPurify() {
4149
4764
  _sanitizeElements(shadowNode);
4150
4765
  /* Check attributes next */
4151
4766
  _sanitizeAttributes(shadowNode);
4152
- /* Deep shadow DOM detected */
4153
- if (shadowNode.content instanceof DocumentFragment) {
4767
+ /* Deep shadow DOM detected.
4768
+ Realm-safe check (GHSA-hpcv-96wg-7vj8): use nodeType against the
4769
+ DOCUMENT_FRAGMENT_NODE constant rather than instanceof, so we
4770
+ recurse into <template>.content from foreign realms too. */
4771
+ if (_isDocumentFragment(shadowNode.content)) {
4154
4772
  _sanitizeShadowDOM2(shadowNode.content);
4155
4773
  }
4774
+ /* An element iterated here may itself host an attached
4775
+ shadow root. The default NodeIterator does not enter shadow
4776
+ trees, so a shadow root nested inside template.content was
4777
+ previously reached by no walk at all (the pre-pass at
4778
+ _sanitizeAttachedShadowRoots descends via childNodes, which
4779
+ doesn't enter template.content; the template-content recursion
4780
+ above iterates the content but never inspected shadowRoot).
4781
+ Walk it explicitly. The nodeType guard avoids reading
4782
+ shadowRoot off text / comment / CDATA / PI nodes that the
4783
+ iterator also surfaces. */
4784
+ const shadowNodeType = getNodeType ? getNodeType(shadowNode) : shadowNode.nodeType;
4785
+ if (shadowNodeType === NODE_TYPE.element) {
4786
+ const innerSr = getShadowRoot(shadowNode);
4787
+ if (_isDocumentFragment(innerSr)) {
4788
+ _sanitizeAttachedShadowRoots(innerSr);
4789
+ _sanitizeShadowDOM2(innerSr);
4790
+ }
4791
+ }
4156
4792
  }
4157
4793
  /* Execute a hook if present */
4158
4794
  _executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
4159
4795
  };
4796
+ /**
4797
+ * _sanitizeAttachedShadowRoots
4798
+ *
4799
+ * Walks `root` and feeds every attached shadow root we encounter into
4800
+ * the existing _sanitizeShadowDOM pipeline. The default node iterator
4801
+ * does not descend into shadow trees, so nodes inside an attached
4802
+ * shadow root would otherwise be skipped entirely.
4803
+ *
4804
+ * Two real input paths put attached shadow roots in front of us:
4805
+ * 1. IN_PLACE on a DOM node that already has shadow roots attached.
4806
+ * 2. DOM-node input where importNode(dirty, true) deep-clones the
4807
+ * shadow root because it was created with `clonable: true`.
4808
+ *
4809
+ * This pass runs once, up front, so the main iteration loop (and the
4810
+ * existing _sanitizeShadowDOM template-content recursion) stay
4811
+ * untouched — string-input paths are not affected.
4812
+ *
4813
+ * @param root the subtree root to walk for attached shadow roots
4814
+ */
4815
+ const _sanitizeAttachedShadowRoots = function _sanitizeAttachedShadowRoots(root) {
4816
+ /* Iterative (explicit stack) rather than per-child recursion. DOM APIs
4817
+ impose no depth cap, so an attacker-shaped tree (JSON/CRDT/editor data
4818
+ built straight into the DOM — the IN_PLACE surface) deeper than the JS
4819
+ call-stack budget would otherwise overflow native recursion here and
4820
+ throw at the IN_PLACE entry pre-pass, before a single node is
4821
+ sanitized, leaving the caller's live tree untouched (fail-open). See
4822
+ campaign-3 F4. A heap stack keeps depth off the call stack.
4823
+ Each work item is either a node to descend into, or a deferred
4824
+ `_sanitizeShadowDOM` for an already-walked shadow root. The deferred
4825
+ form preserves the original post-order discipline: a shadow root's
4826
+ nested shadow roots are discovered before the outer shadow is
4827
+ sanitized (which may remove hosts). Pushes are in reverse of the
4828
+ desired processing order (LIFO): template content, then children, then
4829
+ the shadow-sanitize, then the shadow walk — so the order matches the
4830
+ previous recursion exactly. */
4831
+ const stack = [{
4832
+ node: root,
4833
+ shadow: null
4834
+ }];
4835
+ while (stack.length > 0) {
4836
+ const item = stack.pop();
4837
+ /* Deferred shadow-DOM sanitisation: runs after its subtree was walked. */
4838
+ if (item.shadow) {
4839
+ _sanitizeShadowDOM2(item.shadow);
4840
+ continue;
4841
+ }
4842
+ const node = item.node;
4843
+ const nodeType = getNodeType ? getNodeType(node) : node.nodeType;
4844
+ const isElement = nodeType === NODE_TYPE.element;
4845
+ /* (pushed last → processed first) Children, snapshotted in reverse so
4846
+ the first child is processed first. Snapshotting matters because a
4847
+ hook may detach siblings mid-walk. */
4848
+ const childNodes = getChildNodes(node);
4849
+ if (childNodes) {
4850
+ for (let i = childNodes.length - 1; i >= 0; --i) {
4851
+ stack.push({
4852
+ node: childNodes[i],
4853
+ shadow: null
4854
+ });
4855
+ }
4856
+ }
4857
+ /* (pushed before children → processed after them, matching the old
4858
+ "template content last" order) When the node is a <template>,
4859
+ descend into its content. */
4860
+ if (isElement) {
4861
+ const rootName = getNodeName ? getNodeName(node) : null;
4862
+ if (typeof rootName === 'string' && transformCaseFunc(rootName) === 'template') {
4863
+ const content = node.content;
4864
+ if (_isDocumentFragment(content)) {
4865
+ stack.push({
4866
+ node: content,
4867
+ shadow: null
4868
+ });
4869
+ }
4870
+ }
4871
+ }
4872
+ /* Shadow root (processed first): walk its subtree, then sanitise it.
4873
+ Realm-safe check (GHSA-hpcv-96wg-7vj8): nodeType-based detection
4874
+ rather than `instanceof DocumentFragment`, which is realm-bound and
4875
+ silently skipped foreign-realm shadow roots (e.g.
4876
+ iframe.contentDocument attachShadow). */
4877
+ if (isElement) {
4878
+ const sr = getShadowRoot(node);
4879
+ if (_isDocumentFragment(sr)) {
4880
+ /* Push the deferred sanitise first so it pops after the shadow
4881
+ walk we push next, i.e. nested shadow roots are discovered
4882
+ before this one is sanitised. */
4883
+ stack.push({
4884
+ node: null,
4885
+ shadow: sr
4886
+ }, {
4887
+ node: sr,
4888
+ shadow: null
4889
+ });
4890
+ }
4891
+ }
4892
+ }
4893
+ };
4160
4894
  // eslint-disable-next-line complexity
4161
4895
  DOMPurify.sanitize = function (dirty) {
4162
4896
  let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
@@ -4173,13 +4907,9 @@ function createDOMPurify() {
4173
4907
  }
4174
4908
  /* Stringify, in case dirty is an object */
4175
4909
  if (typeof dirty !== 'string' && !_isNode(dirty)) {
4176
- if (typeof dirty.toString === 'function') {
4177
- dirty = dirty.toString();
4178
- if (typeof dirty !== 'string') {
4179
- throw typeErrorCreate('dirty is not a string, aborting');
4180
- }
4181
- } else {
4182
- throw typeErrorCreate('toString is not a function');
4910
+ dirty = stringifyValue(dirty);
4911
+ if (typeof dirty !== 'string') {
4912
+ throw typeErrorCreate('dirty is not a string, aborting');
4183
4913
  }
4184
4914
  }
4185
4915
  /* Return dirty HTML if DOMPurify cannot run */
@@ -4187,24 +4917,78 @@ function createDOMPurify() {
4187
4917
  return dirty;
4188
4918
  }
4189
4919
  /* Assign config vars */
4190
- if (!SET_CONFIG) {
4920
+ if (SET_CONFIG) {
4921
+ /* Persistent setConfig() path: _parseConfig is skipped, so the sets are
4922
+ * not re-derived per call. Restore them from the pristine bindings
4923
+ * captured at setConfig() time so a previous call's hook clone (mutated
4924
+ * below) does not carry over. */
4925
+ ALLOWED_TAGS = SET_CONFIG_ALLOWED_TAGS;
4926
+ ALLOWED_ATTR = SET_CONFIG_ALLOWED_ATTR;
4927
+ } else {
4191
4928
  _parseConfig(cfg);
4192
4929
  }
4930
+ /* Clone the hook-mutable allowlists before the walk whenever an
4931
+ * uponSanitize* hook is registered. The hook event exposes ALLOWED_TAGS
4932
+ * and ALLOWED_ATTR by reference (as allowedTags / allowedAttributes), so
4933
+ * a hook that widens them would otherwise mutate the shared set
4934
+ * permanently: across later calls and across every element. Cloning per
4935
+ * walk keeps documented in-call widening working while scoping it to the
4936
+ * call. A single guard for both config paths - the per-call path rebinds
4937
+ * the sets in _parseConfig each call, the persistent path restores them
4938
+ * from the captured bindings just above - so the two cannot diverge. */
4939
+ if (hooks.uponSanitizeElement.length > 0 || hooks.uponSanitizeAttribute.length > 0) {
4940
+ ALLOWED_TAGS = clone(ALLOWED_TAGS);
4941
+ }
4942
+ if (hooks.uponSanitizeAttribute.length > 0) {
4943
+ ALLOWED_ATTR = clone(ALLOWED_ATTR);
4944
+ }
4193
4945
  /* Clean up removed elements */
4194
4946
  DOMPurify.removed = [];
4195
- /* Check if dirty is correctly typed for IN_PLACE */
4196
- if (typeof dirty === 'string') {
4197
- IN_PLACE = false;
4198
- }
4199
- if (IN_PLACE) {
4200
- /* Do some early pre-sanitization to avoid unsafe root nodes */
4201
- if (dirty.nodeName) {
4202
- const tagName = transformCaseFunc(dirty.nodeName);
4947
+ /* Resolve IN_PLACE for this call without mutating persistent config.
4948
+ Writing the IN_PLACE closure variable here leaks under setConfig(),
4949
+ where _parseConfig is skipped on later calls: a single string call would
4950
+ disable in-place mode for every subsequent node call, returning a
4951
+ sanitized copy while leaving the caller's node — which in-place callers
4952
+ keep using and whose return value they ignore unsanitized. REPORT-2. */
4953
+ const inPlace = IN_PLACE && typeof dirty !== 'string' && _isNode(dirty);
4954
+ if (inPlace) {
4955
+ /* Do some early pre-sanitization to avoid unsafe root nodes.
4956
+ Read nodeName through the cached prototype getter — a clobbering
4957
+ child named "nodeName" on the form root would otherwise shadow
4958
+ the property and let this check skip the root-allowlist
4959
+ validation entirely. */
4960
+ const nn = getNodeName ? getNodeName(dirty) : dirty.nodeName;
4961
+ if (typeof nn === 'string') {
4962
+ const tagName = transformCaseFunc(nn);
4203
4963
  if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
4204
4964
  throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
4205
4965
  }
4206
4966
  }
4207
- } else if (dirty instanceof Node) {
4967
+ /* Pre-flight the root through _isClobbered. The iterator-driven
4968
+ removal path can not detach a parent-less root: _forceRemove
4969
+ falls through to Element.prototype.remove(), which per spec
4970
+ is a no-op on a node with no parent. A clobbered root would
4971
+ then survive the main loop with its attributes uninspected,
4972
+ because _sanitizeAttributes early-returns on _isClobbered. The
4973
+ result would be an attacker-controlled form, complete with any
4974
+ event-handler attributes the caller passed in, handed back to
4975
+ the application unsanitized. Refuse to sanitize such a root
4976
+ the same way we refuse a forbidden tag. GHSA-r47g-fvhr-h676. */
4977
+ if (_isClobbered(dirty)) {
4978
+ throw typeErrorCreate('root node is clobbered and cannot be sanitized in-place');
4979
+ }
4980
+ /* Sanitize attached shadow roots before the main iterator runs.
4981
+ The iterator does not descend into shadow trees. Same fail-closed
4982
+ barrier as the main walk (campaign-3 F2): a custom-element reaction
4983
+ inside a shadow root could abort this pre-pass before the walk runs,
4984
+ which would otherwise leave the entire live tree unsanitized. */
4985
+ try {
4986
+ _sanitizeAttachedShadowRoots(dirty);
4987
+ } catch (error) {
4988
+ _neutralizeRoot(dirty);
4989
+ throw error;
4990
+ }
4991
+ } else if (_isNode(dirty)) {
4208
4992
  /* If dirty is a DOM element, append to an empty document to avoid
4209
4993
  elements being stripped by the parser */
4210
4994
  body = _initDocument('<!---->');
@@ -4218,12 +5002,18 @@ function createDOMPurify() {
4218
5002
  // eslint-disable-next-line unicorn/prefer-dom-node-append
4219
5003
  body.appendChild(importedNode);
4220
5004
  }
5005
+ /* Clonable shadow roots are deep-cloned by importNode(); sanitize
5006
+ them before the main iterator runs, since the iterator does not
5007
+ descend into shadow trees. The walk routes every read through a
5008
+ cached prototype getter so clobbering descendants on a form root
5009
+ cannot hide a shadow host from this pass. */
5010
+ _sanitizeAttachedShadowRoots(importedNode);
4221
5011
  } else {
4222
5012
  /* Exit directly if we have nothing to do */
4223
5013
  if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
4224
5014
  // eslint-disable-next-line unicorn/prefer-includes
4225
5015
  dirty.indexOf('<') === -1) {
4226
- return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;
5016
+ return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? _createTrustedHTML(dirty) : dirty;
4227
5017
  }
4228
5018
  /* Initialize the document to work on */
4229
5019
  body = _initDocument(dirty);
@@ -4237,31 +5027,59 @@ function createDOMPurify() {
4237
5027
  _forceRemove(body.firstChild);
4238
5028
  }
4239
5029
  /* Get node iterator */
4240
- const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body);
4241
- /* Now start iterating over the created document */
4242
- while (currentNode = nodeIterator.nextNode()) {
4243
- /* Sanitize tags and elements */
4244
- _sanitizeElements(currentNode);
4245
- /* Check attributes next */
4246
- _sanitizeAttributes(currentNode);
4247
- /* Shadow DOM detected, sanitize it */
4248
- if (currentNode.content instanceof DocumentFragment) {
4249
- _sanitizeShadowDOM2(currentNode.content);
5030
+ const nodeIterator = _createNodeIterator(inPlace ? dirty : body);
5031
+ /* Now start iterating over the created document.
5032
+ The walk runs inside an exception barrier (campaign-3 F2): a re-entrant
5033
+ engine/custom-element mutation can detach a node mid-walk so
5034
+ `_forceRemove`'s parentless guard throws, aborting the loop. Without the
5035
+ barrier the caller's in-place tree would be left half-sanitized with the
5036
+ unvisited tail still armed. On any throw we fail closed — strip the
5037
+ in-place root bare then rethrow so the existing throw contract is
5038
+ preserved. (String/DOM-copy paths never return the partial body, so the
5039
+ propagating throw is already fail-closed there.) */
5040
+ try {
5041
+ while (currentNode = nodeIterator.nextNode()) {
5042
+ /* Sanitize tags and elements */
5043
+ _sanitizeElements(currentNode);
5044
+ /* Check attributes next */
5045
+ _sanitizeAttributes(currentNode);
5046
+ /* Shadow DOM detected, sanitize it.
5047
+ Realm-safe check (GHSA-hpcv-96wg-7vj8): nodeType-based detection
5048
+ instead of instanceof, so foreign-realm <template>.content is
5049
+ walked correctly. */
5050
+ if (_isDocumentFragment(currentNode.content)) {
5051
+ _sanitizeShadowDOM2(currentNode.content);
5052
+ }
4250
5053
  }
5054
+ } catch (error) {
5055
+ if (inPlace) {
5056
+ _neutralizeRoot(dirty);
5057
+ }
5058
+ throw error;
4251
5059
  }
4252
5060
  /* If we sanitized `dirty` in-place, return it. */
4253
- if (IN_PLACE) {
5061
+ if (inPlace) {
5062
+ /* Fail-closed completion of the audit-5 F1 fix: every node removed from
5063
+ the caller's live tree is detached but may still hold a queued
5064
+ resource-event handler that fires in page scope after we return. The
5065
+ move-hoist covers only disallowed-tag KEEP_CONTENT removals; strip the
5066
+ non-allow-listed attributes off every other removed subtree (clobber,
5067
+ mXSS, namespace, comments, KEEP_CONTENT:false, …) so those handlers are
5068
+ cancelled before any event can fire. Runs synchronously, pre-return. */
5069
+ arrayForEach(DOMPurify.removed, entry => {
5070
+ if (entry.element) {
5071
+ _neutralizeSubtree(entry.element);
5072
+ }
5073
+ });
5074
+ if (SAFE_FOR_TEMPLATES) {
5075
+ _scrubTemplateExpressions2(dirty);
5076
+ }
4254
5077
  return dirty;
4255
5078
  }
4256
5079
  /* Return sanitized string or DOM */
4257
5080
  if (RETURN_DOM) {
4258
5081
  if (SAFE_FOR_TEMPLATES) {
4259
- body.normalize();
4260
- let html = body.innerHTML;
4261
- arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
4262
- html = stringReplace(html, expr, ' ');
4263
- });
4264
- body.innerHTML = html;
5082
+ _scrubTemplateExpressions2(body);
4265
5083
  }
4266
5084
  if (RETURN_DOM_FRAGMENT) {
4267
5085
  returnNode = createDocumentFragment.call(body.ownerDocument);
@@ -4291,20 +5109,28 @@ function createDOMPurify() {
4291
5109
  }
4292
5110
  /* Sanitize final string template-safe */
4293
5111
  if (SAFE_FOR_TEMPLATES) {
4294
- arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
4295
- serializedHTML = stringReplace(serializedHTML, expr, ' ');
4296
- });
5112
+ serializedHTML = _stripTemplateExpressions(serializedHTML);
4297
5113
  }
4298
- return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;
5114
+ return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? _createTrustedHTML(serializedHTML) : serializedHTML;
4299
5115
  };
4300
5116
  DOMPurify.setConfig = function () {
4301
5117
  let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
4302
5118
  _parseConfig(cfg);
4303
5119
  SET_CONFIG = true;
5120
+ SET_CONFIG_ALLOWED_TAGS = ALLOWED_TAGS;
5121
+ SET_CONFIG_ALLOWED_ATTR = ALLOWED_ATTR;
4304
5122
  };
4305
5123
  DOMPurify.clearConfig = function () {
4306
5124
  CONFIG = null;
4307
5125
  SET_CONFIG = false;
5126
+ SET_CONFIG_ALLOWED_TAGS = null;
5127
+ SET_CONFIG_ALLOWED_ATTR = null;
5128
+ // Drop any caller-supplied Trusted Types policy so it cannot poison later
5129
+ // `RETURN_TRUSTED_TYPE` output. The internal default policy (cached, and
5130
+ // never recreated — Trusted Types throws on duplicate names) is restored by
5131
+ // the next `_parseConfig`. See GHSA-vxr8-fq34-vvx9.
5132
+ trustedTypesPolicy = defaultTrustedTypesPolicy;
5133
+ emptyHTML = '';
4308
5134
  };
4309
5135
  DOMPurify.isValidAttribute = function (tag, attr, value) {
4310
5136
  /* Initialize shared config vars if necessary. */
@@ -4319,9 +5145,19 @@ function createDOMPurify() {
4319
5145
  if (typeof hookFunction !== 'function') {
4320
5146
  return;
4321
5147
  }
5148
+ /* Reject unknown entry points. Without this, a non-hook key (e.g.
5149
+ * '__proto__') indexes off the prototype chain rather than a real
5150
+ * hook array, and arrayPush then writes to Object.prototype. Guard
5151
+ * with an own-property check against the known hook names. */
5152
+ if (!objectHasOwnProperty(hooks, entryPoint)) {
5153
+ return;
5154
+ }
4322
5155
  arrayPush(hooks[entryPoint], hookFunction);
4323
5156
  };
4324
5157
  DOMPurify.removeHook = function (entryPoint, hookFunction) {
5158
+ if (!objectHasOwnProperty(hooks, entryPoint)) {
5159
+ return undefined;
5160
+ }
4325
5161
  if (hookFunction !== undefined) {
4326
5162
  const index = arrayLastIndexOf(hooks[entryPoint], hookFunction);
4327
5163
  return index === -1 ? undefined : arraySplice(hooks[entryPoint], index, 1)[0];
@@ -4329,6 +5165,9 @@ function createDOMPurify() {
4329
5165
  return arrayPop(hooks[entryPoint]);
4330
5166
  };
4331
5167
  DOMPurify.removeHooks = function (entryPoint) {
5168
+ if (!objectHasOwnProperty(hooks, entryPoint)) {
5169
+ return;
5170
+ }
4332
5171
  hooks[entryPoint] = [];
4333
5172
  };
4334
5173
  DOMPurify.removeAllHooks = function () {
@@ -4470,6 +5309,7 @@ var ar = {
4470
5309
  "status.uploading": "جارٍ التحميل",
4471
5310
  "status.sessionExpired": "انتهت صلاحية جلسة الدردشة. سيتم بدء محادثة جديدة — يُرجى إعادة إرسال رسالتك.",
4472
5311
  "status.sessionError": "لم تعد جلسة الدردشة هذه متاحة.",
5312
+ "status.chatEnded": "انتهت هذه المحادثة.",
4473
5313
  "modal.newChatTitle": "بدء محادثة جديدة",
4474
5314
  "modal.newChatBody": "بدء محادثة جديدة سيؤدي إلى مسح المحادثة الحالية. هل ترغب بالمتابعة؟",
4475
5315
  "modal.cancel": "إلغاء",
@@ -4504,6 +5344,7 @@ var en = {
4504
5344
  "status.uploading": "Uploading",
4505
5345
  "status.sessionExpired": "Your chat session expired. Starting a new chat — please resend your message.",
4506
5346
  "status.sessionError": "This chat session is no longer available.",
5347
+ "status.chatEnded": "This chat has ended.",
4507
5348
  "modal.newChatTitle": "Start New Chat",
4508
5349
  "modal.newChatBody": "Starting a new chat will clear your current conversation. Continue?",
4509
5350
  "modal.cancel": "Cancel",
@@ -4538,6 +5379,7 @@ var es = {
4538
5379
  "status.uploading": "Subiendo",
4539
5380
  "status.sessionExpired": "Tu sesión de chat ha expirado. Se iniciará un nuevo chat: vuelve a enviar tu mensaje.",
4540
5381
  "status.sessionError": "Esta sesión de chat ya no está disponible.",
5382
+ "status.chatEnded": "Este chat ha finalizado.",
4541
5383
  "modal.newChatTitle": "Iniciar Nuevo Chat",
4542
5384
  "modal.newChatBody": "Iniciar un nuevo chat borrará tu conversación actual. ¿Continuar?",
4543
5385
  "modal.cancel": "Cancelar",
@@ -4572,6 +5414,7 @@ var fr = {
4572
5414
  "status.uploading": "Téléversement",
4573
5415
  "status.sessionExpired": "Votre session de chat a expiré. Un nouveau chat va démarrer — veuillez renvoyer votre message.",
4574
5416
  "status.sessionError": "Cette session de chat n'est plus disponible.",
5417
+ "status.chatEnded": "Cette conversation est terminée.",
4575
5418
  "modal.newChatTitle": "Nouveau Chat",
4576
5419
  "modal.newChatBody": "Démarrer un nouveau chat effacera votre conversation actuelle. Continuer?",
4577
5420
  "modal.cancel": "Annuler",
@@ -4606,6 +5449,7 @@ var hi = {
4606
5449
  "status.uploading": "अपलोड हो रहा है",
4607
5450
  "status.sessionExpired": "आपका चैट सत्र समाप्त हो गया है। एक नई चैट शुरू हो रही है — कृपया अपना संदेश फिर से भेजें।",
4608
5451
  "status.sessionError": "यह चैट सत्र अब उपलब्ध नहीं है।",
5452
+ "status.chatEnded": "यह चैट समाप्त हो गई है।",
4609
5453
  "modal.newChatTitle": "नई चैट शुरू करें",
4610
5454
  "modal.newChatBody": "नई चैट शुरू करने से आपकी मौजूदा बातचीत हट जाएगी। क्या आप जारी रखना चाहते हैं?",
4611
5455
  "modal.cancel": "रद्द करें",
@@ -4640,6 +5484,7 @@ var it = {
4640
5484
  "status.uploading": "Caricamento",
4641
5485
  "status.sessionExpired": "La tua sessione di chat è scaduta. Verrà avviata una nuova chat: invia di nuovo il tuo messaggio.",
4642
5486
  "status.sessionError": "Questa sessione di chat non è più disponibile.",
5487
+ "status.chatEnded": "Questa chat è terminata.",
4643
5488
  "modal.newChatTitle": "Avvia nuova chat",
4644
5489
  "modal.newChatBody": "Avviare una nuova chat cancellerà la conversazione attuale. Continuare?",
4645
5490
  "modal.cancel": "Annulla",
@@ -4674,6 +5519,7 @@ var pt = {
4674
5519
  "status.uploading": "Carregando",
4675
5520
  "status.sessionExpired": "Sua sessão de chat expirou. Iniciando um novo chat — reenvie sua mensagem.",
4676
5521
  "status.sessionError": "Esta sessão de chat não está mais disponível.",
5522
+ "status.chatEnded": "Este chat foi encerrado.",
4677
5523
  "modal.newChatTitle": "Iniciar novo chat",
4678
5524
  "modal.newChatBody": "Iniciar um novo chat apagará sua conversa atual. Deseja continuar?",
4679
5525
  "modal.cancel": "Cancelar",
@@ -4708,6 +5554,7 @@ var sw = {
4708
5554
  "status.uploading": "Inapakia",
4709
5555
  "status.sessionExpired": "Kipindi chako cha gumzo kimeisha. Gumzo jipya linaanza — tafadhali tuma tena ujumbe wako.",
4710
5556
  "status.sessionError": "Kipindi hiki cha gumzo hakipatikani tena.",
5557
+ "status.chatEnded": "Mazungumzo haya yamekamilika.",
4711
5558
  "modal.newChatTitle": "Anza gumzo jipya",
4712
5559
  "modal.newChatBody": "Kuanza gumzo jipya kutafuta gumzo lako la sasa. Je, ungependa kuendelea?",
4713
5560
  "modal.cancel": "Ghairi",
@@ -4742,6 +5589,7 @@ var uk = {
4742
5589
  "status.uploading": "Завантаження",
4743
5590
  "status.sessionExpired": "Ваш сеанс чату завершився. Розпочинається новий чат — будь ласка, надішліть повідомлення ще раз.",
4744
5591
  "status.sessionError": "Цей сеанс чату більше недоступний.",
5592
+ "status.chatEnded": "Цей чат завершено.",
4745
5593
  "modal.newChatTitle": "Почати новий чат",
4746
5594
  "modal.newChatBody": "Початок нового чату видалить вашу поточну розмову. Продовжити?",
4747
5595
  "modal.cancel": "Скасувати",
@@ -5138,12 +5986,17 @@ class ChatSessionService {
5138
5986
  }
5139
5987
  startMessagePolling(sessionId, callbacks) {
5140
5988
  const poll = async () => {
5989
+ var _a;
5141
5990
  try {
5142
5991
  const since = callbacks.getSince();
5143
5992
  const data = await this.fetchMessages(sessionId, since);
5144
5993
  if (data.messages.length > 0) {
5145
5994
  callbacks.onMessages(data.messages);
5146
5995
  }
5996
+ if (data.session_status === 'ended') {
5997
+ this.stopMessagePolling();
5998
+ (_a = callbacks.onSessionEnded) === null || _a === void 0 ? void 0 : _a.call(callbacks);
5999
+ }
5147
6000
  }
5148
6001
  catch (error) {
5149
6002
  if (callbacks.onError) {
@@ -5389,7 +6242,7 @@ class FileAttachmentManager {
5389
6242
  }
5390
6243
  }
5391
6244
 
5392
- const ocsChatCss = "/*! tailwindcss v4.1.12 | MIT License | https://tailwindcss.com */@layer properties{@supports ((-webkit-hyphens:none) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,::backdrop,:after,:before{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial;--tw-ease:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@layer theme{:host,:root{--font-sans:ui-sans-serif,system-ui,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\",\"Noto Color Emoji\";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;--color-blue-300:oklch(80.9% .105 251.813);--color-slate-500:oklch(55.4% .046 257.417);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--spacing:.25rem;--breakpoint-lg:64rem;--container-sm:24rem;--font-weight-light:300;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--animate-progress:progress 3s infinite linear;--animate-dots:dots 1s steps(5,end)infinite;--transform-origin-left-right:0% 50%}}@layer base{*,::backdrop,:after,:before{border:0 solid;border-color:var(--color-gray-200,currentcolor);box-sizing:border-box;margin:0;padding:0}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button;background-color:#0000;border:0 solid;border-color:var(--color-gray-200,currentcolor);border-radius:0;box-sizing:border-box;color:inherit;font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;margin:0;margin-inline-end:4px;opacity:1;padding:0}:host,html{-webkit-text-size-adjust:100%;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\",\"Noto Color Emoji\");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-size:1em;font-variation-settings:var(--default-mono-font-variation-settings,normal)}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}menu,ol,ul{list-style:none}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}button,input,optgroup,select,textarea{background-color:#0000;border-radius:0;color:inherit;font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;opacity:1}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::-moz-placeholder{opacity:1}::placeholder{opacity:1}@supports (not (-webkit-appearance:-apple-pay-button)) or (contain-intrinsic-size:1px){::-moz-placeholder{color:currentColor}::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::-moz-placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex;padding-block:0}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.fixed{position:fixed}.relative{position:relative}.static{position:static}.container{width:100%}.block{display:block}.contents{display:contents}.flex{display:flex}.hidden{display:none}.w-full{width:100%}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.resize{resize:both}.items-center{align-items:center}.justify-center{justify-content:center}.gap-\\[0\\.5em\\]{gap:.5em}:where(.space-y-\\[0\\.25em\\]>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(.25em*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(.25em*var(--tw-space-y-reverse))}.border{border-style:var(--tw-border-style);border-width:1px}.py-\\[2px\\]{padding-block:2px}.text-\\[0\\.8em\\]{font-size:.8em}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.text-slate-500{color:var(--color-slate-500)}.underline{text-decoration-line:underline}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a)}.ring,.shadow{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor)}} /*! tailwindcss v4.1.12 | MIT License | https://tailwindcss.com */@layer properties{}@layer base{}@layer components; /*! tailwindcss v4.1.12 | MIT License | https://tailwindcss.com */@layer properties{}@layer base{}@layer components{#ocs-chat-window{font-size:var(--chat-window-font-size);z-index:var(--chat-z-index)}.starter-question{border-radius:var(--radius-lg);text-align:left;--tw-duration:.2s;background-color:var(--starter-question-bg-color);border:1px solid var(--starter-question-border-color);color:var(--starter-question-text-color);padding:.75em;transition-duration:.2s}.starter-question:hover{background-color:var(--starter-question-bg-hover-color);border-color:var(--starter-question-border-hover-color)}.chat-btn-text{border-radius:var(--radius-lg);border-style:var(--tw-border-style);transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;--tw-ease:var(--ease-in-out);align-items:center;background-color:var(--button-background-color,#fff);border-width:0;border:1px solid var(--button-border-color);color:var(--button-text-color,#111827);display:flex;font-size:var(--button-font-size);gap:8px;padding:.5em;transition-duration:.2s;transition-timing-function:var(--ease-in-out);z-index:var(--chat-z-index,50)}.chat-btn-text:hover{border:1px solid var(--button-border-color-hover);color:var(--button-text-color-hover,#1d4ed8)}.chat-btn-text span{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);white-space:nowrap}.chat-btn-text img{flex-shrink:0;height:var(--button-icon-size);-o-object-fit:contain;object-fit:contain;width:var(--button-icon-size)}.chat-btn-text.round{border-radius:3.40282e+38px}.chat-btn-icon{border-radius:var(--radius-lg);border-style:var(--tw-border-style);transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;--tw-ease:var(--ease-in-out);background-color:var(--button-background-color,#fff);border-width:0;border:1px solid var(--button-border-color);font-size:var(--button-font-size);padding:.5em;transition-duration:.2s;transition-timing-function:var(--ease-in-out);z-index:var(--chat-z-index,50)}.chat-btn-icon:hover{border:1px solid var(--button-border-color-hover);color:var(--button-text-color-hover,#1d4ed8)}.chat-btn-icon img{height:var(--button-icon-size);-o-object-fit:contain;object-fit:contain;width:var(--button-icon-size)}.chat-btn-icon.round,.round .chat-btn-icon,.round.chat-btn-text{border-radius:3.40282e+38px}.error-message{color:var(--error-text-color);padding:.5em}.chat-window-fullscreen{border-style:var(--tw-border-style);inset:calc(var(--spacing)*0);z-index:9999;--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);height:100%;max-height:100%;transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));width:100%;--tw-duration:.2s;border-radius:0;border-width:0;max-width:var(--chat-window-fullscreen-width);transition-duration:.2s}.chat-window-fullscreen,.chat-window-normal{background-color:var(--chat-window-bg-color);display:flex;flex-direction:column;overflow:hidden;position:fixed}.chat-window-normal{border:1px solid var(--chat-window-border-color);border-radius:var(--radius-lg);height:100vh;max-width:var(--breakpoint-lg);min-height:300px;min-width:300px;width:100vw}.chat-window-normal:not(.chat-window-dragging){--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;transition-duration:.2s}.chat-window-dragging,.chat-window-normal:not(.chat-window-dragging){box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.chat-window-dragging{cursor:grabbing;--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040)}.chat-window-kiosk{background-color:var(--chat-window-bg-color);border-radius:0;box-shadow:none;display:flex;flex-direction:column;font-size:var(--chat-window-font-size);height:100%;inset:calc(var(--spacing)*0);overflow:hidden;position:absolute;width:100%;z-index:var(--chat-z-index)}.chat-header{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.15s;align-items:center;background-color:var(--header-bg-color);border-bottom:1px solid var(--header-border-color);display:flex;font-size:var(--header-font-size);justify-content:space-between;padding:.5em;transition-duration:.15s}.chat-header:active,.chat-header:hover{background-color:var(--header-bg-hover-color)}.header-text{align-items:center;color:var(--header-text-color);display:flex;font-size:var(--header-text-font-size);justify-content:center}.chat-header-draggable{cursor:grab}.chat-header-dragging{cursor:grabbing}.drag-indicator{display:none}.drag-dots{display:flex;gap:2px;margin-left:2px;pointer-events:none}.header-buttons{align-items:center;display:flex;gap:4px}.header-button{border-radius:var(--radius-md);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;color:var(--header-button-text-color);padding:.375em;transition-duration:.2s}.header-button svg{height:var(--header-button-icon-size);width:var(--header-button-icon-size)}.header-button:hover{background-color:var(--header-button-bg-hover-color)}.fullscreen-button{display:none}.chat-content{display:flex;flex-direction:column;flex-grow:1;overflow:hidden}.loading-container{align-items:center;display:flex;flex-grow:1;justify-content:center}.loading-text{color:var(--loading-text-color);margin-left:2px}.messages-container{flex-grow:1;overflow-y:auto;padding:1em}:where(.messages-container>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(var(--spacing)*2*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(var(--spacing)*2*var(--tw-space-y-reverse))}.message-row{display:flex}.message-row-user{justify-content:flex-end}.message-row-assistant{justify-content:flex-start}.message-bubble{border-radius:var(--radius-lg);padding:.5em 1em}.message-bubble-user{background-color:var(--message-user-bg-color);color:var(--message-user-text-color)}.message-bubble-assistant{background-color:var(--message-assistant-bg-color);color:var(--message-assistant-text-color)}.message-bubble-system{background-color:var(--message-system-bg-color);color:var(--message-system-text-color)}.message-timestamp{font-size:var(--chat-window-font-size-sm);margin-top:4px;opacity:.7}.message-attachments{margin-top:8px}:where(.message-attachments>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(var(--spacing)*1*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(var(--spacing)*1*var(--tw-space-y-reverse))}.attachment-link{display:block;text-decoration-line:underline}:where(.welcome-messages>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(var(--spacing)*2*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(var(--spacing)*2*var(--tw-space-y-reverse))}.typing-indicator{height:calc(var(--spacing)*1.5);overflow:hidden;width:100%}.typing-progress{animation:var(--animate-progress);background-color:var(--typing-progress-bg-color);border-radius:var(--radius-lg);height:100%;transform-origin:var(--transform-origin-left-right);width:100%}.typing-text{font-size:var(--chat-window-font-size-sm);justify-content:center;opacity:.7;width:100%}.typing-dots{animation:var(--animate-dots)}:where(.starter-questions>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(var(--spacing)*2*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(var(--spacing)*2*var(--tw-space-y-reverse))}.starter-questions{padding:1em}.starter-question-row{display:flex;justify-content:flex-end}.input-area{background-color:var(--input-bg-color);border-top:1px solid var(--input-border-color);padding:1em 1em 0}.input-container{display:flex;gap:8px}.message-textarea{background-color:var(--input-bg-color);border-color:var(--color-gray-300);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border:1px solid var(--input-border-color);color:var(--input-text-color);flex-grow:1;padding:.5em .75em;resize:none}.message-textarea:focus{outline-color:var(--input-outline-focus-color)}.send-button{border-radius:var(--radius-md);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;padding:.5em 1em;transition-duration:.2s}.send-button-enabled{background-color:var(--send-button-bg-color);color:var(--send-button-text-color)}.send-button-enabled:hover{background-color:var(--send-button-bg-hover-color)}.send-button-disabled{background-color:var(--send-button-bg-disabled-color);color:var(--send-button-text-disabled-color);cursor:not-allowed}.confirmation-overlay{align-items:center;background-color:var(--confirmation-overlay-bg-color);display:flex;inset:calc(var(--spacing)*0);justify-content:center;position:fixed;z-index:9999}.confirmation-dialog{background-color:var(--confirmation-dialog-bg-color);border:1px solid var(--confirmation-dialog-border-color);border-radius:.75em;box-shadow:0 .625em 1.5625em var(--confirmation-dialog-shadow-color);margin-inline:calc(var(--spacing)*4);max-width:var(--container-sm);width:100%}.confirmation-content{padding:1.5em}.confirmation-title{margin-bottom:calc(var(--spacing)*2);--tw-font-weight:var(--font-weight-semibold);color:var(--confirmation-title-color);font-size:var(--confirmation-title-font-size);font-weight:var(--font-weight-semibold)}.confirmation-message{color:var(--confirmation-message-color);font-size:var(--confirmation-message-font-size);margin-bottom:calc(var(--spacing)*4)}.confirmation-buttons{display:flex;gap:.75em;justify-content:flex-end}.confirmation-button{border-radius:var(--radius-md);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;padding:.5em 1em;transition-duration:.2s}.confirmation-button-cancel{background-color:var(--confirmation-button-cancel-bg-color);color:var(--confirmation-button-cancel-text-color)}.confirmation-button-cancel:hover{background-color:var(--confirmation-button-cancel-bg-hover-color)}.confirmation-button-confirm{background-color:var(--confirmation-button-confirm-bg-color);color:var(--confirmation-button-confirm-text-color)}.confirmation-button-confirm:hover{background-color:var(--confirmation-button-confirm-bg-hover-color)}}@layer utilities{:host{--chat-z-index:50;--button-background-color:#fff;--button-background-color-hover:#f3f4f6;--button-text-color:#111827;--button-text-color-hover:#1d4ed8;--button-border-color:#d1d5db;--button-border-color-hover:#6b7280;--button-font-size:1em;--button-icon-size:1.5em;--chat-window-height:60%;--chat-window-width:25%;--chat-window-fullscreen-width:80%;--chat-window-bg-color:#fff;--chat-window-border-color:#d1d5db;--chat-window-shadow-color:#0000001a;--chat-window-font-size:.875em;--chat-window-font-size-sm:.75em;--header-bg-color:transparent;--header-bg-hover-color:#f9fafb;--header-border-color:#f3f4f6;--header-button-text-color:#6b7280;--header-button-bg-hover-color:#f3f4f6;--header-font-size:1em;--header-text-font-size:1em;--header-text-color:#525762;--header-button-icon-size:1.5em;--starter-question-bg-color:transparent;--starter-question-bg-hover-color:#eff6ff;--starter-question-text-color:#3b82f6;--starter-question-border-color:#3b82f6;--starter-question-border-hover-color:#2563eb;--message-user-bg-color:#e4edfb;--message-user-text-color:#1f2937;--message-user-link-color:#155dfc;--message-assistant-bg-color:#eae7e8;--message-assistant-text-color:var(--message-user-text-color);--message-assistant-link-color:var(--message-user-link-color);--message-system-bg-color:#fbe4f8;--message-system-text-color:var(--message-user-text-color);--message-system-link-color:var(--message-user-link-color);--message-timestamp-color:#ffffffb3;--message-timestamp-assistant-color:#4b5563b3;--input-bg-color:transparent;--input-border-color:#d1d5db;--input-text-color:#111827;--input-placeholder-color:#6b7280;--input-outline-focus-color:#3b82f6;--send-button-bg-color:#3b82f6;--send-button-bg-hover-color:#2563eb;--send-button-text-color:#fff;--send-button-bg-disabled-color:#d1d5db;--send-button-text-disabled-color:#6b7280;--loading-text-color:#6b7280;--loading-spinner-track-color:#e5e7eb;--loading-spinner-fill-color:#3b82f6;--loading-spinner-size:1.25em;--typing-progress-bg-color:#ade3ff;--scrollbar-track-color:#f3f4f6;--scrollbar-thumb-color:#d1d5db;--scrollbar-thumb-hover-color:#9ca3af;--error-text-color:#ef4444;--success-text-color:#10b981;--code-bg-user-color:var(--message-user-bg-color);--code-text-user-color:var(--message-user-text-color);--code-border-user-color:var(--message-user-bg-color);--code-bg-assistant-color:var(--message-assistant-bg-color);--code-text-assistant-color:var(--message-assistant-text-color);--code-border-assistant-color:var(--message-assistant-bg-color);--confirmation-overlay-bg-color:#00000080;--confirmation-dialog-bg-color:var(--chat-window-bg-color);--confirmation-dialog-border-color:var(--chat-window-border-color);--confirmation-dialog-shadow-color:var(--chat-window-shadow-color);--confirmation-title-color:#111827;--confirmation-title-font-size:1.125em;--confirmation-message-color:var(--loading-text-color);--confirmation-message-font-size:1em;--confirmation-button-cancel-bg-color:var(--button-background-color-hover);--confirmation-button-cancel-bg-hover-color:#e5e7eb;--confirmation-button-cancel-text-color:var(--header-button-text-color);--confirmation-button-confirm-bg-color:var(--error-text-color);--confirmation-button-confirm-bg-hover-color:var(--error-text-color);--confirmation-button-confirm-text-color:var(--send-button-text-color);--file-attachment-button-bg-color:transparent;--file-attachment-button-bg-hover-color:var(--header-button-bg-hover-color);--file-attachment-button-text-color:var(--header-button-text-color);--file-attachment-button-text-disabled-color:var(--send-button-text-disabled-color);--selected-files-bg-color:var(--chat-window-bg-color);--selected-files-border-color:var(--header-border-color);--selected-file-bg-color:var(--button-background-color-hover);--selected-file-font-size:var(--chat-window-font-size-sm);--selected-file-name-color:var(--message-assistant-text-color);--selected-file-size-color:var(--input-placeholder-color);--selected-file-icon-size:1.25em;--selected-file-remove-icon-color:var(--error-text-color);--selected-file-remove-icon-hover-color:#dc2626;--message-attachment-icon-size:1em;bottom:30px;display:block;position:fixed;right:30px}@supports (color:color-mix(in lab,red,red)){:host{--code-bg-user-color:color-mix(in srgb,var(--message-user-bg-color)80%,#fff 20%);--code-border-user-color:color-mix(in srgb,var(--message-user-bg-color)90%,#000 10%);--code-bg-assistant-color:color-mix(in srgb,var(--message-assistant-bg-color)50%,#fff 50%);--code-border-assistant-color:color-mix(in srgb,var(--message-assistant-bg-color)90%,#000 10%)}}:host([mode=kiosk]){height:100%;inset:0 auto auto 0;position:absolute;width:100%}}textarea{max-height:calc(var(--spacing)*32);min-height:calc(var(--spacing)*10);overflow-y:auto;resize:none}.loading-spinner{animation:var(--animate-spin);border-color:var(--loading-spinner-track-color);border-radius:3.40282e+38px;border-style:var(--tw-border-style);border-top-color:var(--loading-spinner-fill-color);border-width:2px;height:var(--loading-spinner-size);width:var(--loading-spinner-size)}.overflow-y-auto::-webkit-scrollbar{height:calc(var(--spacing)*1.5);width:calc(var(--spacing)*1.5)}.overflow-y-auto::-webkit-scrollbar-track{background-color:var(--scrollbar-track-color);border-radius:var(--radius-sm)}.overflow-y-auto::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb-color);border-radius:var(--radius-sm)}.overflow-y-auto::-webkit-scrollbar-thumb:hover{background-color:var(--scrollbar-thumb-hover-color)}.chat-markdown{color:var(--tw-prose-body);font-size:1rem;font-size:.875rem;line-height:1.75;line-height:1.71429;max-width:65ch;--tw-prose-body:oklch(37.3% .034 259.733);--tw-prose-headings:oklch(21% .034 264.665);--tw-prose-lead:oklch(44.6% .03 256.802);--tw-prose-links:oklch(21% .034 264.665);--tw-prose-bold:oklch(21% .034 264.665);--tw-prose-counters:oklch(55.1% .027 264.364);--tw-prose-bullets:oklch(87.2% .01 258.338);--tw-prose-hr:oklch(92.8% .006 264.531);--tw-prose-quotes:oklch(21% .034 264.665);--tw-prose-quote-borders:oklch(92.8% .006 264.531);--tw-prose-captions:oklch(55.1% .027 264.364);--tw-prose-kbd:oklch(21% .034 264.665);--tw-prose-kbd-shadows:NaN NaN NaN;--tw-prose-code:oklch(21% .034 264.665);--tw-prose-pre-code:oklch(92.8% .006 264.531);--tw-prose-pre-bg:oklch(27.8% .033 256.848);--tw-prose-th-borders:oklch(87.2% .01 258.338);--tw-prose-td-borders:oklch(92.8% .006 264.531);--tw-prose-invert-body:oklch(87.2% .01 258.338);--tw-prose-invert-headings:#fff;--tw-prose-invert-lead:oklch(70.7% .022 261.325);--tw-prose-invert-links:#fff;--tw-prose-invert-bold:#fff;--tw-prose-invert-counters:oklch(70.7% .022 261.325);--tw-prose-invert-bullets:oklch(44.6% .03 256.802);--tw-prose-invert-hr:oklch(37.3% .034 259.733);--tw-prose-invert-quotes:oklch(96.7% .003 264.542);--tw-prose-invert-quote-borders:oklch(37.3% .034 259.733);--tw-prose-invert-captions:oklch(70.7% .022 261.325);--tw-prose-invert-kbd:#fff;--tw-prose-invert-kbd-shadows:255 255 255;--tw-prose-invert-code:#fff;--tw-prose-invert-pre-code:oklch(87.2% .01 258.338);--tw-prose-invert-pre-bg:#00000080;--tw-prose-invert-th-borders:oklch(44.6% .03 256.802);--tw-prose-invert-td-borders:oklch(37.3% .034 259.733);font-size:1em;max-width:none}.chat-markdown :where([class~=lead]):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-lead);font-size:1.25em;font-size:1.28571em;line-height:1.6;line-height:1.55556;margin-bottom:.888889em;margin-top:.888889em}.chat-markdown :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-links);font-weight:500;text-decoration:underline;text-decoration-line:none}.chat-markdown :where(strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-bold);font-weight:600}.chat-markdown :where(a strong):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(blockquote strong):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(thead th strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.chat-markdown :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal;margin-bottom:1.25em;margin-top:1.25em;padding-inline-start:1.625em}.chat-markdown :where(ol[type=A]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.chat-markdown :where(ol[type=a]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.chat-markdown :where(ol[type=A s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.chat-markdown :where(ol[type=a s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.chat-markdown :where(ol[type=I]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.chat-markdown :where(ol[type=i]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.chat-markdown :where(ol[type=I s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.chat-markdown :where(ol[type=i s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.chat-markdown :where(ol[type=\"1\"]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal}.chat-markdown :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:disc;margin-bottom:1.25em;margin-top:1.25em;padding-inline-start:1.625em}.chat-markdown :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-counters);font-weight:400}.chat-markdown :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-bullets)}.chat-markdown :where(dt):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;margin-top:1.14286em}.chat-markdown :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:var(--tw-prose-hr);border-top-width:1px;margin-bottom:2.85714em;margin-top:2.85714em}.chat-markdown :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){border-inline-start-color:var(--tw-prose-quote-borders);border-inline-start-width:.25rem;color:var(--tw-prose-quotes);font-style:italic;font-weight:500;margin-bottom:1.33333em;margin-top:1.33333em;padding-inline-start:1em;padding-inline-start:1.11111em;quotes:\"\u201C\"\"\u201D\"\"\u2018\"\"\u2019\"}.chat-markdown :where(blockquote p:first-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:open-quote}.chat-markdown :where(blockquote p:last-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:close-quote}.chat-markdown :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-size:2.25em;font-size:2.14286em;font-weight:800;line-height:1.11111;line-height:1.2;margin-bottom:.8em;margin-top:0}.chat-markdown :where(h1 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:900}.chat-markdown :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-size:1.5em;font-size:1.42857em;font-weight:700;line-height:1.33333;line-height:1.4;margin-bottom:.8em;margin-top:1.6em}.chat-markdown :where(h2 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:800}.chat-markdown :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-size:1.25em;font-size:1.28571em;font-weight:600;line-height:1.6;line-height:1.55556;margin-bottom:.444444em;margin-top:1.55556em}.chat-markdown :where(h3 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:700}.chat-markdown :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;line-height:1.5;line-height:1.42857;margin-bottom:.571429em;margin-top:1.42857em}.chat-markdown :where(h4 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:700}.chat-markdown :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){display:block;margin-bottom:2em;margin-top:2em}.chat-markdown :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){border-radius:.3125rem;box-shadow:0 0 0 1px rgb(var(--tw-prose-kbd-shadows)/10%),0 3px 0 rgb(var(--tw-prose-kbd-shadows)/10%);color:var(--tw-prose-kbd);font-family:inherit;font-size:.875em;font-size:.857143em;font-weight:500;padding-inline-end:.375em;padding-inline-end:.357143em;padding-bottom:.142857em;padding-inline-start:.375em;padding-top:.142857em;padding-inline-start:.357143em}.chat-markdown :where(code):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-code);font-size:.875em;font-size:.857143em;font-weight:600}.chat-markdown :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):after,.chat-markdown :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:\"\\`\"}.chat-markdown :where(a code):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h1 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.chat-markdown :where(h2 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.875em;font-size:.9em}.chat-markdown :where(h3 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.9em;font-size:.888889em}.chat-markdown :where(blockquote code):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h4 code):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(thead th code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.chat-markdown :where(pre):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:var(--tw-prose-pre-bg);border-radius:.375rem;border-radius:.25rem;color:var(--tw-prose-pre-code);font-size:.875em;font-size:.857143em;font-weight:400;line-height:1.71429;line-height:1.66667;margin-bottom:1.66667em;margin-top:1.66667em;overflow-x:auto;padding-inline-end:1.14286em;padding-inline-end:1em;padding-bottom:.666667em;padding-inline-start:1.14286em;padding-top:.666667em;padding-inline-start:1em}.chat-markdown :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:#0000;border-radius:0;border-width:0;color:inherit;font-family:inherit;font-size:inherit;font-weight:inherit;line-height:inherit;padding:0}.chat-markdown :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):after,.chat-markdown :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:none}.chat-markdown :where(table):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.875em;font-size:.857143em;line-height:1.71429;line-height:1.5;margin-bottom:2em;margin-top:2em;table-layout:auto;width:100%}.chat-markdown :where(thead):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-color:var(--tw-prose-th-borders);border-bottom-width:1px}.chat-markdown :where(thead th):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;padding-inline-end:.571429em;padding-inline-end:1em;padding-bottom:.666667em;padding-inline-start:.571429em;padding-inline-start:1em;vertical-align:bottom}.chat-markdown :where(tbody tr):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-color:var(--tw-prose-td-borders);border-bottom-width:1px}.chat-markdown :where(tbody tr:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:0}.chat-markdown :where(tbody td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:baseline}.chat-markdown :where(tfoot):not(:where([class~=not-prose],[class~=not-prose] *)){border-top-color:var(--tw-prose-th-borders);border-top-width:1px}.chat-markdown :where(tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:top}.chat-markdown :where(th,td):not(:where([class~=not-prose],[class~=not-prose] *)){text-align:start}.chat-markdown :where(figcaption):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-captions);font-size:.875em;font-size:.857143em;line-height:1.42857;line-height:1.33333;margin-top:.666667em}.chat-markdown :where(.prose>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.75em;margin-top:.75em}.chat-markdown :where(.prose>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.chat-markdown :where(.prose>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.chat-markdown :where(.prose>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.chat-markdown :where(.prose>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.chat-markdown :where(.prose>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.chat-markdown :where(.prose>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}.chat-markdown :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.14286em;margin-top:1.14286em}.chat-markdown :where(img):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.71429em;margin-top:1.71429em}.chat-markdown :where(picture>img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0;margin-top:0}.chat-markdown :where(video):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.71429em;margin-top:1.71429em}.chat-markdown :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.14286em;margin-top:1.14286em;padding-inline-start:1.57143em}.chat-markdown :where(li):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.285714em;margin-top:.285714em}.chat-markdown :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.428571em}.chat-markdown :where(.prose-sm>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.571429em;margin-top:.571429em}.chat-markdown :where(.prose-sm>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.14286em}.chat-markdown :where(.prose-sm>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.14286em}.chat-markdown :where(.prose-sm>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.14286em}.chat-markdown :where(.prose-sm>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.14286em}.chat-markdown :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.571429em;margin-top:.571429em}.chat-markdown :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.14286em;margin-top:1.14286em}.chat-markdown :where(dd):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.285714em;padding-inline-start:1.57143em}.chat-markdown :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.chat-markdown :where(thead th:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.chat-markdown :where(thead th:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.chat-markdown :where(tbody td,tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:1em;padding-bottom:.666667em;padding-top:.666667em;padding-inline-start:1em}.chat-markdown :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.chat-markdown :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.chat-markdown :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.71429em;margin-top:1.71429em}.chat-markdown :where(figure>*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0;margin-top:0}.chat-markdown :where(.prose-sm>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.chat-markdown :where(.prose-sm>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}.chat-markdown>*{margin-bottom:.1em;margin-top:.1em}.message-bubble-assistant .chat-markdown{--tw-prose-body:var(--message-assistant-text-color);--tw-prose-headings:var(--message-assistant-text-color);--tw-prose-lead:var(--message-assistant-text-color);--tw-prose-links:var(--message-assistant-link-color);--tw-prose-bold:var(--message-assistant-text-color);--tw-prose-counters:var(--message-assistant-text-color);--tw-prose-bullets:var(--message-assistant-text-color);--tw-prose-hr:var(--message-assistant-text-color);--tw-prose-quotes:var(--message-assistant-text-color);--tw-prose-quote-borders:var(--message-assistant-text-color);--tw-prose-captions:var(--message-assistant-text-color);--tw-prose-kbd:var(--message-assistant-text-color);--tw-prose-kbd-shadows:var(--message-assistant-text-color);--tw-prose-code:var(--code-text-assistant-color);--tw-prose-pre-code:var(--code-text-assistant-color);--tw-prose-pre-bg:var(--code-bg-assistant-color);--tw-prose-th-borders:var(--message-assistant-text-color);--tw-prose-td-borders:var(--message-assistant-text-color)}.message-bubble-user .chat-markdown{--tw-prose-body:var(--message-user-text-color);--tw-prose-headings:var(--message-user-text-color);--tw-prose-lead:var(--message-user-text-color);--tw-prose-links:var(--message-user-link-color);--tw-prose-bold:var(--message-user-text-color);--tw-prose-counters:var(--message-user-text-color);--tw-prose-bullets:var(--message-user-text-color);--tw-prose-hr:var(--message-user-text-color);--tw-prose-quotes:var(--message-user-text-color);--tw-prose-quote-borders:var(--message-user-text-color);--tw-prose-captions:var(--message-user-text-color);--tw-prose-kbd:var(--message-user-text-color);--tw-prose-kbd-shadows:var(--message-user-text-color);--tw-prose-code:var(--code-text-user-color);--tw-prose-pre-code:var(--code-text-user-color);--tw-prose-pre-bg:var(--code-bg-user-color);--tw-prose-th-borders:var(--message-user-text-color);--tw-prose-td-borders:var(--message-user-text-color)}.message-bubble-system .chat-markdown{--tw-prose-body:var(--message-system-text-color);--tw-prose-headings:var(--message-system-text-color);--tw-prose-lead:var(--message-system-text-color);--tw-prose-links:var(--message-system-link-color);--tw-prose-bold:var(--message-system-text-color);--tw-prose-counters:var(--message-system-text-color);--tw-prose-bullets:var(--message-system-text-color);--tw-prose-hr:var(--message-system-text-color);--tw-prose-quotes:var(--message-system-text-color);--tw-prose-quote-borders:var(--message-system-text-color);--tw-prose-captions:var(--message-system-text-color);--tw-prose-kbd:var(--message-system-text-color);--tw-prose-kbd-shadows:var(--message-system-text-color);--tw-prose-code:var(--message-system-text-color);--tw-prose-pre-code:var(--message-system-text-color);--tw-prose-pre-bg:var(--message-system-text-color);--tw-prose-th-borders:var(--message-system-text-color);--tw-prose-td-borders:var(--message-system-text-color)}.message-bubble-user .chat-markdown pre{border:1px solid var(--code-border-user-color)}.message-bubble-assistant .chat-markdown pre{border:1px solid var(--code-border-assistant-color)}.loading:after{content:\" .\"}.file-attachment-button{border-radius:var(--radius-md);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;background-color:var(--file-attachment-button-bg-color);color:var(--file-attachment-button-text-color);padding:.375em;transition-duration:.2s}.file-attachment-button:disabled{color:var(--file-attachment-button-text-disabled-color);cursor:not-allowed;opacity:.5}.file-attachment-button svg{height:1.5em;width:1.5em}.file-attachment-button:hover:not(:disabled){background-color:var(--file-attachment-button-bg-hover-color)}.selected-files-container{background-color:var(--selected-files-bg-color);border-top:1px solid var(--selected-files-border-color);padding:1em 1em .5em}.selected-file-item{align-items:center;background-color:var(--selected-file-bg-color);border-radius:.375em;color:var(--selected-file-name-color);display:flex;font-size:var(--selected-file-font-size);justify-content:space-between;padding:.25em .5em}.selected-file-icon{align-items:center;display:flex;justify-content:center}.selected-file-icon svg{height:var(--selected-file-icon-size);width:var(--selected-file-icon-size)}.selected-file-size{color:var(--selected-file-size-color)}.selected-file-error{color:var(--error-text-color)}.selected-file-success-icon{align-items:center;color:var(--success-text-color);display:flex;height:var(--selected-file-icon-size);justify-content:center;width:var(--selected-file-icon-size)}.selected-file-remove-button{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;color:var(--selected-file-remove-icon-color);padding:.375em;transition-duration:.2s}.selected-file-remove-button svg{height:var(--selected-file-icon-size);width:var(--selected-file-icon-size)}.selected-file-remove-button:hover{color:var(--selected-file-remove-icon-hover-color)}.message-attachments{font-size:var(--chat-window-font-size-sm);margin-top:.5em}:where(.message-attachments>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(.25em*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(.25em*var(--tw-space-y-reverse))}.message-attachment-icon{align-items:center;display:flex;height:var(--message-attachment-icon-size);justify-content:center;width:var(--message-attachment-icon-size)}.send-button-disabled{background-color:var(--send-button-bg-disabled-color);color:var(--send-button-text-disabled-color);cursor:not-allowed}@property --tw-rotate-x{syntax:\"*\";inherits:false}@property --tw-rotate-y{syntax:\"*\";inherits:false}@property --tw-rotate-z{syntax:\"*\";inherits:false}@property --tw-skew-x{syntax:\"*\";inherits:false}@property --tw-skew-y{syntax:\"*\";inherits:false}@property --tw-space-y-reverse{syntax:\"*\";inherits:false;initial-value:0}@property --tw-border-style{syntax:\"*\";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:\"*\";inherits:false}@property --tw-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:\"*\";inherits:false}@property --tw-shadow-alpha{syntax:\"<percentage>\";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:\"*\";inherits:false}@property --tw-inset-shadow-alpha{syntax:\"<percentage>\";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:\"*\";inherits:false}@property --tw-ring-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:\"*\";inherits:false}@property --tw-inset-ring-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:\"*\";inherits:false}@property --tw-ring-offset-width{syntax:\"<length>\";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:\"*\";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-duration{syntax:\"*\";inherits:false}@property --tw-ease{syntax:\"*\";inherits:false}@property --tw-scale-x{syntax:\"*\";inherits:false;initial-value:1}@property --tw-scale-y{syntax:\"*\";inherits:false;initial-value:1}@property --tw-scale-z{syntax:\"*\";inherits:false;initial-value:1}@keyframes spin{to{transform:rotate(1turn)}}@keyframes progress{0%{transform:translate(0)scaleX(0)}10%{transform:translate(0)scaleX(.3)}50%{transform:translate(100%)scaleX(.3)}90%{transform:translate(0)scaleX(.3)}to{transform:translate(0)scaleX(0)}}@keyframes dots{0%,20%{color:#0000;text-shadow:.25em 0 #0000,.5em 0 #0000}40%{color:#000;text-shadow:.25em 0 #0000,.5em 0 #0000}60%{text-shadow:.25em 0 #000,.5em 0 #0000}80%,to{text-shadow:.25em 0 #000,.5em 0 #000}}@media (min-width:40rem){.container{max-width:40rem}.chat-window-normal{height:var(--chat-window-height);width:var(--chat-window-width)}.drag-indicator{display:flex}.fullscreen-button{display:block}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}@media (hover:hover){.chat-btn-icon:hover,.chat-btn-text:hover{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y)}.attachment-link:hover{text-decoration-line:none}.chat-markdown :where(a):not(:where([class~=not-prose],[class~=not-prose] *)):hover{text-decoration-line:underline}}";
6245
+ const ocsChatCss = "/*! tailwindcss v4.1.12 | MIT License | https://tailwindcss.com */@layer properties{@supports ((-webkit-hyphens:none) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,::backdrop,:after,:before{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial;--tw-ease:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@layer theme{:host,:root{--font-sans:ui-sans-serif,system-ui,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\",\"Noto Color Emoji\";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;--color-blue-300:oklch(80.9% .105 251.813);--color-slate-500:oklch(55.4% .046 257.417);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--spacing:.25rem;--breakpoint-lg:64rem;--container-sm:24rem;--font-weight-light:300;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--animate-progress:progress 3s infinite linear;--animate-dots:dots 1s steps(5,end)infinite;--transform-origin-left-right:0% 50%}}@layer base{*,::backdrop,:after,:before{border:0 solid;border-color:var(--color-gray-200,currentcolor);box-sizing:border-box;margin:0;padding:0}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button;background-color:#0000;border:0 solid;border-color:var(--color-gray-200,currentcolor);border-radius:0;box-sizing:border-box;color:inherit;font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;margin:0;margin-inline-end:4px;opacity:1;padding:0}:host,html{-webkit-text-size-adjust:100%;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\",\"Noto Color Emoji\");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-size:1em;font-variation-settings:var(--default-mono-font-variation-settings,normal)}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}menu,ol,ul{list-style:none}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}button,input,optgroup,select,textarea{background-color:#0000;border-radius:0;color:inherit;font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;opacity:1}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::-moz-placeholder{opacity:1}::placeholder{opacity:1}@supports (not (-webkit-appearance:-apple-pay-button)) or (contain-intrinsic-size:1px){::-moz-placeholder{color:currentColor}::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::-moz-placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex;padding-block:0}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.fixed{position:fixed}.relative{position:relative}.static{position:static}.container{width:100%}.block{display:block}.contents{display:contents}.flex{display:flex}.hidden{display:none}.w-full{width:100%}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.resize{resize:both}.items-center{align-items:center}.justify-center{justify-content:center}.gap-\\[0\\.5em\\]{gap:.5em}:where(.space-y-\\[0\\.25em\\]>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(.25em*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(.25em*var(--tw-space-y-reverse))}.border{border-style:var(--tw-border-style);border-width:1px}.py-\\[2px\\]{padding-block:2px}.text-\\[0\\.8em\\]{font-size:.8em}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.text-slate-500{color:var(--color-slate-500)}.underline{text-decoration-line:underline}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a)}.ring,.shadow{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor)}.transition{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,backdrop-filter,display,visibility,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))}} /*! tailwindcss v4.1.12 | MIT License | https://tailwindcss.com */@layer properties{}@layer base{}@layer components; /*! tailwindcss v4.1.12 | MIT License | https://tailwindcss.com */@layer properties{}@layer base{}@layer components{#ocs-chat-window{font-size:var(--chat-window-font-size);z-index:var(--chat-z-index)}.starter-question{border-radius:var(--radius-lg);text-align:left;--tw-duration:.2s;background-color:var(--starter-question-bg-color);border:1px solid var(--starter-question-border-color);color:var(--starter-question-text-color);padding:.75em;transition-duration:.2s}.starter-question:hover{background-color:var(--starter-question-bg-hover-color);border-color:var(--starter-question-border-hover-color)}.chat-btn-text{border-radius:var(--radius-lg);border-style:var(--tw-border-style);transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;--tw-ease:var(--ease-in-out);align-items:center;background-color:var(--button-background-color,#fff);border-width:0;border:1px solid var(--button-border-color);color:var(--button-text-color,#111827);display:flex;font-size:var(--button-font-size);gap:8px;padding:.5em;transition-duration:.2s;transition-timing-function:var(--ease-in-out);z-index:var(--chat-z-index,50)}.chat-btn-text:hover{border:1px solid var(--button-border-color-hover);color:var(--button-text-color-hover,#1d4ed8)}.chat-btn-text span{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);white-space:nowrap}.chat-btn-text img{flex-shrink:0;height:var(--button-icon-size);-o-object-fit:contain;object-fit:contain;width:var(--button-icon-size)}.chat-btn-text.round{border-radius:3.40282e+38px}.chat-btn-icon{border-radius:var(--radius-lg);border-style:var(--tw-border-style);transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;--tw-ease:var(--ease-in-out);background-color:var(--button-background-color,#fff);border-width:0;border:1px solid var(--button-border-color);font-size:var(--button-font-size);padding:.5em;transition-duration:.2s;transition-timing-function:var(--ease-in-out);z-index:var(--chat-z-index,50)}.chat-btn-icon:hover{border:1px solid var(--button-border-color-hover);color:var(--button-text-color-hover,#1d4ed8)}.chat-btn-icon img{height:var(--button-icon-size);-o-object-fit:contain;object-fit:contain;width:var(--button-icon-size)}.chat-btn-icon.round,.round .chat-btn-icon,.round.chat-btn-text{border-radius:3.40282e+38px}.error-message{color:var(--error-text-color);padding:.5em}.chat-window-fullscreen{border-style:var(--tw-border-style);inset:calc(var(--spacing)*0);z-index:9999;--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);height:100%;max-height:100%;transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));width:100%;--tw-duration:.2s;border-radius:0;border-width:0;max-width:var(--chat-window-fullscreen-width);transition-duration:.2s}.chat-window-fullscreen,.chat-window-normal{background-color:var(--chat-window-bg-color);display:flex;flex-direction:column;overflow:hidden;position:fixed}.chat-window-normal{border:1px solid var(--chat-window-border-color);border-radius:var(--radius-lg);height:100vh;max-width:var(--breakpoint-lg);min-height:300px;min-width:300px;width:100vw}.chat-window-normal:not(.chat-window-dragging){--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;transition-duration:.2s}.chat-window-dragging,.chat-window-normal:not(.chat-window-dragging){box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.chat-window-dragging{cursor:grabbing;--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040)}.chat-window-kiosk{background-color:var(--chat-window-bg-color);border-radius:0;box-shadow:none;display:flex;flex-direction:column;font-size:var(--chat-window-font-size);height:100%;inset:calc(var(--spacing)*0);overflow:hidden;position:absolute;width:100%;z-index:var(--chat-z-index)}.chat-header{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.15s;align-items:center;background-color:var(--header-bg-color);border-bottom:1px solid var(--header-border-color);display:flex;font-size:var(--header-font-size);justify-content:space-between;padding:.5em;transition-duration:.15s}.chat-header:active,.chat-header:hover{background-color:var(--header-bg-hover-color)}.header-text{align-items:center;color:var(--header-text-color);display:flex;font-size:var(--header-text-font-size);justify-content:center}.chat-header-draggable{cursor:grab}.chat-header-dragging{cursor:grabbing}.drag-indicator{display:none}.drag-dots{display:flex;gap:2px;margin-left:2px;pointer-events:none}.header-buttons{align-items:center;display:flex;gap:4px}.header-button{border-radius:var(--radius-md);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;color:var(--header-button-text-color);padding:.375em;transition-duration:.2s}.header-button svg{height:var(--header-button-icon-size);width:var(--header-button-icon-size)}.header-button:hover{background-color:var(--header-button-bg-hover-color)}.fullscreen-button{display:none}.chat-content{display:flex;flex-direction:column;flex-grow:1;overflow:hidden}.loading-container{align-items:center;display:flex;flex-grow:1;justify-content:center}.loading-text{color:var(--loading-text-color);margin-left:2px}.messages-container{flex-grow:1;overflow-y:auto;padding:1em}:where(.messages-container>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(var(--spacing)*2*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(var(--spacing)*2*var(--tw-space-y-reverse))}.message-row{display:flex}.message-row-user{justify-content:flex-end}.message-row-assistant{justify-content:flex-start}.message-bubble{border-radius:var(--radius-lg);padding:.5em 1em}.message-bubble-user{background-color:var(--message-user-bg-color);color:var(--message-user-text-color)}.message-bubble-assistant{background-color:var(--message-assistant-bg-color);color:var(--message-assistant-text-color)}.message-bubble-system{background-color:var(--message-system-bg-color);color:var(--message-system-text-color)}.message-timestamp{font-size:var(--chat-window-font-size-sm);margin-top:4px;opacity:.7}.message-attachments{margin-top:8px}:where(.message-attachments>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(var(--spacing)*1*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(var(--spacing)*1*var(--tw-space-y-reverse))}.attachment-link{display:block;text-decoration-line:underline}:where(.welcome-messages>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(var(--spacing)*2*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(var(--spacing)*2*var(--tw-space-y-reverse))}.typing-indicator{height:calc(var(--spacing)*1.5);overflow:hidden;width:100%}.typing-progress{animation:var(--animate-progress);background-color:var(--typing-progress-bg-color);border-radius:var(--radius-lg);height:100%;transform-origin:var(--transform-origin-left-right);width:100%}.typing-text{font-size:var(--chat-window-font-size-sm);justify-content:center;opacity:.7;width:100%}.typing-dots{animation:var(--animate-dots)}:where(.starter-questions>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(var(--spacing)*2*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(var(--spacing)*2*var(--tw-space-y-reverse))}.starter-questions{padding:1em}.starter-question-row{display:flex;justify-content:flex-end}.input-area{background-color:var(--input-bg-color);border-top:1px solid var(--input-border-color);padding:1em 1em 0}.input-container{display:flex;gap:8px}.message-textarea{background-color:var(--input-bg-color);border-color:var(--color-gray-300);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border:1px solid var(--input-border-color);color:var(--input-text-color);flex-grow:1;padding:.5em .75em;resize:none}.message-textarea:focus{outline-color:var(--input-outline-focus-color)}.send-button{border-radius:var(--radius-md);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;padding:.5em 1em;transition-duration:.2s}.send-button-enabled{background-color:var(--send-button-bg-color);color:var(--send-button-text-color)}.send-button-enabled:hover{background-color:var(--send-button-bg-hover-color)}.send-button-disabled{background-color:var(--send-button-bg-disabled-color);color:var(--send-button-text-disabled-color);cursor:not-allowed}.confirmation-overlay{align-items:center;background-color:var(--confirmation-overlay-bg-color);display:flex;inset:calc(var(--spacing)*0);justify-content:center;position:fixed;z-index:9999}.confirmation-dialog{background-color:var(--confirmation-dialog-bg-color);border:1px solid var(--confirmation-dialog-border-color);border-radius:.75em;box-shadow:0 .625em 1.5625em var(--confirmation-dialog-shadow-color);margin-inline:calc(var(--spacing)*4);max-width:var(--container-sm);width:100%}.confirmation-content{padding:1.5em}.confirmation-title{margin-bottom:calc(var(--spacing)*2);--tw-font-weight:var(--font-weight-semibold);color:var(--confirmation-title-color);font-size:var(--confirmation-title-font-size);font-weight:var(--font-weight-semibold)}.confirmation-message{color:var(--confirmation-message-color);font-size:var(--confirmation-message-font-size);margin-bottom:calc(var(--spacing)*4)}.confirmation-buttons{display:flex;gap:.75em;justify-content:flex-end}.confirmation-button{border-radius:var(--radius-md);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;padding:.5em 1em;transition-duration:.2s}.confirmation-button-cancel{background-color:var(--confirmation-button-cancel-bg-color);color:var(--confirmation-button-cancel-text-color)}.confirmation-button-cancel:hover{background-color:var(--confirmation-button-cancel-bg-hover-color)}.confirmation-button-confirm{background-color:var(--confirmation-button-confirm-bg-color);color:var(--confirmation-button-confirm-text-color)}.confirmation-button-confirm:hover{background-color:var(--confirmation-button-confirm-bg-hover-color)}}@layer utilities{:host{--chat-z-index:50;--button-background-color:#fff;--button-background-color-hover:#f3f4f6;--button-text-color:#111827;--button-text-color-hover:#1d4ed8;--button-border-color:#d1d5db;--button-border-color-hover:#6b7280;--button-font-size:1em;--button-icon-size:1.5em;--chat-window-height:60%;--chat-window-width:25%;--chat-window-fullscreen-width:80%;--chat-window-bg-color:#fff;--chat-window-border-color:#d1d5db;--chat-window-shadow-color:#0000001a;--chat-window-font-size:.875em;--chat-window-font-size-sm:.75em;--header-bg-color:transparent;--header-bg-hover-color:#f9fafb;--header-border-color:#f3f4f6;--header-button-text-color:#6b7280;--header-button-bg-hover-color:#f3f4f6;--header-font-size:1em;--header-text-font-size:1em;--header-text-color:#525762;--header-button-icon-size:1.5em;--starter-question-bg-color:transparent;--starter-question-bg-hover-color:#eff6ff;--starter-question-text-color:#3b82f6;--starter-question-border-color:#3b82f6;--starter-question-border-hover-color:#2563eb;--message-user-bg-color:#e4edfb;--message-user-text-color:#1f2937;--message-user-link-color:#155dfc;--message-assistant-bg-color:#eae7e8;--message-assistant-text-color:var(--message-user-text-color);--message-assistant-link-color:var(--message-user-link-color);--message-system-bg-color:#fbe4f8;--message-system-text-color:var(--message-user-text-color);--message-system-link-color:var(--message-user-link-color);--message-timestamp-color:#ffffffb3;--message-timestamp-assistant-color:#4b5563b3;--input-bg-color:transparent;--input-border-color:#d1d5db;--input-text-color:#111827;--input-placeholder-color:#6b7280;--input-outline-focus-color:#3b82f6;--send-button-bg-color:#3b82f6;--send-button-bg-hover-color:#2563eb;--send-button-text-color:#fff;--send-button-bg-disabled-color:#d1d5db;--send-button-text-disabled-color:#6b7280;--loading-text-color:#6b7280;--loading-spinner-track-color:#e5e7eb;--loading-spinner-fill-color:#3b82f6;--loading-spinner-size:1.25em;--typing-progress-bg-color:#ade3ff;--scrollbar-track-color:#f3f4f6;--scrollbar-thumb-color:#d1d5db;--scrollbar-thumb-hover-color:#9ca3af;--error-text-color:#ef4444;--success-text-color:#10b981;--code-bg-user-color:var(--message-user-bg-color);--code-text-user-color:var(--message-user-text-color);--code-border-user-color:var(--message-user-bg-color);--code-bg-assistant-color:var(--message-assistant-bg-color);--code-text-assistant-color:var(--message-assistant-text-color);--code-border-assistant-color:var(--message-assistant-bg-color);--confirmation-overlay-bg-color:#00000080;--confirmation-dialog-bg-color:var(--chat-window-bg-color);--confirmation-dialog-border-color:var(--chat-window-border-color);--confirmation-dialog-shadow-color:var(--chat-window-shadow-color);--confirmation-title-color:#111827;--confirmation-title-font-size:1.125em;--confirmation-message-color:var(--loading-text-color);--confirmation-message-font-size:1em;--confirmation-button-cancel-bg-color:var(--button-background-color-hover);--confirmation-button-cancel-bg-hover-color:#e5e7eb;--confirmation-button-cancel-text-color:var(--header-button-text-color);--confirmation-button-confirm-bg-color:var(--error-text-color);--confirmation-button-confirm-bg-hover-color:var(--error-text-color);--confirmation-button-confirm-text-color:var(--send-button-text-color);--file-attachment-button-bg-color:transparent;--file-attachment-button-bg-hover-color:var(--header-button-bg-hover-color);--file-attachment-button-text-color:var(--header-button-text-color);--file-attachment-button-text-disabled-color:var(--send-button-text-disabled-color);--selected-files-bg-color:var(--chat-window-bg-color);--selected-files-border-color:var(--header-border-color);--selected-file-bg-color:var(--button-background-color-hover);--selected-file-font-size:var(--chat-window-font-size-sm);--selected-file-name-color:var(--message-assistant-text-color);--selected-file-size-color:var(--input-placeholder-color);--selected-file-icon-size:1.25em;--selected-file-remove-icon-color:var(--error-text-color);--selected-file-remove-icon-hover-color:#dc2626;--message-attachment-icon-size:1em;bottom:30px;display:block;position:fixed;right:30px}@supports (color:color-mix(in lab,red,red)){:host{--code-bg-user-color:color-mix(in srgb,var(--message-user-bg-color)80%,#fff 20%);--code-border-user-color:color-mix(in srgb,var(--message-user-bg-color)90%,#000 10%);--code-bg-assistant-color:color-mix(in srgb,var(--message-assistant-bg-color)50%,#fff 50%);--code-border-assistant-color:color-mix(in srgb,var(--message-assistant-bg-color)90%,#000 10%)}}:host([mode=kiosk]){height:100%;inset:0 auto auto 0;position:absolute;width:100%}}textarea{max-height:calc(var(--spacing)*32);min-height:calc(var(--spacing)*10);overflow-y:auto;resize:none}.loading-spinner{animation:var(--animate-spin);border-color:var(--loading-spinner-track-color);border-radius:3.40282e+38px;border-style:var(--tw-border-style);border-top-color:var(--loading-spinner-fill-color);border-width:2px;height:var(--loading-spinner-size);width:var(--loading-spinner-size)}.overflow-y-auto::-webkit-scrollbar{height:calc(var(--spacing)*1.5);width:calc(var(--spacing)*1.5)}.overflow-y-auto::-webkit-scrollbar-track{background-color:var(--scrollbar-track-color);border-radius:var(--radius-sm)}.overflow-y-auto::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb-color);border-radius:var(--radius-sm)}.overflow-y-auto::-webkit-scrollbar-thumb:hover{background-color:var(--scrollbar-thumb-hover-color)}.chat-markdown{color:var(--tw-prose-body);font-size:1rem;font-size:.875rem;line-height:1.75;line-height:1.71429;max-width:65ch;--tw-prose-body:oklch(37.3% .034 259.733);--tw-prose-headings:oklch(21% .034 264.665);--tw-prose-lead:oklch(44.6% .03 256.802);--tw-prose-links:oklch(21% .034 264.665);--tw-prose-bold:oklch(21% .034 264.665);--tw-prose-counters:oklch(55.1% .027 264.364);--tw-prose-bullets:oklch(87.2% .01 258.338);--tw-prose-hr:oklch(92.8% .006 264.531);--tw-prose-quotes:oklch(21% .034 264.665);--tw-prose-quote-borders:oklch(92.8% .006 264.531);--tw-prose-captions:oklch(55.1% .027 264.364);--tw-prose-kbd:oklch(21% .034 264.665);--tw-prose-kbd-shadows:NaN NaN NaN;--tw-prose-code:oklch(21% .034 264.665);--tw-prose-pre-code:oklch(92.8% .006 264.531);--tw-prose-pre-bg:oklch(27.8% .033 256.848);--tw-prose-th-borders:oklch(87.2% .01 258.338);--tw-prose-td-borders:oklch(92.8% .006 264.531);--tw-prose-invert-body:oklch(87.2% .01 258.338);--tw-prose-invert-headings:#fff;--tw-prose-invert-lead:oklch(70.7% .022 261.325);--tw-prose-invert-links:#fff;--tw-prose-invert-bold:#fff;--tw-prose-invert-counters:oklch(70.7% .022 261.325);--tw-prose-invert-bullets:oklch(44.6% .03 256.802);--tw-prose-invert-hr:oklch(37.3% .034 259.733);--tw-prose-invert-quotes:oklch(96.7% .003 264.542);--tw-prose-invert-quote-borders:oklch(37.3% .034 259.733);--tw-prose-invert-captions:oklch(70.7% .022 261.325);--tw-prose-invert-kbd:#fff;--tw-prose-invert-kbd-shadows:255 255 255;--tw-prose-invert-code:#fff;--tw-prose-invert-pre-code:oklch(87.2% .01 258.338);--tw-prose-invert-pre-bg:#00000080;--tw-prose-invert-th-borders:oklch(44.6% .03 256.802);--tw-prose-invert-td-borders:oklch(37.3% .034 259.733);font-size:1em;max-width:none}.chat-markdown :where([class~=lead]):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-lead);font-size:1.25em;font-size:1.28571em;line-height:1.6;line-height:1.55556;margin-bottom:.888889em;margin-top:.888889em}.chat-markdown :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-links);font-weight:500;text-decoration:underline;text-decoration-line:none}.chat-markdown :where(strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-bold);font-weight:600}.chat-markdown :where(a strong):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(blockquote strong):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(thead th strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.chat-markdown :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal;margin-bottom:1.25em;margin-top:1.25em;padding-inline-start:1.625em}.chat-markdown :where(ol[type=A]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.chat-markdown :where(ol[type=a]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.chat-markdown :where(ol[type=A s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.chat-markdown :where(ol[type=a s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.chat-markdown :where(ol[type=I]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.chat-markdown :where(ol[type=i]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.chat-markdown :where(ol[type=I s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.chat-markdown :where(ol[type=i s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.chat-markdown :where(ol[type=\"1\"]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal}.chat-markdown :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:disc;margin-bottom:1.25em;margin-top:1.25em;padding-inline-start:1.625em}.chat-markdown :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-counters);font-weight:400}.chat-markdown :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-bullets)}.chat-markdown :where(dt):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;margin-top:1.14286em}.chat-markdown :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:var(--tw-prose-hr);border-top-width:1px;margin-bottom:2.85714em;margin-top:2.85714em}.chat-markdown :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){border-inline-start-color:var(--tw-prose-quote-borders);border-inline-start-width:.25rem;color:var(--tw-prose-quotes);font-style:italic;font-weight:500;margin-bottom:1.33333em;margin-top:1.33333em;padding-inline-start:1em;padding-inline-start:1.11111em;quotes:\"\u201C\"\"\u201D\"\"\u2018\"\"\u2019\"}.chat-markdown :where(blockquote p:first-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:open-quote}.chat-markdown :where(blockquote p:last-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:close-quote}.chat-markdown :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-size:2.25em;font-size:2.14286em;font-weight:800;line-height:1.11111;line-height:1.2;margin-bottom:.8em;margin-top:0}.chat-markdown :where(h1 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:900}.chat-markdown :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-size:1.5em;font-size:1.42857em;font-weight:700;line-height:1.33333;line-height:1.4;margin-bottom:.8em;margin-top:1.6em}.chat-markdown :where(h2 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:800}.chat-markdown :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-size:1.25em;font-size:1.28571em;font-weight:600;line-height:1.6;line-height:1.55556;margin-bottom:.444444em;margin-top:1.55556em}.chat-markdown :where(h3 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:700}.chat-markdown :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;line-height:1.5;line-height:1.42857;margin-bottom:.571429em;margin-top:1.42857em}.chat-markdown :where(h4 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:700}.chat-markdown :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){display:block;margin-bottom:2em;margin-top:2em}.chat-markdown :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){border-radius:.3125rem;box-shadow:0 0 0 1px rgb(var(--tw-prose-kbd-shadows)/10%),0 3px 0 rgb(var(--tw-prose-kbd-shadows)/10%);color:var(--tw-prose-kbd);font-family:inherit;font-size:.875em;font-size:.857143em;font-weight:500;padding-inline-end:.375em;padding-inline-end:.357143em;padding-bottom:.142857em;padding-inline-start:.375em;padding-top:.142857em;padding-inline-start:.357143em}.chat-markdown :where(code):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-code);font-size:.875em;font-size:.857143em;font-weight:600}.chat-markdown :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):after,.chat-markdown :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:\"\\`\"}.chat-markdown :where(a code):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h1 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.chat-markdown :where(h2 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.875em;font-size:.9em}.chat-markdown :where(h3 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.9em;font-size:.888889em}.chat-markdown :where(blockquote code):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h4 code):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(thead th code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.chat-markdown :where(pre):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:var(--tw-prose-pre-bg);border-radius:.375rem;border-radius:.25rem;color:var(--tw-prose-pre-code);font-size:.875em;font-size:.857143em;font-weight:400;line-height:1.71429;line-height:1.66667;margin-bottom:1.66667em;margin-top:1.66667em;overflow-x:auto;padding-inline-end:1.14286em;padding-inline-end:1em;padding-bottom:.666667em;padding-inline-start:1.14286em;padding-top:.666667em;padding-inline-start:1em}.chat-markdown :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:#0000;border-radius:0;border-width:0;color:inherit;font-family:inherit;font-size:inherit;font-weight:inherit;line-height:inherit;padding:0}.chat-markdown :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):after,.chat-markdown :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:none}.chat-markdown :where(table):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.875em;font-size:.857143em;line-height:1.71429;line-height:1.5;margin-bottom:2em;margin-top:2em;table-layout:auto;width:100%}.chat-markdown :where(thead):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-color:var(--tw-prose-th-borders);border-bottom-width:1px}.chat-markdown :where(thead th):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;padding-inline-end:.571429em;padding-inline-end:1em;padding-bottom:.666667em;padding-inline-start:.571429em;padding-inline-start:1em;vertical-align:bottom}.chat-markdown :where(tbody tr):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-color:var(--tw-prose-td-borders);border-bottom-width:1px}.chat-markdown :where(tbody tr:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:0}.chat-markdown :where(tbody td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:baseline}.chat-markdown :where(tfoot):not(:where([class~=not-prose],[class~=not-prose] *)){border-top-color:var(--tw-prose-th-borders);border-top-width:1px}.chat-markdown :where(tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:top}.chat-markdown :where(th,td):not(:where([class~=not-prose],[class~=not-prose] *)){text-align:start}.chat-markdown :where(figcaption):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-captions);font-size:.875em;font-size:.857143em;line-height:1.42857;line-height:1.33333;margin-top:.666667em}.chat-markdown :where(.prose>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.75em;margin-top:.75em}.chat-markdown :where(.prose>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.chat-markdown :where(.prose>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.chat-markdown :where(.prose>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.chat-markdown :where(.prose>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.chat-markdown :where(.prose>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.chat-markdown :where(.prose>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}.chat-markdown :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.14286em;margin-top:1.14286em}.chat-markdown :where(img):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.71429em;margin-top:1.71429em}.chat-markdown :where(picture>img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0;margin-top:0}.chat-markdown :where(video):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.71429em;margin-top:1.71429em}.chat-markdown :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.14286em;margin-top:1.14286em;padding-inline-start:1.57143em}.chat-markdown :where(li):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.285714em;margin-top:.285714em}.chat-markdown :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.428571em}.chat-markdown :where(.prose-sm>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.571429em;margin-top:.571429em}.chat-markdown :where(.prose-sm>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.14286em}.chat-markdown :where(.prose-sm>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.14286em}.chat-markdown :where(.prose-sm>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.14286em}.chat-markdown :where(.prose-sm>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.14286em}.chat-markdown :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.571429em;margin-top:.571429em}.chat-markdown :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.14286em;margin-top:1.14286em}.chat-markdown :where(dd):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.285714em;padding-inline-start:1.57143em}.chat-markdown :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.chat-markdown :where(thead th:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.chat-markdown :where(thead th:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.chat-markdown :where(tbody td,tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:1em;padding-bottom:.666667em;padding-top:.666667em;padding-inline-start:1em}.chat-markdown :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.chat-markdown :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.chat-markdown :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.71429em;margin-top:1.71429em}.chat-markdown :where(figure>*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0;margin-top:0}.chat-markdown :where(.prose-sm>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.chat-markdown :where(.prose-sm>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}.chat-markdown>*{margin-bottom:.1em;margin-top:.1em}.message-bubble-assistant .chat-markdown{--tw-prose-body:var(--message-assistant-text-color);--tw-prose-headings:var(--message-assistant-text-color);--tw-prose-lead:var(--message-assistant-text-color);--tw-prose-links:var(--message-assistant-link-color);--tw-prose-bold:var(--message-assistant-text-color);--tw-prose-counters:var(--message-assistant-text-color);--tw-prose-bullets:var(--message-assistant-text-color);--tw-prose-hr:var(--message-assistant-text-color);--tw-prose-quotes:var(--message-assistant-text-color);--tw-prose-quote-borders:var(--message-assistant-text-color);--tw-prose-captions:var(--message-assistant-text-color);--tw-prose-kbd:var(--message-assistant-text-color);--tw-prose-kbd-shadows:var(--message-assistant-text-color);--tw-prose-code:var(--code-text-assistant-color);--tw-prose-pre-code:var(--code-text-assistant-color);--tw-prose-pre-bg:var(--code-bg-assistant-color);--tw-prose-th-borders:var(--message-assistant-text-color);--tw-prose-td-borders:var(--message-assistant-text-color)}.message-bubble-user .chat-markdown{--tw-prose-body:var(--message-user-text-color);--tw-prose-headings:var(--message-user-text-color);--tw-prose-lead:var(--message-user-text-color);--tw-prose-links:var(--message-user-link-color);--tw-prose-bold:var(--message-user-text-color);--tw-prose-counters:var(--message-user-text-color);--tw-prose-bullets:var(--message-user-text-color);--tw-prose-hr:var(--message-user-text-color);--tw-prose-quotes:var(--message-user-text-color);--tw-prose-quote-borders:var(--message-user-text-color);--tw-prose-captions:var(--message-user-text-color);--tw-prose-kbd:var(--message-user-text-color);--tw-prose-kbd-shadows:var(--message-user-text-color);--tw-prose-code:var(--code-text-user-color);--tw-prose-pre-code:var(--code-text-user-color);--tw-prose-pre-bg:var(--code-bg-user-color);--tw-prose-th-borders:var(--message-user-text-color);--tw-prose-td-borders:var(--message-user-text-color)}.message-bubble-system .chat-markdown{--tw-prose-body:var(--message-system-text-color);--tw-prose-headings:var(--message-system-text-color);--tw-prose-lead:var(--message-system-text-color);--tw-prose-links:var(--message-system-link-color);--tw-prose-bold:var(--message-system-text-color);--tw-prose-counters:var(--message-system-text-color);--tw-prose-bullets:var(--message-system-text-color);--tw-prose-hr:var(--message-system-text-color);--tw-prose-quotes:var(--message-system-text-color);--tw-prose-quote-borders:var(--message-system-text-color);--tw-prose-captions:var(--message-system-text-color);--tw-prose-kbd:var(--message-system-text-color);--tw-prose-kbd-shadows:var(--message-system-text-color);--tw-prose-code:var(--message-system-text-color);--tw-prose-pre-code:var(--message-system-text-color);--tw-prose-pre-bg:var(--message-system-text-color);--tw-prose-th-borders:var(--message-system-text-color);--tw-prose-td-borders:var(--message-system-text-color)}.message-bubble-user .chat-markdown pre{border:1px solid var(--code-border-user-color)}.message-bubble-assistant .chat-markdown pre{border:1px solid var(--code-border-assistant-color)}.loading:after{content:\" .\"}.file-attachment-button{border-radius:var(--radius-md);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;background-color:var(--file-attachment-button-bg-color);color:var(--file-attachment-button-text-color);padding:.375em;transition-duration:.2s}.file-attachment-button:disabled{color:var(--file-attachment-button-text-disabled-color);cursor:not-allowed;opacity:.5}.file-attachment-button svg{height:1.5em;width:1.5em}.file-attachment-button:hover:not(:disabled){background-color:var(--file-attachment-button-bg-hover-color)}.selected-files-container{background-color:var(--selected-files-bg-color);border-top:1px solid var(--selected-files-border-color);padding:1em 1em .5em}.selected-file-item{align-items:center;background-color:var(--selected-file-bg-color);border-radius:.375em;color:var(--selected-file-name-color);display:flex;font-size:var(--selected-file-font-size);justify-content:space-between;padding:.25em .5em}.selected-file-icon{align-items:center;display:flex;justify-content:center}.selected-file-icon svg{height:var(--selected-file-icon-size);width:var(--selected-file-icon-size)}.selected-file-size{color:var(--selected-file-size-color)}.selected-file-error{color:var(--error-text-color)}.selected-file-success-icon{align-items:center;color:var(--success-text-color);display:flex;height:var(--selected-file-icon-size);justify-content:center;width:var(--selected-file-icon-size)}.selected-file-remove-button{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;color:var(--selected-file-remove-icon-color);padding:.375em;transition-duration:.2s}.selected-file-remove-button svg{height:var(--selected-file-icon-size);width:var(--selected-file-icon-size)}.selected-file-remove-button:hover{color:var(--selected-file-remove-icon-hover-color)}.message-attachments{font-size:var(--chat-window-font-size-sm);margin-top:.5em}:where(.message-attachments>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(.25em*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(.25em*var(--tw-space-y-reverse))}.message-attachment-icon{align-items:center;display:flex;height:var(--message-attachment-icon-size);justify-content:center;width:var(--message-attachment-icon-size)}.send-button-disabled{background-color:var(--send-button-bg-disabled-color);color:var(--send-button-text-disabled-color);cursor:not-allowed}@property --tw-rotate-x{syntax:\"*\";inherits:false}@property --tw-rotate-y{syntax:\"*\";inherits:false}@property --tw-rotate-z{syntax:\"*\";inherits:false}@property --tw-skew-x{syntax:\"*\";inherits:false}@property --tw-skew-y{syntax:\"*\";inherits:false}@property --tw-space-y-reverse{syntax:\"*\";inherits:false;initial-value:0}@property --tw-border-style{syntax:\"*\";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:\"*\";inherits:false}@property --tw-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:\"*\";inherits:false}@property --tw-shadow-alpha{syntax:\"<percentage>\";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:\"*\";inherits:false}@property --tw-inset-shadow-alpha{syntax:\"<percentage>\";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:\"*\";inherits:false}@property --tw-ring-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:\"*\";inherits:false}@property --tw-inset-ring-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:\"*\";inherits:false}@property --tw-ring-offset-width{syntax:\"<length>\";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:\"*\";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-duration{syntax:\"*\";inherits:false}@property --tw-ease{syntax:\"*\";inherits:false}@property --tw-scale-x{syntax:\"*\";inherits:false;initial-value:1}@property --tw-scale-y{syntax:\"*\";inherits:false;initial-value:1}@property --tw-scale-z{syntax:\"*\";inherits:false;initial-value:1}@keyframes spin{to{transform:rotate(1turn)}}@keyframes progress{0%{transform:translate(0)scaleX(0)}10%{transform:translate(0)scaleX(.3)}50%{transform:translate(100%)scaleX(.3)}90%{transform:translate(0)scaleX(.3)}to{transform:translate(0)scaleX(0)}}@keyframes dots{0%,20%{color:#0000;text-shadow:.25em 0 #0000,.5em 0 #0000}40%{color:#000;text-shadow:.25em 0 #0000,.5em 0 #0000}60%{text-shadow:.25em 0 #000,.5em 0 #0000}80%,to{text-shadow:.25em 0 #000,.5em 0 #000}}@media (min-width:40rem){.container{max-width:40rem}.chat-window-normal{height:var(--chat-window-height);width:var(--chat-window-width)}.drag-indicator{display:flex}.fullscreen-button{display:block}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}@media (hover:hover){.chat-btn-icon:hover,.chat-btn-text:hover{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y)}.attachment-link:hover{text-decoration-line:none}.chat-markdown :where(a):not(:where([class~=not-prose],[class~=not-prose] *)):hover{text-decoration-line:underline}}";
5393
6246
 
5394
6247
  const OcsChat = class {
5395
6248
  constructor(hostRef) {
@@ -5455,6 +6308,7 @@ const OcsChat = class {
5455
6308
  this.parsedStarterQuestions = [];
5456
6309
  this.isFullscreen = false;
5457
6310
  this.showNewChatConfirmation = false;
6311
+ this.sessionEnded = false;
5458
6312
  this.selectedFiles = [];
5459
6313
  this.isUploadingFiles = false;
5460
6314
  this.buttonPosition = { x: 30, y: 30 };
@@ -5680,6 +6534,23 @@ const OcsChat = class {
5680
6534
  this.removeButtonEventListeners();
5681
6535
  window.removeEventListener('resize', this.handleWindowResize);
5682
6536
  }
6537
+ // ---------------------------------------------------------------------------
6538
+ // Public event API
6539
+ // ---------------------------------------------------------------------------
6540
+ /**
6541
+ * Dispatch a composed, bubbling CustomEvent on the host element so that
6542
+ * embedders can listen with plain addEventListener outside the shadow root.
6543
+ *
6544
+ * All events are dispatched synchronously so that handlers can update
6545
+ * reactive props (e.g. `pageContext`) before the calling code reads them.
6546
+ */
6547
+ dispatchWidgetEvent(name, detail = null) {
6548
+ this.host.dispatchEvent(new CustomEvent(name, {
6549
+ bubbles: true,
6550
+ composed: true,
6551
+ detail,
6552
+ }));
6553
+ }
5683
6554
  applySessionToken(token) {
5684
6555
  var _a;
5685
6556
  this.currentSessionToken = token;
@@ -5731,6 +6602,38 @@ const OcsChat = class {
5731
6602
  this.clearSessionStorage();
5732
6603
  this.addErrorMessage(this.translationManager.get('status.sessionExpired', 'Your chat session expired. Starting a new chat — please resend your message.'));
5733
6604
  }
6605
+ /**
6606
+ * The server reported the session has ended (e.g. closed from another tab or
6607
+ * by the bot). Polling has already stopped; disable the composer and tell the
6608
+ * user. Unbound widgets can recover via the "new chat" button (clearSession).
6609
+ */
6610
+ handleSessionEnded() {
6611
+ var _a;
6612
+ if (this.sessionEnded) {
6613
+ return;
6614
+ }
6615
+ this.sessionEnded = true;
6616
+ this.dispatchWidgetEvent('ocs:session:ended', { sessionId: this.activeSessionId });
6617
+ this.stopMessagePolling();
6618
+ this.isTyping = false;
6619
+ this.typingProgressMessage = '';
6620
+ const content = (_a = this.translationManager.get('status.chatEnded')) !== null && _a !== void 0 ? _a : 'This chat has ended.';
6621
+ // An unbound widget restores persisted messages and re-polls after a reload,
6622
+ // so the notice may already be the last message.
6623
+ const last = this.messages.at(-1);
6624
+ if ((last === null || last === void 0 ? void 0 : last.role) === 'system' && last.content === content) {
6625
+ return;
6626
+ }
6627
+ const notice = {
6628
+ created_at: new Date().toISOString(),
6629
+ role: 'system',
6630
+ content,
6631
+ attachments: [],
6632
+ };
6633
+ this.messages = [...this.messages, notice];
6634
+ this.saveSessionToStorage();
6635
+ this.scrollToBottom();
6636
+ }
5734
6637
  handleError(errorText) {
5735
6638
  // show as system message
5736
6639
  this.addErrorMessage(errorText);
@@ -5829,6 +6732,7 @@ const OcsChat = class {
5829
6732
  this.activeSessionId = data.session_id;
5830
6733
  this.applySessionToken((_a = data.session_token) !== null && _a !== void 0 ? _a : undefined);
5831
6734
  this.saveSessionToStorage();
6735
+ this.dispatchWidgetEvent('ocs:session:started', { sessionId: this.activeSessionId });
5832
6736
  this.startMessagePolling();
5833
6737
  }
5834
6738
  catch (_error) {
@@ -5892,7 +6796,8 @@ const OcsChat = class {
5892
6796
  }
5893
6797
  }
5894
6798
  async sendMessage(message) {
5895
- if (!message.trim())
6799
+ var _a;
6800
+ if (!message.trim() || this.sessionEnded)
5896
6801
  return;
5897
6802
  const epoch = this.sessionEpoch;
5898
6803
  // Start session if we don't have one yet
@@ -5954,6 +6859,12 @@ const OcsChat = class {
5954
6859
  this.selectedFiles = []; // Clear selected files after sending
5955
6860
  }
5956
6861
  this.scrollToBottom();
6862
+ // Fire before-send first so handlers can update pageContext synchronously
6863
+ // before we read internalPageContext into the request body.
6864
+ this.dispatchWidgetEvent('ocs:message:before-send', {
6865
+ message: message.trim(),
6866
+ sessionId: (_a = this.activeSessionId) !== null && _a !== void 0 ? _a : '',
6867
+ });
5957
6868
  const requestBody = { message: message.trim() };
5958
6869
  if (this.allowAttachments && attachmentIds.length > 0) {
5959
6870
  requestBody.attachment_ids = attachmentIds;
@@ -5971,6 +6882,10 @@ const OcsChat = class {
5971
6882
  throw new Error(data.error || 'Failed to send message');
5972
6883
  }
5973
6884
  this.internalPageContext = undefined;
6885
+ this.dispatchWidgetEvent('ocs:message:sent', {
6886
+ message: message.trim(),
6887
+ sessionId: this.activeSessionId,
6888
+ });
5974
6889
  this.startTaskPolling(data.task_id);
5975
6890
  }
5976
6891
  catch (error) {
@@ -6086,6 +7001,7 @@ const OcsChat = class {
6086
7001
  return;
6087
7002
  }
6088
7003
  this.saveVisibleState(visible);
7004
+ this.dispatchWidgetEvent(visible ? 'ocs:open' : 'ocs:close');
6089
7005
  if (this.isButtonDragging) {
6090
7006
  this.isButtonDragging = false;
6091
7007
  this.buttonWasDragged = false;
@@ -6122,8 +7038,13 @@ const OcsChat = class {
6122
7038
  }
6123
7039
  this.taskPollingHandle = this.getChatService().pollTask(this.activeSessionId, taskId, {
6124
7040
  onMessage: message => {
7041
+ var _a;
6125
7042
  this.messages = [...this.messages, message];
6126
7043
  this.saveSessionToStorage();
7044
+ this.dispatchWidgetEvent('ocs:message:received', {
7045
+ message: Object.assign({}, message),
7046
+ sessionId: (_a = this.activeSessionId) !== null && _a !== void 0 ? _a : '',
7047
+ });
6127
7048
  this.scrollToBottom();
6128
7049
  this.isTyping = false;
6129
7050
  this.typingProgressMessage = '';
@@ -6165,7 +7086,7 @@ const OcsChat = class {
6165
7086
  });
6166
7087
  }
6167
7088
  startMessagePolling() {
6168
- if (!this.activeSessionId || this.currentPollTaskId || !this.visible) {
7089
+ if (!this.activeSessionId || this.currentPollTaskId || !this.visible || this.sessionEnded) {
6169
7090
  return;
6170
7091
  }
6171
7092
  if (this.messagePollingHandle) {
@@ -6174,13 +7095,26 @@ const OcsChat = class {
6174
7095
  this.messagePollingHandle = this.getChatService().startMessagePolling(this.activeSessionId, {
6175
7096
  getSince: () => { var _a; return (this.messages.length > 0 ? (_a = this.messages.at(-1)) === null || _a === void 0 ? void 0 : _a.created_at : undefined); },
6176
7097
  onMessages: messages => {
7098
+ var _a;
6177
7099
  if (messages.length === 0)
6178
7100
  return;
6179
7101
  this.messages = [...this.messages, ...messages];
6180
7102
  this.saveSessionToStorage();
7103
+ for (const message of messages) {
7104
+ if (message.role !== 'user') {
7105
+ this.dispatchWidgetEvent('ocs:message:received', {
7106
+ message: Object.assign({}, message),
7107
+ sessionId: (_a = this.activeSessionId) !== null && _a !== void 0 ? _a : '',
7108
+ });
7109
+ }
7110
+ }
6181
7111
  this.scrollToBottom();
6182
7112
  this.focusInput();
6183
7113
  },
7114
+ onSessionEnded: () => {
7115
+ this.messagePollingHandle = undefined;
7116
+ this.handleSessionEnded();
7117
+ },
6184
7118
  onError: () => {
6185
7119
  // Silently ignore polling errors to match previous behaviour
6186
7120
  },
@@ -6619,7 +7553,9 @@ const OcsChat = class {
6619
7553
  return newUserId;
6620
7554
  }
6621
7555
  saveVisibleState(visible) {
6622
- if (!this.persistentSession)
7556
+ // Kiosk visibility is forced, so persisting it would only leak into a
7557
+ // standard-mode widget for the same chatbot on another page.
7558
+ if (!this.persistentSession || this.isKioskMode())
6623
7559
  return;
6624
7560
  try {
6625
7561
  const keys = this.getStorageKeys();
@@ -6693,6 +7629,7 @@ const OcsChat = class {
6693
7629
  this.applySessionToken(this.isSessionBound() ? this.sessionToken : undefined);
6694
7630
  this.messages = [];
6695
7631
  this.isTyping = false;
7632
+ this.sessionEnded = false;
6696
7633
  this.currentPollTaskId = '';
6697
7634
  if (this.allowAttachments) {
6698
7635
  this.selectedFiles = [];
@@ -6714,12 +7651,12 @@ const OcsChat = class {
6714
7651
  if (this.error && !this.activeSessionId) {
6715
7652
  return (h(Host, null, h("p", { class: "error-message" }, this.error)));
6716
7653
  }
6717
- return (h(Host, null, this.renderButton(), this.visible && (h("div", { ref: el => (this.chatWindowRef = el), id: "ocs-chat-window", class: this.getPositionClasses(), style: this.getPositionStyles() }, !this.isKioskMode() && (h("div", { class: `chat-header ${this.isDragging ? 'chat-header-dragging' : 'chat-header-draggable'}`, onMouseDown: this.handleMouseDown, onTouchStart: this.handleTouchStart }, h("div", { class: "drag-indicator" }, h("div", { class: "drag-dots header-button" }, h(GripDotsVerticalIcon, null))), h("div", { class: "header-text" }, this.translationManager.get('branding.headerText', this.headerText)), h("div", { class: "header-buttons" }, this.messages.length > 0 && !this.isSessionBound() && (h("button", { class: "header-button", onClick: () => this.showConfirmationDialog(), title: this.translationManager.get('window.newChat'), "aria-label": this.translationManager.get('window.newChat') }, h(PlusWithCircleIcon, null))), this.allowFullScreen && (h("button", { class: "header-button fullscreen-button", onClick: () => this.toggleFullscreen(), title: this.isFullscreen ? this.translationManager.get('window.exitFullscreen') : this.translationManager.get('window.fullscreen'), "aria-label": this.isFullscreen ? this.translationManager.get('window.exitFullscreen') : this.translationManager.get('window.fullscreen') }, this.isFullscreen ? h(ArrowsPointingInIcon, null) : h(ArrowsPointingOutIcon, null))), h("button", { class: "header-button", onClick: () => (this.visible = false), "aria-label": this.translationManager.get('window.close') }, h(XMarkIcon, null))))), !this.isKioskMode() && this.showNewChatConfirmation && (h("div", { class: "confirmation-overlay" }, h("div", { class: "confirmation-dialog" }, h("div", { class: "confirmation-content" }, h("h3", { class: "confirmation-title" }, this.translationManager.get('modal.newChatTitle')), h("p", { class: "confirmation-message" }, this.translationManager.get('modal.newChatBody', this.newChatConfirmationMessage)), h("div", { class: "confirmation-buttons" }, h("button", { class: "confirmation-button confirmation-button-cancel", onClick: () => this.hideConfirmationDialog() }, this.translationManager.get('modal.cancel')), h("button", { class: "confirmation-button confirmation-button-confirm", onClick: () => this.confirmNewChat() }, this.translationManager.get('modal.confirm'))))))), h("div", { class: "chat-content" }, this.isLoading && !this.activeSessionId && (h("div", { class: "loading-container" }, h("div", { class: "loading-spinner" }), h("span", { class: "loading-text" }, this.translationManager.get('status.starting')))), h("div", { ref: el => (this.messageListRef = el), class: "messages-container" }, this.messages.length === 0 && this.getWelcomeMessages().length > 0 && (h("div", { class: "welcome-messages" }, this.getWelcomeMessages().map((message, index) => (h("div", { key: `welcome-${index}`, class: "message-row message-row-assistant" }, h("div", { class: "message-bubble message-bubble-assistant" }, h("div", { class: "chat-markdown", innerHTML: renderMarkdownSync(message) }))))))), this.messages.map((message, index) => (h("div", { key: index, class: `message-row ${message.role === 'user' ? 'message-row-user' : 'message-row-assistant'}` }, h("div", { class: `message-bubble ${message.role === 'user' ? 'message-bubble-user' : message.role === 'assistant' ? 'message-bubble-assistant' : 'message-bubble-system'}` }, h("div", { class: "chat-markdown", innerHTML: renderMarkdownSync(message.content) }), message.attachments && message.attachments.length > 0 && (h("div", { class: "message-attachments" }, message.attachments.map((attachment, attachmentIndex) => (h("div", { key: attachmentIndex, class: "flex items-center gap-[0.5em]" }, h("span", { class: "message-attachment-icon" }, h(PaperClipIcon, null)), h("span", { class: "message-attachment-name" }, attachment.name)))))), h("div", { class: "message-timestamp" }, this.formatTime(message.created_at)))))), this.isTyping && (h("div", null, h("div", { class: "typing-indicator" }, h("div", { class: "typing-progress" })), h("div", { class: "typing-text" }, h("span", null, this.typingProgressMessage || this.translationManager.get('status.typing', this.typingIndicatorText)), h("span", { class: "typing-dots loading" }))))), this.messages.length === 0 && this.getStarterQuestions().length > 0 && (h("div", { class: "starter-questions" }, this.getStarterQuestions().map((question, index) => (h("div", { key: `starter-${index}`, class: "starter-question-row" }, h("button", { class: "starter-question", onClick: () => this.handleStarterQuestionClick(question) }, question)))))), this.allowAttachments && this.selectedFiles.length > 0 && (h("div", { class: "selected-files-container" }, h("div", { class: "space-y-[0.25em]" }, this.selectedFiles.map((selectedFile, index) => (h("div", { key: index, class: "selected-file-item" }, h("div", { class: "flex items-center gap-[0.5em]" }, h("span", { class: "selected-file-icon" }, h(PaperClipIcon, null)), h("span", null, selectedFile.file.name), h("span", { class: "selected-file-size" }, "(", this.formatFileSize(selectedFile.file.size), ")"), selectedFile.error && h("span", { class: "selected-file-error" }, selectedFile.error), selectedFile.uploaded && (h("span", { class: "selected-file-success-icon" }, h(CheckDocumentIcon, null)))), h("button", { onClick: () => this.removeSelectedFile(index), class: "selected-file-remove-button", "aria-label": this.translationManager.get('attach.remove') }, h(XIcon, null)))))))), h("div", { class: "input-area" }, h("div", { class: "input-container" }, h("textarea", { ref: el => (this.textareaRef = el), class: "message-textarea", rows: 1, placeholder: this.translationManager.get('composer.placeholder'), value: this.messageInput, onInput: e => this.handleInputChange(e), onKeyPress: e => this.handleKeyPress(e), disabled: this.isTyping || this.isUploadingFiles || this.isLoading }), this.allowAttachments && (h("input", { ref: el => {
7654
+ return (h(Host, null, this.renderButton(), this.visible && (h("div", { ref: el => (this.chatWindowRef = el), id: "ocs-chat-window", class: this.getPositionClasses(), style: this.getPositionStyles() }, !this.isKioskMode() && (h("div", { class: `chat-header ${this.isDragging ? 'chat-header-dragging' : 'chat-header-draggable'}`, onMouseDown: this.handleMouseDown, onTouchStart: this.handleTouchStart }, h("div", { class: "drag-indicator" }, h("div", { class: "drag-dots header-button" }, h(GripDotsVerticalIcon, null))), h("div", { class: "header-text" }, this.translationManager.get('branding.headerText', this.headerText)), h("div", { class: "header-buttons" }, this.messages.length > 0 && !this.isSessionBound() && (h("button", { class: "header-button", onClick: () => this.showConfirmationDialog(), title: this.translationManager.get('window.newChat'), "aria-label": this.translationManager.get('window.newChat') }, h(PlusWithCircleIcon, null))), this.allowFullScreen && (h("button", { class: "header-button fullscreen-button", onClick: () => this.toggleFullscreen(), title: this.isFullscreen ? this.translationManager.get('window.exitFullscreen') : this.translationManager.get('window.fullscreen'), "aria-label": this.isFullscreen ? this.translationManager.get('window.exitFullscreen') : this.translationManager.get('window.fullscreen') }, this.isFullscreen ? h(ArrowsPointingInIcon, null) : h(ArrowsPointingOutIcon, null))), h("button", { class: "header-button", onClick: () => (this.visible = false), "aria-label": this.translationManager.get('window.close') }, h(XMarkIcon, null))))), !this.isKioskMode() && this.showNewChatConfirmation && (h("div", { class: "confirmation-overlay" }, h("div", { class: "confirmation-dialog" }, h("div", { class: "confirmation-content" }, h("h3", { class: "confirmation-title" }, this.translationManager.get('modal.newChatTitle')), h("p", { class: "confirmation-message" }, this.translationManager.get('modal.newChatBody', this.newChatConfirmationMessage)), h("div", { class: "confirmation-buttons" }, h("button", { class: "confirmation-button confirmation-button-cancel", onClick: () => this.hideConfirmationDialog() }, this.translationManager.get('modal.cancel')), h("button", { class: "confirmation-button confirmation-button-confirm", onClick: () => this.confirmNewChat() }, this.translationManager.get('modal.confirm'))))))), h("div", { class: "chat-content" }, this.isLoading && !this.activeSessionId && (h("div", { class: "loading-container" }, h("div", { class: "loading-spinner" }), h("span", { class: "loading-text" }, this.translationManager.get('status.starting')))), h("div", { ref: el => (this.messageListRef = el), class: "messages-container" }, this.messages.length === 0 && this.getWelcomeMessages().length > 0 && (h("div", { class: "welcome-messages" }, this.getWelcomeMessages().map((message, index) => (h("div", { key: `welcome-${index}`, class: "message-row message-row-assistant" }, h("div", { class: "message-bubble message-bubble-assistant" }, h("div", { class: "chat-markdown", innerHTML: renderMarkdownSync(message) }))))))), this.messages.map((message, index) => (h("div", { key: index, class: `message-row ${message.role === 'user' ? 'message-row-user' : 'message-row-assistant'}` }, h("div", { class: `message-bubble ${message.role === 'user' ? 'message-bubble-user' : message.role === 'assistant' ? 'message-bubble-assistant' : 'message-bubble-system'}` }, h("div", { class: "chat-markdown", innerHTML: renderMarkdownSync(message.content) }), message.attachments && message.attachments.length > 0 && (h("div", { class: "message-attachments" }, message.attachments.map((attachment, attachmentIndex) => (h("div", { key: attachmentIndex, class: "flex items-center gap-[0.5em]" }, h("span", { class: "message-attachment-icon" }, h(PaperClipIcon, null)), h("span", { class: "message-attachment-name" }, attachment.name)))))), h("div", { class: "message-timestamp" }, this.formatTime(message.created_at)))))), this.isTyping && (h("div", null, h("div", { class: "typing-indicator" }, h("div", { class: "typing-progress" })), h("div", { class: "typing-text" }, h("span", null, this.typingProgressMessage || this.translationManager.get('status.typing', this.typingIndicatorText)), h("span", { class: "typing-dots loading" }))))), this.messages.length === 0 && this.getStarterQuestions().length > 0 && (h("div", { class: "starter-questions" }, this.getStarterQuestions().map((question, index) => (h("div", { key: `starter-${index}`, class: "starter-question-row" }, h("button", { class: "starter-question", onClick: () => this.handleStarterQuestionClick(question) }, question)))))), this.allowAttachments && this.selectedFiles.length > 0 && (h("div", { class: "selected-files-container" }, h("div", { class: "space-y-[0.25em]" }, this.selectedFiles.map((selectedFile, index) => (h("div", { key: index, class: "selected-file-item" }, h("div", { class: "flex items-center gap-[0.5em]" }, h("span", { class: "selected-file-icon" }, h(PaperClipIcon, null)), h("span", null, selectedFile.file.name), h("span", { class: "selected-file-size" }, "(", this.formatFileSize(selectedFile.file.size), ")"), selectedFile.error && h("span", { class: "selected-file-error" }, selectedFile.error), selectedFile.uploaded && (h("span", { class: "selected-file-success-icon" }, h(CheckDocumentIcon, null)))), h("button", { onClick: () => this.removeSelectedFile(index), class: "selected-file-remove-button", "aria-label": this.translationManager.get('attach.remove') }, h(XIcon, null)))))))), h("div", { class: "input-area" }, h("div", { class: "input-container" }, h("textarea", { ref: el => (this.textareaRef = el), class: "message-textarea", rows: 1, placeholder: this.sessionEnded ? this.translationManager.get('status.chatEnded') : this.translationManager.get('composer.placeholder'), value: this.messageInput, onInput: e => this.handleInputChange(e), onKeyPress: e => this.handleKeyPress(e), disabled: this.isTyping || this.isUploadingFiles || this.isLoading || this.sessionEnded }), this.allowAttachments && (h("input", { ref: el => {
6718
7655
  // Unclear why but after removing all attachments this is being set to `null`.
6719
7656
  if (el) {
6720
7657
  this.fileInputRef = el;
6721
7658
  }
6722
- }, id: "ocs-file-input", type: "file", multiple: true, accept: OcsChat.SUPPORTED_FILE_EXTENSIONS.join(',') + ',text/*', onChange: e => this.handleFileSelect(e), class: "hidden" })), this.allowAttachments && (h("button", { class: "file-attachment-button", onClick: () => { var _a; return (_a = this.fileInputRef) === null || _a === void 0 ? void 0 : _a.click(); }, disabled: this.isTyping || this.isUploadingFiles || this.isLoading, title: this.translationManager.get('attach.add'), "aria-label": this.translationManager.get('attach.add') }, h(PaperClipIcon, null))), h("button", { class: `send-button ${!this.isTyping && !this.isLoading && !!this.messageInput.trim() ? 'send-button-enabled' : 'send-button-disabled'}`, onClick: () => this.sendMessage(this.messageInput), disabled: this.isTyping || this.isUploadingFiles || this.isLoading || !this.messageInput.trim() }, this.isUploadingFiles ? `${this.translationManager.get('status.uploading')}...` : this.translationManager.get('composer.send')))), h("div", { class: "flex items-center justify-center text-[0.8em] font-light w-full text-slate-500 py-[2px]" }, h("p", null, this.translationManager.get('branding.poweredBy'), ' ', h("a", { class: "underline", href: "https://www.dimagi.com", target: "_blank", rel: "noopener noreferrer" }, "Dimagi"))))))));
7659
+ }, id: "ocs-file-input", type: "file", multiple: true, accept: OcsChat.SUPPORTED_FILE_EXTENSIONS.join(',') + ',text/*', onChange: e => this.handleFileSelect(e), class: "hidden" })), this.allowAttachments && (h("button", { class: "file-attachment-button", onClick: () => { var _a; return (_a = this.fileInputRef) === null || _a === void 0 ? void 0 : _a.click(); }, disabled: this.isTyping || this.isUploadingFiles || this.isLoading || this.sessionEnded, title: this.translationManager.get('attach.add'), "aria-label": this.translationManager.get('attach.add') }, h(PaperClipIcon, null))), h("button", { class: `send-button ${!this.isTyping && !this.isLoading && !this.sessionEnded && !!this.messageInput.trim() ? 'send-button-enabled' : 'send-button-disabled'}`, onClick: () => this.sendMessage(this.messageInput), disabled: this.isTyping || this.isUploadingFiles || this.isLoading || this.sessionEnded || !this.messageInput.trim() }, this.isUploadingFiles ? `${this.translationManager.get('status.uploading')}...` : this.translationManager.get('composer.send')))), h("div", { class: "flex items-center justify-center text-[0.8em] font-light w-full text-slate-500 py-[2px]" }, h("p", null, this.translationManager.get('branding.poweredBy'), ' ', h("a", { class: "underline", href: "https://www.dimagi.com", target: "_blank", rel: "noopener noreferrer" }, "Dimagi"))))))));
6723
7660
  }
6724
7661
  get host() { return getElement(this); }
6725
7662
  static get watchers() { return {