@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,132 @@
|
|
|
1
|
+
// src/assets/js/gsap-modules/cursor-preview.js
|
|
2
|
+
import { gsap } from 'gsap';
|
|
3
|
+
|
|
4
|
+
export function init() {
|
|
5
|
+
const lists = document.querySelectorAll('[data-sq-preview]');
|
|
6
|
+
if (!lists.length) return;
|
|
7
|
+
|
|
8
|
+
// Create shared preview image element
|
|
9
|
+
const preview = document.createElement('div');
|
|
10
|
+
preview.className = 'sq-preview-image';
|
|
11
|
+
preview.innerHTML = '<img src="" alt="" />';
|
|
12
|
+
document.body.appendChild(preview);
|
|
13
|
+
|
|
14
|
+
injectStyles(`
|
|
15
|
+
.sq-preview-image {
|
|
16
|
+
position: fixed;
|
|
17
|
+
top: 0; left: 0;
|
|
18
|
+
width: 320px;
|
|
19
|
+
pointer-events: none;
|
|
20
|
+
z-index: 9998;
|
|
21
|
+
opacity: 0;
|
|
22
|
+
overflow: hidden;
|
|
23
|
+
border-radius: 12px;
|
|
24
|
+
transform: translate(-50%, -60%);
|
|
25
|
+
transform-origin: center center;
|
|
26
|
+
}
|
|
27
|
+
.sq-preview-image img {
|
|
28
|
+
width: 100%;
|
|
29
|
+
height: 100%;
|
|
30
|
+
object-fit: cover;
|
|
31
|
+
display: block;
|
|
32
|
+
will-change: transform;
|
|
33
|
+
}
|
|
34
|
+
`);
|
|
35
|
+
|
|
36
|
+
const img = preview.querySelector('img');
|
|
37
|
+
let currentHover = null;
|
|
38
|
+
|
|
39
|
+
lists.forEach(list => {
|
|
40
|
+
const attr = list.getAttribute('data-sq-preview') || '';
|
|
41
|
+
const config = parseModuleAttr(attr);
|
|
42
|
+
const ratio = config.ratio || '4:5';
|
|
43
|
+
const trans = config.transition || 'fade';
|
|
44
|
+
const rotate = config.rotate !== undefined ? parseFloat(config.rotate) : -5;
|
|
45
|
+
|
|
46
|
+
const items = list.querySelectorAll('[data-preview-src]');
|
|
47
|
+
|
|
48
|
+
items.forEach(item => {
|
|
49
|
+
item.addEventListener('mouseenter', () => {
|
|
50
|
+
img.src = item.dataset.previewSrc;
|
|
51
|
+
img.alt = item.dataset.previewAlt || '';
|
|
52
|
+
currentHover = item;
|
|
53
|
+
|
|
54
|
+
// Reset
|
|
55
|
+
gsap.killTweensOf(preview);
|
|
56
|
+
gsap.killTweensOf(img);
|
|
57
|
+
gsap.set(preview, { opacity: 1, scale: 1, rotation: rotate, clipPath: 'none', xPercent: 0, yPercent: 0 });
|
|
58
|
+
gsap.set(img, { scale: 1, xPercent: 0, yPercent: 0, clipPath: 'none' });
|
|
59
|
+
|
|
60
|
+
img.style.aspectRatio = ratio === 'auto' ? 'auto' : ratio.replace(':', '/');
|
|
61
|
+
img.style.height = ratio === 'auto' ? 'auto' : '100%';
|
|
62
|
+
|
|
63
|
+
const tl = gsap.timeline();
|
|
64
|
+
|
|
65
|
+
switch (trans) {
|
|
66
|
+
case 'slide-y':
|
|
67
|
+
tl.fromTo(preview, { yPercent: 20, opacity: 0 }, { yPercent: 0, opacity: 1, duration: 0.4, ease: 'power3.out' });
|
|
68
|
+
break;
|
|
69
|
+
case 'slide-x':
|
|
70
|
+
tl.fromTo(preview, { xPercent: 20, opacity: 0 }, { xPercent: 0, opacity: 1, duration: 0.4, ease: 'power3.out' });
|
|
71
|
+
break;
|
|
72
|
+
case 'reveal-y':
|
|
73
|
+
tl.fromTo(preview, { clipPath: 'inset(100% 0% 0% 0%)' }, { clipPath: 'inset(0% 0% 0% 0%)', duration: 1, ease: 'expo.inOut' })
|
|
74
|
+
.fromTo(img, { scale: 1.2 }, { scale: 1, duration: 1, ease: 'expo.inOut' }, 0);
|
|
75
|
+
break;
|
|
76
|
+
case 'reveal-x':
|
|
77
|
+
tl.fromTo(preview, { clipPath: 'inset(0% 100% 0% 0%)' }, { clipPath: 'inset(0% 0% 0% 0%)', duration: 1, ease: 'expo.inOut' })
|
|
78
|
+
.fromTo(img, { scale: 1.2 }, { scale: 1, duration: 1, ease: 'expo.inOut' }, 0);
|
|
79
|
+
break;
|
|
80
|
+
case 'scale-up':
|
|
81
|
+
tl.fromTo(preview, { scale: 0.8, opacity: 0 }, { scale: 1, opacity: 1, duration: 0.4, ease: 'back.out(1.5)' });
|
|
82
|
+
break;
|
|
83
|
+
case 'scale-down':
|
|
84
|
+
tl.fromTo(preview, { scale: 1.2, opacity: 0 }, { scale: 1, opacity: 1, duration: 0.4, ease: 'power3.out' });
|
|
85
|
+
break;
|
|
86
|
+
case 'none':
|
|
87
|
+
gsap.set(preview, { opacity: 1 });
|
|
88
|
+
break;
|
|
89
|
+
case 'fade':
|
|
90
|
+
default:
|
|
91
|
+
tl.fromTo(preview, { opacity: 0, scale: 0.95 }, { opacity: 1, scale: 1, duration: 0.4, ease: 'power3.out' });
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
item.addEventListener('mouseleave', () => {
|
|
97
|
+
if (currentHover === item) {
|
|
98
|
+
gsap.to(preview, { opacity: 0, scale: 0.9, duration: 0.3, ease: 'power2.in' });
|
|
99
|
+
currentHover = null;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
item.addEventListener('mousemove', (e) => {
|
|
104
|
+
gsap.to(preview, {
|
|
105
|
+
x: e.clientX,
|
|
106
|
+
y: e.clientY,
|
|
107
|
+
duration: 0.6,
|
|
108
|
+
ease: 'power3.out',
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function injectStyles(css) {
|
|
116
|
+
const style = document.createElement('style');
|
|
117
|
+
style.textContent = css;
|
|
118
|
+
document.head.appendChild(style);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function parseModuleAttr(str) {
|
|
122
|
+
const config = {};
|
|
123
|
+
if (!str) return config;
|
|
124
|
+
str.split(';').forEach(part => {
|
|
125
|
+
const idx = part.indexOf(':');
|
|
126
|
+
if (idx === -1) return;
|
|
127
|
+
const key = part.slice(0, idx).trim();
|
|
128
|
+
const val = part.slice(idx + 1).trim().replace(/['"]/g, '');
|
|
129
|
+
config[key] = isNaN(val) && val !== 'auto' && !val.includes(':') && !val.includes('-') ? val : val;
|
|
130
|
+
});
|
|
131
|
+
return config;
|
|
132
|
+
}
|
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
// src/assets/js/gsap-modules/cursor.js
|
|
2
|
+
import { gsap } from 'gsap';
|
|
3
|
+
|
|
4
|
+
export function init() {
|
|
5
|
+
const trigger = document.querySelector('[data-sq-cursor]');
|
|
6
|
+
if (!trigger) return;
|
|
7
|
+
|
|
8
|
+
const attr = trigger.getAttribute('data-sq-cursor') || '';
|
|
9
|
+
const config = parseModuleAttr(attr);
|
|
10
|
+
|
|
11
|
+
const size = config.size ?? 32;
|
|
12
|
+
let color = config.color ?? '#000000';
|
|
13
|
+
const blend = config.blend ?? 'normal';
|
|
14
|
+
const scaleOnHover = config['scale-on-hover'] ?? 1.4;
|
|
15
|
+
const backdropBlur = config['backdrop-blur'] ?? '';
|
|
16
|
+
const fillAlpha = config['fill-opacity'] ?? 0; // 0 means traditional outline
|
|
17
|
+
const outlineAlpha = config['outline-opacity'] ?? 0.25; // default 0.25 outline
|
|
18
|
+
const delay = config.delay ?? 0.5;
|
|
19
|
+
const stick = typeof config.stick === 'string' ? `${config.stick}, [data-sq-cursor-stick]` : '[data-sq-cursor-stick]';
|
|
20
|
+
let contrast = config.contrast ?? true;
|
|
21
|
+
const hideNative = config['hide-native'] ?? false;
|
|
22
|
+
|
|
23
|
+
// Auto-correct 'difference' blend mode. Difference requires white to invert colors. Black is invisible.
|
|
24
|
+
if (blend === 'difference') {
|
|
25
|
+
contrast = false; // Difference mode handles contrast mathematically on its own
|
|
26
|
+
if (color === '#000' || color === '#000000') color = '#ffffff';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Create cursor elements
|
|
30
|
+
const dot = createEl('sq-cursor__dot');
|
|
31
|
+
const circle = createEl('sq-cursor__circle');
|
|
32
|
+
const textSpan = createEl('sq-cursor__text');
|
|
33
|
+
const iconSpan = createEl('sq-cursor__icon');
|
|
34
|
+
|
|
35
|
+
dot.appendChild(textSpan);
|
|
36
|
+
dot.appendChild(iconSpan);
|
|
37
|
+
document.body.appendChild(dot);
|
|
38
|
+
document.body.appendChild(circle);
|
|
39
|
+
|
|
40
|
+
const defaultRgb = hexToRgb(color);
|
|
41
|
+
const darkRgb = '0, 0, 0';
|
|
42
|
+
const lightRgb = '255, 255, 255';
|
|
43
|
+
|
|
44
|
+
// Inject base styles
|
|
45
|
+
injectStyles(`
|
|
46
|
+
:root {
|
|
47
|
+
--sq-cursor-rgb: ${defaultRgb};
|
|
48
|
+
}
|
|
49
|
+
body.sq-cursor-is-dark {
|
|
50
|
+
--sq-cursor-rgb: ${lightRgb};
|
|
51
|
+
}
|
|
52
|
+
body.sq-cursor-is-light {
|
|
53
|
+
--sq-cursor-rgb: ${darkRgb};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.sq-cursor__dot,
|
|
57
|
+
.sq-cursor__circle {
|
|
58
|
+
position: fixed;
|
|
59
|
+
top: 0; left: 0;
|
|
60
|
+
pointer-events: none;
|
|
61
|
+
z-index: 9999;
|
|
62
|
+
border-radius: 50%;
|
|
63
|
+
mix-blend-mode: ${blend};
|
|
64
|
+
opacity: 0;
|
|
65
|
+
}
|
|
66
|
+
.sq-cursor__dot {
|
|
67
|
+
display: flex; align-items: center; justify-content: center;
|
|
68
|
+
width: 6px; height: 6px;
|
|
69
|
+
background: rgb(var(--sq-cursor-rgb));
|
|
70
|
+
margin: -3px 0 0 -3px;
|
|
71
|
+
transition: background-color 0.2s ease;
|
|
72
|
+
overflow: hidden;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.sq-cursor__text {
|
|
76
|
+
color: var(--sq-color-on-primary, #fff); /* Fallback hardcoded white */
|
|
77
|
+
font-size: 14px;
|
|
78
|
+
font-weight: 500;
|
|
79
|
+
white-space: pre-wrap;
|
|
80
|
+
text-align: center;
|
|
81
|
+
line-height: 1.1;
|
|
82
|
+
opacity: 0;
|
|
83
|
+
transition: opacity 0.2s ease;
|
|
84
|
+
padding: 0 12px;
|
|
85
|
+
width: 80px;
|
|
86
|
+
flex-shrink: 0;
|
|
87
|
+
position: absolute;
|
|
88
|
+
top: 50%; left: 50%;
|
|
89
|
+
transform: translate(-50%, -50%);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.sq-cursor__icon {
|
|
93
|
+
color: var(--sq-color-on-primary, #fff);
|
|
94
|
+
display: flex; align-items: center; justify-content: center;
|
|
95
|
+
font-size: 20px;
|
|
96
|
+
opacity: 0;
|
|
97
|
+
transition: opacity 0.2s ease;
|
|
98
|
+
position: absolute;
|
|
99
|
+
top: 50%; left: 50%;
|
|
100
|
+
transform: translate(-50%, -50%);
|
|
101
|
+
}
|
|
102
|
+
.sq-cursor__icon i { font-size: inherit; }
|
|
103
|
+
|
|
104
|
+
body.sq-cursor-has-content .sq-cursor__dot {
|
|
105
|
+
background: rgb(var(--sq-cursor-rgb));
|
|
106
|
+
}
|
|
107
|
+
body.sq-cursor-has-text .sq-cursor__text,
|
|
108
|
+
body.sq-cursor-has-icon .sq-cursor__icon {
|
|
109
|
+
opacity: 1; transition-delay: 0.1s;
|
|
110
|
+
}
|
|
111
|
+
.sq-cursor__circle {
|
|
112
|
+
width: ${size}px; height: ${size}px;
|
|
113
|
+
border: ${fillAlpha > 0 ? 'none' : `2px solid rgba(var(--sq-cursor-rgb), ${outlineAlpha})`};
|
|
114
|
+
background: ${fillAlpha > 0 ? `rgba(var(--sq-cursor-rgb), ${fillAlpha})` : 'transparent'};
|
|
115
|
+
margin: -${size / 2}px 0 0 -${size / 2}px;
|
|
116
|
+
transition: border-color 0.2s ease, background-color 0.2s ease;
|
|
117
|
+
${backdropBlur ? `backdrop-filter: blur(${backdropBlur}); -webkit-backdrop-filter: blur(${backdropBlur});` : ''}
|
|
118
|
+
}
|
|
119
|
+
.sq-cursor-zoom .sq-cursor__circle {
|
|
120
|
+
background: ${fillAlpha > 0 ? `rgba(var(--sq-cursor-rgb), ${fillAlpha})` : 'transparent'};
|
|
121
|
+
border-color: rgb(var(--sq-cursor-rgb));
|
|
122
|
+
}
|
|
123
|
+
.sq-cursor-zoom .sq-cursor__circle::before,
|
|
124
|
+
.sq-cursor-zoom .sq-cursor__circle::after {
|
|
125
|
+
content: ''; position: absolute; top: 50%; left: 50%;
|
|
126
|
+
background: rgb(var(--sq-cursor-rgb)); transform: translate(-50%, -50%);
|
|
127
|
+
opacity: 0; transition: opacity 0.2s ease, background-color 0.2s ease;
|
|
128
|
+
}
|
|
129
|
+
.sq-cursor-zoom .sq-cursor__circle::before { width: 12px; height: 2px; opacity: 1; }
|
|
130
|
+
.sq-cursor-zoom .sq-cursor__circle::after { width: 2px; height: 12px; opacity: 1; }
|
|
131
|
+
|
|
132
|
+
.sq-cursor-loading .sq-cursor__circle {
|
|
133
|
+
border: 2px solid rgba(var(--sq-cursor-rgb), ${outlineAlpha}) !important;
|
|
134
|
+
border-top-color: transparent !important;
|
|
135
|
+
border-right-color: transparent !important;
|
|
136
|
+
background: transparent !important;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
${hideNative ? `
|
|
140
|
+
body[data-sq-cursor],
|
|
141
|
+
body[data-sq-cursor] a,
|
|
142
|
+
body[data-sq-cursor] button { cursor: none !important; }
|
|
143
|
+
` : ''}
|
|
144
|
+
`);
|
|
145
|
+
|
|
146
|
+
// Track mouse position
|
|
147
|
+
let mouseX = 0, mouseY = 0;
|
|
148
|
+
let isHovering = false;
|
|
149
|
+
let isZooming = false;
|
|
150
|
+
let spinTween = null;
|
|
151
|
+
let initialized = false;
|
|
152
|
+
let lastContrastCheck = 0;
|
|
153
|
+
|
|
154
|
+
// Custom Color State tracking
|
|
155
|
+
let currentColorStr = null;
|
|
156
|
+
let customRgbState = null;
|
|
157
|
+
|
|
158
|
+
// Initial GSAP setup (x/y with opacity 0)
|
|
159
|
+
gsap.set([dot, circle], { xPercent: 0, yPercent: 0, transformOrigin: 'center center' });
|
|
160
|
+
|
|
161
|
+
document.addEventListener('mousemove', (e) => {
|
|
162
|
+
mouseX = e.clientX;
|
|
163
|
+
mouseY = e.clientY;
|
|
164
|
+
|
|
165
|
+
if (!initialized) {
|
|
166
|
+
// Reveal instantly on first movement at exact coordinates
|
|
167
|
+
gsap.set([dot, circle], { x: mouseX, y: mouseY, opacity: 1 });
|
|
168
|
+
initialized = true;
|
|
169
|
+
} else {
|
|
170
|
+
// Dot follows instantly
|
|
171
|
+
gsap.set(dot, { x: mouseX, y: mouseY });
|
|
172
|
+
|
|
173
|
+
let targetX = mouseX;
|
|
174
|
+
let targetY = mouseY;
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const stickEl = e.target.closest(stick);
|
|
178
|
+
if (stickEl) {
|
|
179
|
+
const rect = stickEl.getBoundingClientRect();
|
|
180
|
+
const centerX = rect.left + rect.width / 2;
|
|
181
|
+
const centerY = rect.top + rect.height / 2;
|
|
182
|
+
// 15% magnetic elasticity around the element's center
|
|
183
|
+
targetX = centerX + (mouseX - centerX) * 0.15;
|
|
184
|
+
targetY = centerY + (mouseY - centerY) * 0.15;
|
|
185
|
+
document.body.classList.add('sq-cursor-stick');
|
|
186
|
+
} else {
|
|
187
|
+
document.body.classList.remove('sq-cursor-stick');
|
|
188
|
+
}
|
|
189
|
+
} catch (err) { }
|
|
190
|
+
|
|
191
|
+
// Circle follows with lag
|
|
192
|
+
gsap.to(circle, {
|
|
193
|
+
x: targetX, y: targetY,
|
|
194
|
+
duration: delay,
|
|
195
|
+
ease: 'power3.out',
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (contrast) {
|
|
200
|
+
const now = Date.now();
|
|
201
|
+
if (now - lastContrastCheck > 100) { // Check every 100ms
|
|
202
|
+
lastContrastCheck = now;
|
|
203
|
+
dot.style.display = 'none'; // Temporarily hide to get accurate elementFromPoint
|
|
204
|
+
circle.style.display = 'none';
|
|
205
|
+
|
|
206
|
+
let el = document.elementFromPoint(mouseX, mouseY);
|
|
207
|
+
// Occasionally, fast movement catches the HTML root. We want whatever is structurally under the mouse.
|
|
208
|
+
if (!el || el === document.documentElement) {
|
|
209
|
+
el = document.body;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
dot.style.display = '';
|
|
213
|
+
circle.style.display = '';
|
|
214
|
+
|
|
215
|
+
// Check for custom color overrides (data-sq-cursor-color="hex")
|
|
216
|
+
const customColorEl = el.closest('[data-sq-cursor-color]');
|
|
217
|
+
if (customColorEl) {
|
|
218
|
+
const customHex = customColorEl.getAttribute('data-sq-cursor-color');
|
|
219
|
+
if (customHex && customHex !== currentColorStr) {
|
|
220
|
+
customRgbState = hexToRgb(customHex);
|
|
221
|
+
currentColorStr = customHex;
|
|
222
|
+
// Inject variable DIRECTLY onto the dot/circle to bypass CSS class specificity (body.sq-cursor-is-dark)
|
|
223
|
+
dot.style.setProperty('--sq-cursor-rgb', customRgbState);
|
|
224
|
+
circle.style.setProperty('--sq-cursor-rgb', customRgbState);
|
|
225
|
+
}
|
|
226
|
+
// Temporarily suspend contrast switching if a hardcoded color is overriden.
|
|
227
|
+
return;
|
|
228
|
+
} else if (customRgbState) {
|
|
229
|
+
// Remove the explicit inline override so it can fallback to body CSS scopes
|
|
230
|
+
dot.style.removeProperty('--sq-cursor-rgb');
|
|
231
|
+
circle.style.removeProperty('--sq-cursor-rgb');
|
|
232
|
+
customRgbState = null;
|
|
233
|
+
currentColorStr = null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const contrastType = getBgContrast(el);
|
|
237
|
+
if (contrastType === 'dark') {
|
|
238
|
+
document.body.classList.add('sq-cursor-is-dark');
|
|
239
|
+
document.body.classList.remove('sq-cursor-is-light');
|
|
240
|
+
} else if (contrastType === 'light') {
|
|
241
|
+
document.body.classList.add('sq-cursor-is-light');
|
|
242
|
+
document.body.classList.remove('sq-cursor-is-dark');
|
|
243
|
+
} else {
|
|
244
|
+
document.body.classList.remove('sq-cursor-is-light', 'sq-cursor-is-dark');
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Custom text/icon parsing trackers
|
|
251
|
+
let activeText = null;
|
|
252
|
+
let activeIcon = null;
|
|
253
|
+
|
|
254
|
+
// Scale and Content injection on interactives
|
|
255
|
+
const interactives = 'a, button, [data-sq-cursor-hover], input, label, [data-sq-cursor-text], [data-sq-cursor-icon]';
|
|
256
|
+
const zoomables = '.uk-lightbox, [data-uk-lightbox], .sq-zoom';
|
|
257
|
+
|
|
258
|
+
document.querySelectorAll(interactives).forEach(el => {
|
|
259
|
+
el.addEventListener('mouseenter', (e) => {
|
|
260
|
+
isHovering = true;
|
|
261
|
+
|
|
262
|
+
const customText = el.getAttribute('data-sq-cursor-text');
|
|
263
|
+
const customIcon = el.getAttribute('data-sq-cursor-icon');
|
|
264
|
+
|
|
265
|
+
if (customText) {
|
|
266
|
+
activeText = customText;
|
|
267
|
+
textSpan.textContent = customText;
|
|
268
|
+
document.body.classList.add('sq-cursor-has-content', 'sq-cursor-has-text');
|
|
269
|
+
|
|
270
|
+
// Expand the dot to fit a perfect wrap circle
|
|
271
|
+
gsap.to(dot, { width: 80, height: 80, margin: '-40px 0 0 -40px', borderRadius: '50%', duration: 0.3, ease: 'power2.out' });
|
|
272
|
+
gsap.to(circle, { opacity: 0, duration: 0.2 }); // Hide outer circle to reduce clutter
|
|
273
|
+
} else if (customIcon) {
|
|
274
|
+
activeIcon = customIcon;
|
|
275
|
+
iconSpan.innerHTML = `<i class="${customIcon}"></i>`;
|
|
276
|
+
document.body.classList.add('sq-cursor-has-content', 'sq-cursor-has-icon');
|
|
277
|
+
|
|
278
|
+
// Expand the dot to fit a uniform icon circle
|
|
279
|
+
gsap.to(dot, { width: 48, height: 48, margin: '-24px 0 0 -24px', borderRadius: '50%', duration: 0.3, ease: 'power2.out' });
|
|
280
|
+
gsap.to(circle, { opacity: 0, duration: 0.2 });
|
|
281
|
+
} else {
|
|
282
|
+
gsap.to(circle, { scale: scaleOnHover, duration: 0.3, ease: 'power2.out' });
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Check if it's a zoomable element
|
|
286
|
+
if (el.matches(zoomables) || el.closest(zoomables)) {
|
|
287
|
+
isZooming = true;
|
|
288
|
+
document.body.classList.add('sq-cursor-zoom');
|
|
289
|
+
gsap.to(dot, { opacity: 0, duration: 0.1 });
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
el.addEventListener('mouseleave', () => {
|
|
294
|
+
isHovering = false;
|
|
295
|
+
isZooming = false;
|
|
296
|
+
document.body.classList.remove('sq-cursor-zoom', 'sq-cursor-has-content', 'sq-cursor-has-text', 'sq-cursor-has-icon');
|
|
297
|
+
|
|
298
|
+
if (activeText || activeIcon) {
|
|
299
|
+
activeText = null;
|
|
300
|
+
activeIcon = null;
|
|
301
|
+
textSpan.textContent = '';
|
|
302
|
+
iconSpan.innerHTML = '';
|
|
303
|
+
// Restore standard dot size
|
|
304
|
+
gsap.to(dot, { width: 6, height: 6, margin: '-3px 0 0 -3px', borderRadius: '50%', duration: 0.3, ease: 'power2.out', clearProps: 'width,height,margin,borderRadius' });
|
|
305
|
+
gsap.to(circle, { opacity: 1, duration: 0.2 });
|
|
306
|
+
} else {
|
|
307
|
+
gsap.to(circle, { scale: 1, duration: 0.3, ease: 'power2.out' });
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
gsap.to(dot, { opacity: 1, duration: 0.1 });
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// Click physics (scale down slightly to feel the click)
|
|
315
|
+
document.addEventListener('mousedown', () => {
|
|
316
|
+
const targetScale = isHovering ? scaleOnHover * 0.8 : 0.8;
|
|
317
|
+
gsap.to(circle, { scale: targetScale, duration: 0.1, ease: 'power1.out' });
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
document.addEventListener('mouseup', () => {
|
|
321
|
+
const targetScale = isHovering ? scaleOnHover : 1;
|
|
322
|
+
gsap.to(circle, { scale: targetScale, duration: 0.3, ease: 'power2.out', overwrite: 'auto' });
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// Loading State (Event Delegation on Document Body)
|
|
326
|
+
document.body.addEventListener('click', (e) => {
|
|
327
|
+
const link = e.target.closest('a:not([target="_blank"]):not([href^="#"]):not([href^="mailto:"]):not([href^="tel:"])');
|
|
328
|
+
if (!link) return;
|
|
329
|
+
|
|
330
|
+
if (e.defaultPrevented) return;
|
|
331
|
+
if (link.matches(zoomables) || link.closest(zoomables)) return;
|
|
332
|
+
if (link.closest('.uk-slideshow-nav, .uk-slider-nav, .uk-dotnav, .uk-tab')) return; // ignore UIkit navigation
|
|
333
|
+
|
|
334
|
+
const href = link.getAttribute('href') || '';
|
|
335
|
+
if (href.match(/\.(jpg|jpeg|png|gif|svg|webp|mp4)$/i)) return;
|
|
336
|
+
|
|
337
|
+
document.body.classList.add('sq-cursor-loading');
|
|
338
|
+
document.body.classList.remove('sq-cursor-zoom');
|
|
339
|
+
gsap.to(dot, { opacity: 0, duration: 0.1 });
|
|
340
|
+
gsap.to(circle, { scale: 1.5, duration: 0.3, ease: 'power2.out' });
|
|
341
|
+
|
|
342
|
+
if (spinTween) spinTween.kill();
|
|
343
|
+
spinTween = gsap.to(circle, {
|
|
344
|
+
rotation: "+=360",
|
|
345
|
+
repeat: -1,
|
|
346
|
+
ease: "none",
|
|
347
|
+
duration: 0.6
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// Explicit Before Unload mapping to guarantee loader triggers on browser navigation
|
|
352
|
+
window.addEventListener('beforeunload', () => {
|
|
353
|
+
document.body.classList.add('sq-cursor-loading');
|
|
354
|
+
document.body.classList.remove('sq-cursor-zoom');
|
|
355
|
+
gsap.to(dot, { opacity: 0, duration: 0.1 });
|
|
356
|
+
gsap.to(circle, { scale: 1.5, duration: 0.3, ease: 'power2.out' });
|
|
357
|
+
if (spinTween) spinTween.kill();
|
|
358
|
+
spinTween = gsap.to(circle, { rotation: "+=360", repeat: -1, ease: "none", duration: 0.6 });
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// Reset loading state on back/forward navigation
|
|
362
|
+
window.addEventListener('pageshow', (e) => {
|
|
363
|
+
if (e.persisted) {
|
|
364
|
+
document.body.classList.remove('sq-cursor-loading');
|
|
365
|
+
if (spinTween) spinTween.kill();
|
|
366
|
+
gsap.set(circle, { rotation: 0, scale: 1 });
|
|
367
|
+
gsap.to(dot, { opacity: 1, duration: 0.2 });
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// Hide on window leave, show on enter
|
|
372
|
+
document.addEventListener('mouseleave', () => {
|
|
373
|
+
gsap.to([dot, circle], { opacity: 0, duration: 0.3 });
|
|
374
|
+
});
|
|
375
|
+
document.addEventListener('mouseenter', () => {
|
|
376
|
+
// Only show if we already initialized internal position
|
|
377
|
+
if (initialized) gsap.to([dot, circle], { opacity: 1, duration: 0.3 });
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function getBgContrast(el) {
|
|
382
|
+
if (!el || el === document) return 'none';
|
|
383
|
+
|
|
384
|
+
// Explicitly check for UIkit dark mode theme wrappers first
|
|
385
|
+
if (el.matches('.sq-theme-dark') || el.closest('.sq-theme-dark')) return 'dark';
|
|
386
|
+
if (el.matches('.sq-theme-light') || el.closest('.sq-theme-light')) return 'light';
|
|
387
|
+
|
|
388
|
+
// Recursively search for the closest non-transparent background
|
|
389
|
+
let currentEl = el;
|
|
390
|
+
while (currentEl && currentEl !== document.documentElement) {
|
|
391
|
+
const bg = window.getComputedStyle(currentEl).backgroundColor;
|
|
392
|
+
if (bg && bg !== 'rgba(0, 0, 0, 0)' && bg !== 'transparent') {
|
|
393
|
+
const rgb = bg.match(/[\d.]+/g);
|
|
394
|
+
if (rgb && rgb.length >= 3) {
|
|
395
|
+
// If it's a very light opacity background, it doesn't count as the true background
|
|
396
|
+
const a = rgb.length === 4 ? parseFloat(rgb[3]) : 1;
|
|
397
|
+
if (a > 0.5) {
|
|
398
|
+
const r = parseInt(rgb[0]), g = parseInt(rgb[1]), b = parseInt(rgb[2]);
|
|
399
|
+
// Calculate YIQ to get relative brightness
|
|
400
|
+
const yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
|
|
401
|
+
return yiq >= 128 ? 'light' : 'dark';
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Safely fallback for images, videos, and canvas (assume dark for cursor visibility)
|
|
407
|
+
if (['IMG', 'VIDEO', 'CANVAS'].includes(currentEl.tagName)) {
|
|
408
|
+
return 'dark';
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
currentEl = currentEl.parentElement;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Fallback: Check if the main HTML root itself is dark mode
|
|
415
|
+
if (document.documentElement.classList.contains('sq-theme-dark') || document.body.classList.contains('sq-theme-dark')) {
|
|
416
|
+
return 'dark';
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// If we reach the top and found no background, assume light mode default natively
|
|
420
|
+
return 'light';
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function hexToRgb(hex) {
|
|
424
|
+
if (!hex.startsWith('#')) return '0, 0, 0'; // Default to black if not a hex
|
|
425
|
+
hex = hex.replace('#', '');
|
|
426
|
+
if (hex.length === 3) hex = hex.split('').map(x => x + x).join(''); // Handle shorthand hex
|
|
427
|
+
const r = parseInt(hex.substring(0, 2), 16) || 0;
|
|
428
|
+
const g = parseInt(hex.substring(2, 4), 16) || 0;
|
|
429
|
+
const b = parseInt(hex.substring(4, 6), 16) || 0;
|
|
430
|
+
return `${r}, ${g}, ${b}`;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function createEl(className) {
|
|
434
|
+
const el = document.createElement('div');
|
|
435
|
+
el.className = className;
|
|
436
|
+
return el;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function injectStyles(css) {
|
|
440
|
+
const style = document.createElement('style');
|
|
441
|
+
style.textContent = css;
|
|
442
|
+
document.head.appendChild(style);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function parseModuleAttr(str) {
|
|
446
|
+
const config = {};
|
|
447
|
+
if (!str) return config;
|
|
448
|
+
str.split(';').forEach(part => {
|
|
449
|
+
const idx = part.indexOf(':');
|
|
450
|
+
if (idx === -1) return;
|
|
451
|
+
const key = part.slice(0, idx).trim();
|
|
452
|
+
const val = part.slice(idx + 1).trim().replace(/['"]/g, '');
|
|
453
|
+
config[key] = isNaN(val) ? (val === 'true' ? true : (val === 'false' ? false : val)) : parseFloat(val);
|
|
454
|
+
});
|
|
455
|
+
return config;
|
|
456
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// src/assets/js/gsap-modules/loop-panels.js
|
|
2
|
+
import { gsap } from 'gsap';
|
|
3
|
+
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
|
4
|
+
|
|
5
|
+
gsap.registerPlugin(ScrollTrigger);
|
|
6
|
+
|
|
7
|
+
export function init() {
|
|
8
|
+
const wrappers = document.querySelectorAll('[data-sq-loop-panels]');
|
|
9
|
+
if (!wrappers.length) return;
|
|
10
|
+
|
|
11
|
+
wrappers.forEach(wrapper => {
|
|
12
|
+
// Reveal if it was hidden
|
|
13
|
+
wrapper.removeAttribute('hidden');
|
|
14
|
+
|
|
15
|
+
let panels = gsap.utils.toArray(wrapper.children);
|
|
16
|
+
if (!panels.length) return;
|
|
17
|
+
|
|
18
|
+
// Scope physics to wrapper rather than window to avoid hijacking whole page
|
|
19
|
+
gsap.set(wrapper, {
|
|
20
|
+
height: '80vh',
|
|
21
|
+
minHeight: '600px',
|
|
22
|
+
overflowY: 'scroll',
|
|
23
|
+
overflowX: 'hidden',
|
|
24
|
+
position: 'relative',
|
|
25
|
+
overscrollBehavior: 'none' // Prevent bounding
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
gsap.set(panels, { width: '100%', height: '100%', position: 'relative' });
|
|
29
|
+
|
|
30
|
+
// Clone the first panel to the end for seamless looping
|
|
31
|
+
let copy = panels[0].cloneNode(true);
|
|
32
|
+
wrapper.appendChild(copy);
|
|
33
|
+
panels.push(copy);
|
|
34
|
+
|
|
35
|
+
panels.forEach((panel) => {
|
|
36
|
+
ScrollTrigger.create({
|
|
37
|
+
scroller: wrapper,
|
|
38
|
+
trigger: panel,
|
|
39
|
+
start: "top top",
|
|
40
|
+
pin: true,
|
|
41
|
+
pinSpacing: false
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
let maxScroll;
|
|
46
|
+
let pageScrollTrigger = ScrollTrigger.create({
|
|
47
|
+
scroller: wrapper,
|
|
48
|
+
snap(value) {
|
|
49
|
+
let snappedValue = gsap.utils.snap(1 / panels.length, value);
|
|
50
|
+
if (snappedValue <= 0) {
|
|
51
|
+
return 1.05 / maxScroll;
|
|
52
|
+
} else if (snappedValue >= 1) {
|
|
53
|
+
return maxScroll / (maxScroll + 1.05);
|
|
54
|
+
}
|
|
55
|
+
return snappedValue;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
function onResize() {
|
|
60
|
+
maxScroll = ScrollTrigger.maxScroll(wrapper) - 1;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
onResize();
|
|
64
|
+
window.addEventListener("resize", onResize);
|
|
65
|
+
|
|
66
|
+
wrapper.addEventListener("scroll", e => {
|
|
67
|
+
let scroll = pageScrollTrigger.scroll();
|
|
68
|
+
if (scroll > maxScroll) {
|
|
69
|
+
pageScrollTrigger.scroll(1);
|
|
70
|
+
e.preventDefault();
|
|
71
|
+
} else if (scroll < 1) {
|
|
72
|
+
pageScrollTrigger.scroll(maxScroll - 1);
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
}
|
|
75
|
+
}, { passive: false });
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|