@squeditor/squeditor-framework 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/LICENSE +21 -0
- package/README.md +106 -0
- package/package.json +36 -0
- package/php/functions.php +92 -0
- package/project-template/package.json +29 -0
- package/project-template/postcss.config.js +6 -0
- package/project-template/squeditor.config.js +81 -0
- package/project-template/src/404.php +21 -0
- package/project-template/src/assets/css/squeditor-icons.css +4719 -0
- package/project-template/src/assets/css/tailwind.css +3 -0
- package/project-template/src/assets/css/uikit-components.css +14586 -0
- package/project-template/src/assets/js/gsap-advanced.js +26 -0
- package/project-template/src/assets/js/gsap-init.js +672 -0
- package/project-template/src/assets/js/gsap-modules/cursor-preview.js +132 -0
- package/project-template/src/assets/js/gsap-modules/cursor.js +456 -0
- package/project-template/src/assets/js/gsap-modules/loop-panels.js +78 -0
- package/project-template/src/assets/js/gsap-modules/marquee.js +106 -0
- package/project-template/src/assets/js/gsap-modules/pinned-panels.js +105 -0
- package/project-template/src/assets/js/gsap-modules/scroll-to.js +54 -0
- package/project-template/src/assets/js/gsap-modules/swipe-slider.js +121 -0
- package/project-template/src/assets/js/gsap-modules/text-mask.js +93 -0
- package/project-template/src/assets/js/gsap-modules/tilt.js +70 -0
- package/project-template/src/assets/js/main.js +302 -0
- package/project-template/src/assets/js/uikit-components.js +18171 -0
- package/project-template/src/assets/scss/_base.scss +140 -0
- package/project-template/src/assets/scss/_components.scss +165 -0
- package/project-template/src/assets/scss/_config.scss +13 -0
- package/project-template/src/assets/scss/_functions.scss +81 -0
- package/project-template/src/assets/scss/_tokens.scss +229 -0
- package/project-template/src/assets/scss/_transitions.scss +36 -0
- package/project-template/src/assets/scss/_uikit-overrides.scss +187 -0
- package/project-template/src/assets/scss/_uikit_dynamic.scss +43 -0
- package/project-template/src/assets/scss/_utilities.scss +31 -0
- package/project-template/src/assets/scss/custom.scss +10 -0
- package/project-template/src/assets/scss/main.scss +11 -0
- package/project-template/src/assets/static/fonts/squeditor-icons/squeditor-icons.eot +0 -0
- package/project-template/src/assets/static/fonts/squeditor-icons/squeditor-icons.svg +1183 -0
- package/project-template/src/assets/static/fonts/squeditor-icons/squeditor-icons.ttf +0 -0
- package/project-template/src/assets/static/fonts/squeditor-icons/squeditor-icons.woff +0 -0
- package/project-template/src/config/site-config.php +34 -0
- package/project-template/src/data/blog.php +21 -0
- package/project-template/src/data/portfolio.php +23 -0
- package/project-template/src/data/team.php +23 -0
- package/project-template/src/index.php +57 -0
- package/project-template/src/init.php +19 -0
- package/project-template/src/page-templates/base.php +39 -0
- package/project-template/src/page-templates/body-scripts.php +26 -0
- package/project-template/src/page-templates/head.php +47 -0
- package/project-template/src/page-templates/transition.php +45 -0
- package/project-template/src/sections/cards/cards-grid.php +34 -0
- package/project-template/src/sections/cards/cards-horizontal.php +28 -0
- package/project-template/src/sections/cta/cta-banner.php +34 -0
- package/project-template/src/sections/cta/cta-newsletter.php +19 -0
- package/project-template/src/sections/footer/layout-01.php +35 -0
- package/project-template/src/sections/header/layout-01.php +36 -0
- package/project-template/src/sections/hero/hero-centered.php +44 -0
- package/project-template/src/sections/hero/hero-split.php +132 -0
- package/project-template/src/sections/hero/hero-video.php +22 -0
- package/project-template/src/sections/sidebar/sidebar-right.php +11 -0
- package/project-template/src/template-parts/breadcrumbs.php +17 -0
- package/project-template/src/template-parts/footer.php +74 -0
- package/project-template/src/template-parts/header.php +120 -0
- package/project-template/src/template-parts/mega-menu.php +7 -0
- package/project-template/src/template-parts/nav.php +16 -0
- package/project-template/src/template-parts/page-title-bar.php +14 -0
- package/project-template/tailwind.config.js +26 -0
- package/project-template/vite.config.js +67 -0
- package/scripts/build-components.js +109 -0
- package/scripts/copy-static.js +150 -0
- package/scripts/dev-router.php +23 -0
- package/scripts/dev.js +55 -0
- package/scripts/get-port.js +27 -0
- package/scripts/package-customer.js +278 -0
- package/scripts/package-dist.js +54 -0
- package/scripts/scaffold.js +72 -0
- package/scripts/snapshot.js +74 -0
- package/uikit-manifest.json +248 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// src/assets/js/gsap-advanced.js
|
|
2
|
+
// Squeditor Advanced GSAP Module Loader
|
|
3
|
+
// Scans DOM for data-sq-* attributes and loads only required modules
|
|
4
|
+
|
|
5
|
+
const moduleMap = {
|
|
6
|
+
'[data-sq-cursor]': () => import('./gsap-modules/cursor.js'),
|
|
7
|
+
'[data-sq-preview]': () => import('./gsap-modules/cursor-preview.js'),
|
|
8
|
+
'[data-sq-tilt]': () => import('./gsap-modules/tilt.js'),
|
|
9
|
+
'[data-sq-marquee]': () => import('./gsap-modules/marquee.js'),
|
|
10
|
+
'[data-sq-panels]': () => import('./gsap-modules/pinned-panels.js'),
|
|
11
|
+
'[data-sq-loop-panels]': () => import('./gsap-modules/loop-panels.js'),
|
|
12
|
+
'[data-sq-mask]': () => import('./gsap-modules/text-mask.js'),
|
|
13
|
+
'[data-sq-swipe]': () => import('./gsap-modules/swipe-slider.js'),
|
|
14
|
+
'[data-sq-scrollto]': () => import('./gsap-modules/scroll-to.js'),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
document.addEventListener('DOMContentLoaded', async () => {
|
|
18
|
+
for (const [selector, loader] of Object.entries(moduleMap)) {
|
|
19
|
+
if (document.querySelector(selector)) {
|
|
20
|
+
const module = await loader();
|
|
21
|
+
if (typeof module.init === 'function') {
|
|
22
|
+
module.init();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
// src/assets/js/gsap-init.js
|
|
2
|
+
// Squeditor declarative GSAP engine - Next Gen
|
|
3
|
+
// Reads data-gsap, data-gsap-split, data-gsap-draw, data-gsap-scroll, data-gsap-smooth, data-gsap-trigger
|
|
4
|
+
// Full support for ScrollTrigger, timelines, SplitText, ScrollSmoother, DrawSVG
|
|
5
|
+
|
|
6
|
+
import { gsap } from 'gsap';
|
|
7
|
+
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
|
8
|
+
import { SplitText } from 'gsap/SplitText';
|
|
9
|
+
import { ScrollSmoother } from 'gsap/ScrollSmoother';
|
|
10
|
+
import { DrawSVGPlugin } from 'gsap/DrawSVGPlugin';
|
|
11
|
+
import { Observer } from 'gsap/observer';
|
|
12
|
+
import { TextPlugin } from 'gsap/TextPlugin';
|
|
13
|
+
import { MorphSVGPlugin } from 'gsap/MorphSVGPlugin';
|
|
14
|
+
|
|
15
|
+
gsap.registerPlugin(ScrollTrigger, SplitText, ScrollSmoother, DrawSVGPlugin, Observer, TextPlugin, MorphSVGPlugin);
|
|
16
|
+
|
|
17
|
+
// ------------------------------------------------------------------------------
|
|
18
|
+
// PARSER — converts attribute string to config object
|
|
19
|
+
// Handles nested objects: {y: -16, opacity: 0}, arrays/strings: "0% 100%"
|
|
20
|
+
// ------------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
function parseGsapAttr(attrString) {
|
|
23
|
+
if (!attrString) return {};
|
|
24
|
+
|
|
25
|
+
attrString = attrString.trim().replace(/;$/, '');
|
|
26
|
+
|
|
27
|
+
function parseValue(val) {
|
|
28
|
+
val = val.trim();
|
|
29
|
+
if (val.startsWith('{') && val.endsWith('}')) {
|
|
30
|
+
return parseObjectString(val);
|
|
31
|
+
} else if (val.startsWith('[') && val.endsWith(']')) {
|
|
32
|
+
return parseArrayString(val);
|
|
33
|
+
} else if (val === 'true') {
|
|
34
|
+
return true;
|
|
35
|
+
} else if (val === 'false') {
|
|
36
|
+
return false;
|
|
37
|
+
} else {
|
|
38
|
+
const numVal = parseFloat(val);
|
|
39
|
+
const strippedVal = val.replace(/^['"]|['"]$/g, '');
|
|
40
|
+
if (!isNaN(numVal) && !val.includes('%') && !val.includes(' ') && !val.includes('px') && strippedVal == numVal) {
|
|
41
|
+
return numVal;
|
|
42
|
+
} else {
|
|
43
|
+
return strippedVal;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseArrayString(str) {
|
|
49
|
+
const inner = str.slice(1, -1);
|
|
50
|
+
let depth = 0;
|
|
51
|
+
let inString = false;
|
|
52
|
+
let stringChar = null;
|
|
53
|
+
let currentItem = '';
|
|
54
|
+
const items = [];
|
|
55
|
+
for (let j = 0; j < inner.length; j++) {
|
|
56
|
+
const c = inner[j];
|
|
57
|
+
if (c === "'" || c === '"') {
|
|
58
|
+
if (!inString) {
|
|
59
|
+
inString = true;
|
|
60
|
+
stringChar = c;
|
|
61
|
+
} else if (stringChar === c) {
|
|
62
|
+
inString = false;
|
|
63
|
+
stringChar = null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (!inString && (c === '{' || c === '[')) depth++;
|
|
67
|
+
if (!inString && (c === '}' || c === ']')) depth--;
|
|
68
|
+
|
|
69
|
+
if (!inString && c === ',' && depth === 0) {
|
|
70
|
+
if (currentItem.trim()) items.push(parseValue(currentItem));
|
|
71
|
+
currentItem = '';
|
|
72
|
+
} else {
|
|
73
|
+
currentItem += c;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (currentItem.trim()) items.push(parseValue(currentItem));
|
|
77
|
+
return items;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function parseObjectString(str) {
|
|
81
|
+
if (!str.startsWith('{') || !str.endsWith('}')) {
|
|
82
|
+
str = `{${str}}`;
|
|
83
|
+
}
|
|
84
|
+
const obj = {};
|
|
85
|
+
const inner = str.slice(1, -1);
|
|
86
|
+
let depth = 0;
|
|
87
|
+
let inString = false;
|
|
88
|
+
let stringChar = null;
|
|
89
|
+
let currentPair = '';
|
|
90
|
+
const pairs = [];
|
|
91
|
+
for (let j = 0; j < inner.length; j++) {
|
|
92
|
+
const c = inner[j];
|
|
93
|
+
|
|
94
|
+
if (c === "'" || c === '"') {
|
|
95
|
+
if (!inString) {
|
|
96
|
+
inString = true;
|
|
97
|
+
stringChar = c;
|
|
98
|
+
} else if (stringChar === c) {
|
|
99
|
+
inString = false;
|
|
100
|
+
stringChar = null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!inString && (c === '{' || c === '[')) depth++;
|
|
105
|
+
if (!inString && (c === '}' || c === ']')) depth--;
|
|
106
|
+
|
|
107
|
+
if (!inString && c === ',' && depth === 0) {
|
|
108
|
+
pairs.push(currentPair);
|
|
109
|
+
currentPair = '';
|
|
110
|
+
} else {
|
|
111
|
+
currentPair += c;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (currentPair) pairs.push(currentPair);
|
|
115
|
+
|
|
116
|
+
pairs.forEach(pair => {
|
|
117
|
+
const subColonIdx = pair.indexOf(':');
|
|
118
|
+
if (subColonIdx === -1) return;
|
|
119
|
+
const pKey = pair.slice(0, subColonIdx).trim();
|
|
120
|
+
const pVal = pair.slice(subColonIdx + 1).trim();
|
|
121
|
+
obj[pKey] = parseValue(pVal);
|
|
122
|
+
});
|
|
123
|
+
return obj;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (attrString.startsWith('{') && attrString.endsWith('}')) {
|
|
127
|
+
return parseObjectString(attrString);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (attrString.startsWith('[') && attrString.endsWith(']')) {
|
|
131
|
+
return parseArrayString(attrString);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const config = {};
|
|
135
|
+
const parts = [];
|
|
136
|
+
let current = '';
|
|
137
|
+
let depth = 0;
|
|
138
|
+
let inStringOuter = false;
|
|
139
|
+
let stringCharOuter = null;
|
|
140
|
+
|
|
141
|
+
for (let i = 0; i < attrString.length; i++) {
|
|
142
|
+
const char = attrString[i];
|
|
143
|
+
|
|
144
|
+
if (char === "'" || char === '"') {
|
|
145
|
+
if (!inStringOuter) {
|
|
146
|
+
inStringOuter = true;
|
|
147
|
+
stringCharOuter = char;
|
|
148
|
+
} else if (stringCharOuter === char) {
|
|
149
|
+
inStringOuter = false;
|
|
150
|
+
stringCharOuter = null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!inStringOuter && (char === '{' || char === '[')) depth++;
|
|
155
|
+
if (!inStringOuter && (char === '}' || char === ']')) depth--;
|
|
156
|
+
|
|
157
|
+
if (!inStringOuter && char === ';' && depth === 0) {
|
|
158
|
+
parts.push(current);
|
|
159
|
+
current = '';
|
|
160
|
+
} else {
|
|
161
|
+
current += char;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (current) parts.push(current);
|
|
165
|
+
|
|
166
|
+
parts.forEach(part => {
|
|
167
|
+
const colonIdx = part.indexOf(':');
|
|
168
|
+
if (colonIdx === -1) return;
|
|
169
|
+
|
|
170
|
+
const key = part.slice(0, colonIdx).trim();
|
|
171
|
+
const value = part.slice(colonIdx + 1).trim();
|
|
172
|
+
|
|
173
|
+
config[key] = parseValue(value);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
return config;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ------------------------------------------------------------------------------
|
|
180
|
+
// HELPER — Create Tweens (handles from, to, and fromTo correctly)
|
|
181
|
+
// ------------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
function buildTweenArgs(config, childElement) {
|
|
184
|
+
const baseVars = {
|
|
185
|
+
duration: config.duration ?? 0.75,
|
|
186
|
+
ease: config.ease ?? 'power2.out',
|
|
187
|
+
};
|
|
188
|
+
if (config.stagger !== undefined) {
|
|
189
|
+
baseVars.stagger = config.stagger;
|
|
190
|
+
}
|
|
191
|
+
if (config.delay) baseVars.delay = config.delay;
|
|
192
|
+
if (config.scroll) baseVars.scrollTrigger = config.scrollTrigger;
|
|
193
|
+
|
|
194
|
+
// Resolve horizontal magic
|
|
195
|
+
const resolveX = (varsObj) => {
|
|
196
|
+
if (varsObj.x === 'horizontal' || varsObj.x === 'horizontal-reverse') {
|
|
197
|
+
const dir = varsObj.x === 'horizontal' ? -1 : 1;
|
|
198
|
+
varsObj.x = () => (childElement.scrollWidth - window.innerWidth) * dir;
|
|
199
|
+
varsObj.ease = config.ease ?? 'none';
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
if (config.from && config.to) {
|
|
204
|
+
const fromVars = { ...config.from };
|
|
205
|
+
const toVars = { ...baseVars, ...config.to };
|
|
206
|
+
resolveX(fromVars);
|
|
207
|
+
resolveX(toVars);
|
|
208
|
+
return { method: 'fromTo', args: [fromVars, toVars] };
|
|
209
|
+
} else if (config.from) {
|
|
210
|
+
const vars = { ...baseVars, ...config.from };
|
|
211
|
+
resolveX(vars);
|
|
212
|
+
return { method: 'from', args: [vars] };
|
|
213
|
+
} else {
|
|
214
|
+
const vars = { ...baseVars, ...config.to };
|
|
215
|
+
resolveX(vars);
|
|
216
|
+
return { method: 'to', args: [vars] };
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ------------------------------------------------------------------------------
|
|
221
|
+
// HELPER — Setup Triggers & Targets
|
|
222
|
+
// ------------------------------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
function resolveTarget(el, config, isSplit) {
|
|
225
|
+
if (config.selector) {
|
|
226
|
+
let sel = config.selector;
|
|
227
|
+
if (sel.startsWith('>')) {
|
|
228
|
+
sel = `:scope ${sel}`;
|
|
229
|
+
}
|
|
230
|
+
const found = el.querySelectorAll(sel);
|
|
231
|
+
return found.length > 0 ? Array.from(found) : el;
|
|
232
|
+
}
|
|
233
|
+
if (config.closest) {
|
|
234
|
+
const parent = el.closest(config.closest);
|
|
235
|
+
return parent ? parent : el;
|
|
236
|
+
}
|
|
237
|
+
// Text and Morph plugins should always target the parent element itself
|
|
238
|
+
if (el.hasAttribute('data-gsap-text') || el.hasAttribute('data-gsap-morph')) {
|
|
239
|
+
return el;
|
|
240
|
+
}
|
|
241
|
+
// DrawSVG applies to child vectors when placed on the SVG container
|
|
242
|
+
if (el.hasAttribute('data-gsap-draw') && el.tagName.toLowerCase() === 'svg') {
|
|
243
|
+
const found = el.querySelectorAll('path, line, polyline, polygon, rect, circle, ellipse');
|
|
244
|
+
return found.length > 0 ? Array.from(found) : el;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Auto-target children if stagger is implicitly declared anywhere in the payload
|
|
248
|
+
const hasStagger = config.stagger !== undefined || config.to?.stagger !== undefined || config.from?.stagger !== undefined;
|
|
249
|
+
if (hasStagger && !isSplit) {
|
|
250
|
+
return Array.from(el.children);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return el;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function processSplitText(el, config) {
|
|
257
|
+
// In trigger arrays, config.type will be 'split'. In that case, use splitType instead to determine the actual split method.
|
|
258
|
+
let type = config.splitType || config.type;
|
|
259
|
+
if (type === 'split' || !type) type = 'words,lines';
|
|
260
|
+
|
|
261
|
+
const split = new SplitText(el, {
|
|
262
|
+
type,
|
|
263
|
+
linesClass: 'sq-split-line',
|
|
264
|
+
wordsClass: 'sq-split-word',
|
|
265
|
+
charsClass: 'sq-split-char',
|
|
266
|
+
});
|
|
267
|
+
if (type.includes('chars')) return split.chars;
|
|
268
|
+
if (type.includes('words')) return split.words;
|
|
269
|
+
return split.lines;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ------------------------------------------------------------------------------
|
|
273
|
+
// INIT BOOT SEQUENCE
|
|
274
|
+
// ------------------------------------------------------------------------------
|
|
275
|
+
|
|
276
|
+
export function initGsapFeatures() {
|
|
277
|
+
initScrollSmoother();
|
|
278
|
+
initTimelines(); // Handles [data-gsap-timeline]
|
|
279
|
+
initTimelineControls(); // Handles external [data-gsap-play/pause/reverse/toggle] buttons
|
|
280
|
+
initScrollScenes(); // Handles [data-gsap-scroll]
|
|
281
|
+
initStandaloneElements(); // Handles isolated [data-gsap], [data-gsap-split], [data-gsap-draw], [data-gsap-text], [data-gsap-morph]
|
|
282
|
+
initInteractionTriggers(); // Handles hover, click
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// 1. SCROLL SMOOTHER
|
|
286
|
+
function initScrollSmoother() {
|
|
287
|
+
const smoothEl = document.querySelector('[data-gsap-smooth]');
|
|
288
|
+
if (!smoothEl) return;
|
|
289
|
+
|
|
290
|
+
const config = parseGsapAttr(smoothEl.getAttribute('data-gsap-smooth') || "");
|
|
291
|
+
|
|
292
|
+
let wrapper = config.wrapper || '#smooth-wrapper';
|
|
293
|
+
let content = config.content || '#smooth-content';
|
|
294
|
+
|
|
295
|
+
if (smoothEl.tagName === 'BODY' && !document.querySelector(wrapper)) {
|
|
296
|
+
const wrapperEl = document.createElement('div');
|
|
297
|
+
wrapperEl.id = 'smooth-wrapper';
|
|
298
|
+
const contentEl = document.createElement('div');
|
|
299
|
+
contentEl.id = 'smooth-content';
|
|
300
|
+
|
|
301
|
+
// Only wrap main content and footer to protect fixed headers
|
|
302
|
+
const main = document.querySelector('#main-content');
|
|
303
|
+
const footer = document.querySelector('footer');
|
|
304
|
+
|
|
305
|
+
if (main) contentEl.appendChild(main);
|
|
306
|
+
if (footer) contentEl.appendChild(footer);
|
|
307
|
+
|
|
308
|
+
wrapperEl.appendChild(contentEl);
|
|
309
|
+
document.body.appendChild(wrapperEl);
|
|
310
|
+
|
|
311
|
+
wrapperEl.style.overflow = 'hidden';
|
|
312
|
+
wrapperEl.style.position = 'fixed';
|
|
313
|
+
wrapperEl.style.zIndex = '1';
|
|
314
|
+
wrapperEl.style.top = '0';
|
|
315
|
+
wrapperEl.style.left = '0';
|
|
316
|
+
wrapperEl.style.right = '0';
|
|
317
|
+
wrapperEl.style.bottom = '0';
|
|
318
|
+
contentEl.style.width = '100%';
|
|
319
|
+
|
|
320
|
+
wrapper = '#smooth-wrapper';
|
|
321
|
+
content = '#smooth-content';
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
ScrollSmoother.create({
|
|
325
|
+
wrapper: wrapper,
|
|
326
|
+
content: content,
|
|
327
|
+
smooth: config.smooth ?? 1.5,
|
|
328
|
+
effects: config.effects ?? true,
|
|
329
|
+
smoothTouch: config.smoothTouch ?? 0.1,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// 2. SCROLL SCENES & MASTER TIMELINES
|
|
334
|
+
function initScrollScenes() {
|
|
335
|
+
const scenes = document.querySelectorAll('[data-gsap-scroll]');
|
|
336
|
+
|
|
337
|
+
scenes.forEach(sceneEl => {
|
|
338
|
+
const sceneConfig = parseGsapAttr(sceneEl.getAttribute('data-gsap-scroll'));
|
|
339
|
+
const isPinned = sceneConfig.pin === true;
|
|
340
|
+
|
|
341
|
+
const children = Array.from(sceneEl.querySelectorAll('[data-gsap], [data-gsap-split], [data-gsap-draw], [data-gsap-text], [data-gsap-morph]'))
|
|
342
|
+
.filter(child => child.closest('[data-gsap-scroll]') === sceneEl);
|
|
343
|
+
|
|
344
|
+
let horizontalTarget = null;
|
|
345
|
+
children.forEach(child => {
|
|
346
|
+
const attr = child.getAttribute('data-gsap') || child.getAttribute('data-gsap-split') || child.getAttribute('data-gsap-draw') || child.getAttribute('data-gsap-text') || child.getAttribute('data-gsap-morph') || '';
|
|
347
|
+
const cfg = parseGsapAttr(attr);
|
|
348
|
+
if (cfg.to?.x === 'horizontal' || cfg.from?.x === 'horizontal' || cfg.to?.x === 'horizontal-reverse' || cfg.from?.x === 'horizontal-reverse') {
|
|
349
|
+
horizontalTarget = child;
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
const startPos = isPinned ? (sceneConfig.start ?? 'top top') : (sceneConfig.start ?? 'top center');
|
|
354
|
+
|
|
355
|
+
let calculatedEnd = sceneConfig.end ?? 'bottom center';
|
|
356
|
+
if (horizontalTarget) {
|
|
357
|
+
// Match the codepen timing: pin for the total width of the element.
|
|
358
|
+
calculatedEnd = sceneConfig.end ? sceneConfig.end : () => `+=${horizontalTarget.scrollWidth}`;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const tl = gsap.timeline({
|
|
362
|
+
scrollTrigger: {
|
|
363
|
+
trigger: sceneConfig.trigger ? document.querySelector(sceneConfig.trigger) : sceneEl,
|
|
364
|
+
start: startPos,
|
|
365
|
+
end: calculatedEnd,
|
|
366
|
+
pin: isPinned,
|
|
367
|
+
scrub: sceneConfig.scrub ?? false,
|
|
368
|
+
pinSpacing: sceneConfig.pinSpacing ?? true,
|
|
369
|
+
invalidateOnRefresh: true,
|
|
370
|
+
markers: sceneConfig.markers ?? false,
|
|
371
|
+
toggleActions: sceneConfig.toggleActions ?? 'play none none none'
|
|
372
|
+
},
|
|
373
|
+
defaults: {
|
|
374
|
+
ease: sceneConfig.ease ?? 'none'
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
children.forEach(child => {
|
|
379
|
+
let attr = '';
|
|
380
|
+
let isSplit = false;
|
|
381
|
+
|
|
382
|
+
if (child.hasAttribute('data-gsap-split')) { attr = 'data-gsap-split'; isSplit = true; }
|
|
383
|
+
else if (child.hasAttribute('data-gsap-draw')) { attr = 'data-gsap-draw'; }
|
|
384
|
+
else if (child.hasAttribute('data-gsap-text')) { attr = 'data-gsap-text'; }
|
|
385
|
+
else if (child.hasAttribute('data-gsap-morph')) { attr = 'data-gsap-morph'; }
|
|
386
|
+
else attr = 'data-gsap';
|
|
387
|
+
|
|
388
|
+
const config = parseGsapAttr(child.getAttribute(attr));
|
|
389
|
+
let target = isSplit ? processSplitText(child, config) : resolveTarget(child, config, false);
|
|
390
|
+
|
|
391
|
+
if (Array.isArray(target) && target[0] instanceof HTMLElement) {
|
|
392
|
+
target.forEach(c => c.style.transition = 'none');
|
|
393
|
+
} else if (target instanceof HTMLElement) {
|
|
394
|
+
target.style.transition = 'none';
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const { method, args } = buildTweenArgs(config, child);
|
|
398
|
+
|
|
399
|
+
const position = String(config.position ?? (config.delay ? `<${config.delay}` : '<'));
|
|
400
|
+
|
|
401
|
+
// Remove delay from toVars/fromVars if scrub avoids gap
|
|
402
|
+
if (sceneConfig.scrub && position.startsWith('<')) {
|
|
403
|
+
const varsObj = args.length === 2 ? args[1] : args[0]; // delay is always in the last argument
|
|
404
|
+
delete varsObj.delay;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
tl[method](target, ...args, position);
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// 3. STANDALONE ELEMENTS & SIMPLE SCROLL TRIGGERS
|
|
413
|
+
function initStandaloneElements() {
|
|
414
|
+
const standalone = document.querySelectorAll('[data-gsap], [data-gsap-split], [data-gsap-draw], [data-gsap-text], [data-gsap-morph]');
|
|
415
|
+
|
|
416
|
+
standalone.forEach(child => {
|
|
417
|
+
if (child.closest('[data-gsap-scroll]') || child.closest('[data-gsap-timeline]')) return;
|
|
418
|
+
|
|
419
|
+
let attr = '';
|
|
420
|
+
let isSplit = false;
|
|
421
|
+
|
|
422
|
+
if (child.hasAttribute('data-gsap-split')) { attr = 'data-gsap-split'; isSplit = true; }
|
|
423
|
+
else if (child.hasAttribute('data-gsap-draw')) { attr = 'data-gsap-draw'; }
|
|
424
|
+
else if (child.hasAttribute('data-gsap-text')) { attr = 'data-gsap-text'; }
|
|
425
|
+
else if (child.hasAttribute('data-gsap-morph')) { attr = 'data-gsap-morph'; }
|
|
426
|
+
else attr = 'data-gsap';
|
|
427
|
+
|
|
428
|
+
const config = parseGsapAttr(child.getAttribute(attr));
|
|
429
|
+
|
|
430
|
+
if (config.scroll) {
|
|
431
|
+
config.scrollTrigger = {
|
|
432
|
+
trigger: config.trigger ? document.querySelector(config.trigger) : child,
|
|
433
|
+
start: config.start ?? 'top 85%',
|
|
434
|
+
toggleActions: config.toggleActions ?? 'play none none none',
|
|
435
|
+
markers: config.markers ?? false
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const target = isSplit ? processSplitText(child, config) : resolveTarget(child, config, false);
|
|
440
|
+
if (Array.isArray(target) && target[0] instanceof HTMLElement) {
|
|
441
|
+
target.forEach(c => c.style.transition = 'none');
|
|
442
|
+
} else if (target instanceof HTMLElement) {
|
|
443
|
+
target.style.transition = 'none';
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const { method, args } = buildTweenArgs(config, child);
|
|
447
|
+
if (attr === 'data-gsap-text') {
|
|
448
|
+
console.log("🔥 GSAP TEXT PLUGIN DEBUG:", {
|
|
449
|
+
element: target,
|
|
450
|
+
method: method,
|
|
451
|
+
arguments: args,
|
|
452
|
+
originalConfig: config
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
gsap[method](target, ...args);
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// 3.5. AUTOPLAY TIMELINES
|
|
460
|
+
function initTimelines() {
|
|
461
|
+
const timelines = document.querySelectorAll('[data-gsap-timeline]');
|
|
462
|
+
|
|
463
|
+
timelines.forEach(timelineEl => {
|
|
464
|
+
const tlConfig = parseGsapAttr(timelineEl.getAttribute('data-gsap-timeline'));
|
|
465
|
+
const triggerType = timelineEl.getAttribute('data-gsap-trigger');
|
|
466
|
+
|
|
467
|
+
if (triggerType === 'hover' || triggerType === 'click') {
|
|
468
|
+
tlConfig.paused = true;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (tlConfig.scroll) {
|
|
472
|
+
tlConfig.scrollTrigger = {
|
|
473
|
+
trigger: tlConfig.trigger ? document.querySelector(tlConfig.trigger) : timelineEl,
|
|
474
|
+
start: tlConfig.start ?? 'top 85%',
|
|
475
|
+
toggleActions: tlConfig.toggleActions ?? 'play none none none',
|
|
476
|
+
markers: tlConfig.markers ?? false
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Initialize timeline with data-gsap-timeline parameters (e.g. {delay: 1, repeat: -1, yoyo: true})
|
|
481
|
+
const tl = gsap.timeline(tlConfig);
|
|
482
|
+
timelineEl._sqTimeline = tl; // Bind to DOM node so external buttons can trigger it.
|
|
483
|
+
|
|
484
|
+
// Find all GSAP-enabled children belonging to this specific timeline
|
|
485
|
+
const children = Array.from(timelineEl.querySelectorAll('[data-gsap], [data-gsap-split], [data-gsap-draw], [data-gsap-text], [data-gsap-morph]'))
|
|
486
|
+
.filter(child => child.closest('[data-gsap-timeline]') === timelineEl);
|
|
487
|
+
|
|
488
|
+
children.forEach(child => {
|
|
489
|
+
let attr = '';
|
|
490
|
+
let isSplit = false;
|
|
491
|
+
|
|
492
|
+
if (child.hasAttribute('data-gsap-split')) { attr = 'data-gsap-split'; isSplit = true; }
|
|
493
|
+
else if (child.hasAttribute('data-gsap-draw')) { attr = 'data-gsap-draw'; }
|
|
494
|
+
else if (child.hasAttribute('data-gsap-text')) { attr = 'data-gsap-text'; }
|
|
495
|
+
else if (child.hasAttribute('data-gsap-morph')) { attr = 'data-gsap-morph'; }
|
|
496
|
+
else attr = 'data-gsap';
|
|
497
|
+
|
|
498
|
+
const config = parseGsapAttr(child.getAttribute(attr));
|
|
499
|
+
let target = isSplit ? processSplitText(child, config) : resolveTarget(child, config, false);
|
|
500
|
+
|
|
501
|
+
if (Array.isArray(target) && target[0] instanceof HTMLElement) {
|
|
502
|
+
target.forEach(c => c.style.transition = 'none');
|
|
503
|
+
} else if (target instanceof HTMLElement) {
|
|
504
|
+
target.style.transition = 'none';
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const { method, args } = buildTweenArgs(config, child);
|
|
508
|
+
|
|
509
|
+
// By default GSAP timelines append ('>'). We allow users to explicitly declare `<` via position,
|
|
510
|
+
// or we use JS defaults if nothing is provided so that elements cascade sequentially.
|
|
511
|
+
const position = config.position !== undefined ? String(config.position) : (config.delay ? `>${config.delay}` : '>');
|
|
512
|
+
|
|
513
|
+
tl[method](target, ...args, position);
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
if (triggerType === 'hover') {
|
|
517
|
+
timelineEl.addEventListener('mouseenter', () => tl.play());
|
|
518
|
+
timelineEl.addEventListener('mouseleave', () => tl.reverse());
|
|
519
|
+
} else if (triggerType === 'click') {
|
|
520
|
+
let played = false;
|
|
521
|
+
timelineEl.addEventListener('click', () => {
|
|
522
|
+
if (played && tl.reversed() === false) {
|
|
523
|
+
tl.reverse();
|
|
524
|
+
played = false;
|
|
525
|
+
} else {
|
|
526
|
+
tl.play();
|
|
527
|
+
played = true;
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// 3.6. TIMELINE CONTROLS (External Play/Pause/Reverse/Toggle)
|
|
535
|
+
function initTimelineControls() {
|
|
536
|
+
['play', 'pause', 'reverse', 'toggle'].forEach(action => {
|
|
537
|
+
const buttons = document.querySelectorAll(`[data-gsap-${action}]`);
|
|
538
|
+
|
|
539
|
+
buttons.forEach(btn => {
|
|
540
|
+
btn.addEventListener('click', (e) => {
|
|
541
|
+
if (btn.tagName === 'A') e.preventDefault();
|
|
542
|
+
|
|
543
|
+
const selector = btn.getAttribute(`data-gsap-${action}`);
|
|
544
|
+
if (!selector) return;
|
|
545
|
+
|
|
546
|
+
const targets = document.querySelectorAll(selector);
|
|
547
|
+
targets.forEach(t => {
|
|
548
|
+
const tl = t._sqTimeline;
|
|
549
|
+
if (tl) {
|
|
550
|
+
if (action === 'toggle') {
|
|
551
|
+
tl.reversed() ? tl.play() : tl.reverse();
|
|
552
|
+
} else {
|
|
553
|
+
tl[action]();
|
|
554
|
+
}
|
|
555
|
+
} else {
|
|
556
|
+
console.warn(`[GSAP initTimelineControls] Could not find initialized timeline on timeline wrapper: ${selector}`);
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// 4. INTERACTIVE TRIGGERS
|
|
565
|
+
function initInteractionTriggers() {
|
|
566
|
+
const triggers = document.querySelectorAll('[data-gsap-trigger]');
|
|
567
|
+
|
|
568
|
+
triggers.forEach(el => {
|
|
569
|
+
const triggerType = el.getAttribute('data-gsap-trigger');
|
|
570
|
+
const rawAttr = el.getAttribute('data-gsap-trigger-anim');
|
|
571
|
+
if (!rawAttr) return;
|
|
572
|
+
|
|
573
|
+
let parsedConfigs = parseGsapAttr(rawAttr);
|
|
574
|
+
// Force array structure for consistent iteration
|
|
575
|
+
if (!Array.isArray(parsedConfigs)) {
|
|
576
|
+
parsedConfigs = [parsedConfigs];
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const applyTimeline = (tl) => {
|
|
580
|
+
parsedConfigs.forEach((config) => {
|
|
581
|
+
let target;
|
|
582
|
+
if (config.type === 'split') {
|
|
583
|
+
const splitEls = resolveTarget(el, config, false);
|
|
584
|
+
if (Array.isArray(splitEls)) {
|
|
585
|
+
// Concatenate the split arrays from all matching elements
|
|
586
|
+
target = [];
|
|
587
|
+
splitEls.forEach(node => {
|
|
588
|
+
target = target.concat(processSplitText(node, config));
|
|
589
|
+
});
|
|
590
|
+
} else {
|
|
591
|
+
target = processSplitText(splitEls, config);
|
|
592
|
+
}
|
|
593
|
+
} else {
|
|
594
|
+
target = resolveTarget(el, config, false);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const { method, args } = buildTweenArgs(config, el);
|
|
598
|
+
const position = String(config.position ?? (config.delay ? `<${config.delay}` : '<'));
|
|
599
|
+
|
|
600
|
+
// Allow fromTo, from, or to
|
|
601
|
+
tl[method](target, ...args, position);
|
|
602
|
+
});
|
|
603
|
+
return tl;
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
if (triggerType === 'hover') {
|
|
607
|
+
const hoverTl = applyTimeline(gsap.timeline({ paused: true }));
|
|
608
|
+
|
|
609
|
+
el.addEventListener('mouseenter', () => hoverTl.play());
|
|
610
|
+
el.addEventListener('mouseleave', () => hoverTl.reverse());
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
if (triggerType === 'click') {
|
|
614
|
+
const clickTl = applyTimeline(gsap.timeline({ paused: true }));
|
|
615
|
+
|
|
616
|
+
let played = false;
|
|
617
|
+
el.addEventListener('click', () => {
|
|
618
|
+
if (played && clickTl.reversed() === false) {
|
|
619
|
+
clickTl.reverse();
|
|
620
|
+
played = false;
|
|
621
|
+
} else {
|
|
622
|
+
clickTl.play();
|
|
623
|
+
played = true;
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// 5. CUSTOM CODEPEN LOGIC FOR HORIZONTAL
|
|
631
|
+
function initCodepenHorizontal() {
|
|
632
|
+
if (document.getElementById("portfolio")) {
|
|
633
|
+
const horizontalSections = gsap.utils.toArray(".horiz-gallery-wrapper");
|
|
634
|
+
|
|
635
|
+
horizontalSections.forEach(function (sec, i) {
|
|
636
|
+
const pinWrap = sec.querySelector(".horiz-gallery-strip");
|
|
637
|
+
|
|
638
|
+
if (!pinWrap) return;
|
|
639
|
+
|
|
640
|
+
let pinWrapWidth;
|
|
641
|
+
let horizontalScrollLength;
|
|
642
|
+
|
|
643
|
+
function refresh() {
|
|
644
|
+
pinWrapWidth = pinWrap.scrollWidth;
|
|
645
|
+
horizontalScrollLength = pinWrapWidth - window.innerWidth;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
refresh();
|
|
649
|
+
|
|
650
|
+
// Pinning and horizontal scrolling
|
|
651
|
+
gsap.to(pinWrap, {
|
|
652
|
+
scrollTrigger: {
|
|
653
|
+
scrub: true,
|
|
654
|
+
trigger: sec,
|
|
655
|
+
pin: sec,
|
|
656
|
+
start: "center center",
|
|
657
|
+
end: () => `+=${pinWrapWidth}`,
|
|
658
|
+
invalidateOnRefresh: true
|
|
659
|
+
},
|
|
660
|
+
x: () => -horizontalScrollLength,
|
|
661
|
+
ease: "none"
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
ScrollTrigger.addEventListener("refreshInit", refresh);
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
670
|
+
initGsapFeatures();
|
|
671
|
+
initCodepenHorizontal();
|
|
672
|
+
});
|