@hortonstudio/main 1.2.15 → 1.2.19
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/.claude/settings.local.json +40 -0
- package/CLAUDE.md +45 -0
- package/animations/hero.js +613 -0
- package/animations/text.js +379 -0
- package/animations/transition.js +57 -0
- package/autoInit/smooth-scroll.js +89 -0
- package/debug-version.html +37 -0
- package/index.js +232 -0
- package/package.json +4 -24
- package/styles.css +29 -0
- package/utils/navbar.js +214 -0
- package/utils/scroll-progress.js +29 -0
- package/utils/toc.js +77 -0
- package/dist/index.js +0 -1
- package/dist/src-hero-UYNS76KU.js +0 -1
- package/dist/src-load-3UYE3H3N.js +0 -1
- package/dist/src-navbar-763V2PWX.js +0 -1
- package/dist/src-scroll-progress-EX62FWYD.js +0 -1
- package/dist/src-smooth-scroll-TG4SEEIC.js +0 -1
- package/dist/src-text-BZVCCQJP.js +0 -1
- package/dist/src-toc-SHTDW6BF.js +0 -1
- package/dist/src-transition-UBAPWTIU.js +0 -1
@@ -0,0 +1,379 @@
|
|
1
|
+
const API_NAME = 'hsmain';
|
2
|
+
|
3
|
+
// Check for reduced motion preference
|
4
|
+
const prefersReducedMotion = () => {
|
5
|
+
return window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
6
|
+
};
|
7
|
+
|
8
|
+
const config = {
|
9
|
+
global: {
|
10
|
+
animationDelay: 0
|
11
|
+
},
|
12
|
+
wordSplit: {
|
13
|
+
duration: 1.5,
|
14
|
+
stagger: 0.075,
|
15
|
+
yPercent: 110,
|
16
|
+
ease: "power4.out",
|
17
|
+
start: "top 97%"
|
18
|
+
},
|
19
|
+
lineSplit: {
|
20
|
+
duration: 1.5,
|
21
|
+
stagger: 0.1,
|
22
|
+
yPercent: 110,
|
23
|
+
ease: "power4.out",
|
24
|
+
start: "top 97%"
|
25
|
+
},
|
26
|
+
charSplit: {
|
27
|
+
duration: 1.2,
|
28
|
+
stagger: 0.03,
|
29
|
+
yPercent: 110,
|
30
|
+
ease: "power4.out",
|
31
|
+
start: "top 97%"
|
32
|
+
},
|
33
|
+
appear: {
|
34
|
+
y: 50,
|
35
|
+
duration: 1.5,
|
36
|
+
ease: "power3.out",
|
37
|
+
start: "top 97%"
|
38
|
+
}
|
39
|
+
};
|
40
|
+
|
41
|
+
function updateConfig(newConfig) {
|
42
|
+
function deepMerge(target, source) {
|
43
|
+
for (const key in source) {
|
44
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
45
|
+
target[key] = target[key] || {};
|
46
|
+
deepMerge(target[key], source[key]);
|
47
|
+
} else {
|
48
|
+
target[key] = source[key];
|
49
|
+
}
|
50
|
+
}
|
51
|
+
return target;
|
52
|
+
}
|
53
|
+
|
54
|
+
deepMerge(config, newConfig);
|
55
|
+
}
|
56
|
+
|
57
|
+
function killTextAnimations() {
|
58
|
+
activeAnimations.forEach(({ timeline, element }) => {
|
59
|
+
if (timeline) {
|
60
|
+
timeline.kill();
|
61
|
+
}
|
62
|
+
if (element?.splitTextInstance) {
|
63
|
+
element.splitTextInstance.revert();
|
64
|
+
}
|
65
|
+
});
|
66
|
+
activeAnimations.length = 0;
|
67
|
+
}
|
68
|
+
|
69
|
+
function startTextAnimations() {
|
70
|
+
if (prefersReducedMotion()) {
|
71
|
+
// For reduced motion, just show elements without animation
|
72
|
+
showElementsWithoutAnimation();
|
73
|
+
return;
|
74
|
+
}
|
75
|
+
|
76
|
+
setInitialStates().then(() => {
|
77
|
+
initAnimations();
|
78
|
+
});
|
79
|
+
}
|
80
|
+
|
81
|
+
let activeAnimations = [];
|
82
|
+
|
83
|
+
function waitForFonts() {
|
84
|
+
return document.fonts.ready;
|
85
|
+
}
|
86
|
+
|
87
|
+
function showElementsWithoutAnimation() {
|
88
|
+
// Simply show all text elements without any animation or split text
|
89
|
+
const allTextElements = [
|
90
|
+
...document.querySelectorAll(".a-char-split > *:first-child"),
|
91
|
+
...document.querySelectorAll(".a-word-split > *:first-child"),
|
92
|
+
...document.querySelectorAll(".a-line-split > *:first-child"),
|
93
|
+
...document.querySelectorAll('.a-appear')
|
94
|
+
];
|
95
|
+
|
96
|
+
allTextElements.forEach(element => {
|
97
|
+
gsap.set(element, {
|
98
|
+
autoAlpha: 1,
|
99
|
+
y: 0,
|
100
|
+
yPercent: 0,
|
101
|
+
opacity: 1
|
102
|
+
});
|
103
|
+
});
|
104
|
+
}
|
105
|
+
|
106
|
+
const CharSplitAnimations = {
|
107
|
+
async initial() {
|
108
|
+
await waitForFonts();
|
109
|
+
|
110
|
+
if (prefersReducedMotion()) {
|
111
|
+
return;
|
112
|
+
}
|
113
|
+
|
114
|
+
const elements = document.querySelectorAll(".a-char-split > *:first-child");
|
115
|
+
|
116
|
+
elements.forEach((textElement) => {
|
117
|
+
const split = SplitText.create(textElement, {
|
118
|
+
type: "chars",
|
119
|
+
mask: "chars",
|
120
|
+
charsClass: "char",
|
121
|
+
});
|
122
|
+
textElement.splitTextInstance = split;
|
123
|
+
|
124
|
+
gsap.set(split.chars, {
|
125
|
+
yPercent: config.charSplit.yPercent
|
126
|
+
});
|
127
|
+
gsap.set(textElement, { autoAlpha: 1 });
|
128
|
+
});
|
129
|
+
},
|
130
|
+
|
131
|
+
async animate() {
|
132
|
+
await waitForFonts();
|
133
|
+
|
134
|
+
if (prefersReducedMotion()) {
|
135
|
+
return;
|
136
|
+
}
|
137
|
+
|
138
|
+
document.querySelectorAll(".a-char-split > *:first-child").forEach((textElement) => {
|
139
|
+
const chars = textElement.querySelectorAll('.char');
|
140
|
+
const tl = gsap.timeline({
|
141
|
+
scrollTrigger: {
|
142
|
+
trigger: textElement,
|
143
|
+
start: config.charSplit.start,
|
144
|
+
invalidateOnRefresh: true,
|
145
|
+
},
|
146
|
+
onComplete: () => {
|
147
|
+
|
148
|
+
}
|
149
|
+
});
|
150
|
+
|
151
|
+
tl.to(chars, {
|
152
|
+
yPercent: 0,
|
153
|
+
duration: config.charSplit.duration,
|
154
|
+
stagger: config.charSplit.stagger,
|
155
|
+
ease: config.charSplit.ease,
|
156
|
+
});
|
157
|
+
|
158
|
+
activeAnimations.push({ timeline: tl, element: textElement });
|
159
|
+
});
|
160
|
+
}
|
161
|
+
};
|
162
|
+
|
163
|
+
const WordSplitAnimations = {
|
164
|
+
async initial() {
|
165
|
+
await waitForFonts();
|
166
|
+
|
167
|
+
if (prefersReducedMotion()) {
|
168
|
+
return;
|
169
|
+
}
|
170
|
+
|
171
|
+
const elements = document.querySelectorAll(".a-word-split > *:first-child");
|
172
|
+
|
173
|
+
elements.forEach((textElement) => {
|
174
|
+
const split = SplitText.create(textElement, {
|
175
|
+
type: "words",
|
176
|
+
mask: "words",
|
177
|
+
wordsClass: "word",
|
178
|
+
});
|
179
|
+
textElement.splitTextInstance = split;
|
180
|
+
|
181
|
+
gsap.set(split.words, {
|
182
|
+
yPercent: config.wordSplit.yPercent
|
183
|
+
});
|
184
|
+
gsap.set(textElement, { autoAlpha: 1 });
|
185
|
+
});
|
186
|
+
},
|
187
|
+
|
188
|
+
async animate() {
|
189
|
+
await waitForFonts();
|
190
|
+
|
191
|
+
if (prefersReducedMotion()) {
|
192
|
+
return;
|
193
|
+
}
|
194
|
+
|
195
|
+
document.querySelectorAll(".a-word-split > *:first-child").forEach((textElement) => {
|
196
|
+
const words = textElement.querySelectorAll('.word');
|
197
|
+
const tl = gsap.timeline({
|
198
|
+
scrollTrigger: {
|
199
|
+
trigger: textElement,
|
200
|
+
start: config.wordSplit.start,
|
201
|
+
invalidateOnRefresh: true,
|
202
|
+
},
|
203
|
+
onComplete: () => {
|
204
|
+
|
205
|
+
}
|
206
|
+
});
|
207
|
+
|
208
|
+
tl.to(words, {
|
209
|
+
yPercent: 0,
|
210
|
+
duration: config.wordSplit.duration,
|
211
|
+
stagger: config.wordSplit.stagger,
|
212
|
+
ease: config.wordSplit.ease,
|
213
|
+
});
|
214
|
+
|
215
|
+
activeAnimations.push({ timeline: tl, element: textElement });
|
216
|
+
});
|
217
|
+
}
|
218
|
+
};
|
219
|
+
|
220
|
+
const LineSplitAnimations = {
|
221
|
+
async initial() {
|
222
|
+
await waitForFonts();
|
223
|
+
|
224
|
+
if (prefersReducedMotion()) {
|
225
|
+
return;
|
226
|
+
}
|
227
|
+
|
228
|
+
const elements = document.querySelectorAll(".a-line-split > *:first-child");
|
229
|
+
|
230
|
+
elements.forEach((textElement) => {
|
231
|
+
const split = SplitText.create(textElement, {
|
232
|
+
type: "lines",
|
233
|
+
mask: "lines",
|
234
|
+
linesClass: "line",
|
235
|
+
});
|
236
|
+
textElement.splitTextInstance = split;
|
237
|
+
|
238
|
+
gsap.set(split.lines, {
|
239
|
+
yPercent: config.lineSplit.yPercent
|
240
|
+
});
|
241
|
+
gsap.set(textElement, { autoAlpha: 1 });
|
242
|
+
});
|
243
|
+
},
|
244
|
+
|
245
|
+
async animate() {
|
246
|
+
await waitForFonts();
|
247
|
+
|
248
|
+
if (prefersReducedMotion()) {
|
249
|
+
return;
|
250
|
+
}
|
251
|
+
|
252
|
+
document.querySelectorAll(".a-line-split > *:first-child").forEach((textElement) => {
|
253
|
+
const lines = textElement.querySelectorAll('.line');
|
254
|
+
const tl = gsap.timeline({
|
255
|
+
scrollTrigger: {
|
256
|
+
trigger: textElement,
|
257
|
+
start: config.lineSplit.start,
|
258
|
+
invalidateOnRefresh: true,
|
259
|
+
},
|
260
|
+
onComplete: () => {
|
261
|
+
|
262
|
+
}
|
263
|
+
});
|
264
|
+
|
265
|
+
tl.to(lines, {
|
266
|
+
yPercent: 0,
|
267
|
+
duration: config.lineSplit.duration,
|
268
|
+
stagger: config.lineSplit.stagger,
|
269
|
+
ease: config.lineSplit.ease,
|
270
|
+
});
|
271
|
+
|
272
|
+
activeAnimations.push({ timeline: tl, element: textElement });
|
273
|
+
});
|
274
|
+
}
|
275
|
+
};
|
276
|
+
|
277
|
+
const AppearAnimations = {
|
278
|
+
async initial() {
|
279
|
+
await waitForFonts();
|
280
|
+
|
281
|
+
if (prefersReducedMotion()) {
|
282
|
+
return;
|
283
|
+
}
|
284
|
+
|
285
|
+
const elements = document.querySelectorAll('.a-appear');
|
286
|
+
elements.forEach(element => {
|
287
|
+
gsap.set(element, {
|
288
|
+
y: config.appear.y,
|
289
|
+
opacity: 0
|
290
|
+
});
|
291
|
+
});
|
292
|
+
},
|
293
|
+
|
294
|
+
async animate() {
|
295
|
+
await waitForFonts();
|
296
|
+
|
297
|
+
if (prefersReducedMotion()) {
|
298
|
+
return;
|
299
|
+
}
|
300
|
+
|
301
|
+
document.querySelectorAll('.a-appear').forEach(element => {
|
302
|
+
const tl = gsap.timeline({
|
303
|
+
scrollTrigger: {
|
304
|
+
trigger: element,
|
305
|
+
start: config.appear.start,
|
306
|
+
invalidateOnRefresh: true,
|
307
|
+
}
|
308
|
+
});
|
309
|
+
|
310
|
+
tl.to(element, {
|
311
|
+
y: 0,
|
312
|
+
opacity: 1,
|
313
|
+
duration: config.appear.duration,
|
314
|
+
ease: config.appear.ease
|
315
|
+
});
|
316
|
+
|
317
|
+
activeAnimations.push({ timeline: tl, element: element });
|
318
|
+
});
|
319
|
+
|
320
|
+
}
|
321
|
+
};
|
322
|
+
|
323
|
+
async function setInitialStates() {
|
324
|
+
await Promise.all([
|
325
|
+
CharSplitAnimations.initial(),
|
326
|
+
WordSplitAnimations.initial(),
|
327
|
+
LineSplitAnimations.initial(),
|
328
|
+
AppearAnimations.initial()
|
329
|
+
]);
|
330
|
+
}
|
331
|
+
|
332
|
+
async function initAnimations() {
|
333
|
+
if (config.global.animationDelay > 0) {
|
334
|
+
await new Promise(resolve => setTimeout(resolve, config.global.animationDelay * 1000));
|
335
|
+
}
|
336
|
+
|
337
|
+
await Promise.all([
|
338
|
+
CharSplitAnimations.animate(),
|
339
|
+
WordSplitAnimations.animate(),
|
340
|
+
LineSplitAnimations.animate(),
|
341
|
+
AppearAnimations.animate()
|
342
|
+
]);
|
343
|
+
}
|
344
|
+
|
345
|
+
export async function init() {
|
346
|
+
// Check if there's a persistent config stored globally
|
347
|
+
const api = window[API_NAME] || {};
|
348
|
+
if (api.textAnimations?.config && api.textAnimations.config !== config) {
|
349
|
+
// Merge persistent config into current config
|
350
|
+
updateConfig(api.textAnimations.config);
|
351
|
+
}
|
352
|
+
|
353
|
+
if (prefersReducedMotion()) {
|
354
|
+
// For reduced motion, just show elements without animation
|
355
|
+
showElementsWithoutAnimation();
|
356
|
+
} else {
|
357
|
+
await setInitialStates();
|
358
|
+
initAnimations();
|
359
|
+
}
|
360
|
+
|
361
|
+
window.addEventListener('resize', ScrollTrigger.refresh());
|
362
|
+
|
363
|
+
api.textAnimations = {
|
364
|
+
config: config,
|
365
|
+
updateConfig: (newConfig) => {
|
366
|
+
updateConfig(newConfig);
|
367
|
+
// Store config reference for persistence across restarts
|
368
|
+
api.textAnimations.config = config;
|
369
|
+
},
|
370
|
+
start: startTextAnimations,
|
371
|
+
kill: killTextAnimations,
|
372
|
+
restart: () => {
|
373
|
+
killTextAnimations();
|
374
|
+
startTextAnimations();
|
375
|
+
}
|
376
|
+
};
|
377
|
+
|
378
|
+
return { result: 'anim-text initialized' };
|
379
|
+
}
|
@@ -0,0 +1,57 @@
|
|
1
|
+
// Page Transition Module
|
2
|
+
const API_NAME = 'hsmain';
|
3
|
+
export async function init() {
|
4
|
+
|
5
|
+
// Register the transition logic to run after library is ready
|
6
|
+
window[API_NAME].afterReady(() => {
|
7
|
+
// Only run if jQuery is available
|
8
|
+
if (typeof $ !== 'undefined') {
|
9
|
+
initTransitions();
|
10
|
+
}
|
11
|
+
});
|
12
|
+
|
13
|
+
return { result: 'anim-transition initialized' };
|
14
|
+
}
|
15
|
+
|
16
|
+
function initTransitions() {
|
17
|
+
let transitionTrigger = $(".transition-trigger");
|
18
|
+
let introDurationMS = 800;
|
19
|
+
let exitDurationMS = 400;
|
20
|
+
let excludedClass = "no-transition";
|
21
|
+
|
22
|
+
// On Page Load
|
23
|
+
if (transitionTrigger.length > 0) {
|
24
|
+
if (window.Webflow && window.Webflow.push) {
|
25
|
+
Webflow.push(function () {
|
26
|
+
transitionTrigger.click();
|
27
|
+
});
|
28
|
+
} else {
|
29
|
+
// Non-Webflow initialization
|
30
|
+
setTimeout(() => {
|
31
|
+
transitionTrigger.click();
|
32
|
+
}, 100);
|
33
|
+
}
|
34
|
+
$("body").addClass("no-scroll-transition");
|
35
|
+
setTimeout(() => {$("body").removeClass("no-scroll-transition");}, introDurationMS);
|
36
|
+
}
|
37
|
+
|
38
|
+
// On Link Click
|
39
|
+
$("a").on("click", function (e) {
|
40
|
+
if ($(this).prop("hostname") == window.location.host && $(this).attr("href").indexOf("#") === -1 &&
|
41
|
+
!$(this).hasClass(excludedClass) && $(this).attr("target") !== "_blank" && transitionTrigger.length > 0) {
|
42
|
+
e.preventDefault();
|
43
|
+
$("body").addClass("no-scroll-transition");
|
44
|
+
let transitionURL = $(this).attr("href");
|
45
|
+
transitionTrigger.click();
|
46
|
+
setTimeout(function () {window.location = transitionURL;}, exitDurationMS);
|
47
|
+
}
|
48
|
+
});
|
49
|
+
|
50
|
+
// On Back Button Tap
|
51
|
+
window.onpageshow = function(event) {if (event.persisted) {window.location.reload()}};
|
52
|
+
|
53
|
+
// Hide Transition on Window Width Resize
|
54
|
+
setTimeout(() => {$(window).on("resize", function () {
|
55
|
+
setTimeout(() => {$(".transition").css("display", "none");}, 50);});
|
56
|
+
}, introDurationMS);
|
57
|
+
}
|
@@ -0,0 +1,89 @@
|
|
1
|
+
const API_NAME = 'hsmain';
|
2
|
+
|
3
|
+
export async function init() {
|
4
|
+
const api = window[API_NAME];
|
5
|
+
api.afterWebflowReady(() => {
|
6
|
+
if (typeof $ !== 'undefined') {
|
7
|
+
$(document).off('click.wf-scroll');
|
8
|
+
}
|
9
|
+
});
|
10
|
+
|
11
|
+
// Disable CSS smooth scrolling
|
12
|
+
document.documentElement.style.scrollBehavior = 'auto';
|
13
|
+
document.body.style.scrollBehavior = 'auto';
|
14
|
+
|
15
|
+
// Check if user prefers reduced motion
|
16
|
+
function prefersReducedMotion() {
|
17
|
+
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
18
|
+
}
|
19
|
+
|
20
|
+
function getScrollOffset() {
|
21
|
+
const offsetValue = getComputedStyle(document.documentElement)
|
22
|
+
.getPropertyValue('--misc--scroll-offset').trim();
|
23
|
+
return parseInt(offsetValue) || 0;
|
24
|
+
}
|
25
|
+
|
26
|
+
// Smooth scroll to element with offset
|
27
|
+
function scrollToElement(target, offset = 0) {
|
28
|
+
if (!target) return;
|
29
|
+
|
30
|
+
// Skip animation if user prefers reduced motion
|
31
|
+
if (prefersReducedMotion()) {
|
32
|
+
const targetPosition = target.getBoundingClientRect().top + window.scrollY - offset;
|
33
|
+
window.scrollTo(0, targetPosition);
|
34
|
+
target.setAttribute('tabindex', '-1');
|
35
|
+
target.focus({ preventScroll: true });
|
36
|
+
return;
|
37
|
+
}
|
38
|
+
|
39
|
+
gsap.to(window, {
|
40
|
+
duration: 1,
|
41
|
+
scrollTo: {
|
42
|
+
y: target,
|
43
|
+
offsetY: offset
|
44
|
+
},
|
45
|
+
ease: "power2.out",
|
46
|
+
onComplete: function() {
|
47
|
+
target.setAttribute('tabindex', '-1');
|
48
|
+
target.focus({ preventScroll: true });
|
49
|
+
}
|
50
|
+
});
|
51
|
+
}
|
52
|
+
|
53
|
+
// Handle anchor link clicks and keyboard activation
|
54
|
+
function handleAnchorClicks() {
|
55
|
+
document.addEventListener('click', handleAnchorActivation);
|
56
|
+
document.addEventListener('keydown', function(e) {
|
57
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
58
|
+
handleAnchorActivation(e);
|
59
|
+
}
|
60
|
+
});
|
61
|
+
}
|
62
|
+
|
63
|
+
function handleAnchorActivation(e) {
|
64
|
+
const link = e.target.closest('a[href^="#"]');
|
65
|
+
if (!link) return;
|
66
|
+
|
67
|
+
const href = link.getAttribute('href');
|
68
|
+
if (!href || href === '#') return;
|
69
|
+
|
70
|
+
const targetId = href.substring(1);
|
71
|
+
const targetElement = document.getElementById(targetId);
|
72
|
+
|
73
|
+
if (targetElement) {
|
74
|
+
e.preventDefault();
|
75
|
+
if (history.replaceState) {
|
76
|
+
history.replaceState(null, null, `#${targetElement.id}`);
|
77
|
+
}
|
78
|
+
const offset = getScrollOffset();
|
79
|
+
scrollToElement(targetElement, offset);
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
// Initialize anchor link handling
|
84
|
+
handleAnchorClicks();
|
85
|
+
|
86
|
+
return {
|
87
|
+
result: 'autoInit-smooth-scroll initialized'
|
88
|
+
};
|
89
|
+
}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>CDN Version Debug Test</title>
|
5
|
+
</head>
|
6
|
+
<body>
|
7
|
+
<h1>CDN Version Debug Test</h1>
|
8
|
+
<div id="results"></div>
|
9
|
+
|
10
|
+
<!-- Test with major version @1 -->
|
11
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/@hortonstudio/main@1/index.js" data-hs-main data-hs-anim-hero></script>
|
12
|
+
|
13
|
+
<script>
|
14
|
+
setTimeout(() => {
|
15
|
+
const results = document.getElementById('results');
|
16
|
+
|
17
|
+
// Check what scripts are found
|
18
|
+
const scripts = [...document.querySelectorAll('script[type="module"]')];
|
19
|
+
results.innerHTML += `<h2>Found Scripts:</h2>`;
|
20
|
+
scripts.forEach((script, i) => {
|
21
|
+
results.innerHTML += `<p>Script ${i}: ${script.src}</p>`;
|
22
|
+
});
|
23
|
+
|
24
|
+
// Check if hsmain loaded and what scripts it found
|
25
|
+
if (window.hsmain) {
|
26
|
+
results.innerHTML += `<h2>HSMAIN Scripts Found:</h2>`;
|
27
|
+
results.innerHTML += `<p>Found ${window.hsmain.scripts.length} matching scripts</p>`;
|
28
|
+
window.hsmain.scripts.forEach((script, i) => {
|
29
|
+
results.innerHTML += `<p>HSMAIN Script ${i}: ${script.src}</p>`;
|
30
|
+
});
|
31
|
+
} else {
|
32
|
+
results.innerHTML += `<p>HSMAIN not loaded</p>`;
|
33
|
+
}
|
34
|
+
}, 2000);
|
35
|
+
</script>
|
36
|
+
</body>
|
37
|
+
</html>
|