js-kompo-utils 1.0.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/README.md +1 -0
- package/app.js +22 -0
- package/components/VlApexChart.vue +89 -0
- package/components/VlCKEditorTemplate.vue +52 -0
- package/components/VlCKEditorWithVars.vue +80 -0
- package/components/VlCollapsible.vue +91 -0
- package/components/VlCopyBox.vue +61 -0
- package/components/VlCreditCardInput.vue +104 -0
- package/components/VlDateMonthDay.vue +327 -0
- package/components/VlDateTextInput.vue +151 -0
- package/components/VlInternationalPhoneInput.vue +902 -0
- package/components/VlPartialDate.vue +283 -0
- package/components/VlPasswordInput.vue +358 -0
- package/components/VlQrCodeScanner.vue +91 -0
- package/components/VlSwipeableTab.vue +35 -0
- package/components/VlSwipeableTabs.vue +325 -0
- package/components/VlText.vue +72 -0
- package/components/VlTutorialStepBuilder.vue +671 -0
- package/components/VlValidatedInput.vue +262 -0
- package/components/data/countries.js +253 -0
- package/components/getAllDefaultComponents.js +12 -0
- package/docs/architecture.md +71 -0
- package/docs/tutorials.md +247 -0
- package/docs/usage.md +128 -0
- package/functions/index.js +7 -0
- package/functions/inputs.js +64 -0
- package/functions/loading.js +20 -0
- package/kompo-ckeditor-1.0.17/index.js +5 -0
- package/kompo-ckeditor-1.0.17/komponents/CKEditor.vue +36 -0
- package/kompo-ckeditor-1.0.17/komponents/CKEditorExtended.vue +66 -0
- package/kompo-ckeditor-1.0.17/komponents/EditableCKEditor.vue +42 -0
- package/kompo-ckeditor-1.0.17/komponents/TranslatableEditor.vue +28 -0
- package/kompo-ckeditor-1.0.17/mixins/CKEditor.js +291 -0
- package/kompo-ckeditor-1.0.17/mixins/CKEditorUtilities.js +52 -0
- package/kompo-ckeditor-1.0.17/mixins/CkUploadAdapter.js +95 -0
- package/kompo-ckeditor-1.0.17/mixins/translations/en.js +1 -0
- package/kompo-ckeditor-1.0.17/mixins/translations/fr.js +1 -0
- package/kompo-ckeditor-1.0.17/plugins/JsonBlockCommand.js +100 -0
- package/kompo-ckeditor-1.0.17/plugins/JsonBlockEditing.js +225 -0
- package/kompo-ckeditor-1.0.17/plugins/JsonBlockUi.js +21 -0
- package/kompo-ckeditor-1.0.17/plugins/MentionPlugin.js +118 -0
- package/orchestrator/KompoJsBuilder.js +333 -0
- package/package.json +25 -0
- package/raw_animations/count-up-animation.js +220 -0
- package/raw_animations/magic-poof.js +210 -0
- package/tutorials/TutorialConfig.js +34 -0
- package/tutorials/TutorialEngine.js +448 -0
- package/tutorials/index.js +2 -0
- package/tutorials/modules/AutoNext.js +86 -0
- package/tutorials/modules/Avatar.js +94 -0
- package/tutorials/modules/Bubble.js +617 -0
- package/tutorials/modules/Cursor.js +315 -0
- package/tutorials/modules/Highlight.js +282 -0
- package/tutorials/modules/HoverForcer.js +250 -0
- package/tutorials/modules/Mobile.js +148 -0
- package/tutorials/modules/Options.js +98 -0
- package/tutorials/modules/Overlay.js +142 -0
- package/tutorials/modules/Scroll.js +37 -0
- package/tutorials/modules/Typewriter.js +132 -0
- package/tutorials/step-builder/step-builder-bridge.js +436 -0
- package/tutorials/step-builder/utils/bezier-editor.js +379 -0
- package/tutorials/step-builder/utils/code-generator.js +127 -0
- package/tutorials/step-builder/utils/element-picker.js +125 -0
- package/tutorials/tutorial.scss +816 -0
- package/tutorials/utils/selectors.js +225 -0
- package/tutorials/utils/stability.js +76 -0
- package/tutorials/utils/svg.js +46 -0
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "js-kompo-utils",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "app.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [],
|
|
10
|
+
"author": "xkairo",
|
|
11
|
+
"license": "ISC",
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@ckeditor/ckeditor5-vue2": "^3.0.1",
|
|
14
|
+
"@floating-ui/dom": "^1.7.6",
|
|
15
|
+
"apexcharts": "^5.10.4",
|
|
16
|
+
"ckeditor5": "^44.3.0",
|
|
17
|
+
"gsap": "^3.14.2",
|
|
18
|
+
"highlight.js": "^11.11.1",
|
|
19
|
+
"jquery": "^3.6.0",
|
|
20
|
+
"kompo-googlemaps": "^1.0.14",
|
|
21
|
+
"libphonenumber-js": "^1.12.40",
|
|
22
|
+
"typed.js": "^3.0.0",
|
|
23
|
+
"vue-qrcode-reader": "^3.1.0"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
// Inject hiding style IMMEDIATELY (before DOMContentLoaded) so values are never visible
|
|
2
|
+
(function() {
|
|
3
|
+
var style = document.createElement('style');
|
|
4
|
+
style.id = 'count-up-hide';
|
|
5
|
+
style.textContent = `
|
|
6
|
+
.box-label-val:not([data-count-up-done]) {
|
|
7
|
+
-webkit-text-fill-color: transparent !important;
|
|
8
|
+
position: relative !important;
|
|
9
|
+
}
|
|
10
|
+
.box-label-val:not([data-count-up-done])::before {
|
|
11
|
+
content: '0';
|
|
12
|
+
-webkit-text-fill-color: initial !important;
|
|
13
|
+
position: absolute !important;
|
|
14
|
+
right: 0 !important;
|
|
15
|
+
}
|
|
16
|
+
`;
|
|
17
|
+
// Insert into head ASAP
|
|
18
|
+
if (document.head) {
|
|
19
|
+
document.head.appendChild(style);
|
|
20
|
+
} else {
|
|
21
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
22
|
+
document.head.appendChild(style);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
})();
|
|
26
|
+
|
|
27
|
+
var SELECTORS = '.box-label-val';
|
|
28
|
+
|
|
29
|
+
function parseNumericValue(text) {
|
|
30
|
+
var cleaned = text.trim();
|
|
31
|
+
if (!cleaned || cleaned === '-' || cleaned === 'N/A') return null;
|
|
32
|
+
|
|
33
|
+
var match = cleaned.match(/-?[\d][\d\s,.]*/);
|
|
34
|
+
if (!match) return null;
|
|
35
|
+
|
|
36
|
+
var numStr = match[0].trim();
|
|
37
|
+
var idx = cleaned.indexOf(numStr);
|
|
38
|
+
var prefix = cleaned.substring(0, idx);
|
|
39
|
+
var suffix = cleaned.substring(idx + numStr.length);
|
|
40
|
+
|
|
41
|
+
var lastComma = numStr.lastIndexOf(',');
|
|
42
|
+
var lastDot = numStr.lastIndexOf('.');
|
|
43
|
+
|
|
44
|
+
var decimals = 0;
|
|
45
|
+
var value;
|
|
46
|
+
var frFormat = false;
|
|
47
|
+
|
|
48
|
+
if (lastComma > lastDot) {
|
|
49
|
+
frFormat = true;
|
|
50
|
+
var afterComma = numStr.substring(lastComma + 1);
|
|
51
|
+
if (afterComma.length <= 2) {
|
|
52
|
+
decimals = afterComma.length;
|
|
53
|
+
value = parseFloat(numStr.replace(/\s/g, '').replace(',', '.'));
|
|
54
|
+
} else {
|
|
55
|
+
value = parseFloat(numStr.replace(/[,\s]/g, ''));
|
|
56
|
+
}
|
|
57
|
+
} else if (lastDot > lastComma) {
|
|
58
|
+
var afterDot = numStr.substring(lastDot + 1);
|
|
59
|
+
if (afterDot.length <= 2) {
|
|
60
|
+
decimals = afterDot.length;
|
|
61
|
+
value = parseFloat(numStr.replace(/[,\s]/g, ''));
|
|
62
|
+
} else {
|
|
63
|
+
value = parseFloat(numStr.replace(/[.\s]/g, ''));
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
value = parseInt(numStr.replace(/[,.\s]/g, ''), 10);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (isNaN(value) || value === 0) return null;
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
value: value,
|
|
73
|
+
decimals: decimals,
|
|
74
|
+
prefix: prefix,
|
|
75
|
+
suffix: suffix,
|
|
76
|
+
frFormat: frFormat,
|
|
77
|
+
original: cleaned
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function formatValue(val, info) {
|
|
82
|
+
var abs = Math.abs(val);
|
|
83
|
+
var fixed = abs.toFixed(info.decimals);
|
|
84
|
+
var parts = fixed.split('.');
|
|
85
|
+
|
|
86
|
+
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, info.frFormat ? ' ' : ',');
|
|
87
|
+
|
|
88
|
+
var formatted;
|
|
89
|
+
if (info.decimals > 0) {
|
|
90
|
+
formatted = parts.join(info.frFormat ? ',' : '.');
|
|
91
|
+
} else {
|
|
92
|
+
formatted = parts[0];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return info.prefix + (val < 0 ? '-' : '') + formatted + info.suffix;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function animateElement(el) {
|
|
99
|
+
if (el.dataset.countUpDone) return;
|
|
100
|
+
|
|
101
|
+
// If data-amount is set, use that as the target value
|
|
102
|
+
var targetText = el.dataset.amount || el.textContent;
|
|
103
|
+
var info = parseNumericValue(targetText);
|
|
104
|
+
|
|
105
|
+
if (!info && targetText.trim() !== '0' && targetText.trim() !== '-') {
|
|
106
|
+
// No numeric value - just reveal it
|
|
107
|
+
el.dataset.countUpReady = '1';
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
el.dataset.countUpDone = '1';
|
|
112
|
+
|
|
113
|
+
var duration = Math.random() * 1500 + 800;
|
|
114
|
+
var startTime = null;
|
|
115
|
+
|
|
116
|
+
function step(timestamp) {
|
|
117
|
+
if (!startTime) startTime = timestamp;
|
|
118
|
+
var progress = Math.min((timestamp - startTime) / duration, 1);
|
|
119
|
+
var eased = 1 - Math.pow(1 - progress, 3);
|
|
120
|
+
|
|
121
|
+
var current = info.value * eased;
|
|
122
|
+
el.textContent = formatValue(current, info);
|
|
123
|
+
|
|
124
|
+
if (progress < 1) {
|
|
125
|
+
requestAnimationFrame(step);
|
|
126
|
+
} else {
|
|
127
|
+
el.textContent = info.original;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
requestAnimationFrame(step);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* Pie chart animation (conic-gradient fill + percentage count-up) */
|
|
135
|
+
function animatePieChart(pie) {
|
|
136
|
+
if (pie.dataset.pieAnimDone) return;
|
|
137
|
+
|
|
138
|
+
var style = pie.getAttribute('style') || '';
|
|
139
|
+
var gradientMatch = style.match(/conic-gradient\(\s*(\S+)\s+([\d.]+)deg/);
|
|
140
|
+
if (!gradientMatch) return;
|
|
141
|
+
|
|
142
|
+
var color = gradientMatch[1];
|
|
143
|
+
var targetDegrees = parseFloat(gradientMatch[2]);
|
|
144
|
+
|
|
145
|
+
var innerDiv = pie.querySelector('.flex-center');
|
|
146
|
+
if (!innerDiv) return;
|
|
147
|
+
|
|
148
|
+
var text = innerDiv.textContent.trim();
|
|
149
|
+
var numMatch = text.match(/(-?\d+)/);
|
|
150
|
+
if (!numMatch) return;
|
|
151
|
+
|
|
152
|
+
var targetPct = parseInt(numMatch[0]);
|
|
153
|
+
var suffix = text.substring(text.indexOf(numMatch[0]) + numMatch[0].length);
|
|
154
|
+
|
|
155
|
+
pie.dataset.pieAnimDone = '1';
|
|
156
|
+
|
|
157
|
+
var duration = 1200;
|
|
158
|
+
var startTime = null;
|
|
159
|
+
|
|
160
|
+
function step(timestamp) {
|
|
161
|
+
if (!startTime) startTime = timestamp;
|
|
162
|
+
var progress = Math.min((timestamp - startTime) / duration, 1);
|
|
163
|
+
var eased = 1 - Math.pow(1 - progress, 3);
|
|
164
|
+
|
|
165
|
+
var currentDeg = targetDegrees * eased;
|
|
166
|
+
var currentPct = Math.round(targetPct * eased);
|
|
167
|
+
var grayStart = currentDeg > 0 ? currentDeg + 2 : 0;
|
|
168
|
+
|
|
169
|
+
pie.style.backgroundImage = 'conic-gradient(' + color + ' ' + currentDeg + 'deg, #f0f0f0 ' + grayStart + 'deg ' + (360 - currentDeg) + 'deg)';
|
|
170
|
+
innerDiv.textContent = currentPct + suffix;
|
|
171
|
+
|
|
172
|
+
if (progress < 1) {
|
|
173
|
+
requestAnimationFrame(step);
|
|
174
|
+
} else {
|
|
175
|
+
pie.style.backgroundImage = 'conic-gradient(' + color + ' ' + targetDegrees + 'deg, #f0f0f0 ' + (targetDegrees > 0 ? targetDegrees + 2 : 0) + 'deg ' + (360 - targetDegrees) + 'deg)';
|
|
176
|
+
innerDiv.textContent = targetPct + suffix;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
requestAnimationFrame(step);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function processElements() {
|
|
184
|
+
document.querySelectorAll(SELECTORS).forEach(animateElement);
|
|
185
|
+
document.querySelectorAll('[style*="conic-gradient"]').forEach(animatePieChart);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const interval = setInterval(function () {
|
|
189
|
+
const newElements = document.querySelectorAll(SELECTORS + ':not([data-count-up-done]), [style*="conic-gradient"]:not([data-pie-anim-done])');
|
|
190
|
+
|
|
191
|
+
if (newElements.length) {
|
|
192
|
+
processElements();
|
|
193
|
+
}
|
|
194
|
+
}, 30);
|
|
195
|
+
setTimeout(() => clearInterval(interval), 3000);
|
|
196
|
+
|
|
197
|
+
var observer = new MutationObserver(function(mutations) {
|
|
198
|
+
var found = false;
|
|
199
|
+
mutations.forEach(function(mutation) {
|
|
200
|
+
mutation.addedNodes.forEach(function(node) {
|
|
201
|
+
if (node.nodeType === 1) {
|
|
202
|
+
if (node.matches && node.matches(SELECTORS)) {
|
|
203
|
+
found = true;
|
|
204
|
+
} else if (node.querySelectorAll) {
|
|
205
|
+
if (node.querySelectorAll(SELECTORS).length || node.querySelectorAll('[style*="conic-gradient"]').length) {
|
|
206
|
+
found = true;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (node.getAttribute && (node.getAttribute('style') || '').indexOf('conic-gradient') !== -1) {
|
|
210
|
+
found = true;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
if (found) {
|
|
216
|
+
setTimeout(processElements, 50);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Magic Poof Animation (Created by claude)
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* MagicPoof.appear(element, options) - Make element appear with magic effect
|
|
6
|
+
* MagicPoof.disappear(element, options) - Make element disappear with magic effect
|
|
7
|
+
* MagicPoof.toggle(element, options) - Toggle visibility with magic effect
|
|
8
|
+
*
|
|
9
|
+
* Options:
|
|
10
|
+
* color: 'gold' | 'purple' | 'blue' | 'pink' | custom hex (default: 'purple')
|
|
11
|
+
* sparkles: number of sparkles (default: 12)
|
|
12
|
+
* smoke: boolean to show smoke puff (default: true)
|
|
13
|
+
* onComplete: callback when animation finishes
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const MagicPoof = (function() {
|
|
17
|
+
|
|
18
|
+
const sparkleColors = {
|
|
19
|
+
gold: ['#FFD700', '#FFA500', '#FFEC8B', '#FFE4B5'],
|
|
20
|
+
purple: ['#9B59B6', '#8E44AD', '#D4A5FF', '#E8DAEF'],
|
|
21
|
+
blue: ['#3498DB', '#2980B9', '#85C1E9', '#AED6F1'],
|
|
22
|
+
pink: ['#FF69B4', '#FF1493', '#FFB6C1', '#FFC0CB'],
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const defaultOptions = {
|
|
26
|
+
color: 'purple',
|
|
27
|
+
sparkles: 12,
|
|
28
|
+
smoke: true,
|
|
29
|
+
onComplete: null
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function getColors(color) {
|
|
33
|
+
if (sparkleColors[color]) {
|
|
34
|
+
return sparkleColors[color];
|
|
35
|
+
}
|
|
36
|
+
// If custom hex color, generate variations
|
|
37
|
+
return [color, color, color, color];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function createSparkle(x, y, colors) {
|
|
41
|
+
const sparkle = document.createElement('div');
|
|
42
|
+
sparkle.className = 'magic-sparkle';
|
|
43
|
+
|
|
44
|
+
// Random direction and distance
|
|
45
|
+
const angle = Math.random() * Math.PI * 2;
|
|
46
|
+
const distance = 30 + Math.random() * 50;
|
|
47
|
+
const tx = Math.cos(angle) * distance;
|
|
48
|
+
const ty = Math.sin(angle) * distance;
|
|
49
|
+
|
|
50
|
+
sparkle.style.cssText = `
|
|
51
|
+
left: ${x}px;
|
|
52
|
+
top: ${y}px;
|
|
53
|
+
--tx: ${tx}px;
|
|
54
|
+
--ty: ${ty}px;
|
|
55
|
+
color: ${colors[Math.floor(Math.random() * colors.length)]};
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
return sparkle;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function createSmoke(x, y, size, color) {
|
|
62
|
+
const smoke = document.createElement('div');
|
|
63
|
+
smoke.className = 'magic-smoke';
|
|
64
|
+
|
|
65
|
+
const smokeColor = sparkleColors[color]
|
|
66
|
+
? `rgba(${hexToRgb(sparkleColors[color][0])}, 0.4)`
|
|
67
|
+
: `rgba(155, 89, 182, 0.4)`;
|
|
68
|
+
|
|
69
|
+
smoke.style.cssText = `
|
|
70
|
+
left: ${x - size/2}px;
|
|
71
|
+
top: ${y - size/2}px;
|
|
72
|
+
width: ${size}px;
|
|
73
|
+
height: ${size}px;
|
|
74
|
+
background: radial-gradient(circle, rgba(255,255,255,0.9) 0%, ${smokeColor} 40%, transparent 70%);
|
|
75
|
+
`;
|
|
76
|
+
|
|
77
|
+
return smoke;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function hexToRgb(hex) {
|
|
81
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
82
|
+
return result
|
|
83
|
+
? `${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}`
|
|
84
|
+
: '155, 89, 182';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function getElementCenter(element) {
|
|
88
|
+
const rect = element.getBoundingClientRect();
|
|
89
|
+
return {
|
|
90
|
+
x: rect.left + rect.width / 2 + window.scrollX,
|
|
91
|
+
y: rect.top + rect.height / 2 + window.scrollY,
|
|
92
|
+
width: rect.width,
|
|
93
|
+
height: rect.height
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function createEffectsContainer() {
|
|
98
|
+
let container = document.getElementById('magic-poof-effects');
|
|
99
|
+
if (!container) {
|
|
100
|
+
container = document.createElement('div');
|
|
101
|
+
container.id = 'magic-poof-effects';
|
|
102
|
+
container.style.cssText = 'position: absolute; top: 0; left: 0; pointer-events: none; z-index: 9999; overflow: visible;';
|
|
103
|
+
document.body.appendChild(container);
|
|
104
|
+
}
|
|
105
|
+
return container;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function playEffect(element, options) {
|
|
109
|
+
const container = createEffectsContainer();
|
|
110
|
+
const center = getElementCenter(element);
|
|
111
|
+
const colors = getColors(options.color);
|
|
112
|
+
|
|
113
|
+
// Create smoke puff
|
|
114
|
+
if (options.smoke) {
|
|
115
|
+
const smokeSize = Math.max(center.width, center.height) * 1.5;
|
|
116
|
+
const smoke = createSmoke(center.x, center.y, smokeSize, options.color);
|
|
117
|
+
container.appendChild(smoke);
|
|
118
|
+
setTimeout(() => smoke.remove(), 600);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Create sparkles
|
|
122
|
+
for (let i = 0; i < options.sparkles; i++) {
|
|
123
|
+
setTimeout(() => {
|
|
124
|
+
const offsetX = (Math.random() - 0.5) * center.width * 0.8;
|
|
125
|
+
const offsetY = (Math.random() - 0.5) * center.height * 0.8;
|
|
126
|
+
const sparkle = createSparkle(center.x + offsetX, center.y + offsetY, colors);
|
|
127
|
+
container.appendChild(sparkle);
|
|
128
|
+
setTimeout(() => sparkle.remove(), 700);
|
|
129
|
+
}, Math.random() * 100);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function appear(element, userOptions = {}) {
|
|
134
|
+
const options = { ...defaultOptions, ...userOptions };
|
|
135
|
+
|
|
136
|
+
// Ensure element is in DOM but hidden initially
|
|
137
|
+
element.style.opacity = '0';
|
|
138
|
+
element.style.maxHeight = '';
|
|
139
|
+
element.style.visibility = 'visible';
|
|
140
|
+
element.style.display = '';
|
|
141
|
+
|
|
142
|
+
// Play effects
|
|
143
|
+
playEffect(element, options);
|
|
144
|
+
|
|
145
|
+
// Trigger appear animation
|
|
146
|
+
element.classList.remove('magic-poof-out');
|
|
147
|
+
element.classList.add('magic-poof');
|
|
148
|
+
|
|
149
|
+
// Cleanup and callback
|
|
150
|
+
setTimeout(() => {
|
|
151
|
+
element.classList.remove('magic-poof');
|
|
152
|
+
element.style.opacity = '';
|
|
153
|
+
if (options.onComplete) options.onComplete(element);
|
|
154
|
+
}, 500);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function disappear(element, userOptions = {}) {
|
|
158
|
+
const options = { ...defaultOptions, ...userOptions };
|
|
159
|
+
|
|
160
|
+
// Play effects
|
|
161
|
+
playEffect(element, options);
|
|
162
|
+
|
|
163
|
+
// Trigger disappear animation
|
|
164
|
+
element.classList.remove('magic-poof');
|
|
165
|
+
element.classList.add('magic-poof-out');
|
|
166
|
+
|
|
167
|
+
// Hide and cleanup
|
|
168
|
+
setTimeout(() => {
|
|
169
|
+
element.style.maxHeight = '0';
|
|
170
|
+
element.style.visibility = 'hidden';
|
|
171
|
+
element.classList.remove('magic-poof-out');
|
|
172
|
+
if (options.onComplete) options.onComplete(element);
|
|
173
|
+
}, 400);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function toggle(element, userOptions = {}) {
|
|
177
|
+
const isHidden = element.style.display === 'none' ||
|
|
178
|
+
window.getComputedStyle(element).display === 'none';
|
|
179
|
+
|
|
180
|
+
if (isHidden) {
|
|
181
|
+
appear(element, userOptions);
|
|
182
|
+
} else {
|
|
183
|
+
disappear(element, userOptions);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Auto-init: Listen for elements with data-magic-poof attribute
|
|
188
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
189
|
+
document.addEventListener('click', function(e) {
|
|
190
|
+
const trigger = e.target.closest('[data-magic-poof-trigger]');
|
|
191
|
+
if (trigger) {
|
|
192
|
+
const targetSelector = trigger.dataset.magicPoofTrigger;
|
|
193
|
+
const target = document.querySelector(targetSelector);
|
|
194
|
+
if (target) {
|
|
195
|
+
const color = trigger.dataset.magicPoofColor || 'purple';
|
|
196
|
+
toggle(target, { color });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
appear,
|
|
204
|
+
disappear,
|
|
205
|
+
toggle
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
})();
|
|
209
|
+
|
|
210
|
+
window.MagicPoof = MagicPoof;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tutorial configuration with fluent builder API.
|
|
3
|
+
*/
|
|
4
|
+
export class TutorialConfig {
|
|
5
|
+
static DEFAULTS = {
|
|
6
|
+
avatar: '/images/tutorial-avatar.png',
|
|
7
|
+
cursorImage: null,
|
|
8
|
+
cursorSize: 'clamp(32px, 8vw, 48px)',
|
|
9
|
+
avatarHeight: 'clamp(200px, 50vh, 475px)',
|
|
10
|
+
avatarMaxHeight: '50vh',
|
|
11
|
+
typewriteSpeed: 25,
|
|
12
|
+
nextLabel: 'tutorial.next',
|
|
13
|
+
doneLabel: 'tutorial.done',
|
|
14
|
+
bubbleFontSize: 'clamp(14px, 3.5vw, 16px)',
|
|
15
|
+
bubbleLineHeight: '1.6',
|
|
16
|
+
bubbleMaxWidth: 'clamp(260px, 85vw, 550px)',
|
|
17
|
+
bubbleMinWidth: 'clamp(200px, 70vw, 350px)',
|
|
18
|
+
devMode: false,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
constructor(userOptions = {}) {
|
|
22
|
+
const windowDefaults = window.TutorialDefaults || {};
|
|
23
|
+
this.options = { ...TutorialConfig.DEFAULTS, ...windowDefaults, ...userOptions };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
set(key, value) {
|
|
27
|
+
this.options[key] = value;
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get(key) {
|
|
32
|
+
return this.options[key];
|
|
33
|
+
}
|
|
34
|
+
}
|