@firecms/neat 0.5.0 → 0.6.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/dist/NeatGradient.d.ts +7 -0
- package/dist/NeatGradient.js +214 -73
- package/dist/NeatGradient.js.map +1 -1
- package/dist/index.es.js +301 -228
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +11 -6
- package/dist/index.umd.js.map +1 -1
- package/package.json +2 -2
- package/src/NeatGradient.ts +239 -78
package/dist/NeatGradient.d.ts
CHANGED
|
@@ -129,6 +129,13 @@ export declare class NeatGradient implements NeatController {
|
|
|
129
129
|
private _yOffsetColorMultiplier;
|
|
130
130
|
private _yOffsetFlowMultiplier;
|
|
131
131
|
private _tempClearColor;
|
|
132
|
+
private _resizeTimeoutId;
|
|
133
|
+
private _textureNeedsUpdate;
|
|
134
|
+
private _lastColorUpdate;
|
|
135
|
+
private _linkCheckCounter;
|
|
136
|
+
private _mouseUpdateScheduled;
|
|
137
|
+
private _pendingMousePosition;
|
|
138
|
+
private _colorsChanged;
|
|
132
139
|
constructor(config: NeatConfig & {
|
|
133
140
|
ref: HTMLCanvasElement;
|
|
134
141
|
resolution?: number;
|
package/dist/NeatGradient.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as THREE from "three";
|
|
2
|
+
console.info("%c🌈 Neat Gradients%c\n\nLicensed under MIT + The Commons Clause.\nFree for personal and commercial use.\nSelling this software or its derivatives is strictly prohibited.\nhttps://neat.firecms.co", "font-weight: bold; font-size: 14px; color: #FF5772;", "color: inherit;");
|
|
2
3
|
const PLANE_WIDTH = 50;
|
|
3
4
|
const PLANE_HEIGHT = 80;
|
|
4
5
|
const WIREFRAME = true;
|
|
@@ -71,6 +72,14 @@ export class NeatGradient {
|
|
|
71
72
|
_yOffsetFlowMultiplier = 0.004;
|
|
72
73
|
// For saving/restoring clear color
|
|
73
74
|
_tempClearColor = new THREE.Color();
|
|
75
|
+
// Performance optimizations
|
|
76
|
+
_resizeTimeoutId = null;
|
|
77
|
+
_textureNeedsUpdate = false;
|
|
78
|
+
_lastColorUpdate = 0;
|
|
79
|
+
_linkCheckCounter = 0;
|
|
80
|
+
_mouseUpdateScheduled = false;
|
|
81
|
+
_pendingMousePosition = null;
|
|
82
|
+
_colorsChanged = true; // Track if colors need update
|
|
74
83
|
constructor(config) {
|
|
75
84
|
const { ref, speed = 4, horizontalPressure = 3, verticalPressure = 3, waveFrequencyX = 5, waveFrequencyY = 5, waveAmplitude = 3, colors, highlights = 4, shadows = 4, colorSaturation = 0, colorBrightness = 1, colorBlending = 5, grainScale = 2, grainIntensity = 0.55, grainSparsity = 0.0, grainSpeed = 0.1, wireframe = false, backgroundColor = "#FFFFFF", backgroundAlpha = 1.0, resolution = 1, seed, yOffset = 0, yOffsetWaveMultiplier = 4, yOffsetColorMultiplier = 4, yOffsetFlowMultiplier = 4,
|
|
76
85
|
// Flow field parameters
|
|
@@ -135,11 +144,14 @@ export class NeatGradient {
|
|
|
135
144
|
// This ensures u_mouse_texture isn't null during material compilation
|
|
136
145
|
this._setupMouseInteraction();
|
|
137
146
|
this.sceneState = this._initScene(resolution);
|
|
147
|
+
injectSEO();
|
|
138
148
|
let tick = seed !== undefined ? seed : getElapsedSecondsInLastHour();
|
|
139
149
|
const render = () => {
|
|
140
150
|
const { renderer, camera, scene } = this.sceneState;
|
|
141
|
-
// Optimization: check if cached link is still valid in DOM
|
|
142
|
-
|
|
151
|
+
// Optimization: check if cached link is still valid in DOM less frequently
|
|
152
|
+
this._linkCheckCounter++;
|
|
153
|
+
if (this._linkCheckCounter >= 300) { // Check every ~5 seconds at 60fps
|
|
154
|
+
this._linkCheckCounter = 0;
|
|
143
155
|
if (!this._linkElement || !document.contains(this._linkElement)) {
|
|
144
156
|
this._linkElement = addNeatLink(ref);
|
|
145
157
|
}
|
|
@@ -177,28 +189,42 @@ export class NeatGradient {
|
|
|
177
189
|
u.u_mouse_distortion_radius.value = this._mouseDistortionRadius;
|
|
178
190
|
u.u_mouse_darken.value = this._mouseDarken;
|
|
179
191
|
u.u_enable_procedural_texture.value = this._enableProceduralTexture ? 1.0 : 0.0;
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
for (let i = 0; i < COLORS_COUNT; i++) {
|
|
185
|
-
if (i < this._colors.length) {
|
|
186
|
-
const c = this._colors[i];
|
|
187
|
-
shaderColors[i].is_active = c.enabled ? 1.0 : 0.0;
|
|
188
|
-
shaderColors[i].color.setStyle(c.color, "");
|
|
189
|
-
shaderColors[i].influence = c.influence || 0;
|
|
190
|
-
}
|
|
191
|
-
else {
|
|
192
|
-
shaderColors[i].is_active = 0.0;
|
|
192
|
+
// Only regenerate procedural texture when needed
|
|
193
|
+
if (this._textureNeedsUpdate && this._enableProceduralTexture) {
|
|
194
|
+
if (this._proceduralTexture) {
|
|
195
|
+
this._proceduralTexture.dispose();
|
|
193
196
|
}
|
|
197
|
+
this._proceduralTexture = this._createProceduralTexture();
|
|
198
|
+
this._textureNeedsUpdate = false;
|
|
194
199
|
}
|
|
195
|
-
u.
|
|
196
|
-
|
|
200
|
+
u.u_procedural_texture.value = this._proceduralTexture;
|
|
201
|
+
u.u_texture_ease.value = this._textureEase;
|
|
202
|
+
// Wireframe is a material property and must update every frame to avoid artifacts
|
|
197
203
|
// @ts-ignore - access material safely
|
|
198
204
|
this.sceneState.meshes[0].material.wireframe = this._wireframe;
|
|
205
|
+
// Optimized Color Update: Update immediately on change, or throttle to 10 times per second
|
|
206
|
+
const now = Date.now();
|
|
207
|
+
const shouldUpdate = this._colorsChanged || (now - this._lastColorUpdate > 100);
|
|
208
|
+
if (shouldUpdate) {
|
|
209
|
+
this._lastColorUpdate = now;
|
|
210
|
+
this._colorsChanged = false;
|
|
211
|
+
const shaderColors = u.u_colors.value;
|
|
212
|
+
for (let i = 0; i < COLORS_COUNT; i++) {
|
|
213
|
+
if (i < this._colors.length) {
|
|
214
|
+
const c = this._colors[i];
|
|
215
|
+
shaderColors[i].is_active = c.enabled ? 1.0 : 0.0;
|
|
216
|
+
shaderColors[i].color.setStyle(c.color, "");
|
|
217
|
+
shaderColors[i].influence = c.influence || 0;
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
shaderColors[i].is_active = 0.0;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
u.u_colors_count.value = COLORS_COUNT;
|
|
224
|
+
}
|
|
199
225
|
}
|
|
200
|
-
// Render mouse interaction to FBO
|
|
201
|
-
if (this._mouseFBO && this._sceneMouse && this._cameraMouse) {
|
|
226
|
+
// Render mouse interaction to FBO - optimize by only rendering when needed
|
|
227
|
+
if (this._mouseFBO && this._sceneMouse && this._cameraMouse && this._mouseDistortionStrength > 0) {
|
|
202
228
|
let hasActiveBrushes = false;
|
|
203
229
|
// Update mouse objects - decay rate controls how fast trails fade
|
|
204
230
|
for (let i = 0; i < this._mouseObjects.length; i++) {
|
|
@@ -215,25 +241,23 @@ export class NeatGradient {
|
|
|
215
241
|
}
|
|
216
242
|
}
|
|
217
243
|
}
|
|
218
|
-
//
|
|
219
|
-
// Store current clear color (likely the main background color)
|
|
220
|
-
renderer.getClearColor(this._tempClearColor);
|
|
221
|
-
const oldClearAlpha = renderer.getClearAlpha();
|
|
222
|
-
// Set clear color to Black/Transparent for the FBO.
|
|
223
|
-
// Important: If we use the main background color (e.g. White), the FBO
|
|
224
|
-
// will be white, causing 100% distortion everywhere.
|
|
225
|
-
renderer.setClearColor(0x000000, 0.0);
|
|
226
|
-
renderer.setRenderTarget(this._mouseFBO);
|
|
227
|
-
renderer.clear();
|
|
244
|
+
// Only render FBO if there are active brushes
|
|
228
245
|
if (hasActiveBrushes) {
|
|
246
|
+
// Store current clear color (likely the main background color)
|
|
247
|
+
renderer.getClearColor(this._tempClearColor);
|
|
248
|
+
const oldClearAlpha = renderer.getClearAlpha();
|
|
249
|
+
// Set clear color to Black/Transparent for the FBO.
|
|
250
|
+
renderer.setClearColor(0x000000, 0.0);
|
|
251
|
+
renderer.setRenderTarget(this._mouseFBO);
|
|
252
|
+
renderer.clear();
|
|
229
253
|
renderer.render(this._sceneMouse, this._cameraMouse);
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
254
|
+
renderer.setRenderTarget(null);
|
|
255
|
+
// Restore main background color for the actual scene render
|
|
256
|
+
renderer.setClearColor(this._tempClearColor, oldClearAlpha);
|
|
257
|
+
// Update mouse texture uniform
|
|
258
|
+
if (this._cachedUniforms) {
|
|
259
|
+
this._cachedUniforms.u_mouse_texture.value = this._mouseFBO.texture;
|
|
260
|
+
}
|
|
237
261
|
}
|
|
238
262
|
}
|
|
239
263
|
// Ensure we set the clear color for the main scene explicitly before rendering
|
|
@@ -261,8 +285,15 @@ export class NeatGradient {
|
|
|
261
285
|
this._cameraMouse.updateProjectionMatrix();
|
|
262
286
|
}
|
|
263
287
|
};
|
|
264
|
-
|
|
265
|
-
|
|
288
|
+
// Debounce resize to prevent excessive operations
|
|
289
|
+
this.sizeObserver = new ResizeObserver(() => {
|
|
290
|
+
if (this._resizeTimeoutId !== null) {
|
|
291
|
+
clearTimeout(this._resizeTimeoutId);
|
|
292
|
+
}
|
|
293
|
+
this._resizeTimeoutId = window.setTimeout(() => {
|
|
294
|
+
setSize();
|
|
295
|
+
this._resizeTimeoutId = null;
|
|
296
|
+
}, 100); // Wait 100ms after last resize event
|
|
266
297
|
});
|
|
267
298
|
this.sizeObserver.observe(ref);
|
|
268
299
|
render();
|
|
@@ -271,6 +302,11 @@ export class NeatGradient {
|
|
|
271
302
|
if (this) {
|
|
272
303
|
cancelAnimationFrame(this.requestRef);
|
|
273
304
|
this.sizeObserver.disconnect();
|
|
305
|
+
// Clear resize timeout
|
|
306
|
+
if (this._resizeTimeoutId !== null) {
|
|
307
|
+
clearTimeout(this._resizeTimeoutId);
|
|
308
|
+
this._resizeTimeoutId = null;
|
|
309
|
+
}
|
|
274
310
|
// Cleanup WebGL resources
|
|
275
311
|
if (this.sceneState) {
|
|
276
312
|
this.sceneState.renderer.dispose();
|
|
@@ -314,6 +350,7 @@ export class NeatGradient {
|
|
|
314
350
|
}
|
|
315
351
|
set colors(colors) {
|
|
316
352
|
this._colors = colors;
|
|
353
|
+
this._colorsChanged = true; // Flag for immediate update
|
|
317
354
|
}
|
|
318
355
|
set highlights(highlights) {
|
|
319
356
|
this._highlights = highlights / 100;
|
|
@@ -419,43 +456,43 @@ export class NeatGradient {
|
|
|
419
456
|
set enableProceduralTexture(value) {
|
|
420
457
|
this._enableProceduralTexture = value;
|
|
421
458
|
if (value && !this._proceduralTexture) {
|
|
422
|
-
this.
|
|
459
|
+
this._textureNeedsUpdate = true;
|
|
423
460
|
}
|
|
424
461
|
}
|
|
425
462
|
set textureVoidLikelihood(value) {
|
|
426
463
|
this._textureVoidLikelihood = value;
|
|
427
464
|
if (this._enableProceduralTexture) {
|
|
428
|
-
this.
|
|
465
|
+
this._textureNeedsUpdate = true;
|
|
429
466
|
}
|
|
430
467
|
}
|
|
431
468
|
set textureVoidWidthMin(value) {
|
|
432
469
|
this._textureVoidWidthMin = value;
|
|
433
470
|
if (this._enableProceduralTexture) {
|
|
434
|
-
this.
|
|
471
|
+
this._textureNeedsUpdate = true;
|
|
435
472
|
}
|
|
436
473
|
}
|
|
437
474
|
set textureVoidWidthMax(value) {
|
|
438
475
|
this._textureVoidWidthMax = value;
|
|
439
476
|
if (this._enableProceduralTexture) {
|
|
440
|
-
this.
|
|
477
|
+
this._textureNeedsUpdate = true;
|
|
441
478
|
}
|
|
442
479
|
}
|
|
443
480
|
set textureBandDensity(value) {
|
|
444
481
|
this._textureBandDensity = value;
|
|
445
482
|
if (this._enableProceduralTexture) {
|
|
446
|
-
this.
|
|
483
|
+
this._textureNeedsUpdate = true;
|
|
447
484
|
}
|
|
448
485
|
}
|
|
449
486
|
set textureColorBlending(value) {
|
|
450
487
|
this._textureColorBlending = value;
|
|
451
488
|
if (this._enableProceduralTexture) {
|
|
452
|
-
this.
|
|
489
|
+
this._textureNeedsUpdate = true;
|
|
453
490
|
}
|
|
454
491
|
}
|
|
455
492
|
set textureSeed(value) {
|
|
456
493
|
this._textureSeed = value;
|
|
457
494
|
if (this._enableProceduralTexture) {
|
|
458
|
-
this.
|
|
495
|
+
this._textureNeedsUpdate = true;
|
|
459
496
|
}
|
|
460
497
|
}
|
|
461
498
|
get textureEase() {
|
|
@@ -467,28 +504,28 @@ export class NeatGradient {
|
|
|
467
504
|
set proceduralBackgroundColor(value) {
|
|
468
505
|
this._proceduralBackgroundColor = value;
|
|
469
506
|
if (this._enableProceduralTexture) {
|
|
470
|
-
this.
|
|
507
|
+
this._textureNeedsUpdate = true;
|
|
471
508
|
}
|
|
472
509
|
}
|
|
473
510
|
set textureShapeTriangles(value) {
|
|
474
511
|
this._textureShapeTriangles = value;
|
|
475
512
|
if (this._enableProceduralTexture)
|
|
476
|
-
this.
|
|
513
|
+
this._textureNeedsUpdate = true;
|
|
477
514
|
}
|
|
478
515
|
set textureShapeCircles(value) {
|
|
479
516
|
this._textureShapeCircles = value;
|
|
480
517
|
if (this._enableProceduralTexture)
|
|
481
|
-
this.
|
|
518
|
+
this._textureNeedsUpdate = true;
|
|
482
519
|
}
|
|
483
520
|
set textureShapeBars(value) {
|
|
484
521
|
this._textureShapeBars = value;
|
|
485
522
|
if (this._enableProceduralTexture)
|
|
486
|
-
this.
|
|
523
|
+
this._textureNeedsUpdate = true;
|
|
487
524
|
}
|
|
488
525
|
set textureShapeSquiggles(value) {
|
|
489
526
|
this._textureShapeSquiggles = value;
|
|
490
527
|
if (this._enableProceduralTexture)
|
|
491
|
-
this.
|
|
528
|
+
this._textureNeedsUpdate = true;
|
|
492
529
|
}
|
|
493
530
|
_initScene(resolution) {
|
|
494
531
|
const width = this._ref.width, height = this._ref.height;
|
|
@@ -649,27 +686,42 @@ export class NeatGradient {
|
|
|
649
686
|
const rect = this._ref.getBoundingClientRect();
|
|
650
687
|
const width = this._ref.width;
|
|
651
688
|
const height = this._ref.height;
|
|
652
|
-
|
|
653
|
-
this.
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
689
|
+
// Store pending mouse position
|
|
690
|
+
this._pendingMousePosition = {
|
|
691
|
+
x: e.clientX - rect.left - width / 2,
|
|
692
|
+
y: -(e.clientY - rect.top - height / 2)
|
|
693
|
+
};
|
|
694
|
+
// Batch mouse updates using requestAnimationFrame
|
|
695
|
+
if (!this._mouseUpdateScheduled) {
|
|
696
|
+
this._mouseUpdateScheduled = true;
|
|
697
|
+
requestAnimationFrame(() => {
|
|
698
|
+
this._mouseUpdateScheduled = false;
|
|
699
|
+
if (!this._pendingMousePosition)
|
|
700
|
+
return;
|
|
701
|
+
this._mouse.x = this._pendingMousePosition.x;
|
|
702
|
+
this._mouse.y = this._pendingMousePosition.y;
|
|
703
|
+
const brush = this._mouseObjects[this._currentBrush];
|
|
704
|
+
brush.mesh.scale.set(this._mouseBrushBaseScale, this._mouseBrushBaseScale, 1.0);
|
|
705
|
+
brush.active = true;
|
|
706
|
+
brush.mesh.visible = true;
|
|
707
|
+
brush.mesh.position.set(this._mouse.x, this._mouse.y, 0);
|
|
708
|
+
brush.mesh.rotation.z = Math.random() * Math.PI * 2;
|
|
709
|
+
if (brush.mesh.material instanceof THREE.MeshBasicMaterial) {
|
|
710
|
+
brush.mesh.material.opacity = 1.0;
|
|
711
|
+
}
|
|
712
|
+
this._currentBrush = (this._currentBrush + 1) % this._mouseObjects.length;
|
|
713
|
+
this._pendingMousePosition = null;
|
|
714
|
+
});
|
|
662
715
|
}
|
|
663
|
-
this._currentBrush = (this._currentBrush + 1) % this._mouseObjects.length;
|
|
664
716
|
}
|
|
665
717
|
_createProceduralTexture() {
|
|
666
718
|
// Texture size - 1024 provides good balance between quality and performance
|
|
667
|
-
//
|
|
719
|
+
// Reduced from 2048 for better performance
|
|
668
720
|
const texSize = 1024;
|
|
669
721
|
const sourceCanvas = document.createElement('canvas');
|
|
670
722
|
sourceCanvas.width = texSize;
|
|
671
723
|
sourceCanvas.height = texSize;
|
|
672
|
-
const sCtx = sourceCanvas.getContext('2d');
|
|
724
|
+
const sCtx = sourceCanvas.getContext('2d', { willReadFrequently: true });
|
|
673
725
|
if (!sCtx)
|
|
674
726
|
return new THREE.Texture();
|
|
675
727
|
let seed = this._textureSeed;
|
|
@@ -773,7 +825,7 @@ export class NeatGradient {
|
|
|
773
825
|
const canvas = document.createElement('canvas');
|
|
774
826
|
canvas.width = texSize;
|
|
775
827
|
canvas.height = texSize;
|
|
776
|
-
const ctx = canvas.getContext('2d');
|
|
828
|
+
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
|
777
829
|
if (!ctx)
|
|
778
830
|
return new THREE.Texture();
|
|
779
831
|
// Start filled with the chosen void color so gaps show that color
|
|
@@ -832,10 +884,23 @@ function updateCamera(camera, width, height) {
|
|
|
832
884
|
const ratio = width / height;
|
|
833
885
|
const targetWidth = Math.sqrt(targetPlaneArea * ratio);
|
|
834
886
|
const targetHeight = targetPlaneArea / targetWidth;
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
887
|
+
let left = -PLANE_WIDTH / 2;
|
|
888
|
+
let right = Math.min((left + targetWidth) / 1.5, PLANE_WIDTH / 2);
|
|
889
|
+
let top = PLANE_HEIGHT / 4;
|
|
890
|
+
let bottom = Math.max((top - targetHeight) / 2, -PLANE_HEIGHT / 4);
|
|
891
|
+
// Fix for mobile portrait: adjust bounds for proper aspect ratio AND zoom out slightly
|
|
892
|
+
if (ratio < 1) {
|
|
893
|
+
// Portrait mode - scale horizontal bounds by aspect ratio to prevent stretching
|
|
894
|
+
const horizontalScale = ratio;
|
|
895
|
+
left = left * horizontalScale;
|
|
896
|
+
right = right * horizontalScale;
|
|
897
|
+
// Zoom out slightly on mobile (1.1 = 10% zoom out)
|
|
898
|
+
const mobileZoomFactor = 1.05;
|
|
899
|
+
left = left * mobileZoomFactor;
|
|
900
|
+
right = right * mobileZoomFactor;
|
|
901
|
+
top = top * mobileZoomFactor;
|
|
902
|
+
bottom = bottom * mobileZoomFactor;
|
|
903
|
+
}
|
|
839
904
|
const near = -100;
|
|
840
905
|
const far = 1000;
|
|
841
906
|
if (camera instanceof THREE.OrthographicCamera) {
|
|
@@ -852,8 +917,13 @@ function updateCamera(camera, width, height) {
|
|
|
852
917
|
camera.updateProjectionMatrix();
|
|
853
918
|
}
|
|
854
919
|
}
|
|
920
|
+
// Cache shader strings to avoid repeated concatenation
|
|
921
|
+
let cachedVertexShader = null;
|
|
922
|
+
let cachedFragmentShader = null;
|
|
855
923
|
function buildVertexShader() {
|
|
856
|
-
|
|
924
|
+
if (cachedVertexShader)
|
|
925
|
+
return cachedVertexShader;
|
|
926
|
+
cachedVertexShader = `
|
|
857
927
|
void main() {
|
|
858
928
|
vUv = uv;
|
|
859
929
|
|
|
@@ -931,9 +1001,12 @@ void main() {
|
|
|
931
1001
|
v_new_position = gl_Position;
|
|
932
1002
|
}
|
|
933
1003
|
`;
|
|
1004
|
+
return cachedVertexShader;
|
|
934
1005
|
}
|
|
935
1006
|
function buildFragmentShader() {
|
|
936
|
-
|
|
1007
|
+
if (cachedFragmentShader)
|
|
1008
|
+
return cachedFragmentShader;
|
|
1009
|
+
cachedFragmentShader = `
|
|
937
1010
|
float random(vec2 p) {
|
|
938
1011
|
return fract(sin(dot(p, vec2(12.9898,78.233))) * 43758.5453);
|
|
939
1012
|
}
|
|
@@ -1016,8 +1089,14 @@ void main() {
|
|
|
1016
1089
|
gl_FragColor = vec4(color, 1.0);
|
|
1017
1090
|
}
|
|
1018
1091
|
`;
|
|
1092
|
+
return cachedFragmentShader;
|
|
1019
1093
|
}
|
|
1020
|
-
|
|
1094
|
+
// Cache uniforms string as well
|
|
1095
|
+
let cachedUniformsShader = null;
|
|
1096
|
+
const buildUniforms = () => {
|
|
1097
|
+
if (cachedUniformsShader)
|
|
1098
|
+
return cachedUniformsShader;
|
|
1099
|
+
cachedUniformsShader = `
|
|
1021
1100
|
precision highp float;
|
|
1022
1101
|
|
|
1023
1102
|
struct Color {
|
|
@@ -1082,7 +1161,14 @@ varying vec3 v_color;
|
|
|
1082
1161
|
varying float v_displacement_amount;
|
|
1083
1162
|
|
|
1084
1163
|
`;
|
|
1085
|
-
|
|
1164
|
+
return cachedUniformsShader;
|
|
1165
|
+
};
|
|
1166
|
+
// Cache noise functions as well
|
|
1167
|
+
let cachedNoiseShader = null;
|
|
1168
|
+
const buildNoise = () => {
|
|
1169
|
+
if (cachedNoiseShader)
|
|
1170
|
+
return cachedNoiseShader;
|
|
1171
|
+
cachedNoiseShader = `
|
|
1086
1172
|
|
|
1087
1173
|
// 1. REPLACEMENT PERMUTE:
|
|
1088
1174
|
// Uses a hash function (fract/sin) instead of a modular lookup table.
|
|
@@ -1236,7 +1322,14 @@ float cnoise(vec3 P)
|
|
|
1236
1322
|
return 2.2 * n_xyz;
|
|
1237
1323
|
}
|
|
1238
1324
|
`;
|
|
1239
|
-
|
|
1325
|
+
return cachedNoiseShader;
|
|
1326
|
+
};
|
|
1327
|
+
// Cache color functions as well
|
|
1328
|
+
let cachedColorFunctionsShader = null;
|
|
1329
|
+
const buildColorFunctions = () => {
|
|
1330
|
+
if (cachedColorFunctionsShader)
|
|
1331
|
+
return cachedColorFunctionsShader;
|
|
1332
|
+
cachedColorFunctionsShader = `
|
|
1240
1333
|
|
|
1241
1334
|
vec3 saturation(vec3 rgb, float adjustment) {
|
|
1242
1335
|
const vec3 W = vec3(0.2125, 0.7154, 0.0721);
|
|
@@ -1280,6 +1373,8 @@ vec3 hsv2rgb(vec3 c)
|
|
|
1280
1373
|
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
|
|
1281
1374
|
}
|
|
1282
1375
|
`;
|
|
1376
|
+
return cachedColorFunctionsShader;
|
|
1377
|
+
};
|
|
1283
1378
|
const setLinkStyles = (link) => {
|
|
1284
1379
|
link.id = LINK_ID;
|
|
1285
1380
|
link.href = "https://neat.firecms.co";
|
|
@@ -1336,4 +1431,50 @@ function downloadURI(uri, name) {
|
|
|
1336
1431
|
link.click();
|
|
1337
1432
|
document.body.removeChild(link);
|
|
1338
1433
|
}
|
|
1434
|
+
function injectSEO() {
|
|
1435
|
+
if (document.getElementById("neat-seo-schema"))
|
|
1436
|
+
return;
|
|
1437
|
+
// 1. JSON-LD Schema
|
|
1438
|
+
const script = document.createElement('script');
|
|
1439
|
+
script.id = "neat-seo-schema";
|
|
1440
|
+
script.type = 'application/ld+json';
|
|
1441
|
+
script.text = JSON.stringify({
|
|
1442
|
+
"@context": "https://schema.org",
|
|
1443
|
+
"@type": "WebSite",
|
|
1444
|
+
"name": "NEAT Gradient",
|
|
1445
|
+
"url": "https://neat.firecms.co",
|
|
1446
|
+
"author": {
|
|
1447
|
+
"@type": "Organization",
|
|
1448
|
+
"name": "FireCMS",
|
|
1449
|
+
"url": "https://firecms.co"
|
|
1450
|
+
},
|
|
1451
|
+
"description": "Beautiful, fast, heavily customizable, WebGL based gradients."
|
|
1452
|
+
});
|
|
1453
|
+
document.head.appendChild(script);
|
|
1454
|
+
// 2. Hidden Backlink via Shadow DOM
|
|
1455
|
+
const hiddenContainer = document.createElement('div');
|
|
1456
|
+
hiddenContainer.style.position = 'absolute';
|
|
1457
|
+
hiddenContainer.style.width = '1px';
|
|
1458
|
+
hiddenContainer.style.height = '1px';
|
|
1459
|
+
hiddenContainer.style.padding = '0';
|
|
1460
|
+
hiddenContainer.style.margin = '-1px';
|
|
1461
|
+
hiddenContainer.style.overflow = 'hidden';
|
|
1462
|
+
hiddenContainer.style.clip = 'rect(0, 0, 0, 0)';
|
|
1463
|
+
hiddenContainer.style.whiteSpace = 'nowrap';
|
|
1464
|
+
hiddenContainer.style.borderWidth = '0';
|
|
1465
|
+
try {
|
|
1466
|
+
const shadow = hiddenContainer.attachShadow({ mode: 'closed' });
|
|
1467
|
+
const link = document.createElement('a');
|
|
1468
|
+
link.href = "https://firecms.co";
|
|
1469
|
+
link.textContent = "FireCMS";
|
|
1470
|
+
shadow.appendChild(link);
|
|
1471
|
+
}
|
|
1472
|
+
catch (e) {
|
|
1473
|
+
const link = document.createElement('a');
|
|
1474
|
+
link.href = "https://firecms.co";
|
|
1475
|
+
link.textContent = "FireCMS";
|
|
1476
|
+
hiddenContainer.appendChild(link);
|
|
1477
|
+
}
|
|
1478
|
+
document.body.appendChild(hiddenContainer);
|
|
1479
|
+
}
|
|
1339
1480
|
//# sourceMappingURL=NeatGradient.js.map
|