create-nativecore 0.1.1 → 0.2.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 +6 -14
- package/bin/index.mjs +402 -431
- package/package.json +3 -2
- package/template/.env.example +28 -0
- package/template/.htmlhintrc +14 -0
- package/template/api/data/dashboard.json +11 -0
- package/template/api/data/users.json +18 -0
- package/template/api/mockApi.js +161 -0
- package/template/assets/icon.svg +13 -0
- package/template/assets/logo.svg +25 -0
- package/template/eslint.config.js +94 -0
- package/template/index.html +137 -0
- package/template/manifest.json +19 -0
- package/template/public/.well-known/security.txt +9 -0
- package/template/public/_headers +24 -0
- package/template/public/_redirects +14 -0
- package/template/public/assets/icon.svg +13 -0
- package/template/public/assets/logo.svg +25 -0
- package/template/public/manifest.json +19 -0
- package/template/public/robots.txt +13 -0
- package/template/public/sitemap.xml +27 -0
- package/template/scripts/build-for-bots.mjs +121 -0
- package/template/scripts/convert-to-ts.mjs +106 -0
- package/template/scripts/fix-encoding.mjs +38 -0
- package/template/scripts/fix-svg-paths.mjs +32 -0
- package/template/scripts/generate-cf-router.mjs +52 -0
- package/template/scripts/inject-dev-tools.mjs +41 -0
- package/template/scripts/inject-version.mjs +65 -0
- package/template/scripts/make-component.mjs +445 -0
- package/template/scripts/make-component.mjs.backup +432 -0
- package/template/scripts/make-controller.mjs +119 -0
- package/template/scripts/make-core-component.mjs +303 -0
- package/template/scripts/make-view.mjs +346 -0
- package/template/scripts/minify.mjs +71 -0
- package/template/scripts/prepare-static-assets.mjs +141 -0
- package/template/scripts/prompt-bot-build.mjs +223 -0
- package/template/scripts/remove-component.mjs +170 -0
- package/template/scripts/remove-core-component.mjs +156 -0
- package/template/scripts/remove-dev.mjs +13 -0
- package/template/scripts/remove-view.mjs +200 -0
- package/template/scripts/strip-dev-blocks.mjs +30 -0
- package/template/scripts/watch-compile.mjs +69 -0
- package/template/server.js +1066 -0
- package/template/src/app.ts +115 -0
- package/template/src/components/appRegistry.ts +8 -0
- package/template/src/components/core/app-footer.ts +27 -0
- package/template/src/components/core/app-header.ts +175 -0
- package/template/src/components/core/app-sidebar.ts +238 -0
- package/template/src/components/core/loading-spinner.ts +25 -0
- package/template/src/components/core/nc-a.ts +313 -0
- package/template/src/components/core/nc-accordion.ts +186 -0
- package/template/src/components/core/nc-alert.ts +153 -0
- package/template/src/components/core/nc-animation.ts +1150 -0
- package/template/src/components/core/nc-autocomplete.ts +271 -0
- package/template/src/components/core/nc-avatar-group.ts +113 -0
- package/template/src/components/core/nc-avatar.ts +148 -0
- package/template/src/components/core/nc-badge.ts +86 -0
- package/template/src/components/core/nc-bottom-nav.ts +214 -0
- package/template/src/components/core/nc-breadcrumb.ts +96 -0
- package/template/src/components/core/nc-button.ts +307 -0
- package/template/src/components/core/nc-card.ts +160 -0
- package/template/src/components/core/nc-checkbox.ts +282 -0
- package/template/src/components/core/nc-chip.ts +115 -0
- package/template/src/components/core/nc-code.ts +314 -0
- package/template/src/components/core/nc-collapsible.ts +154 -0
- package/template/src/components/core/nc-color-picker.ts +268 -0
- package/template/src/components/core/nc-copy-button.ts +119 -0
- package/template/src/components/core/nc-date-picker.ts +443 -0
- package/template/src/components/core/nc-div.ts +280 -0
- package/template/src/components/core/nc-divider.ts +81 -0
- package/template/src/components/core/nc-drawer.ts +230 -0
- package/template/src/components/core/nc-dropdown.ts +178 -0
- package/template/src/components/core/nc-empty-state.ts +134 -0
- package/template/src/components/core/nc-file-upload.ts +354 -0
- package/template/src/components/core/nc-form.ts +312 -0
- package/template/src/components/core/nc-image.ts +184 -0
- package/template/src/components/core/nc-input.ts +383 -0
- package/template/src/components/core/nc-kbd.ts +48 -0
- package/template/src/components/core/nc-menu-item.ts +193 -0
- package/template/src/components/core/nc-menu.ts +376 -0
- package/template/src/components/core/nc-modal.ts +238 -0
- package/template/src/components/core/nc-nav-item.ts +151 -0
- package/template/src/components/core/nc-number-input.ts +350 -0
- package/template/src/components/core/nc-otp-input.ts +235 -0
- package/template/src/components/core/nc-pagination.ts +178 -0
- package/template/src/components/core/nc-popover.ts +260 -0
- package/template/src/components/core/nc-progress-circular.ts +119 -0
- package/template/src/components/core/nc-progress.ts +134 -0
- package/template/src/components/core/nc-radio.ts +235 -0
- package/template/src/components/core/nc-rating.ts +266 -0
- package/template/src/components/core/nc-rich-text.ts +283 -0
- package/template/src/components/core/nc-scroll-top.ts +116 -0
- package/template/src/components/core/nc-select.ts +452 -0
- package/template/src/components/core/nc-skeleton.ts +107 -0
- package/template/src/components/core/nc-slider.ts +285 -0
- package/template/src/components/core/nc-snackbar.ts +230 -0
- package/template/src/components/core/nc-splash.ts +343 -0
- package/template/src/components/core/nc-stepper.ts +247 -0
- package/template/src/components/core/nc-switch.ts +281 -0
- package/template/src/components/core/nc-tab-item.ts +138 -0
- package/template/src/components/core/nc-table.ts +279 -0
- package/template/src/components/core/nc-tabs.ts +554 -0
- package/template/src/components/core/nc-tag-input.ts +279 -0
- package/template/src/components/core/nc-textarea.ts +216 -0
- package/template/src/components/core/nc-time-picker.ts +438 -0
- package/template/src/components/core/nc-timeline.ts +186 -0
- package/template/src/components/core/nc-tooltip.ts +143 -0
- package/template/src/components/frameworkRegistry.ts +68 -0
- package/template/src/components/preloadRegistry.ts +28 -0
- package/template/src/components/registry.ts +8 -0
- package/template/src/components/ui/dashboard-signal-lab.ts +284 -0
- package/template/src/constants/apiEndpoints.ts +27 -0
- package/template/src/constants/errorMessages.ts +23 -0
- package/template/src/constants/index.ts +8 -0
- package/template/src/constants/routePaths.ts +15 -0
- package/template/src/constants/storageKeys.ts +18 -0
- package/template/src/controllers/dashboard.controller.ts +200 -0
- package/template/src/controllers/home.controller.ts +21 -0
- package/template/src/controllers/index.ts +11 -0
- package/template/src/controllers/login.controller.ts +131 -0
- package/template/src/core/component.ts +354 -0
- package/template/src/core/errorHandler.ts +85 -0
- package/template/src/core/gpu-animation.ts +604 -0
- package/template/src/core/http.ts +173 -0
- package/template/src/core/lazyComponents.ts +90 -0
- package/template/src/core/router.ts +642 -0
- package/template/src/core/signals.ts +146 -0
- package/template/src/core/state.ts +248 -0
- package/template/src/dev/component-editor.ts +1363 -0
- package/template/src/dev/component-overlay.ts +278 -0
- package/template/src/dev/context-menu.ts +223 -0
- package/template/src/dev/denc-tools.ts +250 -0
- package/template/src/dev/hmr.ts +189 -0
- package/template/src/dev/nfbs.code-workspace +27 -0
- package/template/src/dev/outline-panel.ts +1247 -0
- package/template/src/middleware/auth.middleware.ts +23 -0
- package/template/src/routes/routes.ts +38 -0
- package/template/src/services/api.service.ts +394 -0
- package/template/src/services/auth.service.ts +176 -0
- package/template/src/services/index.ts +8 -0
- package/template/src/services/logger.service.ts +74 -0
- package/template/src/services/storage.service.ts +88 -0
- package/template/src/stores/appStore.ts +57 -0
- package/template/src/stores/uiStore.ts +36 -0
- package/template/src/styles/core-variables.css +219 -0
- package/template/src/styles/core.css +710 -0
- package/template/src/styles/main.css +3164 -0
- package/template/src/styles/variables.css +152 -0
- package/template/src/types/global.d.ts +47 -0
- package/template/src/utils/cacheBuster.ts +20 -0
- package/template/src/utils/dom.ts +149 -0
- package/template/src/utils/events.ts +203 -0
- package/template/src/utils/form.ts +176 -0
- package/template/src/utils/formatters.ts +169 -0
- package/template/src/utils/helpers.ts +195 -0
- package/template/src/utils/markdown.ts +307 -0
- package/template/src/utils/sidebar.ts +96 -0
- package/template/src/utils/smoothScroll.ts +85 -0
- package/template/src/utils/templates.ts +23 -0
- package/template/src/utils/validation.ts +73 -0
- package/template/src/views/protected/dashboard.html +293 -0
- package/template/src/views/public/home.html +150 -0
- package/template/src/views/public/login.html +102 -0
- package/template/tests/unit/component.test.ts +87 -0
- package/template/tests/unit/computed.test.ts +79 -0
- package/template/tests/unit/form.test.ts +68 -0
- package/template/tests/unit/formatters.test.ts +49 -0
- package/template/tests/unit/lazy-components.test.ts +59 -0
- package/template/tests/unit/markdown.test.ts +62 -0
- package/template/tests/unit/router.test.ts +112 -0
- package/template/tests/unit/signals.test.ts +54 -0
- package/template/tests/unit/validation.test.ts +50 -0
- package/template/tsconfig.build.json +21 -0
- package/template/tsconfig.json +51 -0
- package/template/vitest.config.ts +36 -0
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GPU Animation Utilities
|
|
3
|
+
*
|
|
4
|
+
* High-performance animation primitives that leverage:
|
|
5
|
+
* - CSS transforms (GPU composited)
|
|
6
|
+
* - CSS custom properties for dynamic values
|
|
7
|
+
* - Web Animations API for smooth interpolation
|
|
8
|
+
* - RequestAnimationFrame with delta time
|
|
9
|
+
* - Passive event listeners
|
|
10
|
+
* - will-change optimization
|
|
11
|
+
* - contain property for layout isolation
|
|
12
|
+
*
|
|
13
|
+
* These utilities make NativeCore animations outperform other frameworks
|
|
14
|
+
* by maximizing GPU utilization and minimizing main thread work.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// Types
|
|
18
|
+
export interface AnimationOptions {
|
|
19
|
+
duration?: number;
|
|
20
|
+
easing?: string;
|
|
21
|
+
delay?: number;
|
|
22
|
+
fill?: FillMode;
|
|
23
|
+
iterations?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ParticleConfig {
|
|
27
|
+
count: number;
|
|
28
|
+
colors?: string[];
|
|
29
|
+
size?: { min: number; max: number };
|
|
30
|
+
speed?: { min: number; max: number };
|
|
31
|
+
type?: 'shower' | 'burst' | 'float' | 'spiral' | 'explode' | 'converge' | 'electricity' | 'fire' | 'ripple' | 'firework' | 'explosion';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface Particle {
|
|
35
|
+
x: number;
|
|
36
|
+
y: number;
|
|
37
|
+
vx: number;
|
|
38
|
+
vy: number;
|
|
39
|
+
size: number;
|
|
40
|
+
color: string;
|
|
41
|
+
alpha: number;
|
|
42
|
+
angle: number;
|
|
43
|
+
life: number;
|
|
44
|
+
maxLife: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ============================================
|
|
48
|
+
// GPU Transform Utilities
|
|
49
|
+
// ============================================
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Apply GPU-accelerated transform using translate3d
|
|
53
|
+
* translate3d forces GPU layer creation even for 2D transforms
|
|
54
|
+
*/
|
|
55
|
+
export function setGPUTransform(element: HTMLElement, x: number, y: number, z = 0, scale = 1, rotate = 0): void {
|
|
56
|
+
element.style.transform = `translate3d(${x}px, ${y}px, ${z}px) scale(${scale}) rotate(${rotate}deg)`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Apply transform using CSS custom properties
|
|
61
|
+
* This allows CSS transitions to handle the animation on GPU
|
|
62
|
+
*/
|
|
63
|
+
export function setTransformVars(element: HTMLElement, vars: Record<string, string | number>): void {
|
|
64
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
65
|
+
element.style.setProperty(`--${key}`, typeof value === 'number' ? `${value}px` : value);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Prepare element for GPU animation with proper hints
|
|
71
|
+
*/
|
|
72
|
+
export function prepareForAnimation(element: HTMLElement, properties: string[] = ['transform', 'opacity']): void {
|
|
73
|
+
element.style.willChange = properties.join(', ');
|
|
74
|
+
element.style.contain = 'layout style paint';
|
|
75
|
+
element.style.backfaceVisibility = 'hidden';
|
|
76
|
+
// Force GPU layer
|
|
77
|
+
element.style.transform = element.style.transform || 'translateZ(0)';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Clean up animation hints to free GPU memory
|
|
82
|
+
*/
|
|
83
|
+
export function cleanupAnimation(element: HTMLElement): void {
|
|
84
|
+
element.style.willChange = 'auto';
|
|
85
|
+
element.style.contain = '';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ============================================
|
|
89
|
+
// Web Animations API Wrappers
|
|
90
|
+
// ============================================
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Animate using Web Animations API (GPU accelerated)
|
|
94
|
+
* Returns a Promise that resolves when animation completes
|
|
95
|
+
*/
|
|
96
|
+
export function animate(
|
|
97
|
+
element: HTMLElement,
|
|
98
|
+
keyframes: Keyframe[],
|
|
99
|
+
options: AnimationOptions = {}
|
|
100
|
+
): Promise<void> {
|
|
101
|
+
const {
|
|
102
|
+
duration = 300,
|
|
103
|
+
easing = 'cubic-bezier(0.4, 0, 0.2, 1)',
|
|
104
|
+
delay = 0,
|
|
105
|
+
fill = 'forwards',
|
|
106
|
+
iterations = 1
|
|
107
|
+
} = options;
|
|
108
|
+
|
|
109
|
+
return new Promise((resolve) => {
|
|
110
|
+
const animation = element.animate(keyframes, {
|
|
111
|
+
duration,
|
|
112
|
+
easing,
|
|
113
|
+
delay,
|
|
114
|
+
fill,
|
|
115
|
+
iterations
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
animation.onfinish = () => resolve();
|
|
119
|
+
animation.oncancel = () => resolve();
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* GPU-accelerated fade animation
|
|
125
|
+
*/
|
|
126
|
+
export function fadeIn(element: HTMLElement, duration = 300): Promise<void> {
|
|
127
|
+
prepareForAnimation(element, ['opacity']);
|
|
128
|
+
return animate(element, [
|
|
129
|
+
{ opacity: 0 },
|
|
130
|
+
{ opacity: 1 }
|
|
131
|
+
], { duration });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function fadeOut(element: HTMLElement, duration = 300): Promise<void> {
|
|
135
|
+
prepareForAnimation(element, ['opacity']);
|
|
136
|
+
return animate(element, [
|
|
137
|
+
{ opacity: 1 },
|
|
138
|
+
{ opacity: 0 }
|
|
139
|
+
], { duration });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* GPU-accelerated slide animation
|
|
144
|
+
*/
|
|
145
|
+
export function slideIn(element: HTMLElement, direction: 'up' | 'down' | 'left' | 'right' = 'up', distance = 40, duration = 400): Promise<void> {
|
|
146
|
+
prepareForAnimation(element);
|
|
147
|
+
|
|
148
|
+
const translations: Record<string, [string, string]> = {
|
|
149
|
+
up: [`translate3d(0, ${distance}px, 0)`, 'translate3d(0, 0, 0)'],
|
|
150
|
+
down: [`translate3d(0, -${distance}px, 0)`, 'translate3d(0, 0, 0)'],
|
|
151
|
+
left: [`translate3d(${distance}px, 0, 0)`, 'translate3d(0, 0, 0)'],
|
|
152
|
+
right: [`translate3d(-${distance}px, 0, 0)`, 'translate3d(0, 0, 0)']
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const [from, to] = translations[direction];
|
|
156
|
+
|
|
157
|
+
return animate(element, [
|
|
158
|
+
{ transform: from, opacity: 0 },
|
|
159
|
+
{ transform: to, opacity: 1 }
|
|
160
|
+
], { duration, easing: 'cubic-bezier(0.4, 0, 0.2, 1)' });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* GPU-accelerated scale animation
|
|
165
|
+
*/
|
|
166
|
+
export function scaleIn(element: HTMLElement, duration = 300): Promise<void> {
|
|
167
|
+
prepareForAnimation(element);
|
|
168
|
+
return animate(element, [
|
|
169
|
+
{ transform: 'scale3d(0.8, 0.8, 1)', opacity: 0 },
|
|
170
|
+
{ transform: 'scale3d(1, 1, 1)', opacity: 1 }
|
|
171
|
+
], { duration, easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)' });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ============================================
|
|
175
|
+
// High-Performance Animation Loop
|
|
176
|
+
// ============================================
|
|
177
|
+
|
|
178
|
+
export interface AnimationLoop {
|
|
179
|
+
start: () => void;
|
|
180
|
+
stop: () => void;
|
|
181
|
+
isRunning: () => boolean;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Create a high-performance animation loop with delta time
|
|
186
|
+
* Automatically handles frame timing and cleanup
|
|
187
|
+
*/
|
|
188
|
+
export function createAnimationLoop(
|
|
189
|
+
callback: (deltaTime: number, elapsed: number) => boolean | void
|
|
190
|
+
): AnimationLoop {
|
|
191
|
+
let rafId: number | null = null;
|
|
192
|
+
let lastTime = 0;
|
|
193
|
+
let startTime = 0;
|
|
194
|
+
let running = false;
|
|
195
|
+
|
|
196
|
+
const tick = (currentTime: number) => {
|
|
197
|
+
if (!running) return;
|
|
198
|
+
|
|
199
|
+
if (lastTime === 0) {
|
|
200
|
+
lastTime = currentTime;
|
|
201
|
+
startTime = currentTime;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Delta time in seconds, capped to prevent huge jumps
|
|
205
|
+
const deltaTime = Math.min((currentTime - lastTime) / 1000, 0.1);
|
|
206
|
+
const elapsed = (currentTime - startTime) / 1000;
|
|
207
|
+
lastTime = currentTime;
|
|
208
|
+
|
|
209
|
+
// Callback returns false to stop the loop
|
|
210
|
+
const shouldContinue = callback(deltaTime, elapsed);
|
|
211
|
+
|
|
212
|
+
if (shouldContinue !== false && running) {
|
|
213
|
+
rafId = requestAnimationFrame(tick);
|
|
214
|
+
} else {
|
|
215
|
+
running = false;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
start() {
|
|
221
|
+
if (running) return;
|
|
222
|
+
running = true;
|
|
223
|
+
lastTime = 0;
|
|
224
|
+
rafId = requestAnimationFrame(tick);
|
|
225
|
+
},
|
|
226
|
+
stop() {
|
|
227
|
+
running = false;
|
|
228
|
+
if (rafId !== null) {
|
|
229
|
+
cancelAnimationFrame(rafId);
|
|
230
|
+
rafId = null;
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
isRunning() {
|
|
234
|
+
return running;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ============================================
|
|
240
|
+
// WebGL Particle System for High Counts
|
|
241
|
+
// ============================================
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Create a WebGL-based particle system for thousands of particles
|
|
245
|
+
* Uses vertex shaders for GPU-computed positions
|
|
246
|
+
*/
|
|
247
|
+
export function createWebGLParticleSystem(
|
|
248
|
+
canvas: HTMLCanvasElement,
|
|
249
|
+
config: ParticleConfig
|
|
250
|
+
): { start: () => void; stop: () => void; destroy: () => void } | null {
|
|
251
|
+
const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
|
|
252
|
+
if (!gl) {
|
|
253
|
+
console.warn('[GPU Animation] WebGL not available, falling back to canvas');
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const { count, colors = ['#667eea', '#764ba2', '#f093fb'], size = { min: 2, max: 6 } } = config;
|
|
258
|
+
|
|
259
|
+
// Vertex shader - positions computed on GPU
|
|
260
|
+
const vertexShaderSource = `
|
|
261
|
+
attribute vec2 a_position;
|
|
262
|
+
attribute vec2 a_velocity;
|
|
263
|
+
attribute float a_size;
|
|
264
|
+
attribute vec4 a_color;
|
|
265
|
+
attribute float a_life;
|
|
266
|
+
|
|
267
|
+
uniform vec2 u_resolution;
|
|
268
|
+
uniform float u_time;
|
|
269
|
+
uniform float u_deltaTime;
|
|
270
|
+
|
|
271
|
+
varying vec4 v_color;
|
|
272
|
+
varying float v_life;
|
|
273
|
+
|
|
274
|
+
void main() {
|
|
275
|
+
vec2 pos = a_position + a_velocity * u_time;
|
|
276
|
+
|
|
277
|
+
// Normalize to clip space
|
|
278
|
+
vec2 clipSpace = (pos / u_resolution) * 2.0 - 1.0;
|
|
279
|
+
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
|
|
280
|
+
gl_PointSize = a_size;
|
|
281
|
+
|
|
282
|
+
v_color = a_color;
|
|
283
|
+
v_life = a_life;
|
|
284
|
+
}
|
|
285
|
+
`;
|
|
286
|
+
|
|
287
|
+
// Fragment shader - draws circular particles with alpha
|
|
288
|
+
const fragmentShaderSource = `
|
|
289
|
+
precision mediump float;
|
|
290
|
+
|
|
291
|
+
varying vec4 v_color;
|
|
292
|
+
varying float v_life;
|
|
293
|
+
|
|
294
|
+
void main() {
|
|
295
|
+
vec2 coord = gl_PointCoord - vec2(0.5);
|
|
296
|
+
float dist = length(coord);
|
|
297
|
+
|
|
298
|
+
if (dist > 0.5) discard;
|
|
299
|
+
|
|
300
|
+
float alpha = smoothstep(0.5, 0.2, dist) * v_color.a * v_life;
|
|
301
|
+
gl_FragColor = vec4(v_color.rgb, alpha);
|
|
302
|
+
}
|
|
303
|
+
`;
|
|
304
|
+
|
|
305
|
+
// Compile shaders
|
|
306
|
+
function createShader(type: number, source: string): WebGLShader | null {
|
|
307
|
+
const shader = gl!.createShader(type);
|
|
308
|
+
if (!shader) return null;
|
|
309
|
+
gl!.shaderSource(shader, source);
|
|
310
|
+
gl!.compileShader(shader);
|
|
311
|
+
if (!gl!.getShaderParameter(shader, gl!.COMPILE_STATUS)) {
|
|
312
|
+
console.error(gl!.getShaderInfoLog(shader));
|
|
313
|
+
gl!.deleteShader(shader);
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
return shader;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const vertexShader = createShader(gl.VERTEX_SHADER, vertexShaderSource);
|
|
320
|
+
const fragmentShader = createShader(gl.FRAGMENT_SHADER, fragmentShaderSource);
|
|
321
|
+
if (!vertexShader || !fragmentShader) return null;
|
|
322
|
+
|
|
323
|
+
const program = gl.createProgram()!;
|
|
324
|
+
gl.attachShader(program, vertexShader);
|
|
325
|
+
gl.attachShader(program, fragmentShader);
|
|
326
|
+
gl.linkProgram(program);
|
|
327
|
+
|
|
328
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
329
|
+
console.error(gl.getProgramInfoLog(program));
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Get locations
|
|
334
|
+
const positionLoc = gl.getAttribLocation(program, 'a_position');
|
|
335
|
+
const velocityLoc = gl.getAttribLocation(program, 'a_velocity');
|
|
336
|
+
const sizeLoc = gl.getAttribLocation(program, 'a_size');
|
|
337
|
+
const colorLoc = gl.getAttribLocation(program, 'a_color');
|
|
338
|
+
const lifeLoc = gl.getAttribLocation(program, 'a_life');
|
|
339
|
+
const resolutionLoc = gl.getUniformLocation(program, 'u_resolution');
|
|
340
|
+
const timeLoc = gl.getUniformLocation(program, 'u_time');
|
|
341
|
+
|
|
342
|
+
// Create buffers
|
|
343
|
+
const positions = new Float32Array(count * 2);
|
|
344
|
+
const velocities = new Float32Array(count * 2);
|
|
345
|
+
const sizes = new Float32Array(count);
|
|
346
|
+
const particleColors = new Float32Array(count * 4);
|
|
347
|
+
const lives = new Float32Array(count);
|
|
348
|
+
|
|
349
|
+
// Initialize particles
|
|
350
|
+
function hexToRgb(hex: string): [number, number, number] {
|
|
351
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
352
|
+
return result ? [
|
|
353
|
+
parseInt(result[1], 16) / 255,
|
|
354
|
+
parseInt(result[2], 16) / 255,
|
|
355
|
+
parseInt(result[3], 16) / 255
|
|
356
|
+
] : [1, 1, 1];
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
for (let i = 0; i < count; i++) {
|
|
360
|
+
positions[i * 2] = Math.random() * canvas.width;
|
|
361
|
+
positions[i * 2 + 1] = Math.random() * -canvas.height;
|
|
362
|
+
velocities[i * 2] = (Math.random() - 0.5) * 50;
|
|
363
|
+
velocities[i * 2 + 1] = 50 + Math.random() * 100;
|
|
364
|
+
sizes[i] = size.min + Math.random() * (size.max - size.min);
|
|
365
|
+
|
|
366
|
+
const color = hexToRgb(colors[Math.floor(Math.random() * colors.length)]);
|
|
367
|
+
particleColors[i * 4] = color[0];
|
|
368
|
+
particleColors[i * 4 + 1] = color[1];
|
|
369
|
+
particleColors[i * 4 + 2] = color[2];
|
|
370
|
+
particleColors[i * 4 + 3] = 0.8;
|
|
371
|
+
|
|
372
|
+
lives[i] = 1.0;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Create and fill buffers
|
|
376
|
+
const posBuffer = gl.createBuffer();
|
|
377
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);
|
|
378
|
+
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.DYNAMIC_DRAW);
|
|
379
|
+
|
|
380
|
+
const velBuffer = gl.createBuffer();
|
|
381
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, velBuffer);
|
|
382
|
+
gl.bufferData(gl.ARRAY_BUFFER, velocities, gl.STATIC_DRAW);
|
|
383
|
+
|
|
384
|
+
const sizeBuffer = gl.createBuffer();
|
|
385
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, sizeBuffer);
|
|
386
|
+
gl.bufferData(gl.ARRAY_BUFFER, sizes, gl.STATIC_DRAW);
|
|
387
|
+
|
|
388
|
+
const colorBuffer = gl.createBuffer();
|
|
389
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
|
|
390
|
+
gl.bufferData(gl.ARRAY_BUFFER, particleColors, gl.STATIC_DRAW);
|
|
391
|
+
|
|
392
|
+
const lifeBuffer = gl.createBuffer();
|
|
393
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, lifeBuffer);
|
|
394
|
+
gl.bufferData(gl.ARRAY_BUFFER, lives, gl.STATIC_DRAW);
|
|
395
|
+
|
|
396
|
+
let animationLoop: AnimationLoop | null = null;
|
|
397
|
+
|
|
398
|
+
return {
|
|
399
|
+
start() {
|
|
400
|
+
gl!.enable(gl!.BLEND);
|
|
401
|
+
gl!.blendFunc(gl!.SRC_ALPHA, gl!.ONE_MINUS_SRC_ALPHA);
|
|
402
|
+
|
|
403
|
+
animationLoop = createAnimationLoop((dt, elapsed) => {
|
|
404
|
+
gl!.viewport(0, 0, canvas.width, canvas.height);
|
|
405
|
+
gl!.clearColor(0, 0, 0, 0);
|
|
406
|
+
gl!.clear(gl!.COLOR_BUFFER_BIT);
|
|
407
|
+
|
|
408
|
+
gl!.useProgram(program);
|
|
409
|
+
gl!.uniform2f(resolutionLoc, canvas.width, canvas.height);
|
|
410
|
+
gl!.uniform1f(timeLoc, elapsed);
|
|
411
|
+
|
|
412
|
+
// Update positions (wrap around)
|
|
413
|
+
for (let i = 0; i < count; i++) {
|
|
414
|
+
positions[i * 2 + 1] += velocities[i * 2 + 1] * dt;
|
|
415
|
+
positions[i * 2] += velocities[i * 2] * dt;
|
|
416
|
+
|
|
417
|
+
if (positions[i * 2 + 1] > canvas.height) {
|
|
418
|
+
positions[i * 2 + 1] = -sizes[i];
|
|
419
|
+
positions[i * 2] = Math.random() * canvas.width;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
gl!.bindBuffer(gl!.ARRAY_BUFFER, posBuffer);
|
|
424
|
+
gl!.bufferSubData(gl!.ARRAY_BUFFER, 0, positions);
|
|
425
|
+
|
|
426
|
+
// Bind attributes
|
|
427
|
+
gl!.bindBuffer(gl!.ARRAY_BUFFER, posBuffer);
|
|
428
|
+
gl!.enableVertexAttribArray(positionLoc);
|
|
429
|
+
gl!.vertexAttribPointer(positionLoc, 2, gl!.FLOAT, false, 0, 0);
|
|
430
|
+
|
|
431
|
+
gl!.bindBuffer(gl!.ARRAY_BUFFER, velBuffer);
|
|
432
|
+
gl!.enableVertexAttribArray(velocityLoc);
|
|
433
|
+
gl!.vertexAttribPointer(velocityLoc, 2, gl!.FLOAT, false, 0, 0);
|
|
434
|
+
|
|
435
|
+
gl!.bindBuffer(gl!.ARRAY_BUFFER, sizeBuffer);
|
|
436
|
+
gl!.enableVertexAttribArray(sizeLoc);
|
|
437
|
+
gl!.vertexAttribPointer(sizeLoc, 1, gl!.FLOAT, false, 0, 0);
|
|
438
|
+
|
|
439
|
+
gl!.bindBuffer(gl!.ARRAY_BUFFER, colorBuffer);
|
|
440
|
+
gl!.enableVertexAttribArray(colorLoc);
|
|
441
|
+
gl!.vertexAttribPointer(colorLoc, 4, gl!.FLOAT, false, 0, 0);
|
|
442
|
+
|
|
443
|
+
gl!.bindBuffer(gl!.ARRAY_BUFFER, lifeBuffer);
|
|
444
|
+
gl!.enableVertexAttribArray(lifeLoc);
|
|
445
|
+
gl!.vertexAttribPointer(lifeLoc, 1, gl!.FLOAT, false, 0, 0);
|
|
446
|
+
|
|
447
|
+
gl!.drawArrays(gl!.POINTS, 0, count);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
animationLoop.start();
|
|
451
|
+
},
|
|
452
|
+
stop() {
|
|
453
|
+
animationLoop?.stop();
|
|
454
|
+
},
|
|
455
|
+
destroy() {
|
|
456
|
+
animationLoop?.stop();
|
|
457
|
+
gl!.deleteProgram(program);
|
|
458
|
+
gl!.deleteShader(vertexShader);
|
|
459
|
+
gl!.deleteShader(fragmentShader);
|
|
460
|
+
gl!.deleteBuffer(posBuffer);
|
|
461
|
+
gl!.deleteBuffer(velBuffer);
|
|
462
|
+
gl!.deleteBuffer(sizeBuffer);
|
|
463
|
+
gl!.deleteBuffer(colorBuffer);
|
|
464
|
+
gl!.deleteBuffer(lifeBuffer);
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// ============================================
|
|
470
|
+
// CSS Animation Injection
|
|
471
|
+
// ============================================
|
|
472
|
+
|
|
473
|
+
const injectedAnimations = new Set<string>();
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Inject keyframe animation into document (once)
|
|
477
|
+
* These run entirely on GPU via compositor
|
|
478
|
+
*/
|
|
479
|
+
export function injectKeyframes(name: string, keyframes: string): void {
|
|
480
|
+
if (injectedAnimations.has(name)) return;
|
|
481
|
+
|
|
482
|
+
const style = document.createElement('style');
|
|
483
|
+
style.textContent = `@keyframes ${name} { ${keyframes} }`;
|
|
484
|
+
document.head.appendChild(style);
|
|
485
|
+
injectedAnimations.add(name);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Common GPU-accelerated keyframe animations
|
|
490
|
+
*/
|
|
491
|
+
export function injectCommonAnimations(): void {
|
|
492
|
+
injectKeyframes('nc-fade-in', `
|
|
493
|
+
from { opacity: 0; }
|
|
494
|
+
to { opacity: 1; }
|
|
495
|
+
`);
|
|
496
|
+
|
|
497
|
+
injectKeyframes('nc-fade-out', `
|
|
498
|
+
from { opacity: 1; }
|
|
499
|
+
to { opacity: 0; }
|
|
500
|
+
`);
|
|
501
|
+
|
|
502
|
+
injectKeyframes('nc-slide-up', `
|
|
503
|
+
from { transform: translate3d(0, 40px, 0); opacity: 0; }
|
|
504
|
+
to { transform: translate3d(0, 0, 0); opacity: 1; }
|
|
505
|
+
`);
|
|
506
|
+
|
|
507
|
+
injectKeyframes('nc-slide-down', `
|
|
508
|
+
from { transform: translate3d(0, -40px, 0); opacity: 0; }
|
|
509
|
+
to { transform: translate3d(0, 0, 0); opacity: 1; }
|
|
510
|
+
`);
|
|
511
|
+
|
|
512
|
+
injectKeyframes('nc-scale-in', `
|
|
513
|
+
from { transform: scale3d(0.8, 0.8, 1); opacity: 0; }
|
|
514
|
+
to { transform: scale3d(1, 1, 1); opacity: 1; }
|
|
515
|
+
`);
|
|
516
|
+
|
|
517
|
+
injectKeyframes('nc-spin', `
|
|
518
|
+
from { transform: rotate(0deg); }
|
|
519
|
+
to { transform: rotate(360deg); }
|
|
520
|
+
`);
|
|
521
|
+
|
|
522
|
+
injectKeyframes('nc-pulse', `
|
|
523
|
+
0%, 100% { transform: scale3d(1, 1, 1); }
|
|
524
|
+
50% { transform: scale3d(1.05, 1.05, 1); }
|
|
525
|
+
`);
|
|
526
|
+
|
|
527
|
+
injectKeyframes('nc-shake', `
|
|
528
|
+
0%, 100% { transform: translate3d(0, 0, 0); }
|
|
529
|
+
25% { transform: translate3d(-5px, 0, 0); }
|
|
530
|
+
75% { transform: translate3d(5px, 0, 0); }
|
|
531
|
+
`);
|
|
532
|
+
|
|
533
|
+
injectKeyframes('nc-bounce', `
|
|
534
|
+
0%, 100% { transform: translate3d(0, 0, 0); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); }
|
|
535
|
+
50% { transform: translate3d(0, -25px, 0); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); }
|
|
536
|
+
`);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// ============================================
|
|
540
|
+
// Passive Event Listener Utility
|
|
541
|
+
// ============================================
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Add passive event listener for scroll/touch performance
|
|
545
|
+
*/
|
|
546
|
+
export function addPassiveListener(
|
|
547
|
+
element: HTMLElement | Window,
|
|
548
|
+
event: string,
|
|
549
|
+
handler: EventListener
|
|
550
|
+
): () => void {
|
|
551
|
+
element.addEventListener(event, handler, { passive: true });
|
|
552
|
+
return () => element.removeEventListener(event, handler);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Throttle function for scroll/resize handlers
|
|
557
|
+
*/
|
|
558
|
+
export function throttle<T extends (...args: any[]) => void>(
|
|
559
|
+
fn: T,
|
|
560
|
+
limit: number
|
|
561
|
+
): T {
|
|
562
|
+
let inThrottle = false;
|
|
563
|
+
return ((...args: any[]) => {
|
|
564
|
+
if (!inThrottle) {
|
|
565
|
+
fn(...args);
|
|
566
|
+
inThrottle = true;
|
|
567
|
+
setTimeout(() => inThrottle = false, limit);
|
|
568
|
+
}
|
|
569
|
+
}) as T;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* RAF-based throttle for smooth animations
|
|
574
|
+
*/
|
|
575
|
+
export function rafThrottle<T extends (...args: any[]) => void>(fn: T): T {
|
|
576
|
+
let rafId: number | null = null;
|
|
577
|
+
return ((...args: any[]) => {
|
|
578
|
+
if (rafId !== null) return;
|
|
579
|
+
rafId = requestAnimationFrame(() => {
|
|
580
|
+
fn(...args);
|
|
581
|
+
rafId = null;
|
|
582
|
+
});
|
|
583
|
+
}) as T;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Export all utilities
|
|
587
|
+
export const GPUAnimation = {
|
|
588
|
+
setGPUTransform,
|
|
589
|
+
setTransformVars,
|
|
590
|
+
prepareForAnimation,
|
|
591
|
+
cleanupAnimation,
|
|
592
|
+
animate,
|
|
593
|
+
fadeIn,
|
|
594
|
+
fadeOut,
|
|
595
|
+
slideIn,
|
|
596
|
+
scaleIn,
|
|
597
|
+
createAnimationLoop,
|
|
598
|
+
createWebGLParticleSystem,
|
|
599
|
+
injectKeyframes,
|
|
600
|
+
injectCommonAnimations,
|
|
601
|
+
addPassiveListener,
|
|
602
|
+
throttle,
|
|
603
|
+
rafThrottle
|
|
604
|
+
};
|