@myrmidon/gve-snapshot-rendition 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -10725,7 +10725,7 @@ function _assertThisInitialized(self) { if (self === void 0) { throw new Referen
10725
10725
  function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; }
10726
10726
 
10727
10727
  /*!
10728
- * GSAP 3.13.0
10728
+ * GSAP 3.14.2
10729
10729
  * https://gsap.com
10730
10730
  *
10731
10731
  * @license Copyright 2008-2025, GreenSock. All rights reserved.
@@ -10785,6 +10785,8 @@ var _config = {
10785
10785
  _isTypedArray = typeof ArrayBuffer === "function" && ArrayBuffer.isView || function () {},
10786
10786
  // note: IE10 has ArrayBuffer, but NOT ArrayBuffer.isView().
10787
10787
  _isArray = Array.isArray,
10788
+ _randomExp = /random\([^)]+\)/g,
10789
+ _commaDelimExp = /,\s*/g,
10788
10790
  _strictNumExp = /(?:-?\.?\d|\.)+/gi,
10789
10791
  //only numbers (including negatives and decimals) but NOT relative values.
10790
10792
  _numExp$1 = /[-+=.]*\d+[.e\-+]*\d*[e\-+]*\d*/g,
@@ -11379,7 +11381,7 @@ clamp = function clamp(min, max, value) {
11379
11381
  return _isString(value) && !leaveStrings || _isArrayLike(value, 1) ? (_accumulator = accumulator).push.apply(_accumulator, toArray(value)) : accumulator.push(value);
11380
11382
  }) || accumulator;
11381
11383
  },
11382
- //takes any value and returns an array. If it's a string (and leaveStrings isn't true), it'll use document.querySelectorAll() and convert that to an array. It'll also accept iterables like jQuery objects.
11384
+ // takes any value and returns an Array. If it's a string (and leaveStrings isn't true), it'll use document.querySelectorAll() and convert that to an array. It'll also accept iterables like jQuery objects.
11383
11385
  toArray = function toArray(value, scope, leaveStrings) {
11384
11386
  return _context && !scope && _context.selector ? _context.selector(value) : _isString(value) && !leaveStrings && (_coreInitted$1 || !_wake()) ? _slice.call((scope || _doc$1).querySelectorAll(value), 0) : _isArray(value) ? _flatten(value, leaveStrings) : _isArrayLike(value) ? _slice.call(value, 0) : value ? [value] : [];
11385
11387
  },
@@ -11396,7 +11398,7 @@ toArray = function toArray(value, scope, leaveStrings) {
11396
11398
  });
11397
11399
  },
11398
11400
  // alternative that's a bit faster and more reliably diverse but bigger: for (let j, v, i = a.length; i; j = (Math.random() * i) | 0, v = a[--i], a[i] = a[j], a[j] = v); return a;
11399
- //for distributing values across an array. Can accept a number, a function or (most commonly) a function which can contain the following properties: {base, amount, from, ease, grid, axis, length, each}. Returns a function that expects the following parameters: index, target, array. Recognizes the following
11401
+ // for distributing values across an Array. Can accept a number, a function or (most commonly) an object which can contain the following properties: {base, amount, from, ease, grid, axis, length, each}. Returns a function that expects the following parameters: index, target, array.
11400
11402
  distribute = function distribute(v) {
11401
11403
  if (_isFunction(v)) {
11402
11404
  return v;
@@ -11583,24 +11585,13 @@ distribute = function distribute(v) {
11583
11585
  return min + (value > range ? total - value : value);
11584
11586
  });
11585
11587
  },
11586
- _replaceRandom = function _replaceRandom(value) {
11587
- //replaces all occurrences of random(...) in a string with the calculated random value. can be a range like random(-100, 100, 5) or an array like random([0, 100, 500])
11588
- var prev = 0,
11589
- s = "",
11590
- i,
11591
- nums,
11592
- end,
11593
- isArray;
11594
-
11595
- while (~(i = value.indexOf("random(", prev))) {
11596
- end = value.indexOf(")", i);
11597
- isArray = value.charAt(i + 7) === "[";
11598
- nums = value.substr(i + 7, end - i - 7).match(isArray ? _delimitedValueExp : _strictNumExp);
11599
- s += value.substr(prev, i - prev) + random(isArray ? nums : +nums[0], isArray ? 0 : +nums[1], +nums[2] || 1e-5);
11600
- prev = end + 1;
11601
- }
11602
-
11603
- return s + value.substr(prev, value.length - prev);
11588
+ _replaceRandom = function _replaceRandom(s) {
11589
+ return s.replace(_randomExp, function (match) {
11590
+ //replaces all occurrences of random(...) in a string with the calculated random value. can be a range like random(-100, 100, 5) or an array like random([0, 100, 500])
11591
+ var arIndex = match.indexOf("[") + 1,
11592
+ values = match.substring(arIndex || 7, arIndex ? match.indexOf("]") : match.length - 1).split(_commaDelimExp);
11593
+ return random(arIndex ? values : +values[0], arIndex ? 0 : +values[1], +values[2] || 1e-5);
11594
+ });
11604
11595
  },
11605
11596
  mapRange = function mapRange(inMin, inMax, outMin, outMax, value) {
11606
11597
  var inRange = inMax - inMin,
@@ -12436,7 +12427,7 @@ var Animation = /*#__PURE__*/function () {
12436
12427
  }
12437
12428
  }
12438
12429
 
12439
- if (this._tTime !== _totalTime || !this._dur && !suppressEvents || this._initted && Math.abs(this._zTime) === _tinyNum || !_totalTime && !this._initted && (this.add || this._ptLookup)) {
12430
+ if (this._tTime !== _totalTime || !this._dur && !suppressEvents || this._initted && Math.abs(this._zTime) === _tinyNum || !this._initted && this._dur && _totalTime || !_totalTime && !this._initted && (this.add || this._ptLookup)) {
12440
12431
  // check for _ptLookup on a Tween instance to ensure it has actually finished being instantiated, otherwise if this.reverse() gets called in the Animation constructor, it could trigger a render() here even though the _targets weren't populated, thus when _init() is called there won't be any PropTweens (it'll act like the tween is non-functional)
12441
12432
  this._ts || (this._pTime = _totalTime); // otherwise, if an animation is paused, then the playhead is moved back to zero, then resumed, it'd revert back to the original time at the pause
12442
12433
  //if (!this._lock) { // avoid endless recursion (not sure we need this yet or if it's worth the performance hit)
@@ -12531,9 +12522,9 @@ var Animation = /*#__PURE__*/function () {
12531
12522
 
12532
12523
  _proto.startTime = function startTime(value) {
12533
12524
  if (arguments.length) {
12534
- this._start = value;
12525
+ this._start = _roundPrecise(value);
12535
12526
  var parent = this.parent || this._dp;
12536
- parent && (parent._sort || !this.parent) && _addToTimeline(parent, this, value - this._delay);
12527
+ parent && (parent._sort || !this.parent) && _addToTimeline(parent, this, this._start - this._delay);
12537
12528
  return this;
12538
12529
  }
12539
12530
 
@@ -12683,13 +12674,15 @@ var Animation = /*#__PURE__*/function () {
12683
12674
  };
12684
12675
 
12685
12676
  _proto.then = function then(onFulfilled) {
12686
- var self = this;
12677
+ var self = this,
12678
+ prevProm = self._prom;
12687
12679
  return new Promise(function (resolve) {
12688
12680
  var f = _isFunction(onFulfilled) ? onFulfilled : _passThrough,
12689
12681
  _resolve = function _resolve() {
12690
12682
  var _then = self.then;
12691
12683
  self.then = null; // temporarily null the then() method to avoid an infinite loop (see https://github.com/greensock/GSAP/issues/322)
12692
12684
 
12685
+ prevProm && prevProm();
12693
12686
  _isFunction(f) && (f = f(self)) && (f.then || f === self) && (self.then = _then);
12694
12687
  resolve(f);
12695
12688
  self.then = _then;
@@ -12910,7 +12903,11 @@ var Timeline = /*#__PURE__*/function (_Animation) {
12910
12903
  this._tTime = tTime; // if a user gets the iteration() inside the onRepeat, for example, it should be accurate.
12911
12904
 
12912
12905
  !suppressEvents && this.parent && _callback(this, "onRepeat");
12913
- this.vars.repeatRefresh && !isYoyo && (this.invalidate()._lock = 1);
12906
+
12907
+ if (this.vars.repeatRefresh && !isYoyo) {
12908
+ this.invalidate()._lock = 1;
12909
+ prevIteration = iteration; // otherwise, the onStart() may fire on the 2nd iteration.
12910
+ }
12914
12911
 
12915
12912
  if (prevTime && prevTime !== this._time || prevPaused !== !this._ts || this.vars.onRepeat && !this.parent && !this._act) {
12916
12913
  // if prevTime is 0 and we render at the very end, _time will be the end, thus won't match. So in this edge case, prevTime won't match _time but that's okay. If it gets killed in the onRepeat, eject as well.
@@ -12958,7 +12955,7 @@ var Timeline = /*#__PURE__*/function (_Animation) {
12958
12955
  prevTime = 0; // upon init, the playhead should always go forward; someone could invalidate() a completed timeline and then if they restart(), that would make child tweens render in reverse order which could lock in the wrong starting values if they build on each other, like tl.to(obj, {x: 100}).to(obj, {x: 0}).
12959
12956
  }
12960
12957
 
12961
- if (!prevTime && tTime && !suppressEvents && !prevIteration) {
12958
+ if (!prevTime && tTime && dur && !suppressEvents && !prevIteration) {
12962
12959
  _callback(this, "onStart");
12963
12960
 
12964
12961
  if (this._tTime !== tTime) {
@@ -13305,6 +13302,7 @@ var Timeline = /*#__PURE__*/function (_Animation) {
13305
13302
  var child = this._first,
13306
13303
  labels = this.labels,
13307
13304
  p;
13305
+ amount = _roundPrecise(amount);
13308
13306
 
13309
13307
  while (child) {
13310
13308
  if (child._start >= ignoreBeforeTime) {
@@ -13394,7 +13392,7 @@ var Timeline = /*#__PURE__*/function (_Animation) {
13394
13392
  max -= start;
13395
13393
 
13396
13394
  if (!parent && !self._dp || parent && parent.smoothChildTiming) {
13397
- self._start += start / self._ts;
13395
+ self._start += _roundPrecise(start / self._ts);
13398
13396
  self._time -= start;
13399
13397
  self._tTime -= start;
13400
13398
  }
@@ -15194,7 +15192,7 @@ var gsap$1 = _gsap.registerPlugin({
15194
15192
  }
15195
15193
  }, _buildModifierPlugin("roundProps", _roundModifier), _buildModifierPlugin("modifiers"), _buildModifierPlugin("snap", snap)) || _gsap; //to prevent the core plugins from being dropped via aggressive tree shaking, we must include them in the variable declaration in this way.
15196
15194
 
15197
- Tween.version = Timeline.version = gsap$1.version = "3.13.0";
15195
+ Tween.version = Timeline.version = gsap$1.version = "3.14.2";
15198
15196
  _coreReady = 1;
15199
15197
  _windowExists$2() && _wake();
15200
15198
  _easeMap.Power0;
@@ -15217,7 +15215,7 @@ _easeMap.Power0;
15217
15215
  _easeMap.Circ;
15218
15216
 
15219
15217
  /*!
15220
- * CSSPlugin 3.13.0
15218
+ * CSSPlugin 3.14.2
15221
15219
  * https://gsap.com
15222
15220
  *
15223
15221
  * Copyright 2008-2025, GreenSock. All rights reserved.
@@ -15259,6 +15257,10 @@ var _win$1,
15259
15257
  return data.set(data.t, data.p, ratio ? Math.round((data.s + data.c * ratio) * 10000) / 10000 + data.u : data.b, data);
15260
15258
  },
15261
15259
  //if units change, we need a way to render the original unit/value when the tween goes all the way back to the beginning (ratio:0)
15260
+ _renderCSSPropWithBeginningAndEnd = function _renderCSSPropWithBeginningAndEnd(ratio, data) {
15261
+ return data.set(data.t, data.p, ratio === 1 ? data.e : ratio ? Math.round((data.s + data.c * ratio) * 10000) / 10000 + data.u : data.b, data);
15262
+ },
15263
+ //if units change, we need a way to render the original unit/value when the tween goes all the way back to the beginning (ratio:0)
15262
15264
  _renderRoundedCSSProp = function _renderRoundedCSSProp(ratio, data) {
15263
15265
  var value = data.s + data.c * ratio;
15264
15266
  data.set(data.t, data.p, ~~(value + (value < 0 ? -0.5 : .5)) + data.u, data);
@@ -16558,7 +16560,8 @@ var CSSPlugin = {
16558
16560
  cache,
16559
16561
  smooth,
16560
16562
  hasPriority,
16561
- inlineProps;
16563
+ inlineProps,
16564
+ finalTransformValue;
16562
16565
  _pluginInitted || _initCore$1(); // we may call init() multiple times on the same plugin instance, like when adding special properties, so make sure we don't overwrite the revert data or inlineProps
16563
16566
 
16564
16567
  this.styles = this.styles || _getStyleSaver$1(target);
@@ -16601,9 +16604,9 @@ var CSSPlugin = {
16601
16604
  // colors don't have units
16602
16605
  startUnit = getUnit(startValue);
16603
16606
  endUnit = getUnit(endValue);
16607
+ endUnit ? startUnit !== endUnit && (startValue = _convertToUnit(target, p, startValue, endUnit) + endUnit) : startUnit && (endValue += startUnit);
16604
16608
  }
16605
16609
 
16606
- endUnit ? startUnit !== endUnit && (startValue = _convertToUnit(target, p, startValue, endUnit) + endUnit) : startUnit && (endValue += startUnit);
16607
16610
  this.add(style, "setProperty", startValue, endValue, index, targets, 0, 0, p);
16608
16611
  props.push(p);
16609
16612
  inlineProps.push(p, 0, style[p]);
@@ -16647,9 +16650,18 @@ var CSSPlugin = {
16647
16650
 
16648
16651
  if (isTransformRelated) {
16649
16652
  this.styles.save(p);
16653
+ finalTransformValue = endValue; // this is always the same as endValue except when it's a var(--) value, in which case we need to calculate the end value.
16650
16654
 
16651
16655
  if (type === "string" && endValue.substring(0, 6) === "var(--") {
16652
16656
  endValue = _getComputedProperty(target, endValue.substring(4, endValue.indexOf(")")));
16657
+
16658
+ if (endValue.substring(0, 5) === "calc(") {
16659
+ var origPerspective = target.style.perspective;
16660
+ target.style.perspective = endValue;
16661
+ endValue = _getComputedProperty(target, "perspective");
16662
+ origPerspective ? target.style.perspective = origPerspective : _removeProperty(target, "perspective");
16663
+ }
16664
+
16653
16665
  endNum = parseFloat(endValue);
16654
16666
  }
16655
16667
 
@@ -16716,7 +16728,11 @@ var CSSPlugin = {
16716
16728
  this._pt = new PropTween(this._pt, isTransformRelated ? cache : style, p, startNum, (relative ? _parseRelative(startNum, relative + endNum) : endNum) - startNum, !isTransformRelated && (endUnit === "px" || p === "zIndex") && vars.autoRound !== false ? _renderRoundedCSSProp : _renderCSSProp);
16717
16729
  this._pt.u = endUnit || 0;
16718
16730
 
16719
- if (startUnit !== endUnit && endUnit !== "%") {
16731
+ if (isTransformRelated && finalTransformValue !== endValue) {
16732
+ this._pt.b = startValue;
16733
+ this._pt.e = finalTransformValue;
16734
+ this._pt.r = _renderCSSPropWithBeginningAndEnd;
16735
+ } else if (startUnit !== endUnit && endUnit !== "%") {
16720
16736
  //when the tween goes all the way back to the beginning, we need to revert it to the OLD/ORIGINAL value (with those units). We record that as a "b" (beginning) property and point to a render method that handles that. (performance optimization)
16721
16737
  this._pt.b = startValue;
16722
16738
  this._pt.r = _renderCSSPropWithBeginning;
@@ -80731,7 +80747,7 @@ var libExports = /*@__PURE__*/ requireLib();
80731
80747
  var HighlightJS = /*@__PURE__*/getDefaultExportFromCjs(libExports);
80732
80748
 
80733
80749
  /*!
80734
- * DrawSVGPlugin 3.13.0
80750
+ * DrawSVGPlugin 3.14.2
80735
80751
  * https://gsap.com
80736
80752
  *
80737
80753
  * @license Copyright 2008-2025, GreenSock. All rights reserved.
@@ -80930,7 +80946,7 @@ _parse = function _parse(value, length, defaultStart) {
80930
80946
  };
80931
80947
 
80932
80948
  var DrawSVGPlugin = {
80933
- version: "3.13.0",
80949
+ version: "3.14.2",
80934
80950
  name: "drawSVG",
80935
80951
  register: function register(core) {
80936
80952
  gsap = core;
@@ -81058,7 +81074,9 @@ class GveHintDesigner extends HTMLElement {
81058
81074
  super();
81059
81075
  // Properties
81060
81076
  this._data = { hints: {}, animations: {} };
81061
- this._settings = { ...DEFAULT_HINT_DESIGNER_SETTINGS };
81077
+ this._settings = {
81078
+ ...DEFAULT_HINT_DESIGNER_SETTINGS,
81079
+ };
81062
81080
  this._hintVariables = [];
81063
81081
  // Custom zoom/pan state
81064
81082
  this._zoomLevel = 1;
@@ -81143,7 +81161,9 @@ class GveHintDesigner extends HTMLElement {
81143
81161
  set hintVariables(value) {
81144
81162
  this._hintVariables = value || [];
81145
81163
  this.refreshVariablesTable();
81146
- this.fireEvent("hintVariablesChange", { hintVariables: this._hintVariables });
81164
+ this.fireEvent("hintVariablesChange", {
81165
+ hintVariables: this._hintVariables,
81166
+ });
81147
81167
  }
81148
81168
  // ==================== RENDERING ====================
81149
81169
  /**
@@ -81202,7 +81222,8 @@ class GveHintDesigner extends HTMLElement {
81202
81222
  // Hints dropdown
81203
81223
  this._hintsDropdown = document.createElement("select");
81204
81224
  this._hintsDropdown.className = "hints-dropdown";
81205
- this._hintsDropdown.innerHTML = '<option value="">-- Select Hint --</option>';
81225
+ this._hintsDropdown.innerHTML =
81226
+ '<option value="">-- Select Hint --</option>';
81206
81227
  toolbar.appendChild(this._hintsDropdown);
81207
81228
  // Save button (accent)
81208
81229
  const saveBtn = this.createButton("save-hint", "save", "Save hint");
@@ -81514,7 +81535,8 @@ class GveHintDesigner extends HTMLElement {
81514
81535
  // Animation dropdown
81515
81536
  this._animationsDropdown = document.createElement("select");
81516
81537
  this._animationsDropdown.className = "animations-dropdown";
81517
- this._animationsDropdown.innerHTML = '<option value="">-- Select Animation --</option>';
81538
+ this._animationsDropdown.innerHTML =
81539
+ '<option value="">-- Select Animation --</option>';
81518
81540
  animControls.appendChild(this._animationsDropdown);
81519
81541
  // Load button
81520
81542
  const loadAnimBtn = this.createButton("load-animation", "download-cloud", "Load animation code");
@@ -81542,10 +81564,14 @@ class GveHintDesigner extends HTMLElement {
81542
81564
  createHintVariablesList() {
81543
81565
  const panel = document.createElement("div");
81544
81566
  panel.className = "variables-panel resizable-panel";
81545
- // Header
81567
+ // Header with warning icon placeholder
81546
81568
  const header = document.createElement("div");
81547
81569
  header.className = "panel-header";
81548
- header.textContent = "Hint Variables";
81570
+ const headerText = document.createElement("span");
81571
+ headerText.textContent = "Hint Variables";
81572
+ header.appendChild(headerText);
81573
+ // Store reference to header for adding warning icon
81574
+ this._variablesPanelHeader = header;
81549
81575
  panel.appendChild(header);
81550
81576
  // Toolbar
81551
81577
  const toolbar = document.createElement("div");
@@ -81553,10 +81579,18 @@ class GveHintDesigner extends HTMLElement {
81553
81579
  const addVarBtn = this.createButton("add-variable", "plus-circle", "Add new variable");
81554
81580
  addVarBtn.classList.add("btn-primary");
81555
81581
  toolbar.appendChild(addVarBtn);
81582
+ const populateBtn = this.createButton("populate-from-hints", "download-cloud", "Populate from all hints");
81583
+ populateBtn.classList.add("btn-accent");
81584
+ toolbar.appendChild(populateBtn);
81556
81585
  const deleteAllBtn = this.createButton("delete-all-vars", "trash-2", "Delete all variables");
81557
81586
  deleteAllBtn.classList.add("btn-danger");
81558
81587
  toolbar.appendChild(deleteAllBtn);
81559
81588
  panel.appendChild(toolbar);
81589
+ // Validation message (initially hidden)
81590
+ this._variablesValidationMsg = document.createElement("div");
81591
+ this._variablesValidationMsg.className = "validation-message";
81592
+ this._variablesValidationMsg.style.display = "none";
81593
+ panel.appendChild(this._variablesValidationMsg);
81560
81594
  // Content
81561
81595
  const content = document.createElement("div");
81562
81596
  content.className = "panel-content";
@@ -81579,7 +81613,12 @@ class GveHintDesigner extends HTMLElement {
81579
81613
  // Use feather.icons to get the SVG directly (for Shadow DOM compatibility)
81580
81614
  const icon = featherExports.icons[iconName];
81581
81615
  if (icon) {
81582
- btn.innerHTML = icon.toSvg({ class: 'feather-icon', width: 16, height: 16, 'stroke-width': 2 });
81616
+ btn.innerHTML = icon.toSvg({
81617
+ class: "feather-icon",
81618
+ width: 16,
81619
+ height: 16,
81620
+ "stroke-width": 2,
81621
+ });
81583
81622
  }
81584
81623
  else {
81585
81624
  this._logger.warn("Feather icon not found:", iconName);
@@ -81649,14 +81688,14 @@ class GveHintDesigner extends HTMLElement {
81649
81688
  splitter.addEventListener("pointerdown", (e) => {
81650
81689
  const startY = e.clientY;
81651
81690
  const startHeightAbove = panelAbove.offsetHeight;
81652
- const container = this._shadow.querySelector('.hint-designer-container');
81691
+ const container = this._shadow.querySelector(".hint-designer-container");
81653
81692
  if (!container)
81654
81693
  return;
81655
81694
  const onMove = (moveEvent) => {
81656
81695
  const delta = moveEvent.clientY - startY;
81657
81696
  const newHeightAbove = Math.max(300, startHeightAbove + delta);
81658
81697
  // Determine which splitter this is based on the panel above
81659
- const isTopSplitter = panelAbove.classList.contains('top-panels-wrapper');
81698
+ const isTopSplitter = panelAbove.classList.contains("top-panels-wrapper");
81660
81699
  if (isTopSplitter) {
81661
81700
  // Splitter 1: between top and properties
81662
81701
  // Set top height, let properties and variables share remaining space
@@ -81665,7 +81704,7 @@ class GveHintDesigner extends HTMLElement {
81665
81704
  else {
81666
81705
  // Splitter 2: between properties and variables
81667
81706
  // Get top panel height and keep it fixed
81668
- const topPanel = this._shadow.querySelector('.top-panels-wrapper');
81707
+ const topPanel = this._shadow.querySelector(".top-panels-wrapper");
81669
81708
  const topHeight = topPanel ? topPanel.offsetHeight : 300;
81670
81709
  // Set top and properties heights, let variables fill remaining
81671
81710
  container.style.gridTemplateRows = `${topHeight}px 6px ${newHeightAbove}px 6px minmax(200px, 1fr)`;
@@ -81723,6 +81762,7 @@ class GveHintDesigner extends HTMLElement {
81723
81762
  // Variables panel
81724
81763
  this.addClickListener("delete-all-vars", () => this.deleteAllVariables());
81725
81764
  this.addClickListener("add-variable", () => this.addVariable());
81765
+ this.addClickListener("populate-from-hints", () => this.populateVariablesFromHints());
81726
81766
  // Word wrap toggles
81727
81767
  this.addClickListener("toggle-svg-wrap", () => this.toggleSvgWordWrap());
81728
81768
  this.addClickListener("toggle-js-wrap", () => this.toggleJsWordWrap());
@@ -81745,17 +81785,17 @@ class GveHintDesigner extends HTMLElement {
81745
81785
  * Initialize panning with mouse drag.
81746
81786
  */
81747
81787
  initializePanning() {
81748
- const panel = this._shadow.querySelector('.svg-display-panel');
81788
+ const panel = this._shadow.querySelector(".svg-display-panel");
81749
81789
  if (!panel)
81750
81790
  return;
81751
- panel.addEventListener('mousedown', (e) => {
81791
+ panel.addEventListener("mousedown", (e) => {
81752
81792
  const mouseEvent = e;
81753
81793
  this._isPanning = true;
81754
81794
  this._lastPanX = mouseEvent.clientX;
81755
81795
  this._lastPanY = mouseEvent.clientY;
81756
- panel.style.cursor = 'grabbing';
81796
+ panel.style.cursor = "grabbing";
81757
81797
  });
81758
- document.addEventListener('mousemove', (e) => {
81798
+ document.addEventListener("mousemove", (e) => {
81759
81799
  if (!this._isPanning)
81760
81800
  return;
81761
81801
  const deltaX = e.clientX - this._lastPanX;
@@ -81766,24 +81806,24 @@ class GveHintDesigner extends HTMLElement {
81766
81806
  this._lastPanY = e.clientY;
81767
81807
  this.updateSvgTransform();
81768
81808
  });
81769
- document.addEventListener('mouseup', () => {
81809
+ document.addEventListener("mouseup", () => {
81770
81810
  if (this._isPanning) {
81771
81811
  this._isPanning = false;
81772
81812
  const panelEl = panel;
81773
- panelEl.style.cursor = 'grab';
81813
+ panelEl.style.cursor = "grab";
81774
81814
  }
81775
81815
  });
81776
81816
  // Set initial cursor
81777
- panel.style.cursor = 'grab';
81817
+ panel.style.cursor = "grab";
81778
81818
  }
81779
81819
  /**
81780
81820
  * Initialize zooming with mouse wheel.
81781
81821
  */
81782
81822
  initializeWheelZoom() {
81783
- const panel = this._shadow.querySelector('.svg-display-panel');
81823
+ const panel = this._shadow.querySelector(".svg-display-panel");
81784
81824
  if (!panel)
81785
81825
  return;
81786
- panel.addEventListener('wheel', (e) => {
81826
+ panel.addEventListener("wheel", (e) => {
81787
81827
  const wheelEvent = e;
81788
81828
  wheelEvent.preventDefault();
81789
81829
  // Determine zoom direction based on wheel delta
@@ -81838,7 +81878,8 @@ class GveHintDesigner extends HTMLElement {
81838
81878
  if (!this._hintsDropdown)
81839
81879
  return;
81840
81880
  const currentValue = this._hintsDropdown.value;
81841
- this._hintsDropdown.innerHTML = '<option value="">-- Select Hint --</option>';
81881
+ this._hintsDropdown.innerHTML =
81882
+ '<option value="">-- Select Hint --</option>';
81842
81883
  const hintIds = Object.keys(this._data.hints);
81843
81884
  hintIds.forEach((hintId) => {
81844
81885
  const option = document.createElement("option");
@@ -81860,7 +81901,8 @@ class GveHintDesigner extends HTMLElement {
81860
81901
  if (!this._animationsDropdown)
81861
81902
  return;
81862
81903
  const currentValue = this._animationsDropdown.value;
81863
- this._animationsDropdown.innerHTML = '<option value="">-- Select Animation --</option>';
81904
+ this._animationsDropdown.innerHTML =
81905
+ '<option value="">-- Select Animation --</option>';
81864
81906
  Object.keys(this._data.animations).forEach((animId) => {
81865
81907
  const option = document.createElement("option");
81866
81908
  option.value = animId;
@@ -81931,7 +81973,8 @@ class GveHintDesigner extends HTMLElement {
81931
81973
  if (this._jsTextarea) {
81932
81974
  // If the ID is not found, use the template; otherwise use empty string
81933
81975
  const animCode = this._data.animations[animId];
81934
- this._jsTextarea.value = animCode !== undefined ? animCode : NEW_ANIMATION_TEMPLATE;
81976
+ this._jsTextarea.value =
81977
+ animCode !== undefined ? animCode : NEW_ANIMATION_TEMPLATE;
81935
81978
  this.highlightJs();
81936
81979
  }
81937
81980
  }
@@ -81988,6 +82031,49 @@ class GveHintDesigner extends HTMLElement {
81988
82031
  if (this._embeddedJsCheckbox)
81989
82032
  this._embeddedJsCheckbox.checked = false;
81990
82033
  }
82034
+ /**
82035
+ * Validate SVG code for well-formedness and structure.
82036
+ * Returns an error message if validation fails, or null if valid.
82037
+ */
82038
+ validateSvgCode(svgCode) {
82039
+ // Check if SVG code is empty
82040
+ if (!svgCode || !svgCode.trim()) {
82041
+ return "SVG code cannot be empty";
82042
+ }
82043
+ const trimmedCode = svgCode.trim();
82044
+ // Check that it starts with <g and ends with </g>
82045
+ if (!trimmedCode.startsWith("<g")) {
82046
+ return "SVG must start with a <g> element (e.g., <g> or <g ...>)";
82047
+ }
82048
+ if (!trimmedCode.endsWith("</g>")) {
82049
+ return "SVG must end with </g> (closing tag for the root <g> element)";
82050
+ }
82051
+ // Use DOMParser to validate XML well-formedness
82052
+ const parser = new DOMParser();
82053
+ const wrappedSvg = `<svg xmlns="http://www.w3.org/2000/svg">${svgCode}</svg>`;
82054
+ const doc = parser.parseFromString(wrappedSvg, "image/svg+xml");
82055
+ // Check for parsing errors
82056
+ const errorNode = doc.querySelector("parsererror");
82057
+ if (errorNode) {
82058
+ const errorText = errorNode.textContent || "Unknown XML parsing error";
82059
+ return `XML is not well-formed: ${errorText}`;
82060
+ }
82061
+ // Verify there is exactly one root <g> element
82062
+ const svgElement = doc.documentElement;
82063
+ const rootChildren = Array.from(svgElement.children);
82064
+ if (rootChildren.length === 0) {
82065
+ return "SVG must contain a <g> element";
82066
+ }
82067
+ if (rootChildren.length > 1) {
82068
+ return `SVG must have exactly one root <g> element, but found ${rootChildren.length} root elements. Wrap all content in a single <g> element.`;
82069
+ }
82070
+ const rootElement = rootChildren[0];
82071
+ if (rootElement.tagName.toLowerCase() !== "g") {
82072
+ return `Root element must be <g>, but found <${rootElement.tagName}>`;
82073
+ }
82074
+ // All validations passed
82075
+ return null;
82076
+ }
81991
82077
  /**
81992
82078
  * Add a new hint.
81993
82079
  */
@@ -82027,19 +82113,21 @@ class GveHintDesigner extends HTMLElement {
82027
82113
  // Validate SVG and JS
82028
82114
  const svgCode = this._svgTextarea?.value || "";
82029
82115
  const animationValue = this._jsTextarea?.value || "";
82116
+ // Validate SVG code for well-formedness and structure
82117
+ const svgValidationError = this.validateSvgCode(svgCode);
82118
+ if (svgValidationError) {
82119
+ this.showMessage(`SVG validation error: ${svgValidationError}`, "error");
82120
+ return; // Prevent saving on error
82121
+ }
82122
+ // Validate JS syntax if present
82030
82123
  try {
82031
- // Validate SVG (basic check)
82032
- if (svgCode && !svgCode.trim().startsWith("<g")) {
82033
- throw new Error("SVG must start with <g> element");
82034
- }
82035
- // Validate JS syntax if present (we'll do more specific validation below)
82036
82124
  if (animationValue && !animationValue.trim().startsWith("#")) {
82037
82125
  // Try to create a function to validate syntax
82038
82126
  new Function("gsap", "targetEl", "rootEl", animationValue);
82039
82127
  }
82040
82128
  }
82041
82129
  catch (err) {
82042
- this.showMessage(`Validation error: ${err}`, "error");
82130
+ this.showMessage(`JS validation error: ${err}`, "error");
82043
82131
  return; // Prevent saving on error
82044
82132
  }
82045
82133
  // Build hint object
@@ -82178,23 +82266,33 @@ class GveHintDesigner extends HTMLElement {
82178
82266
  if (node.nodeType === Node.ELEMENT_NODE) {
82179
82267
  const element = node;
82180
82268
  const tagName = element.tagName.toLowerCase();
82181
- const id = element.getAttribute('id');
82269
+ const id = element.getAttribute("id");
82182
82270
  // Keep defs and grid-and-rulers, remove everything else
82183
- if (tagName !== 'defs' && id !== 'grid-and-rulers') {
82271
+ if (tagName !== "defs" && id !== "grid-and-rulers") {
82184
82272
  nodesToRemove.push(node);
82185
82273
  }
82186
82274
  }
82187
82275
  }
82188
- nodesToRemove.forEach(node => this._svgDisplay.removeChild(node));
82276
+ nodesToRemove.forEach((node) => this._svgDisplay.removeChild(node));
82189
82277
  // If grid and rulers don't exist yet, add them
82190
- if (!this._svgDisplay.querySelector('#grid-and-rulers')) {
82278
+ if (!this._svgDisplay.querySelector("#grid-and-rulers")) {
82191
82279
  this.addGridAndRulers(this._svgDisplay);
82192
82280
  }
82193
- if (!this._hintId || !this._data.hints[this._hintId]) {
82281
+ if (!this._hintId) {
82194
82282
  return;
82195
82283
  }
82196
- const hint = this._data.hints[this._hintId];
82197
- let svgCode = hint.svg;
82284
+ // Get SVG code from textarea if it exists, otherwise from saved data
82285
+ let svgCode = this._svgTextarea?.value || "";
82286
+ // Fallback to saved data if textarea is empty and we have saved data
82287
+ if (!svgCode && this._data.hints[this._hintId]) {
82288
+ svgCode = this._data.hints[this._hintId].svg;
82289
+ }
82290
+ // If still no SVG code, return early
82291
+ if (!svgCode) {
82292
+ return;
82293
+ }
82294
+ // Validate variables before resolving them
82295
+ this.validateSvgVariables(svgCode);
82198
82296
  // Resolve variables
82199
82297
  svgCode = this.resolveVariables(svgCode);
82200
82298
  try {
@@ -82244,13 +82342,103 @@ class GveHintDesigner extends HTMLElement {
82244
82342
  });
82245
82343
  return resolved;
82246
82344
  }
82345
+ /**
82346
+ * Extract variable names from SVG code (variables in {{...}} placeholders).
82347
+ */
82348
+ extractVariablesFromSvg(svgCode) {
82349
+ const variablePattern = /\{\{([^}]+)\}\}/g;
82350
+ const variables = [];
82351
+ let match;
82352
+ while ((match = variablePattern.exec(svgCode)) !== null) {
82353
+ const varName = match[1].trim();
82354
+ if (varName && !variables.includes(varName)) {
82355
+ variables.push(varName);
82356
+ }
82357
+ }
82358
+ return variables;
82359
+ }
82360
+ /**
82361
+ * Validate SVG variables and update validation message.
82362
+ * Shows a warning if any variables used in SVG are not defined in hint variables.
82363
+ */
82364
+ validateSvgVariables(svgCode) {
82365
+ if (!this._variablesValidationMsg || !this._variablesPanelHeader) {
82366
+ return;
82367
+ }
82368
+ // Extract variables from SVG
82369
+ const usedVariables = this.extractVariablesFromSvg(svgCode);
82370
+ if (usedVariables.length === 0) {
82371
+ // No variables used - hide validation message and warning icon
82372
+ this._variablesValidationMsg.style.display = "none";
82373
+ this.updateVariablesHeaderWarning(false);
82374
+ return;
82375
+ }
82376
+ // Get defined variable names
82377
+ const definedVariables = this._hintVariables.map((v) => v.name);
82378
+ // Find undefined variables
82379
+ const undefinedVariables = usedVariables.filter((v) => !definedVariables.includes(v));
82380
+ if (undefinedVariables.length === 0) {
82381
+ // All variables are defined - hide validation message and warning icon
82382
+ this._variablesValidationMsg.style.display = "none";
82383
+ this.updateVariablesHeaderWarning(false);
82384
+ return;
82385
+ }
82386
+ // Show validation message with list of undefined variables
82387
+ this._variablesValidationMsg.style.display = "block";
82388
+ this._variablesValidationMsg.innerHTML = `
82389
+ <strong>⚠️ Undefined Variables:</strong>
82390
+ <div style="margin-top: 0.5rem;">
82391
+ The following variables are used in the SVG code but not defined in Hint Variables:
82392
+ <ul style="margin: 0.5rem 0; padding-left: 1.5rem;">
82393
+ ${undefinedVariables
82394
+ .map((v) => `<li><code>{{${v}}}</code></li>`)
82395
+ .join("")}
82396
+ </ul>
82397
+ <em>Add these variables to the list below to ensure the hint displays correctly.</em>
82398
+ </div>
82399
+ `;
82400
+ // Show warning icon in header
82401
+ this.updateVariablesHeaderWarning(true);
82402
+ }
82403
+ /**
82404
+ * Update warning icon in variables panel header.
82405
+ */
82406
+ updateVariablesHeaderWarning(showWarning) {
82407
+ if (!this._variablesPanelHeader)
82408
+ return;
82409
+ // Remove existing warning icon if present
82410
+ const existingWarning = this._variablesPanelHeader.querySelector(".warning-icon");
82411
+ if (existingWarning) {
82412
+ existingWarning.remove();
82413
+ }
82414
+ if (showWarning) {
82415
+ // Add warning icon
82416
+ const warningIcon = document.createElement("span");
82417
+ warningIcon.className = "warning-icon";
82418
+ warningIcon.title = "Some variables used in SVG are not defined";
82419
+ const icon = featherExports.icons["alert-triangle"];
82420
+ if (icon) {
82421
+ warningIcon.innerHTML = icon.toSvg({
82422
+ class: "feather-icon",
82423
+ width: 16,
82424
+ height: 16,
82425
+ "stroke-width": 2,
82426
+ color: "#ff9800",
82427
+ });
82428
+ }
82429
+ else {
82430
+ warningIcon.textContent = "⚠️";
82431
+ }
82432
+ this._variablesPanelHeader.appendChild(warningIcon);
82433
+ }
82434
+ }
82247
82435
  /**
82248
82436
  * Play animation.
82249
82437
  */
82250
82438
  playAnimation() {
82251
82439
  this._logger.debug("Animation", "playAnimation called", {
82252
82440
  hasTimeline: !!this._currentTimeline,
82253
- isPlaying: this._isPlaying
82441
+ isPlaying: this._isPlaying,
82254
82442
  });
82255
82443
  if (this._currentTimeline) {
82256
82444
  // If timeline exists, just play it
@@ -82288,7 +82476,7 @@ class GveHintDesigner extends HTMLElement {
82288
82476
  createTimeline() {
82289
82477
  this._logger.debug("Animation", "createTimeline called", {
82290
82478
  hasSvgDisplay: !!this._svgDisplay,
82291
- hintId: this._hintId
82479
+ hintId: this._hintId,
82292
82480
  });
82293
82481
  if (!this._svgDisplay || !this._hintId || !this._data.hints[this._hintId]) {
82294
82482
  this.showMessage("No hint to animate!", "error");
@@ -82302,11 +82490,16 @@ class GveHintDesigner extends HTMLElement {
82302
82490
  if (hint.animation.startsWith("#")) {
82303
82491
  const animId = hint.animation.substring(1);
82304
82492
  jsCode = this._data.animations[animId] || "";
82305
- this._logger.debug("Animation", "Using referenced animation", { animId, hasCode: !!jsCode });
82493
+ this._logger.debug("Animation", "Using referenced animation", {
82494
+ animId,
82495
+ hasCode: !!jsCode,
82496
+ });
82306
82497
  }
82307
82498
  else {
82308
82499
  jsCode = hint.animation;
82309
- this._logger.debug("Animation", "Using embedded animation", { codeLength: jsCode.length });
82500
+ this._logger.debug("Animation", "Using embedded animation", {
82501
+ codeLength: jsCode.length,
82502
+ });
82310
82503
  }
82311
82504
  }
82312
82505
  if (!jsCode) {
@@ -82323,7 +82516,10 @@ class GveHintDesigner extends HTMLElement {
82323
82516
  if (!hintEl) {
82324
82517
  throw new Error("No hint <g> element found in SVG");
82325
82518
  }
82326
- this._logger.debug("Animation", "Found hint element", { tagName: hintEl.tagName, id: hintEl.getAttribute('id') });
82519
+ this._logger.debug("Animation", "Found hint element", {
82520
+ tagName: hintEl.tagName,
82521
+ id: hintEl.getAttribute("id"),
82522
+ });
82327
82523
  // Set initial opacity to 0 (on style attribute)
82328
82524
  hintEl.style.opacity = "0";
82329
82525
  // Check if GSAP is available
@@ -82369,12 +82565,12 @@ class GveHintDesigner extends HTMLElement {
82369
82565
  }
82370
82566
  this._logger.debug("Animation", "Animation function executed", {
82371
82567
  resultType: typeof result,
82372
- isPromise: result && typeof result.then === 'function',
82373
- isTimeline: result && typeof result.progress === 'function',
82374
- capturedTweensCount: capturedTweens.length
82568
+ isPromise: result && typeof result.then === "function",
82569
+ isTimeline: result && typeof result.progress === "function",
82570
+ capturedTweensCount: capturedTweens.length,
82375
82571
  });
82376
82572
  // Check what was returned
82377
- if (result && typeof result.progress === 'function') {
82573
+ if (result && typeof result.progress === "function") {
82378
82574
  // Direct timeline/tween return
82379
82575
  this._currentTimeline = result;
82380
82576
  this._currentTimeline.pause();
@@ -82384,7 +82580,7 @@ class GveHintDesigner extends HTMLElement {
82384
82580
  // Animation called gsap methods - create timeline with captured tweens
82385
82581
  this._logger.debug("Animation", "Creating timeline from captured tweens", {
82386
82582
  tweenCount: capturedTweens.length,
82387
- tweenDurations: capturedTweens.map(t => t.duration())
82583
+ tweenDurations: capturedTweens.map((t) => t.duration()),
82388
82584
  });
82389
82585
  // The tweens have already started playing, so we need to:
82390
82586
  // 1. Kill them to stop auto-playing
@@ -82399,14 +82595,14 @@ class GveHintDesigner extends HTMLElement {
82399
82595
  duration: duration,
82400
82596
  totalDuration: totalDuration,
82401
82597
  targets: tween.targets(),
82402
- vars: tween.vars
82598
+ vars: tween.vars,
82403
82599
  });
82404
82600
  // Get the animation method that was used (to, from, fromTo)
82405
82601
  const targets = tween.targets();
82406
82602
  const vars = { ...tween.vars };
82407
82603
  // Get the initial values if this was a fromTo or from tween
82408
82604
  const isFromTo = tween._from !== undefined;
82409
- const isFrom = !isFromTo && tween.vars.hasOwnProperty('startAt');
82605
+ const isFrom = !isFromTo && tween.vars.hasOwnProperty("startAt");
82410
82606
  // Kill the original tween to stop it from playing
82411
82607
  tween.kill();
82412
82608
  // Recreate the tween in our timeline using the appropriate method
@@ -82425,7 +82621,7 @@ class GveHintDesigner extends HTMLElement {
82425
82621
  }
82426
82622
  });
82427
82623
  this._logger.debug("Animation", "Timeline built from tweens", {
82428
- timelineDuration: this._currentTimeline.duration()
82624
+ timelineDuration: this._currentTimeline.duration(),
82429
82625
  });
82430
82626
  }
82431
82627
  else {
@@ -82436,7 +82632,7 @@ class GveHintDesigner extends HTMLElement {
82436
82632
  this._logger.debug("Animation", "Timeline created", {
82437
82633
  duration: this._currentTimeline.duration(),
82438
82634
  progress: this._currentTimeline.progress(),
82439
- paused: this._currentTimeline.paused()
82635
+ paused: this._currentTimeline.paused(),
82440
82636
  });
82441
82637
  // Check if timeline has duration
82442
82638
  if (this._currentTimeline.duration() === 0) {
@@ -82448,7 +82644,7 @@ class GveHintDesigner extends HTMLElement {
82448
82644
  this._logger.debug("Animation", "Animation completed callback", {
82449
82645
  finalProgress: this._currentTimeline.progress(),
82450
82646
  finalTime: this._currentTimeline.time(),
82451
- duration: this._currentTimeline.duration()
82647
+ duration: this._currentTimeline.duration(),
82452
82648
  });
82453
82649
  this._isPlaying = false;
82454
82650
  if (this._playButton)
@@ -82464,7 +82660,7 @@ class GveHintDesigner extends HTMLElement {
82464
82660
  });
82465
82661
  this.showMessage("", "");
82466
82662
  this._logger.info("Animation", "Timeline created successfully", {
82467
- duration: this._currentTimeline.duration()
82663
+ duration: this._currentTimeline.duration(),
82468
82664
  });
82469
82665
  }
82470
82666
  catch (err) {
@@ -82483,7 +82679,7 @@ class GveHintDesigner extends HTMLElement {
82483
82679
  pauseAnimation() {
82484
82680
  this._logger.debug("Animation", "pauseAnimation called", {
82485
82681
  hasTimeline: !!this._currentTimeline,
82486
- isPlaying: this._isPlaying
82682
+ isPlaying: this._isPlaying,
82487
82683
  });
82488
82684
  if (this._currentTimeline) {
82489
82685
  this._currentTimeline.pause();
@@ -82494,7 +82690,7 @@ class GveHintDesigner extends HTMLElement {
82494
82690
  this._pauseButton.disabled = true;
82495
82691
  this.stopProgressUpdate();
82496
82692
  this._logger.debug("Animation", "Animation paused", {
82497
- progress: this._currentTimeline.progress()
82693
+ progress: this._currentTimeline.progress(),
82498
82694
  });
82499
82695
  }
82500
82696
  }
@@ -82504,7 +82700,7 @@ class GveHintDesigner extends HTMLElement {
82504
82700
  restartAnimation() {
82505
82701
  this._logger.debug("Animation", "restartAnimation called", {
82506
82702
  hasTimeline: !!this._currentTimeline,
82507
- isPlaying: this._isPlaying
82703
+ isPlaying: this._isPlaying,
82508
82704
  });
82509
82705
  if (this._currentTimeline) {
82510
82706
  this._logger.debug("Animation", "Restarting existing timeline");
@@ -82529,7 +82725,7 @@ class GveHintDesigner extends HTMLElement {
82529
82725
  this._logger.debug("Animation", "seekAnimation called", {
82530
82726
  hasProgressBar: !!this._progressBar,
82531
82727
  hasTimeline: !!this._currentTimeline,
82532
- progressBarValue: this._progressBar?.value
82728
+ progressBarValue: this._progressBar?.value,
82533
82729
  });
82534
82730
  if (!this._progressBar)
82535
82731
  return;
@@ -82547,7 +82743,7 @@ class GveHintDesigner extends HTMLElement {
82547
82743
  progressBarValue: this._progressBar.value,
82548
82744
  calculatedProgress: progress,
82549
82745
  wasPlaying: this._isPlaying,
82550
- timelineDuration: this._currentTimeline.duration()
82746
+ timelineDuration: this._currentTimeline.duration(),
82551
82747
  });
82552
82748
  // Pause the timeline to prevent it from continuing to play
82553
82749
  this._isPlaying;
@@ -82556,7 +82752,7 @@ class GveHintDesigner extends HTMLElement {
82556
82752
  this._currentTimeline.progress(progress);
82557
82753
  this._logger.debug("Animation", "Seek completed", {
82558
82754
  newProgress: this._currentTimeline.progress(),
82559
- timelinePaused: this._currentTimeline.paused()
82755
+ timelinePaused: this._currentTimeline.paused(),
82560
82756
  });
82561
82757
  // Update playing state
82562
82758
  this._isPlaying = false;
@@ -82580,7 +82776,7 @@ class GveHintDesigner extends HTMLElement {
82580
82776
  }
82581
82777
  }, 50); // Update every 50ms
82582
82778
  this._logger.debug("Animation", "Progress update started", {
82583
- intervalId: this._progressUpdateInterval
82779
+ intervalId: this._progressUpdateInterval,
82584
82780
  });
82585
82781
  }
82586
82782
  /**
@@ -82589,7 +82785,7 @@ class GveHintDesigner extends HTMLElement {
82589
82785
  stopProgressUpdate() {
82590
82786
  if (this._progressUpdateInterval) {
82591
82787
  this._logger.debug("Animation", "Stopping progress update", {
82592
- intervalId: this._progressUpdateInterval
82788
+ intervalId: this._progressUpdateInterval,
82593
82789
  });
82594
82790
  clearInterval(this._progressUpdateInterval);
82595
82791
  this._progressUpdateInterval = undefined;
@@ -82616,7 +82812,7 @@ class GveHintDesigner extends HTMLElement {
82616
82812
  if (!this._svgDisplay)
82617
82813
  return;
82618
82814
  // Get panel size to center the SVG
82619
- const panel = this._shadow.querySelector('.svg-display-panel');
82815
+ const panel = this._shadow.querySelector(".svg-display-panel");
82620
82816
  if (panel) {
82621
82817
  const panelWidth = panel.clientWidth;
82622
82818
  const panelHeight = panel.clientHeight;
@@ -82647,8 +82843,8 @@ class GveHintDesigner extends HTMLElement {
82647
82843
  panY: this._panY,
82648
82844
  zoomLevel: this._zoomLevel,
82649
82845
  transformString,
82650
- svgWidth: this._svgDisplay.getAttribute('width'),
82651
- svgHeight: this._svgDisplay.getAttribute('height'),
82846
+ svgWidth: this._svgDisplay.getAttribute("width"),
82847
+ svgHeight: this._svgDisplay.getAttribute("height"),
82652
82848
  svgBBox: this._svgDisplay.getBBox(),
82653
82849
  svgBoundingClientRect: {
82654
82850
  x: svgBoundingRect.x,
@@ -82658,18 +82854,18 @@ class GveHintDesigner extends HTMLElement {
82658
82854
  top: svgBoundingRect.top,
82659
82855
  left: svgBoundingRect.left,
82660
82856
  right: svgBoundingRect.right,
82661
- bottom: svgBoundingRect.bottom
82662
- }
82857
+ bottom: svgBoundingRect.bottom,
82858
+ },
82663
82859
  });
82664
82860
  // Apply transform: translate first, then scale from the SVG's own center
82665
82861
  this._svgDisplay.style.transform = transformString;
82666
- this._svgDisplay.style.transformOrigin = '0 0';
82862
+ this._svgDisplay.style.transformOrigin = "0 0";
82667
82863
  // Only apply transition during zoom, not during pan (to avoid sluggishness)
82668
82864
  if (!this._isPanning) {
82669
- this._svgDisplay.style.transition = 'transform 0.15s ease-out';
82865
+ this._svgDisplay.style.transition = "transform 0.15s ease-out";
82670
82866
  }
82671
82867
  else {
82672
- this._svgDisplay.style.transition = 'none';
82868
+ this._svgDisplay.style.transition = "none";
82673
82869
  }
82674
82870
  }
82675
82871
  /**
@@ -82689,7 +82885,7 @@ class GveHintDesigner extends HTMLElement {
82689
82885
  // Get the bounding box of the hint content
82690
82886
  const bbox = hintContent.getBBox();
82691
82887
  // Get the SVG display panel size
82692
- const panel = this._shadow.querySelector('.svg-display-panel');
82888
+ const panel = this._shadow.querySelector(".svg-display-panel");
82693
82889
  if (!panel)
82694
82890
  return;
82695
82891
  const panelWidth = panel.clientWidth;
@@ -82710,8 +82906,8 @@ class GveHintDesigner extends HTMLElement {
82710
82906
  // We want this to be at the center of the panel: (panelWidth/2, panelHeight/2)
82711
82907
  // So: panX + (bboxCenterX * scale) = panelWidth / 2
82712
82908
  // panY + (bboxCenterY * scale) = panelHeight / 2
82713
- const panX = (panelWidth / 2) - (bboxCenterX * scale);
82714
- const panY = (panelHeight / 2) - (bboxCenterY * scale);
82909
+ const panX = panelWidth / 2 - bboxCenterX * scale;
82910
+ const panY = panelHeight / 2 - bboxCenterY * scale;
82715
82911
  this._zoomLevel = scale;
82716
82912
  this._panX = panX;
82717
82913
  this._panY = panY;
@@ -82724,7 +82920,7 @@ class GveHintDesigner extends HTMLElement {
82724
82920
  bboxCenterX,
82725
82921
  bboxCenterY,
82726
82922
  panelWidth,
82727
- panelHeight
82923
+ panelHeight,
82728
82924
  });
82729
82925
  }
82730
82926
  catch (err) {
@@ -82737,13 +82933,13 @@ class GveHintDesigner extends HTMLElement {
82737
82933
  */
82738
82934
  toggleSvgWordWrap() {
82739
82935
  this._svgWordWrapEnabled = !this._svgWordWrapEnabled;
82740
- const wrapper = this._shadow.getElementById('svg-wrapper');
82936
+ const wrapper = this._shadow.getElementById("svg-wrapper");
82741
82937
  if (wrapper) {
82742
82938
  if (this._svgWordWrapEnabled) {
82743
- wrapper.classList.add('word-wrap-enabled');
82939
+ wrapper.classList.add("word-wrap-enabled");
82744
82940
  }
82745
82941
  else {
82746
- wrapper.classList.remove('word-wrap-enabled');
82942
+ wrapper.classList.remove("word-wrap-enabled");
82747
82943
  }
82748
82944
  }
82749
82945
  this._logger.debug("Component", "SVG word wrap toggled", this._svgWordWrapEnabled);
@@ -82753,13 +82949,13 @@ class GveHintDesigner extends HTMLElement {
82753
82949
  */
82754
82950
  toggleJsWordWrap() {
82755
82951
  this._jsWordWrapEnabled = !this._jsWordWrapEnabled;
82756
- const wrapper = this._shadow.getElementById('js-wrapper');
82952
+ const wrapper = this._shadow.getElementById("js-wrapper");
82757
82953
  if (wrapper) {
82758
82954
  if (this._jsWordWrapEnabled) {
82759
- wrapper.classList.add('word-wrap-enabled');
82955
+ wrapper.classList.add("word-wrap-enabled");
82760
82956
  }
82761
82957
  else {
82762
- wrapper.classList.remove('word-wrap-enabled');
82958
+ wrapper.classList.remove("word-wrap-enabled");
82763
82959
  }
82764
82960
  }
82765
82961
  this._logger.debug("Component", "JS word wrap toggled", this._jsWordWrapEnabled);
@@ -82855,7 +83051,9 @@ class GveHintDesigner extends HTMLElement {
82855
83051
  }
82856
83052
  this._hintVariables.push({ name, value: "" });
82857
83053
  this.refreshVariablesTable();
82858
- this.fireEvent("hintVariablesChange", { hintVariables: this._hintVariables });
83054
+ this.fireEvent("hintVariablesChange", {
83055
+ hintVariables: this._hintVariables,
83056
+ });
82859
83057
  }
82860
83058
  /**
82861
83059
  * Delete a variable.
@@ -82863,7 +83061,9 @@ class GveHintDesigner extends HTMLElement {
82863
83061
  deleteVariable(index) {
82864
83062
  this._hintVariables.splice(index, 1);
82865
83063
  this.refreshVariablesTable();
82866
- this.fireEvent("hintVariablesChange", { hintVariables: this._hintVariables });
83064
+ this.fireEvent("hintVariablesChange", {
83065
+ hintVariables: this._hintVariables,
83066
+ });
82867
83067
  }
82868
83068
  /**
82869
83069
  * Edit a variable.
@@ -82874,7 +83074,9 @@ class GveHintDesigner extends HTMLElement {
82874
83074
  if (newValue !== null) {
82875
83075
  variable.value = newValue;
82876
83076
  this.refreshVariablesTable();
82877
- this.fireEvent("hintVariablesChange", { hintVariables: this._hintVariables });
83077
+ this.fireEvent("hintVariablesChange", {
83078
+ hintVariables: this._hintVariables,
83079
+ });
82878
83080
  }
82879
83081
  }
82880
83082
  /**
@@ -82886,7 +83088,61 @@ class GveHintDesigner extends HTMLElement {
82886
83088
  return;
82887
83089
  this._hintVariables = [];
82888
83090
  this.refreshVariablesTable();
82889
- this.fireEvent("hintVariablesChange", { hintVariables: this._hintVariables });
83091
+ this.fireEvent("hintVariablesChange", {
83092
+ hintVariables: this._hintVariables,
83093
+ });
83094
+ }
83095
+ /**
83096
+ * Extract all variable names from all hints in data.
83097
+ * Returns a sorted array of unique variable names.
83098
+ */
83099
+ extractAllVariablesFromHints() {
83100
+ const allVariables = new Set();
83101
+ // Iterate through all hints in data
83102
+ Object.values(this._data.hints).forEach((hint) => {
83103
+ if (hint.svg) {
83104
+ // Extract variables from this hint's SVG
83105
+ const variables = this.extractVariablesFromSvg(hint.svg);
83106
+ variables.forEach((varName) => allVariables.add(varName));
83107
+ }
83108
+ });
83109
+ // Convert to array and sort alphabetically
83110
+ return Array.from(allVariables).sort();
83111
+ }
83112
+ /**
83113
+ * Populate hint variables from all hints.
83114
+ * Extracts all variables from all hints' SVG code and merges them into hintVariables.
83115
+ * Only adds variables that don't already exist.
83116
+ */
83117
+ populateVariablesFromHints() {
83118
+ // Extract all variables from hints
83119
+ const extractedVariables = this.extractAllVariablesFromHints();
83120
+ if (extractedVariables.length === 0) {
83121
+ this.showMessage("No variables found in hints", "info");
83122
+ return;
83123
+ }
83124
+ // Get existing variable names for quick lookup
83125
+ const existingNames = new Set(this._hintVariables.map((v) => v.name));
83126
+ // Add new variables (those not already in hintVariables)
83127
+ let addedCount = 0;
83128
+ extractedVariables.forEach((varName) => {
83129
+ if (!existingNames.has(varName)) {
83130
+ this._hintVariables.push({ name: varName, value: "" });
83131
+ addedCount++;
83132
+ }
83133
+ });
83134
+ // Refresh UI and notify
83135
+ this.refreshVariablesTable();
83136
+ this.fireEvent("hintVariablesChange", {
83137
+ hintVariables: this._hintVariables,
83138
+ });
83139
+ // Show feedback message
83140
+ if (addedCount === 0) {
83141
+ this.showMessage(`All ${extractedVariables.length} variable(s) already defined`, "info");
83142
+ }
83143
+ else {
83144
+ this.showMessage(`Added ${addedCount} new variable(s) from hints (${extractedVariables.length} total found)`, "success");
83145
+ }
82890
83146
  }
82891
83147
  /**
82892
83148
  * Refresh the variables table.
@@ -82902,6 +83158,11 @@ class GveHintDesigner extends HTMLElement {
82902
83158
  cell.textContent = "No variables defined";
82903
83159
  cell.style.fontStyle = "italic";
82904
83160
  cell.style.color = "#999";
83161
+ // Re-validate SVG variables after clearing variables
83162
+ const svgCode = this._svgTextarea?.value || "";
83163
+ if (svgCode) {
83164
+ this.validateSvgVariables(svgCode);
83165
+ }
82905
83166
  return;
82906
83167
  }
82907
83168
  this._hintVariables.forEach((variable, index) => {
@@ -82922,6 +83183,11 @@ class GveHintDesigner extends HTMLElement {
82922
83183
  const valueCell = row.insertCell();
82923
83184
  valueCell.textContent = variable.value;
82924
83185
  });
83186
+ // Re-validate SVG variables after updating the variables table
83187
+ const svgCode = this._svgTextarea?.value || "";
83188
+ if (svgCode) {
83189
+ this.validateSvgVariables(svgCode);
83190
+ }
82925
83191
  }
82926
83192
  /**
82927
83193
  * Show a message in the message panel.
@@ -82942,16 +83208,18 @@ class GveHintDesigner extends HTMLElement {
82942
83208
  if (!this._svgTextarea || !this._svgHighlightContainer) {
82943
83209
  this._logger.debug("Component", "Cannot highlight SVG - missing elements", {
82944
83210
  hasTextarea: !!this._svgTextarea,
82945
- hasContainer: !!this._svgHighlightContainer
83211
+ hasContainer: !!this._svgHighlightContainer,
82946
83212
  });
82947
83213
  return;
82948
83214
  }
82949
83215
  const code = this._svgTextarea.value;
82950
- this._logger.debug("Component", "Highlighting SVG", { codeLength: code.length });
83216
+ this._logger.debug("Component", "Highlighting SVG", {
83217
+ codeLength: code.length,
83218
+ });
82951
83219
  try {
82952
83220
  // Highlight as XML
82953
- const highlighted = HighlightJS.highlight(code, { language: 'xml' }).value;
82954
- this._svgHighlightContainer.innerHTML = highlighted + '\n';
83221
+ const highlighted = HighlightJS.highlight(code, { language: "xml" }).value;
83222
+ this._svgHighlightContainer.innerHTML = highlighted + "\n";
82955
83223
  this._logger.debug("Component", "SVG highlighted successfully");
82956
83224
  }
82957
83225
  catch (err) {
@@ -82969,16 +83237,20 @@ class GveHintDesigner extends HTMLElement {
82969
83237
  if (!this._jsTextarea || !this._jsHighlightContainer) {
82970
83238
  this._logger.debug("Component", "Cannot highlight JS - missing elements", {
82971
83239
  hasTextarea: !!this._jsTextarea,
82972
- hasContainer: !!this._jsHighlightContainer
83240
+ hasContainer: !!this._jsHighlightContainer,
82973
83241
  });
82974
83242
  return;
82975
83243
  }
82976
83244
  const code = this._jsTextarea.value;
82977
- this._logger.debug("Component", "Highlighting JS", { codeLength: code.length });
83245
+ this._logger.debug("Component", "Highlighting JS", {
83246
+ codeLength: code.length,
83247
+ });
82978
83248
  try {
82979
83249
  // Highlight as JavaScript
82980
- const highlighted = HighlightJS.highlight(code, { language: 'javascript' }).value;
82981
- this._jsHighlightContainer.innerHTML = highlighted + '\n';
83250
+ const highlighted = HighlightJS.highlight(code, {
83251
+ language: "javascript",
83252
+ }).value;
83253
+ this._jsHighlightContainer.innerHTML = highlighted + "\n";
82982
83254
  this._logger.debug("Component", "JS highlighted successfully");
82983
83255
  }
82984
83256
  catch (err) {
@@ -83576,6 +83848,60 @@ class GveHintDesigner extends HTMLElement {
83576
83848
  background: #ffcdd2;
83577
83849
  }
83578
83850
 
83851
+ /* Validation Message */
83852
+ .validation-message {
83853
+ padding: 0.75rem;
83854
+ margin: 0.5rem;
83855
+ background: #fff3cd;
83856
+ border: 1px solid #ffc107;
83857
+ border-radius: 4px;
83858
+ color: #856404;
83859
+ font-size: 13px;
83860
+ line-height: 1.5;
83861
+ }
83862
+
83863
+ .validation-message strong {
83864
+ display: block;
83865
+ margin-bottom: 0.5rem;
83866
+ color: #ff9800;
83867
+ }
83868
+
83869
+ .validation-message code {
83870
+ background: #fff;
83871
+ padding: 0.2rem 0.4rem;
83872
+ border-radius: 3px;
83873
+ font-family: 'Consolas', 'Monaco', monospace;
83874
+ font-size: 12px;
83875
+ border: 1px solid #e0e0e0;
83876
+ }
83877
+
83878
+ .validation-message ul {
83879
+ margin: 0.5rem 0;
83880
+ }
83881
+
83882
+ .validation-message li {
83883
+ margin: 0.25rem 0;
83884
+ }
83885
+
83886
+ .validation-message em {
83887
+ display: block;
83888
+ margin-top: 0.5rem;
83889
+ font-size: 12px;
83890
+ color: #666;
83891
+ }
83892
+
83893
+ /* Warning Icon in Panel Header */
83894
+ .warning-icon {
83895
+ display: inline-flex;
83896
+ align-items: center;
83897
+ margin-left: 0.5rem;
83898
+ color: #ff9800;
83899
+ }
83900
+
83901
+ .warning-icon .feather-icon {
83902
+ display: block;
83903
+ }
83904
+
83579
83905
  /* Responsive layout - use nested grids for each column */
83580
83906
  @media (min-width: 900px) {
83581
83907
  .properties-panel .panel-content {