@eventop/sdk 1.2.15 → 1.3.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.
package/dist/core.cjs CHANGED
@@ -221,6 +221,71 @@ function injectStyles(theme, pos) {
221
221
  style.textContent = `
222
222
  #sai-trigger, #sai-panel { ${buildCSSVars(theme)} }
223
223
 
224
+ /* ── Ring Light Animation ── */
225
+ @keyframes sai-ring-pulse {
226
+ 0% {
227
+ box-shadow:
228
+ 0 0 0 0 rgba(var(--sai-accent-rgb), 0.7),
229
+ 0 0 0 0 rgba(var(--sai-accent-rgb), 0.5),
230
+ 0 0 0 0 rgba(var(--sai-accent-rgb), 0.3);
231
+ }
232
+ 50% {
233
+ box-shadow:
234
+ 0 0 0 8px rgba(var(--sai-accent-rgb), 0),
235
+ 0 0 0 16px rgba(var(--sai-accent-rgb), 0.3),
236
+ 0 0 0 24px rgba(var(--sai-accent-rgb), 0);
237
+ }
238
+ 100% {
239
+ box-shadow:
240
+ 0 0 0 16px rgba(var(--sai-accent-rgb), 0),
241
+ 0 0 0 32px rgba(var(--sai-accent-rgb), 0),
242
+ 0 0 0 48px rgba(var(--sai-accent-rgb), 0);
243
+ }
244
+ }
245
+
246
+ @keyframes sai-ring-pulse-hard {
247
+ 0% {
248
+ box-shadow:
249
+ inset 0 0 0 2px var(--sai-accent),
250
+ 0 0 0 3px var(--sai-accent),
251
+ 0 0 20px var(--sai-accent);
252
+ }
253
+ 50% {
254
+ box-shadow:
255
+ inset 0 0 0 2px var(--sai-accent),
256
+ 0 0 0 8px var(--sai-accent),
257
+ 0 0 40px var(--sai-accent);
258
+ }
259
+ 100% {
260
+ box-shadow:
261
+ inset 0 0 0 2px var(--sai-accent),
262
+ 0 0 0 3px var(--sai-accent),
263
+ 0 0 20px var(--sai-accent);
264
+ }
265
+ }
266
+
267
+ @keyframes sai-element-highlight {
268
+ 0% { filter: brightness(1); }
269
+ 50% { filter: brightness(1.15); }
270
+ 100% { filter: brightness(1); }
271
+ }
272
+
273
+ /* Highlighted element styles */
274
+ .sai-highlighted {
275
+ position: relative !important;
276
+ animation: sai-ring-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite !important;
277
+ z-index: 99997 !important;
278
+ }
279
+
280
+ .sai-highlighted.sai-highlight-hard {
281
+ animation: sai-ring-pulse-hard 2s cubic-bezier(0.4, 0, 0.6, 1) infinite !important;
282
+ }
283
+
284
+ .sai-highlighted.sai-highlight-subtle {
285
+ animation: sai-element-highlight 2s ease-in-out infinite !important;
286
+ filter: drop-shadow(0 0 8px var(--sai-accent)) !important;
287
+ }
288
+
224
289
  /* ── Trigger ── */
225
290
  #sai-trigger {
226
291
  position: fixed; ${triggerCSS};
@@ -1006,6 +1071,7 @@ function expandFlowSteps(aiStep, feature) {
1006
1071
 
1007
1072
  // Loads Shepherd.js, builds + runs tours, wires up advanceOn listeners,
1008
1073
  // progress indicators, pause/resume, and step-level error display.
1074
+ // ENHANCED: Adds animated ring light effect to highlighted elements.
1009
1075
 
1010
1076
  const SHEPHERD_JS = 'https://cdn.jsdelivr.net/npm/shepherd.js@11.2.0/dist/js/shepherd.min.js';
1011
1077
 
@@ -1026,6 +1092,61 @@ async function ensureShepherd() {
1026
1092
  await loadScript(SHEPHERD_JS);
1027
1093
  }
1028
1094
 
1095
+ // ─── Highlight management ────────────────────────────────────────────────────
1096
+
1097
+ /**
1098
+ * Applies the ring light animation to an element.
1099
+ *
1100
+ * @param {HTMLElement} el
1101
+ * @param {string} [style='default'] - 'default' | 'hard' | 'subtle'
1102
+ */
1103
+ function applyHighlight(el, style = 'default') {
1104
+ if (!el) return;
1105
+
1106
+ // Set CSS variables for accent color parsing in RGB format
1107
+ const accentColor = getComputedStyle(el).getPropertyValue('--sai-accent') || '#e94560';
1108
+ const rgb = hexToRgb(accentColor.trim());
1109
+ if (rgb) {
1110
+ el.style.setProperty('--sai-accent-rgb', `${rgb.r}, ${rgb.g}, ${rgb.b}`);
1111
+ }
1112
+ el.classList.add('sai-highlighted');
1113
+ if (style === 'hard') {
1114
+ el.classList.add('sai-highlight-hard');
1115
+ } else if (style === 'subtle') {
1116
+ el.classList.add('sai-highlight-subtle');
1117
+ }
1118
+
1119
+ // Scroll into view with extra padding
1120
+ if (el.scrollIntoView) {
1121
+ el.scrollIntoView({
1122
+ behavior: 'smooth',
1123
+ block: 'center'
1124
+ });
1125
+ }
1126
+ }
1127
+
1128
+ /**
1129
+ * Removes the ring light animation from an element.
1130
+ */
1131
+ function removeHighlight(el) {
1132
+ if (!el) return;
1133
+ el.classList.remove('sai-highlighted', 'sai-highlight-hard', 'sai-highlight-subtle');
1134
+ }
1135
+
1136
+ /**
1137
+ * Converts hex color to RGB object.
1138
+ * @param {string} hex
1139
+ * @returns {object|null}
1140
+ */
1141
+ function hexToRgb(hex) {
1142
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
1143
+ return result ? {
1144
+ r: parseInt(result[1], 16),
1145
+ g: parseInt(result[2], 16),
1146
+ b: parseInt(result[3], 16)
1147
+ } : null;
1148
+ }
1149
+
1029
1150
  // ─── Feature-map merge ────────────────────────────────────────────────────────
1030
1151
 
1031
1152
  /**
@@ -1059,6 +1180,7 @@ function mergeWithFeature(step) {
1059
1180
  * 1. Cross-page navigation (route prop) with automatic waiting
1060
1181
  * 2. Legacy screen navigation (screen.navigate)
1061
1182
  * 3. Element waiting (waitFor prop)
1183
+ * 4. Ring light animation application
1062
1184
  *
1063
1185
  * @param {object} step
1064
1186
  * @param {number} waitTimeout
@@ -1082,6 +1204,10 @@ function makeBeforeShowPromise(step, waitTimeout) {
1082
1204
  });
1083
1205
  } catch (_) {/* non-fatal */}
1084
1206
  await waitForElement(postNavMerge.selector, waitTimeout);
1207
+
1208
+ // Apply ring light animation to the element
1209
+ const el = document.querySelector(postNavMerge.selector);
1210
+ if (el) applyHighlight(el, step._highlightStyle || 'default');
1085
1211
  return;
1086
1212
  }
1087
1213
  }
@@ -1091,6 +1217,12 @@ function makeBeforeShowPromise(step, waitTimeout) {
1091
1217
  if (freshMerged.waitFor) {
1092
1218
  await waitForElement(freshMerged.waitFor, waitTimeout);
1093
1219
  }
1220
+
1221
+ // Apply ring light animation to the main selector
1222
+ if (freshMerged.selector) {
1223
+ const el = document.querySelector(freshMerged.selector);
1224
+ if (el) applyHighlight(el, step._highlightStyle || 'default');
1225
+ }
1094
1226
  })();
1095
1227
  }
1096
1228
 
@@ -1139,11 +1271,13 @@ function addProgressIndicator(shepherdStep, index, total) {
1139
1271
 
1140
1272
  /**
1141
1273
  * Starts a Shepherd tour from the given steps array.
1274
+ * ENHANCED: Applies ring light animations to highlighted elements.
1142
1275
  *
1143
1276
  * @param {Array} steps
1144
1277
  * @param {object} [options]
1145
1278
  * @param {boolean} [options.showProgress=true]
1146
1279
  * @param {number} [options.waitTimeout=8000]
1280
+ * @param {string} [options.highlightStyle='default'] - 'default' | 'hard' | 'subtle'
1147
1281
  */
1148
1282
  async function runTour(steps, options = {}) {
1149
1283
  var _state$config2;
@@ -1156,9 +1290,13 @@ async function runTour(steps, options = {}) {
1156
1290
  if (!(steps !== null && steps !== void 0 && steps.length)) return;
1157
1291
  const {
1158
1292
  showProgress = true,
1159
- waitTimeout = 8000
1293
+ waitTimeout = 8000,
1294
+ highlightStyle = 'default'
1160
1295
  } = options;
1161
- const mergedSteps = steps.map(mergeWithFeature);
1296
+ const mergedSteps = steps.map(step => ({
1297
+ ...mergeWithFeature(step),
1298
+ _highlightStyle: highlightStyle
1299
+ }));
1162
1300
 
1163
1301
  // Legacy: navigate to the correct screen for the first step
1164
1302
  const firstFeature = (_state$config2 = config) === null || _state$config2 === void 0 || (_state$config2 = _state$config2.features) === null || _state$config2 === void 0 ? void 0 : _state$config2.find(f => {
@@ -1224,12 +1362,24 @@ async function runTour(steps, options = {}) {
1224
1362
  });
1225
1363
  step._shepherdRef = shepherdStep;
1226
1364
  shepherdStep._isLast = isLast;
1365
+
1366
+ // Clean up highlight on hide
1367
+ shepherdStep.on('hide', () => {
1368
+ if (step.selector) {
1369
+ const el = document.querySelector(step.selector);
1370
+ if (el) removeHighlight(el);
1371
+ }
1372
+ });
1227
1373
  if (hasAuto) wireAdvanceOn(shepherdStep, step.advanceOn, tour$1);
1228
1374
  if (showProgress && expandedSteps.length > 1) {
1229
1375
  addProgressIndicator(shepherdStep, i, expandedSteps.length);
1230
1376
  }
1231
1377
  });
1232
1378
  tour$1.on('complete', () => {
1379
+ // Clean up all highlights
1380
+ document.querySelectorAll('.sai-highlighted').forEach(el => {
1381
+ removeHighlight(el);
1382
+ });
1233
1383
  runAndClearCleanups();
1234
1384
  setPausedSteps(null);
1235
1385
  setPausedIndex(0);
@@ -1240,6 +1390,11 @@ async function runTour(steps, options = {}) {
1240
1390
  tour$1.on('cancel', () => {
1241
1391
  const currentStepEl = tour$1.getCurrentStep();
1242
1392
  const currentIdx = currentStepEl ? expandedSteps.findIndex(s => s.id === currentStepEl.id) : 0;
1393
+
1394
+ // Clean up all highlights
1395
+ document.querySelectorAll('.sai-highlighted').forEach(el => {
1396
+ removeHighlight(el);
1397
+ });
1243
1398
  runAndClearCleanups();
1244
1399
  setPausedSteps(expandedSteps);
1245
1400
  setPausedIndex(Math.max(0, currentIdx));
package/dist/core.js CHANGED
@@ -219,6 +219,71 @@ function injectStyles(theme, pos) {
219
219
  style.textContent = `
220
220
  #sai-trigger, #sai-panel { ${buildCSSVars(theme)} }
221
221
 
222
+ /* ── Ring Light Animation ── */
223
+ @keyframes sai-ring-pulse {
224
+ 0% {
225
+ box-shadow:
226
+ 0 0 0 0 rgba(var(--sai-accent-rgb), 0.7),
227
+ 0 0 0 0 rgba(var(--sai-accent-rgb), 0.5),
228
+ 0 0 0 0 rgba(var(--sai-accent-rgb), 0.3);
229
+ }
230
+ 50% {
231
+ box-shadow:
232
+ 0 0 0 8px rgba(var(--sai-accent-rgb), 0),
233
+ 0 0 0 16px rgba(var(--sai-accent-rgb), 0.3),
234
+ 0 0 0 24px rgba(var(--sai-accent-rgb), 0);
235
+ }
236
+ 100% {
237
+ box-shadow:
238
+ 0 0 0 16px rgba(var(--sai-accent-rgb), 0),
239
+ 0 0 0 32px rgba(var(--sai-accent-rgb), 0),
240
+ 0 0 0 48px rgba(var(--sai-accent-rgb), 0);
241
+ }
242
+ }
243
+
244
+ @keyframes sai-ring-pulse-hard {
245
+ 0% {
246
+ box-shadow:
247
+ inset 0 0 0 2px var(--sai-accent),
248
+ 0 0 0 3px var(--sai-accent),
249
+ 0 0 20px var(--sai-accent);
250
+ }
251
+ 50% {
252
+ box-shadow:
253
+ inset 0 0 0 2px var(--sai-accent),
254
+ 0 0 0 8px var(--sai-accent),
255
+ 0 0 40px var(--sai-accent);
256
+ }
257
+ 100% {
258
+ box-shadow:
259
+ inset 0 0 0 2px var(--sai-accent),
260
+ 0 0 0 3px var(--sai-accent),
261
+ 0 0 20px var(--sai-accent);
262
+ }
263
+ }
264
+
265
+ @keyframes sai-element-highlight {
266
+ 0% { filter: brightness(1); }
267
+ 50% { filter: brightness(1.15); }
268
+ 100% { filter: brightness(1); }
269
+ }
270
+
271
+ /* Highlighted element styles */
272
+ .sai-highlighted {
273
+ position: relative !important;
274
+ animation: sai-ring-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite !important;
275
+ z-index: 99997 !important;
276
+ }
277
+
278
+ .sai-highlighted.sai-highlight-hard {
279
+ animation: sai-ring-pulse-hard 2s cubic-bezier(0.4, 0, 0.6, 1) infinite !important;
280
+ }
281
+
282
+ .sai-highlighted.sai-highlight-subtle {
283
+ animation: sai-element-highlight 2s ease-in-out infinite !important;
284
+ filter: drop-shadow(0 0 8px var(--sai-accent)) !important;
285
+ }
286
+
222
287
  /* ── Trigger ── */
223
288
  #sai-trigger {
224
289
  position: fixed; ${triggerCSS};
@@ -1004,6 +1069,7 @@ function expandFlowSteps(aiStep, feature) {
1004
1069
 
1005
1070
  // Loads Shepherd.js, builds + runs tours, wires up advanceOn listeners,
1006
1071
  // progress indicators, pause/resume, and step-level error display.
1072
+ // ENHANCED: Adds animated ring light effect to highlighted elements.
1007
1073
 
1008
1074
  const SHEPHERD_JS = 'https://cdn.jsdelivr.net/npm/shepherd.js@11.2.0/dist/js/shepherd.min.js';
1009
1075
 
@@ -1024,6 +1090,61 @@ async function ensureShepherd() {
1024
1090
  await loadScript(SHEPHERD_JS);
1025
1091
  }
1026
1092
 
1093
+ // ─── Highlight management ────────────────────────────────────────────────────
1094
+
1095
+ /**
1096
+ * Applies the ring light animation to an element.
1097
+ *
1098
+ * @param {HTMLElement} el
1099
+ * @param {string} [style='default'] - 'default' | 'hard' | 'subtle'
1100
+ */
1101
+ function applyHighlight(el, style = 'default') {
1102
+ if (!el) return;
1103
+
1104
+ // Set CSS variables for accent color parsing in RGB format
1105
+ const accentColor = getComputedStyle(el).getPropertyValue('--sai-accent') || '#e94560';
1106
+ const rgb = hexToRgb(accentColor.trim());
1107
+ if (rgb) {
1108
+ el.style.setProperty('--sai-accent-rgb', `${rgb.r}, ${rgb.g}, ${rgb.b}`);
1109
+ }
1110
+ el.classList.add('sai-highlighted');
1111
+ if (style === 'hard') {
1112
+ el.classList.add('sai-highlight-hard');
1113
+ } else if (style === 'subtle') {
1114
+ el.classList.add('sai-highlight-subtle');
1115
+ }
1116
+
1117
+ // Scroll into view with extra padding
1118
+ if (el.scrollIntoView) {
1119
+ el.scrollIntoView({
1120
+ behavior: 'smooth',
1121
+ block: 'center'
1122
+ });
1123
+ }
1124
+ }
1125
+
1126
+ /**
1127
+ * Removes the ring light animation from an element.
1128
+ */
1129
+ function removeHighlight(el) {
1130
+ if (!el) return;
1131
+ el.classList.remove('sai-highlighted', 'sai-highlight-hard', 'sai-highlight-subtle');
1132
+ }
1133
+
1134
+ /**
1135
+ * Converts hex color to RGB object.
1136
+ * @param {string} hex
1137
+ * @returns {object|null}
1138
+ */
1139
+ function hexToRgb(hex) {
1140
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
1141
+ return result ? {
1142
+ r: parseInt(result[1], 16),
1143
+ g: parseInt(result[2], 16),
1144
+ b: parseInt(result[3], 16)
1145
+ } : null;
1146
+ }
1147
+
1027
1148
  // ─── Feature-map merge ────────────────────────────────────────────────────────
1028
1149
 
1029
1150
  /**
@@ -1057,6 +1178,7 @@ function mergeWithFeature(step) {
1057
1178
  * 1. Cross-page navigation (route prop) with automatic waiting
1058
1179
  * 2. Legacy screen navigation (screen.navigate)
1059
1180
  * 3. Element waiting (waitFor prop)
1181
+ * 4. Ring light animation application
1060
1182
  *
1061
1183
  * @param {object} step
1062
1184
  * @param {number} waitTimeout
@@ -1080,6 +1202,10 @@ function makeBeforeShowPromise(step, waitTimeout) {
1080
1202
  });
1081
1203
  } catch (_) {/* non-fatal */}
1082
1204
  await waitForElement(postNavMerge.selector, waitTimeout);
1205
+
1206
+ // Apply ring light animation to the element
1207
+ const el = document.querySelector(postNavMerge.selector);
1208
+ if (el) applyHighlight(el, step._highlightStyle || 'default');
1083
1209
  return;
1084
1210
  }
1085
1211
  }
@@ -1089,6 +1215,12 @@ function makeBeforeShowPromise(step, waitTimeout) {
1089
1215
  if (freshMerged.waitFor) {
1090
1216
  await waitForElement(freshMerged.waitFor, waitTimeout);
1091
1217
  }
1218
+
1219
+ // Apply ring light animation to the main selector
1220
+ if (freshMerged.selector) {
1221
+ const el = document.querySelector(freshMerged.selector);
1222
+ if (el) applyHighlight(el, step._highlightStyle || 'default');
1223
+ }
1092
1224
  })();
1093
1225
  }
1094
1226
 
@@ -1137,11 +1269,13 @@ function addProgressIndicator(shepherdStep, index, total) {
1137
1269
 
1138
1270
  /**
1139
1271
  * Starts a Shepherd tour from the given steps array.
1272
+ * ENHANCED: Applies ring light animations to highlighted elements.
1140
1273
  *
1141
1274
  * @param {Array} steps
1142
1275
  * @param {object} [options]
1143
1276
  * @param {boolean} [options.showProgress=true]
1144
1277
  * @param {number} [options.waitTimeout=8000]
1278
+ * @param {string} [options.highlightStyle='default'] - 'default' | 'hard' | 'subtle'
1145
1279
  */
1146
1280
  async function runTour(steps, options = {}) {
1147
1281
  var _state$config2;
@@ -1154,9 +1288,13 @@ async function runTour(steps, options = {}) {
1154
1288
  if (!(steps !== null && steps !== void 0 && steps.length)) return;
1155
1289
  const {
1156
1290
  showProgress = true,
1157
- waitTimeout = 8000
1291
+ waitTimeout = 8000,
1292
+ highlightStyle = 'default'
1158
1293
  } = options;
1159
- const mergedSteps = steps.map(mergeWithFeature);
1294
+ const mergedSteps = steps.map(step => ({
1295
+ ...mergeWithFeature(step),
1296
+ _highlightStyle: highlightStyle
1297
+ }));
1160
1298
 
1161
1299
  // Legacy: navigate to the correct screen for the first step
1162
1300
  const firstFeature = (_state$config2 = config) === null || _state$config2 === void 0 || (_state$config2 = _state$config2.features) === null || _state$config2 === void 0 ? void 0 : _state$config2.find(f => {
@@ -1222,12 +1360,24 @@ async function runTour(steps, options = {}) {
1222
1360
  });
1223
1361
  step._shepherdRef = shepherdStep;
1224
1362
  shepherdStep._isLast = isLast;
1363
+
1364
+ // Clean up highlight on hide
1365
+ shepherdStep.on('hide', () => {
1366
+ if (step.selector) {
1367
+ const el = document.querySelector(step.selector);
1368
+ if (el) removeHighlight(el);
1369
+ }
1370
+ });
1225
1371
  if (hasAuto) wireAdvanceOn(shepherdStep, step.advanceOn, tour$1);
1226
1372
  if (showProgress && expandedSteps.length > 1) {
1227
1373
  addProgressIndicator(shepherdStep, i, expandedSteps.length);
1228
1374
  }
1229
1375
  });
1230
1376
  tour$1.on('complete', () => {
1377
+ // Clean up all highlights
1378
+ document.querySelectorAll('.sai-highlighted').forEach(el => {
1379
+ removeHighlight(el);
1380
+ });
1231
1381
  runAndClearCleanups();
1232
1382
  setPausedSteps(null);
1233
1383
  setPausedIndex(0);
@@ -1238,6 +1388,11 @@ async function runTour(steps, options = {}) {
1238
1388
  tour$1.on('cancel', () => {
1239
1389
  const currentStepEl = tour$1.getCurrentStep();
1240
1390
  const currentIdx = currentStepEl ? expandedSteps.findIndex(s => s.id === currentStepEl.id) : 0;
1391
+
1392
+ // Clean up all highlights
1393
+ document.querySelectorAll('.sai-highlighted').forEach(el => {
1394
+ removeHighlight(el);
1395
+ });
1241
1396
  runAndClearCleanups();
1242
1397
  setPausedSteps(expandedSteps);
1243
1398
  setPausedIndex(Math.max(0, currentIdx));
@@ -221,6 +221,71 @@ function injectStyles(theme, pos) {
221
221
  style.textContent = `
222
222
  #sai-trigger, #sai-panel { ${buildCSSVars(theme)} }
223
223
 
224
+ /* ── Ring Light Animation ── */
225
+ @keyframes sai-ring-pulse {
226
+ 0% {
227
+ box-shadow:
228
+ 0 0 0 0 rgba(var(--sai-accent-rgb), 0.7),
229
+ 0 0 0 0 rgba(var(--sai-accent-rgb), 0.5),
230
+ 0 0 0 0 rgba(var(--sai-accent-rgb), 0.3);
231
+ }
232
+ 50% {
233
+ box-shadow:
234
+ 0 0 0 8px rgba(var(--sai-accent-rgb), 0),
235
+ 0 0 0 16px rgba(var(--sai-accent-rgb), 0.3),
236
+ 0 0 0 24px rgba(var(--sai-accent-rgb), 0);
237
+ }
238
+ 100% {
239
+ box-shadow:
240
+ 0 0 0 16px rgba(var(--sai-accent-rgb), 0),
241
+ 0 0 0 32px rgba(var(--sai-accent-rgb), 0),
242
+ 0 0 0 48px rgba(var(--sai-accent-rgb), 0);
243
+ }
244
+ }
245
+
246
+ @keyframes sai-ring-pulse-hard {
247
+ 0% {
248
+ box-shadow:
249
+ inset 0 0 0 2px var(--sai-accent),
250
+ 0 0 0 3px var(--sai-accent),
251
+ 0 0 20px var(--sai-accent);
252
+ }
253
+ 50% {
254
+ box-shadow:
255
+ inset 0 0 0 2px var(--sai-accent),
256
+ 0 0 0 8px var(--sai-accent),
257
+ 0 0 40px var(--sai-accent);
258
+ }
259
+ 100% {
260
+ box-shadow:
261
+ inset 0 0 0 2px var(--sai-accent),
262
+ 0 0 0 3px var(--sai-accent),
263
+ 0 0 20px var(--sai-accent);
264
+ }
265
+ }
266
+
267
+ @keyframes sai-element-highlight {
268
+ 0% { filter: brightness(1); }
269
+ 50% { filter: brightness(1.15); }
270
+ 100% { filter: brightness(1); }
271
+ }
272
+
273
+ /* Highlighted element styles */
274
+ .sai-highlighted {
275
+ position: relative !important;
276
+ animation: sai-ring-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite !important;
277
+ z-index: 99997 !important;
278
+ }
279
+
280
+ .sai-highlighted.sai-highlight-hard {
281
+ animation: sai-ring-pulse-hard 2s cubic-bezier(0.4, 0, 0.6, 1) infinite !important;
282
+ }
283
+
284
+ .sai-highlighted.sai-highlight-subtle {
285
+ animation: sai-element-highlight 2s ease-in-out infinite !important;
286
+ filter: drop-shadow(0 0 8px var(--sai-accent)) !important;
287
+ }
288
+
224
289
  /* ── Trigger ── */
225
290
  #sai-trigger {
226
291
  position: fixed; ${triggerCSS};
@@ -1006,6 +1071,7 @@ function expandFlowSteps(aiStep, feature) {
1006
1071
 
1007
1072
  // Loads Shepherd.js, builds + runs tours, wires up advanceOn listeners,
1008
1073
  // progress indicators, pause/resume, and step-level error display.
1074
+ // ENHANCED: Adds animated ring light effect to highlighted elements.
1009
1075
 
1010
1076
  const SHEPHERD_JS = 'https://cdn.jsdelivr.net/npm/shepherd.js@11.2.0/dist/js/shepherd.min.js';
1011
1077
 
@@ -1026,6 +1092,61 @@ async function ensureShepherd() {
1026
1092
  await loadScript(SHEPHERD_JS);
1027
1093
  }
1028
1094
 
1095
+ // ─── Highlight management ────────────────────────────────────────────────────
1096
+
1097
+ /**
1098
+ * Applies the ring light animation to an element.
1099
+ *
1100
+ * @param {HTMLElement} el
1101
+ * @param {string} [style='default'] - 'default' | 'hard' | 'subtle'
1102
+ */
1103
+ function applyHighlight(el, style = 'default') {
1104
+ if (!el) return;
1105
+
1106
+ // Set CSS variables for accent color parsing in RGB format
1107
+ const accentColor = getComputedStyle(el).getPropertyValue('--sai-accent') || '#e94560';
1108
+ const rgb = hexToRgb(accentColor.trim());
1109
+ if (rgb) {
1110
+ el.style.setProperty('--sai-accent-rgb', `${rgb.r}, ${rgb.g}, ${rgb.b}`);
1111
+ }
1112
+ el.classList.add('sai-highlighted');
1113
+ if (style === 'hard') {
1114
+ el.classList.add('sai-highlight-hard');
1115
+ } else if (style === 'subtle') {
1116
+ el.classList.add('sai-highlight-subtle');
1117
+ }
1118
+
1119
+ // Scroll into view with extra padding
1120
+ if (el.scrollIntoView) {
1121
+ el.scrollIntoView({
1122
+ behavior: 'smooth',
1123
+ block: 'center'
1124
+ });
1125
+ }
1126
+ }
1127
+
1128
+ /**
1129
+ * Removes the ring light animation from an element.
1130
+ */
1131
+ function removeHighlight(el) {
1132
+ if (!el) return;
1133
+ el.classList.remove('sai-highlighted', 'sai-highlight-hard', 'sai-highlight-subtle');
1134
+ }
1135
+
1136
+ /**
1137
+ * Converts hex color to RGB object.
1138
+ * @param {string} hex
1139
+ * @returns {object|null}
1140
+ */
1141
+ function hexToRgb(hex) {
1142
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
1143
+ return result ? {
1144
+ r: parseInt(result[1], 16),
1145
+ g: parseInt(result[2], 16),
1146
+ b: parseInt(result[3], 16)
1147
+ } : null;
1148
+ }
1149
+
1029
1150
  // ─── Feature-map merge ────────────────────────────────────────────────────────
1030
1151
 
1031
1152
  /**
@@ -1059,6 +1180,7 @@ function mergeWithFeature(step) {
1059
1180
  * 1. Cross-page navigation (route prop) with automatic waiting
1060
1181
  * 2. Legacy screen navigation (screen.navigate)
1061
1182
  * 3. Element waiting (waitFor prop)
1183
+ * 4. Ring light animation application
1062
1184
  *
1063
1185
  * @param {object} step
1064
1186
  * @param {number} waitTimeout
@@ -1082,6 +1204,10 @@ function makeBeforeShowPromise(step, waitTimeout) {
1082
1204
  });
1083
1205
  } catch (_) {/* non-fatal */}
1084
1206
  await waitForElement(postNavMerge.selector, waitTimeout);
1207
+
1208
+ // Apply ring light animation to the element
1209
+ const el = document.querySelector(postNavMerge.selector);
1210
+ if (el) applyHighlight(el, step._highlightStyle || 'default');
1085
1211
  return;
1086
1212
  }
1087
1213
  }
@@ -1091,6 +1217,12 @@ function makeBeforeShowPromise(step, waitTimeout) {
1091
1217
  if (freshMerged.waitFor) {
1092
1218
  await waitForElement(freshMerged.waitFor, waitTimeout);
1093
1219
  }
1220
+
1221
+ // Apply ring light animation to the main selector
1222
+ if (freshMerged.selector) {
1223
+ const el = document.querySelector(freshMerged.selector);
1224
+ if (el) applyHighlight(el, step._highlightStyle || 'default');
1225
+ }
1094
1226
  })();
1095
1227
  }
1096
1228
 
@@ -1139,11 +1271,13 @@ function addProgressIndicator(shepherdStep, index, total) {
1139
1271
 
1140
1272
  /**
1141
1273
  * Starts a Shepherd tour from the given steps array.
1274
+ * ENHANCED: Applies ring light animations to highlighted elements.
1142
1275
  *
1143
1276
  * @param {Array} steps
1144
1277
  * @param {object} [options]
1145
1278
  * @param {boolean} [options.showProgress=true]
1146
1279
  * @param {number} [options.waitTimeout=8000]
1280
+ * @param {string} [options.highlightStyle='default'] - 'default' | 'hard' | 'subtle'
1147
1281
  */
1148
1282
  async function runTour(steps, options = {}) {
1149
1283
  var _state$config2;
@@ -1156,9 +1290,13 @@ async function runTour(steps, options = {}) {
1156
1290
  if (!(steps !== null && steps !== void 0 && steps.length)) return;
1157
1291
  const {
1158
1292
  showProgress = true,
1159
- waitTimeout = 8000
1293
+ waitTimeout = 8000,
1294
+ highlightStyle = 'default'
1160
1295
  } = options;
1161
- const mergedSteps = steps.map(mergeWithFeature);
1296
+ const mergedSteps = steps.map(step => ({
1297
+ ...mergeWithFeature(step),
1298
+ _highlightStyle: highlightStyle
1299
+ }));
1162
1300
 
1163
1301
  // Legacy: navigate to the correct screen for the first step
1164
1302
  const firstFeature = (_state$config2 = config) === null || _state$config2 === void 0 || (_state$config2 = _state$config2.features) === null || _state$config2 === void 0 ? void 0 : _state$config2.find(f => {
@@ -1224,12 +1362,24 @@ async function runTour(steps, options = {}) {
1224
1362
  });
1225
1363
  step._shepherdRef = shepherdStep;
1226
1364
  shepherdStep._isLast = isLast;
1365
+
1366
+ // Clean up highlight on hide
1367
+ shepherdStep.on('hide', () => {
1368
+ if (step.selector) {
1369
+ const el = document.querySelector(step.selector);
1370
+ if (el) removeHighlight(el);
1371
+ }
1372
+ });
1227
1373
  if (hasAuto) wireAdvanceOn(shepherdStep, step.advanceOn, tour$1);
1228
1374
  if (showProgress && expandedSteps.length > 1) {
1229
1375
  addProgressIndicator(shepherdStep, i, expandedSteps.length);
1230
1376
  }
1231
1377
  });
1232
1378
  tour$1.on('complete', () => {
1379
+ // Clean up all highlights
1380
+ document.querySelectorAll('.sai-highlighted').forEach(el => {
1381
+ removeHighlight(el);
1382
+ });
1233
1383
  runAndClearCleanups();
1234
1384
  setPausedSteps(null);
1235
1385
  setPausedIndex(0);
@@ -1240,6 +1390,11 @@ async function runTour(steps, options = {}) {
1240
1390
  tour$1.on('cancel', () => {
1241
1391
  const currentStepEl = tour$1.getCurrentStep();
1242
1392
  const currentIdx = currentStepEl ? expandedSteps.findIndex(s => s.id === currentStepEl.id) : 0;
1393
+
1394
+ // Clean up all highlights
1395
+ document.querySelectorAll('.sai-highlighted').forEach(el => {
1396
+ removeHighlight(el);
1397
+ });
1243
1398
  runAndClearCleanups();
1244
1399
  setPausedSteps(expandedSteps);
1245
1400
  setPausedIndex(Math.max(0, currentIdx));
@@ -219,6 +219,71 @@ function injectStyles(theme, pos) {
219
219
  style.textContent = `
220
220
  #sai-trigger, #sai-panel { ${buildCSSVars(theme)} }
221
221
 
222
+ /* ── Ring Light Animation ── */
223
+ @keyframes sai-ring-pulse {
224
+ 0% {
225
+ box-shadow:
226
+ 0 0 0 0 rgba(var(--sai-accent-rgb), 0.7),
227
+ 0 0 0 0 rgba(var(--sai-accent-rgb), 0.5),
228
+ 0 0 0 0 rgba(var(--sai-accent-rgb), 0.3);
229
+ }
230
+ 50% {
231
+ box-shadow:
232
+ 0 0 0 8px rgba(var(--sai-accent-rgb), 0),
233
+ 0 0 0 16px rgba(var(--sai-accent-rgb), 0.3),
234
+ 0 0 0 24px rgba(var(--sai-accent-rgb), 0);
235
+ }
236
+ 100% {
237
+ box-shadow:
238
+ 0 0 0 16px rgba(var(--sai-accent-rgb), 0),
239
+ 0 0 0 32px rgba(var(--sai-accent-rgb), 0),
240
+ 0 0 0 48px rgba(var(--sai-accent-rgb), 0);
241
+ }
242
+ }
243
+
244
+ @keyframes sai-ring-pulse-hard {
245
+ 0% {
246
+ box-shadow:
247
+ inset 0 0 0 2px var(--sai-accent),
248
+ 0 0 0 3px var(--sai-accent),
249
+ 0 0 20px var(--sai-accent);
250
+ }
251
+ 50% {
252
+ box-shadow:
253
+ inset 0 0 0 2px var(--sai-accent),
254
+ 0 0 0 8px var(--sai-accent),
255
+ 0 0 40px var(--sai-accent);
256
+ }
257
+ 100% {
258
+ box-shadow:
259
+ inset 0 0 0 2px var(--sai-accent),
260
+ 0 0 0 3px var(--sai-accent),
261
+ 0 0 20px var(--sai-accent);
262
+ }
263
+ }
264
+
265
+ @keyframes sai-element-highlight {
266
+ 0% { filter: brightness(1); }
267
+ 50% { filter: brightness(1.15); }
268
+ 100% { filter: brightness(1); }
269
+ }
270
+
271
+ /* Highlighted element styles */
272
+ .sai-highlighted {
273
+ position: relative !important;
274
+ animation: sai-ring-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite !important;
275
+ z-index: 99997 !important;
276
+ }
277
+
278
+ .sai-highlighted.sai-highlight-hard {
279
+ animation: sai-ring-pulse-hard 2s cubic-bezier(0.4, 0, 0.6, 1) infinite !important;
280
+ }
281
+
282
+ .sai-highlighted.sai-highlight-subtle {
283
+ animation: sai-element-highlight 2s ease-in-out infinite !important;
284
+ filter: drop-shadow(0 0 8px var(--sai-accent)) !important;
285
+ }
286
+
222
287
  /* ── Trigger ── */
223
288
  #sai-trigger {
224
289
  position: fixed; ${triggerCSS};
@@ -1004,6 +1069,7 @@ function expandFlowSteps(aiStep, feature) {
1004
1069
 
1005
1070
  // Loads Shepherd.js, builds + runs tours, wires up advanceOn listeners,
1006
1071
  // progress indicators, pause/resume, and step-level error display.
1072
+ // ENHANCED: Adds animated ring light effect to highlighted elements.
1007
1073
 
1008
1074
  const SHEPHERD_JS = 'https://cdn.jsdelivr.net/npm/shepherd.js@11.2.0/dist/js/shepherd.min.js';
1009
1075
 
@@ -1024,6 +1090,61 @@ async function ensureShepherd() {
1024
1090
  await loadScript(SHEPHERD_JS);
1025
1091
  }
1026
1092
 
1093
+ // ─── Highlight management ────────────────────────────────────────────────────
1094
+
1095
+ /**
1096
+ * Applies the ring light animation to an element.
1097
+ *
1098
+ * @param {HTMLElement} el
1099
+ * @param {string} [style='default'] - 'default' | 'hard' | 'subtle'
1100
+ */
1101
+ function applyHighlight(el, style = 'default') {
1102
+ if (!el) return;
1103
+
1104
+ // Set CSS variables for accent color parsing in RGB format
1105
+ const accentColor = getComputedStyle(el).getPropertyValue('--sai-accent') || '#e94560';
1106
+ const rgb = hexToRgb(accentColor.trim());
1107
+ if (rgb) {
1108
+ el.style.setProperty('--sai-accent-rgb', `${rgb.r}, ${rgb.g}, ${rgb.b}`);
1109
+ }
1110
+ el.classList.add('sai-highlighted');
1111
+ if (style === 'hard') {
1112
+ el.classList.add('sai-highlight-hard');
1113
+ } else if (style === 'subtle') {
1114
+ el.classList.add('sai-highlight-subtle');
1115
+ }
1116
+
1117
+ // Scroll into view with extra padding
1118
+ if (el.scrollIntoView) {
1119
+ el.scrollIntoView({
1120
+ behavior: 'smooth',
1121
+ block: 'center'
1122
+ });
1123
+ }
1124
+ }
1125
+
1126
+ /**
1127
+ * Removes the ring light animation from an element.
1128
+ */
1129
+ function removeHighlight(el) {
1130
+ if (!el) return;
1131
+ el.classList.remove('sai-highlighted', 'sai-highlight-hard', 'sai-highlight-subtle');
1132
+ }
1133
+
1134
+ /**
1135
+ * Converts hex color to RGB object.
1136
+ * @param {string} hex
1137
+ * @returns {object|null}
1138
+ */
1139
+ function hexToRgb(hex) {
1140
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
1141
+ return result ? {
1142
+ r: parseInt(result[1], 16),
1143
+ g: parseInt(result[2], 16),
1144
+ b: parseInt(result[3], 16)
1145
+ } : null;
1146
+ }
1147
+
1027
1148
  // ─── Feature-map merge ────────────────────────────────────────────────────────
1028
1149
 
1029
1150
  /**
@@ -1057,6 +1178,7 @@ function mergeWithFeature(step) {
1057
1178
  * 1. Cross-page navigation (route prop) with automatic waiting
1058
1179
  * 2. Legacy screen navigation (screen.navigate)
1059
1180
  * 3. Element waiting (waitFor prop)
1181
+ * 4. Ring light animation application
1060
1182
  *
1061
1183
  * @param {object} step
1062
1184
  * @param {number} waitTimeout
@@ -1080,6 +1202,10 @@ function makeBeforeShowPromise(step, waitTimeout) {
1080
1202
  });
1081
1203
  } catch (_) {/* non-fatal */}
1082
1204
  await waitForElement(postNavMerge.selector, waitTimeout);
1205
+
1206
+ // Apply ring light animation to the element
1207
+ const el = document.querySelector(postNavMerge.selector);
1208
+ if (el) applyHighlight(el, step._highlightStyle || 'default');
1083
1209
  return;
1084
1210
  }
1085
1211
  }
@@ -1089,6 +1215,12 @@ function makeBeforeShowPromise(step, waitTimeout) {
1089
1215
  if (freshMerged.waitFor) {
1090
1216
  await waitForElement(freshMerged.waitFor, waitTimeout);
1091
1217
  }
1218
+
1219
+ // Apply ring light animation to the main selector
1220
+ if (freshMerged.selector) {
1221
+ const el = document.querySelector(freshMerged.selector);
1222
+ if (el) applyHighlight(el, step._highlightStyle || 'default');
1223
+ }
1092
1224
  })();
1093
1225
  }
1094
1226
 
@@ -1137,11 +1269,13 @@ function addProgressIndicator(shepherdStep, index, total) {
1137
1269
 
1138
1270
  /**
1139
1271
  * Starts a Shepherd tour from the given steps array.
1272
+ * ENHANCED: Applies ring light animations to highlighted elements.
1140
1273
  *
1141
1274
  * @param {Array} steps
1142
1275
  * @param {object} [options]
1143
1276
  * @param {boolean} [options.showProgress=true]
1144
1277
  * @param {number} [options.waitTimeout=8000]
1278
+ * @param {string} [options.highlightStyle='default'] - 'default' | 'hard' | 'subtle'
1145
1279
  */
1146
1280
  async function runTour(steps, options = {}) {
1147
1281
  var _state$config2;
@@ -1154,9 +1288,13 @@ async function runTour(steps, options = {}) {
1154
1288
  if (!(steps !== null && steps !== void 0 && steps.length)) return;
1155
1289
  const {
1156
1290
  showProgress = true,
1157
- waitTimeout = 8000
1291
+ waitTimeout = 8000,
1292
+ highlightStyle = 'default'
1158
1293
  } = options;
1159
- const mergedSteps = steps.map(mergeWithFeature);
1294
+ const mergedSteps = steps.map(step => ({
1295
+ ...mergeWithFeature(step),
1296
+ _highlightStyle: highlightStyle
1297
+ }));
1160
1298
 
1161
1299
  // Legacy: navigate to the correct screen for the first step
1162
1300
  const firstFeature = (_state$config2 = config) === null || _state$config2 === void 0 || (_state$config2 = _state$config2.features) === null || _state$config2 === void 0 ? void 0 : _state$config2.find(f => {
@@ -1222,12 +1360,24 @@ async function runTour(steps, options = {}) {
1222
1360
  });
1223
1361
  step._shepherdRef = shepherdStep;
1224
1362
  shepherdStep._isLast = isLast;
1363
+
1364
+ // Clean up highlight on hide
1365
+ shepherdStep.on('hide', () => {
1366
+ if (step.selector) {
1367
+ const el = document.querySelector(step.selector);
1368
+ if (el) removeHighlight(el);
1369
+ }
1370
+ });
1225
1371
  if (hasAuto) wireAdvanceOn(shepherdStep, step.advanceOn, tour$1);
1226
1372
  if (showProgress && expandedSteps.length > 1) {
1227
1373
  addProgressIndicator(shepherdStep, i, expandedSteps.length);
1228
1374
  }
1229
1375
  });
1230
1376
  tour$1.on('complete', () => {
1377
+ // Clean up all highlights
1378
+ document.querySelectorAll('.sai-highlighted').forEach(el => {
1379
+ removeHighlight(el);
1380
+ });
1231
1381
  runAndClearCleanups();
1232
1382
  setPausedSteps(null);
1233
1383
  setPausedIndex(0);
@@ -1238,6 +1388,11 @@ async function runTour(steps, options = {}) {
1238
1388
  tour$1.on('cancel', () => {
1239
1389
  const currentStepEl = tour$1.getCurrentStep();
1240
1390
  const currentIdx = currentStepEl ? expandedSteps.findIndex(s => s.id === currentStepEl.id) : 0;
1391
+
1392
+ // Clean up all highlights
1393
+ document.querySelectorAll('.sai-highlighted').forEach(el => {
1394
+ removeHighlight(el);
1395
+ });
1241
1396
  runAndClearCleanups();
1242
1397
  setPausedSteps(expandedSteps);
1243
1398
  setPausedIndex(Math.max(0, currentIdx));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventop/sdk",
3
- "version": "1.2.15",
3
+ "version": "1.3.0",
4
4
  "description": "AI-powered guided tours for any web app. Drop-in, themeable, provider-agnostic.",
5
5
  "keywords": [
6
6
  "onboarding",