domma-cms 0.1.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 +469 -0
- package/admin/css/admin.css +1123 -0
- package/admin/index.html +72 -0
- package/admin/js/api.js +210 -0
- package/admin/js/app.js +270 -0
- package/admin/js/config/sidebar-config.js +107 -0
- package/admin/js/lib/card.js +63 -0
- package/admin/js/lib/image-editor.js +869 -0
- package/admin/js/lib/markdown-toolbar.js +421 -0
- package/admin/js/templates/dashboard.html +50 -0
- package/admin/js/templates/documentation.html +237 -0
- package/admin/js/templates/layouts.html +11 -0
- package/admin/js/templates/login.html +58 -0
- package/admin/js/templates/media.html +16 -0
- package/admin/js/templates/navigation.html +50 -0
- package/admin/js/templates/page-editor.html +126 -0
- package/admin/js/templates/pages.html +18 -0
- package/admin/js/templates/plugins.html +12 -0
- package/admin/js/templates/settings.html +190 -0
- package/admin/js/templates/tutorials.html +233 -0
- package/admin/js/templates/user-editor.html +12 -0
- package/admin/js/templates/users.html +10 -0
- package/admin/js/views/dashboard.js +48 -0
- package/admin/js/views/documentation.js +12 -0
- package/admin/js/views/index.js +33 -0
- package/admin/js/views/layouts.js +49 -0
- package/admin/js/views/login.js +254 -0
- package/admin/js/views/media.js +240 -0
- package/admin/js/views/navigation.js +152 -0
- package/admin/js/views/page-editor.js +479 -0
- package/admin/js/views/pages.js +64 -0
- package/admin/js/views/plugins.js +100 -0
- package/admin/js/views/settings.js +64 -0
- package/admin/js/views/tutorials.js +12 -0
- package/admin/js/views/user-editor.js +88 -0
- package/admin/js/views/users.js +73 -0
- package/bin/cli.js +334 -0
- package/config/auth.json +20 -0
- package/config/content.json +10 -0
- package/config/navigation.json +63 -0
- package/config/plugins.json +47 -0
- package/config/presets.json +34 -0
- package/config/server.json +6 -0
- package/config/site.json +33 -0
- package/package.json +67 -0
- package/plugins/back-to-top/admin/templates/back-to-top-settings.html +55 -0
- package/plugins/back-to-top/admin/views/back-to-top-settings.js +44 -0
- package/plugins/back-to-top/config.js +10 -0
- package/plugins/back-to-top/plugin.js +24 -0
- package/plugins/back-to-top/plugin.json +36 -0
- package/plugins/back-to-top/public/inject-body.html +105 -0
- package/plugins/cookie-consent/admin/templates/cookie-consent-settings.html +113 -0
- package/plugins/cookie-consent/admin/views/cookie-consent-settings.js +73 -0
- package/plugins/cookie-consent/config.js +30 -0
- package/plugins/cookie-consent/plugin.js +24 -0
- package/plugins/cookie-consent/plugin.json +36 -0
- package/plugins/cookie-consent/public/inject-body.html +69 -0
- package/plugins/custom-css/admin/templates/custom-css.html +17 -0
- package/plugins/custom-css/admin/views/custom-css.js +35 -0
- package/plugins/custom-css/config.js +1 -0
- package/plugins/custom-css/data/custom.css +0 -0
- package/plugins/custom-css/plugin.js +63 -0
- package/plugins/custom-css/plugin.json +32 -0
- package/plugins/custom-css/public/inject-head.html +1 -0
- package/plugins/domma-effects/admin/templates/domma-effects.html +488 -0
- package/plugins/domma-effects/admin/views/domma-effects.js +56 -0
- package/plugins/domma-effects/config.js +9 -0
- package/plugins/domma-effects/plugin.js +22 -0
- package/plugins/domma-effects/plugin.json +36 -0
- package/plugins/domma-effects/public/celebrations/core/canvas.js +111 -0
- package/plugins/domma-effects/public/celebrations/core/particles.js +144 -0
- package/plugins/domma-effects/public/celebrations/core/physics.js +166 -0
- package/plugins/domma-effects/public/celebrations/index.js +535 -0
- package/plugins/domma-effects/public/celebrations/themes/christmas.js +1805 -0
- package/plugins/domma-effects/public/celebrations/themes/guy-fawkes.js +1477 -0
- package/plugins/domma-effects/public/celebrations/themes/halloween.js +1837 -0
- package/plugins/domma-effects/public/celebrations/themes/st-andrews.js +1175 -0
- package/plugins/domma-effects/public/celebrations/themes/st-davids.js +1258 -0
- package/plugins/domma-effects/public/celebrations/themes/st-georges.js +1754 -0
- package/plugins/domma-effects/public/celebrations/themes/st-patricks.js +1290 -0
- package/plugins/domma-effects/public/celebrations/themes/valentines.js +1361 -0
- package/plugins/domma-effects/public/inject-body.html +268 -0
- package/plugins/example-analytics/admin/templates/analytics.html +10 -0
- package/plugins/example-analytics/admin/views/analytics.js +51 -0
- package/plugins/example-analytics/config.js +6 -0
- package/plugins/example-analytics/plugin.js +58 -0
- package/plugins/example-analytics/plugin.json +27 -0
- package/plugins/example-analytics/public/inject-body.html +13 -0
- package/plugins/example-analytics/public/inject-head.html +1 -0
- package/plugins/example-analytics/stats.json +1 -0
- package/plugins/form-builder/admin/templates/form-editor.html +158 -0
- package/plugins/form-builder/admin/templates/form-settings.html +29 -0
- package/plugins/form-builder/admin/templates/form-submissions.html +30 -0
- package/plugins/form-builder/admin/templates/forms-list.html +17 -0
- package/plugins/form-builder/admin/views/form-editor.js +817 -0
- package/plugins/form-builder/admin/views/form-settings.js +38 -0
- package/plugins/form-builder/admin/views/form-submissions.js +295 -0
- package/plugins/form-builder/admin/views/forms-list.js +164 -0
- package/plugins/form-builder/config.js +9 -0
- package/plugins/form-builder/data/forms/contact-details.json +63 -0
- package/plugins/form-builder/data/forms/contact.json +52 -0
- package/plugins/form-builder/data/submissions/contact-details.json +1 -0
- package/plugins/form-builder/data/submissions/contact.json +14 -0
- package/plugins/form-builder/email.js +103 -0
- package/plugins/form-builder/plugin.js +454 -0
- package/plugins/form-builder/plugin.json +56 -0
- package/plugins/form-builder/public/inject-body.html +270 -0
- package/plugins/form-builder/public/inject-head.html +42 -0
- package/public/css/site.css +189 -0
- package/public/js/site.js +109 -0
- package/scripts/copy-domma.js +48 -0
- package/scripts/fresh.js +41 -0
- package/scripts/reset.js +124 -0
- package/scripts/seed.js +666 -0
- package/scripts/setup.js +263 -0
- package/server/config.js +56 -0
- package/server/middleware/auth.js +97 -0
- package/server/routes/api/auth.js +116 -0
- package/server/routes/api/layouts.js +25 -0
- package/server/routes/api/media.js +93 -0
- package/server/routes/api/navigation.js +37 -0
- package/server/routes/api/pages.js +118 -0
- package/server/routes/api/plugins.js +46 -0
- package/server/routes/api/settings.js +25 -0
- package/server/routes/api/users.js +110 -0
- package/server/routes/public.js +108 -0
- package/server/server.js +169 -0
- package/server/services/content.js +298 -0
- package/server/services/images.js +334 -0
- package/server/services/markdown.js +297 -0
- package/server/services/plugins.js +246 -0
- package/server/services/renderer.js +80 -0
- package/server/services/users.js +212 -0
- package/server/templates/page.html +78 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canvas Management Utilities
|
|
3
|
+
* Handles canvas creation, resizing, and context management
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class CanvasManager {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.canvasId = options.canvasId || 'celebrations-canvas';
|
|
9
|
+
this.zIndex = options.zIndex || 999;
|
|
10
|
+
this.canvas = null;
|
|
11
|
+
this.ctx = null;
|
|
12
|
+
this.resizeTimeout = null;
|
|
13
|
+
this.resizeCallback = options.onResize || null;
|
|
14
|
+
|
|
15
|
+
// Bind methods
|
|
16
|
+
this._handleResize = this._handleResize.bind(this);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create and append canvas to DOM
|
|
21
|
+
*/
|
|
22
|
+
create() {
|
|
23
|
+
if (this.canvas) return; // Already created
|
|
24
|
+
|
|
25
|
+
this.canvas = document.createElement('canvas');
|
|
26
|
+
this.canvas.id = this.canvasId;
|
|
27
|
+
this.canvas.style.cssText = `
|
|
28
|
+
position: fixed;
|
|
29
|
+
top: 0;
|
|
30
|
+
left: 0;
|
|
31
|
+
width: 100vw;
|
|
32
|
+
height: 100vh;
|
|
33
|
+
pointer-events: none;
|
|
34
|
+
z-index: ${this.zIndex};
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
this.ctx = this.canvas.getContext('2d', { alpha: true });
|
|
38
|
+
this.resize();
|
|
39
|
+
document.body.appendChild(this.canvas);
|
|
40
|
+
|
|
41
|
+
// Bind resize listener
|
|
42
|
+
window.addEventListener('resize', this._handleResize);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Resize canvas to match window dimensions
|
|
47
|
+
*/
|
|
48
|
+
resize() {
|
|
49
|
+
if (!this.canvas) return;
|
|
50
|
+
|
|
51
|
+
this.canvas.width = window.innerWidth;
|
|
52
|
+
this.canvas.height = window.innerHeight;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Clear entire canvas
|
|
57
|
+
*/
|
|
58
|
+
clear() {
|
|
59
|
+
if (!this.ctx) return;
|
|
60
|
+
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get canvas dimensions
|
|
65
|
+
*/
|
|
66
|
+
getDimensions() {
|
|
67
|
+
return {
|
|
68
|
+
width: this.canvas?.width || 0,
|
|
69
|
+
height: this.canvas?.height || 0
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Check if mobile device
|
|
75
|
+
*/
|
|
76
|
+
isMobile() {
|
|
77
|
+
return window.innerWidth < 768;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Remove canvas from DOM and cleanup
|
|
82
|
+
*/
|
|
83
|
+
destroy() {
|
|
84
|
+
window.removeEventListener('resize', this._handleResize);
|
|
85
|
+
|
|
86
|
+
if (this.canvas && this.canvas.parentNode) {
|
|
87
|
+
this.canvas.parentNode.removeChild(this.canvas);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
this.canvas = null;
|
|
91
|
+
this.ctx = null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Handle window resize with debouncing
|
|
96
|
+
* @private
|
|
97
|
+
*/
|
|
98
|
+
_handleResize() {
|
|
99
|
+
if (this.resizeTimeout) {
|
|
100
|
+
clearTimeout(this.resizeTimeout);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
this.resizeTimeout = setTimeout(() => {
|
|
104
|
+
this.resize();
|
|
105
|
+
|
|
106
|
+
if (this.resizeCallback) {
|
|
107
|
+
this.resizeCallback();
|
|
108
|
+
}
|
|
109
|
+
}, 250);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Particle System Base
|
|
3
|
+
* Handles particle creation, depth layers, and lifecycle management
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Create a base particle with depth layering
|
|
8
|
+
* @param {Object} config - Configuration for particle creation
|
|
9
|
+
* @param {number} canvasWidth - Canvas width for positioning
|
|
10
|
+
* @param {number} canvasHeight - Canvas height for positioning
|
|
11
|
+
* @returns {Object} Particle object
|
|
12
|
+
*/
|
|
13
|
+
export function createParticle(config, canvasWidth, canvasHeight) {
|
|
14
|
+
// Determine depth layer (front, middle, back)
|
|
15
|
+
const depth = Math.random();
|
|
16
|
+
let size, speed, opacity, windSpeed;
|
|
17
|
+
|
|
18
|
+
if (depth < 0.33) {
|
|
19
|
+
// Front layer - larger, faster, more opaque
|
|
20
|
+
size = config.sizeRange[0] + Math.random() * (config.sizeRange[1] - config.sizeRange[0]) * 1.5;
|
|
21
|
+
speed = config.speedRange[0] + Math.random() * (config.speedRange[1] - config.speedRange[0]) * 1.3;
|
|
22
|
+
opacity = 0.8 + Math.random() * 0.2;
|
|
23
|
+
windSpeed = 0.01 + Math.random() * 0.02;
|
|
24
|
+
} else if (depth < 0.66) {
|
|
25
|
+
// Middle layer
|
|
26
|
+
size = config.sizeRange[0] + Math.random() * (config.sizeRange[1] - config.sizeRange[0]);
|
|
27
|
+
speed = config.speedRange[0] + Math.random() * (config.speedRange[1] - config.speedRange[0]);
|
|
28
|
+
opacity = 0.5 + Math.random() * 0.2;
|
|
29
|
+
windSpeed = 0.02 + Math.random() * 0.03;
|
|
30
|
+
} else {
|
|
31
|
+
// Back layer - smaller, slower, less opaque
|
|
32
|
+
size = config.sizeRange[0] + Math.random() * (config.sizeRange[1] - config.sizeRange[0]) * 0.7;
|
|
33
|
+
speed = config.speedRange[0] + Math.random() * (config.speedRange[1] - config.speedRange[0]) * 0.7;
|
|
34
|
+
opacity = 0.3 + Math.random() * 0.2;
|
|
35
|
+
windSpeed = 0.03 + Math.random() * 0.04;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
x: Math.random() * canvasWidth,
|
|
40
|
+
y: Math.random() * canvasHeight - canvasHeight, // Start above viewport
|
|
41
|
+
size: size,
|
|
42
|
+
speed: speed,
|
|
43
|
+
opacity: opacity,
|
|
44
|
+
windOffset: Math.random() * Math.PI * 2,
|
|
45
|
+
windSpeed: windSpeed,
|
|
46
|
+
rotation: Math.random() * Math.PI * 2,
|
|
47
|
+
rotationSpeed: (Math.random() - 0.5) * 0.02,
|
|
48
|
+
depth: depth
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Create a static decoration (tree, house, etc.)
|
|
54
|
+
* @param {string} type - Type of decoration
|
|
55
|
+
* @param {number} canvasWidth - Canvas width for positioning
|
|
56
|
+
* @param {number} canvasHeight - Canvas height for positioning
|
|
57
|
+
* @param {Object} options - Additional options
|
|
58
|
+
* @returns {Object} Static particle object
|
|
59
|
+
*/
|
|
60
|
+
export function createStaticDecoration(type, canvasWidth, canvasHeight, options = {}) {
|
|
61
|
+
return {
|
|
62
|
+
type: type,
|
|
63
|
+
x: options.x !== undefined ? options.x : Math.random() * canvasWidth,
|
|
64
|
+
y: options.y !== undefined ? options.y : Math.random() * canvasHeight,
|
|
65
|
+
vx: 0,
|
|
66
|
+
vy: 0,
|
|
67
|
+
size: options.size || (20 + Math.random() * 15),
|
|
68
|
+
opacity: options.opacity || (0.6 + Math.random() * 0.3),
|
|
69
|
+
rotation: options.rotation || 0,
|
|
70
|
+
rotationSpeed: 0,
|
|
71
|
+
active: true,
|
|
72
|
+
static: true,
|
|
73
|
+
...options
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Create a moving special particle (sleigh, bird, etc.)
|
|
79
|
+
* @param {string} type - Type of special particle
|
|
80
|
+
* @param {number} canvasWidth - Canvas width for positioning
|
|
81
|
+
* @param {number} canvasHeight - Canvas height for positioning
|
|
82
|
+
* @param {Object} options - Movement and appearance options
|
|
83
|
+
* @returns {Object} Moving particle object
|
|
84
|
+
*/
|
|
85
|
+
export function createMovingParticle(type, canvasWidth, canvasHeight, options = {}) {
|
|
86
|
+
const startX = options.startX !== undefined ? options.startX : -100;
|
|
87
|
+
const startY = options.startY !== undefined ? options.startY : canvasHeight * 0.3;
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
type: type,
|
|
91
|
+
x: startX,
|
|
92
|
+
y: startY,
|
|
93
|
+
vx: options.vx || 1.5,
|
|
94
|
+
vy: options.vy || 0,
|
|
95
|
+
size: options.size || 40,
|
|
96
|
+
opacity: options.opacity || 1,
|
|
97
|
+
rotation: options.rotation || 0,
|
|
98
|
+
rotationSpeed: options.rotationSpeed || 0,
|
|
99
|
+
active: true,
|
|
100
|
+
static: false,
|
|
101
|
+
// Sine wave motion
|
|
102
|
+
waveAmplitude: options.waveAmplitude || 0,
|
|
103
|
+
waveFrequency: options.waveFrequency || 0,
|
|
104
|
+
waveOffset: options.waveOffset || 0,
|
|
105
|
+
...options
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Recycle particle to top/side of canvas
|
|
111
|
+
* @param {Object} particle - Particle to recycle
|
|
112
|
+
* @param {number} canvasWidth - Canvas width
|
|
113
|
+
* @param {number} canvasHeight - Canvas height
|
|
114
|
+
*/
|
|
115
|
+
export function recycleParticle(particle, canvasWidth, canvasHeight) {
|
|
116
|
+
if (particle.y > canvasHeight + 10) {
|
|
117
|
+
particle.y = -10;
|
|
118
|
+
particle.x = Math.random() * canvasWidth;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Wrap horizontally
|
|
122
|
+
if (particle.x < -10) {
|
|
123
|
+
particle.x = canvasWidth + 10;
|
|
124
|
+
} else if (particle.x > canvasWidth + 10) {
|
|
125
|
+
particle.x = -10;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Check if particle is off-screen and should be removed
|
|
131
|
+
* @param {Object} particle - Particle to check
|
|
132
|
+
* @param {number} canvasWidth - Canvas width
|
|
133
|
+
* @param {number} canvasHeight - Canvas height
|
|
134
|
+
* @param {number} buffer - Extra buffer space (default 100)
|
|
135
|
+
* @returns {boolean} True if particle is off-screen
|
|
136
|
+
*/
|
|
137
|
+
export function isOffScreen(particle, canvasWidth, canvasHeight, buffer = 100) {
|
|
138
|
+
return (
|
|
139
|
+
particle.x < -buffer ||
|
|
140
|
+
particle.x > canvasWidth + buffer ||
|
|
141
|
+
particle.y < -buffer ||
|
|
142
|
+
particle.y > canvasHeight + buffer
|
|
143
|
+
);
|
|
144
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Physics Utilities
|
|
3
|
+
* Handles wind, gravity, movement, and physics simulation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class PhysicsEngine {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.windGust = 0; // Current wind strength
|
|
9
|
+
this.windGustTarget = 0; // Target wind strength
|
|
10
|
+
this.lastGustTime = 0;
|
|
11
|
+
this.gustInterval = options.gustInterval || [5000, 15000]; // Min/max ms between gusts
|
|
12
|
+
this.gustStrength = options.gustStrength || [-2, 2]; // Min/max wind force
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Update wind physics
|
|
17
|
+
* @param {number} currentTime - Current timestamp
|
|
18
|
+
*/
|
|
19
|
+
updateWind(currentTime) {
|
|
20
|
+
// Generate new wind gust periodically
|
|
21
|
+
const [minInterval, maxInterval] = this.gustInterval;
|
|
22
|
+
if (currentTime - this.lastGustTime > minInterval + Math.random() * (maxInterval - minInterval)) {
|
|
23
|
+
const [minStrength, maxStrength] = this.gustStrength;
|
|
24
|
+
this.windGustTarget = minStrength + Math.random() * (maxStrength - minStrength);
|
|
25
|
+
this.lastGustTime = currentTime;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Smooth wind transition
|
|
29
|
+
this.windGust += (this.windGustTarget - this.windGust) * 0.02;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get current wind force
|
|
34
|
+
* @returns {number} Current wind force
|
|
35
|
+
*/
|
|
36
|
+
getWindForce() {
|
|
37
|
+
return this.windGust;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Reset wind to calm
|
|
42
|
+
*/
|
|
43
|
+
resetWind() {
|
|
44
|
+
this.windGust = 0;
|
|
45
|
+
this.windGustTarget = 0;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Update particle position with gravity and wind
|
|
51
|
+
* @param {Object} particle - Particle to update
|
|
52
|
+
* @param {number} deltaTime - Time since last frame
|
|
53
|
+
* @param {number} windForce - Current wind force
|
|
54
|
+
*/
|
|
55
|
+
export function updateParticlePhysics(particle, deltaTime, windForce = 0) {
|
|
56
|
+
const normalizedDelta = deltaTime / (1000 / 60); // Normalize to 60fps
|
|
57
|
+
|
|
58
|
+
// Apply gravity (downward movement) - support both speed and vy
|
|
59
|
+
const verticalSpeed = particle.vy || particle.speed || 0;
|
|
60
|
+
particle.y += verticalSpeed * normalizedDelta;
|
|
61
|
+
|
|
62
|
+
// Apply horizontal velocity if present
|
|
63
|
+
if (particle.vx) {
|
|
64
|
+
particle.x += particle.vx * normalizedDelta;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Apply wind drift + wind gust
|
|
68
|
+
if (particle.windOffset !== undefined && particle.windSpeed !== undefined) {
|
|
69
|
+
particle.windOffset += particle.windSpeed * normalizedDelta;
|
|
70
|
+
const baseWind = Math.sin(particle.windOffset) * 0.5;
|
|
71
|
+
particle.x += (baseWind + windForce) * normalizedDelta;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Apply rotation
|
|
75
|
+
if (particle.rotationSpeed) {
|
|
76
|
+
particle.rotation += particle.rotationSpeed * normalizedDelta;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Update moving particle with sine wave motion
|
|
82
|
+
* @param {Object} particle - Moving particle to update
|
|
83
|
+
* @param {number} deltaTime - Time since last frame
|
|
84
|
+
* @param {number} currentTime - Current timestamp
|
|
85
|
+
*/
|
|
86
|
+
export function updateMovingParticle(particle, deltaTime, currentTime) {
|
|
87
|
+
const normalizedDelta = deltaTime / (1000 / 60);
|
|
88
|
+
|
|
89
|
+
// Update horizontal position
|
|
90
|
+
particle.x += particle.vx * normalizedDelta;
|
|
91
|
+
|
|
92
|
+
// Apply sine wave vertical motion if configured
|
|
93
|
+
if (particle.waveAmplitude && particle.waveFrequency) {
|
|
94
|
+
const waveProgress = (currentTime * particle.waveFrequency) / 1000;
|
|
95
|
+
particle.y = particle.baseY + Math.sin(waveProgress + particle.waveOffset) * particle.waveAmplitude;
|
|
96
|
+
} else {
|
|
97
|
+
particle.y += particle.vy * normalizedDelta;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Apply rotation
|
|
101
|
+
if (particle.rotationSpeed) {
|
|
102
|
+
particle.rotation += particle.rotationSpeed * normalizedDelta;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Apply gravity to particle
|
|
108
|
+
* @param {Object} particle - Particle to affect
|
|
109
|
+
* @param {number} deltaTime - Time since last frame
|
|
110
|
+
* @param {number} gravity - Gravity force (default 0.5)
|
|
111
|
+
*/
|
|
112
|
+
export function applyGravity(particle, deltaTime, gravity = 0.5) {
|
|
113
|
+
const normalizedDelta = deltaTime / (1000 / 60);
|
|
114
|
+
particle.vy += gravity * normalizedDelta;
|
|
115
|
+
particle.y += particle.vy * normalizedDelta;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Apply bounce physics when particle hits ground
|
|
120
|
+
* @param {Object} particle - Particle to bounce
|
|
121
|
+
* @param {number} groundY - Y position of ground
|
|
122
|
+
* @param {number} restitution - Bounce factor (0-1, default 0.6)
|
|
123
|
+
*/
|
|
124
|
+
export function applyBounce(particle, groundY, restitution = 0.6) {
|
|
125
|
+
if (particle.y >= groundY && particle.vy > 0) {
|
|
126
|
+
particle.y = groundY;
|
|
127
|
+
particle.vy *= -restitution;
|
|
128
|
+
|
|
129
|
+
// Stop bouncing if velocity is too low
|
|
130
|
+
if (Math.abs(particle.vy) < 0.5) {
|
|
131
|
+
particle.vy = 0;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Calculate normalized delta time for consistent animation
|
|
138
|
+
* @param {number} deltaTime - Time since last frame in ms
|
|
139
|
+
* @param {number} targetFPS - Target framerate (default 60)
|
|
140
|
+
* @returns {number} Normalized delta
|
|
141
|
+
*/
|
|
142
|
+
export function normalizeDelta(deltaTime, targetFPS = 60) {
|
|
143
|
+
return deltaTime / (1000 / targetFPS);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Interpolate between two values
|
|
148
|
+
* @param {number} start - Start value
|
|
149
|
+
* @param {number} end - End value
|
|
150
|
+
* @param {number} t - Interpolation factor (0-1)
|
|
151
|
+
* @returns {number} Interpolated value
|
|
152
|
+
*/
|
|
153
|
+
export function lerp(start, end, t) {
|
|
154
|
+
return start + (end - start) * t;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Clamp value between min and max
|
|
159
|
+
* @param {number} value - Value to clamp
|
|
160
|
+
* @param {number} min - Minimum value
|
|
161
|
+
* @param {number} max - Maximum value
|
|
162
|
+
* @returns {number} Clamped value
|
|
163
|
+
*/
|
|
164
|
+
export function clamp(value, min, max) {
|
|
165
|
+
return Math.min(Math.max(value, min), max);
|
|
166
|
+
}
|