@firecms/neat 0.9.1 → 0.9.3-canary.20260615000629
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 +22 -2
- package/dist/NeatGradient.js +250 -108
- package/dist/NeatGradient.js.map +1 -1
- package/dist/index.es.js +504 -473
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +27 -12
- package/dist/index.umd.js.map +1 -1
- package/package.json +4 -2
- package/src/NeatGradient.ts +278 -111
package/dist/NeatGradient.d.ts
CHANGED
|
@@ -107,7 +107,6 @@ export declare class NeatGradient implements NeatController {
|
|
|
107
107
|
private requestRef;
|
|
108
108
|
private sizeObserver;
|
|
109
109
|
private _initialized;
|
|
110
|
-
private _linkElement;
|
|
111
110
|
private _cachedColorRgb;
|
|
112
111
|
private _yOffset;
|
|
113
112
|
private _yOffsetWaveMultiplier;
|
|
@@ -119,7 +118,6 @@ export declare class NeatGradient implements NeatController {
|
|
|
119
118
|
private _maskedCtx;
|
|
120
119
|
private _resizeTimeoutId;
|
|
121
120
|
private _textureNeedsUpdate;
|
|
122
|
-
private _linkCheckCounter;
|
|
123
121
|
private _colorsChanged;
|
|
124
122
|
private _uniformsDirty;
|
|
125
123
|
private _textureDirty;
|
|
@@ -128,6 +126,15 @@ export declare class NeatGradient implements NeatController {
|
|
|
128
126
|
private _isVisible;
|
|
129
127
|
private _visibilityObserver;
|
|
130
128
|
private _visibilityHandler;
|
|
129
|
+
private _watermarkProgram;
|
|
130
|
+
private _watermarkTexture;
|
|
131
|
+
private _watermarkBuffer;
|
|
132
|
+
private _watermarkTexCoordBuffer;
|
|
133
|
+
private _watermarkWidth;
|
|
134
|
+
private _watermarkHeight;
|
|
135
|
+
private _watermarkMargin;
|
|
136
|
+
private _wmClickHandler;
|
|
137
|
+
private _wmMoveHandler;
|
|
131
138
|
constructor(config: NeatConfig & {
|
|
132
139
|
ref: HTMLCanvasElement;
|
|
133
140
|
resolution?: number;
|
|
@@ -326,4 +333,17 @@ export declare class NeatGradient implements NeatController {
|
|
|
326
333
|
get cameraZoom(): number;
|
|
327
334
|
set cameraZoom(val: number);
|
|
328
335
|
_updateCameraFrustum(): void;
|
|
336
|
+
/**
|
|
337
|
+
* Compiles the watermark shader, creates the text texture, and sets up
|
|
338
|
+
* the screen-space quad buffers. Called once from the constructor.
|
|
339
|
+
*/
|
|
340
|
+
private _initWatermark;
|
|
341
|
+
/** Returns true if the mouse event is inside the watermark's pixel bounds. */
|
|
342
|
+
private _isOverWatermark;
|
|
343
|
+
/**
|
|
344
|
+
* Draws the watermark quad as a second pass after the main gradient.
|
|
345
|
+
* Restores the main program afterwards so the next frame's uniform
|
|
346
|
+
* uploads target the correct program.
|
|
347
|
+
*/
|
|
348
|
+
private _renderWatermark;
|
|
329
349
|
}
|
package/dist/NeatGradient.js
CHANGED
|
@@ -4,7 +4,6 @@ console.info("%c🌈 Neat Gradients%c\n\nLicensed under MIT + The Commons Clause
|
|
|
4
4
|
const PLANE_WIDTH = 50;
|
|
5
5
|
const PLANE_HEIGHT = 80;
|
|
6
6
|
const COLORS_COUNT = 6;
|
|
7
|
-
const LINK_ID = generateRandomString();
|
|
8
7
|
export class NeatGradient {
|
|
9
8
|
_ref;
|
|
10
9
|
_speed = -1;
|
|
@@ -98,7 +97,6 @@ export class NeatGradient {
|
|
|
98
97
|
requestRef = -1;
|
|
99
98
|
sizeObserver;
|
|
100
99
|
_initialized = false;
|
|
101
|
-
_linkElement = null;
|
|
102
100
|
_cachedColorRgb = [];
|
|
103
101
|
_yOffset = 0;
|
|
104
102
|
_yOffsetWaveMultiplier = 0.004;
|
|
@@ -112,7 +110,6 @@ export class NeatGradient {
|
|
|
112
110
|
// Performance optimizations
|
|
113
111
|
_resizeTimeoutId = null;
|
|
114
112
|
_textureNeedsUpdate = false;
|
|
115
|
-
_linkCheckCounter = 0;
|
|
116
113
|
_colorsChanged = true;
|
|
117
114
|
_uniformsDirty = true;
|
|
118
115
|
_textureDirty = true;
|
|
@@ -121,6 +118,16 @@ export class NeatGradient {
|
|
|
121
118
|
_isVisible = true;
|
|
122
119
|
_visibilityObserver = null;
|
|
123
120
|
_visibilityHandler = null;
|
|
121
|
+
// Watermark overlay (rendered inside the canvas via a separate WebGL pass)
|
|
122
|
+
_watermarkProgram = null;
|
|
123
|
+
_watermarkTexture = null;
|
|
124
|
+
_watermarkBuffer = null;
|
|
125
|
+
_watermarkTexCoordBuffer = null;
|
|
126
|
+
_watermarkWidth = 0;
|
|
127
|
+
_watermarkHeight = 0;
|
|
128
|
+
_watermarkMargin = 4;
|
|
129
|
+
_wmClickHandler = null;
|
|
130
|
+
_wmMoveHandler = null;
|
|
124
131
|
constructor(config) {
|
|
125
132
|
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,
|
|
126
133
|
// Flow field parameters
|
|
@@ -220,19 +227,12 @@ export class NeatGradient {
|
|
|
220
227
|
this._planeBend = planeBend;
|
|
221
228
|
this._planeTwist = planeTwist;
|
|
222
229
|
this.glState = this._initScene(resolution);
|
|
223
|
-
|
|
230
|
+
this._initWatermark();
|
|
231
|
+
injectMetaGenerator();
|
|
224
232
|
let tick = seed !== undefined ? seed : getElapsedSecondsInLastHour();
|
|
225
233
|
let lastTime = performance.now();
|
|
226
234
|
const render = () => {
|
|
227
235
|
const { gl, program, locations, indexCount, indexType } = this.glState;
|
|
228
|
-
// Optimization: check if cached link is still valid in DOM less frequently
|
|
229
|
-
this._linkCheckCounter++;
|
|
230
|
-
if (this._linkCheckCounter >= 300) { // Check every ~5 seconds at 60fps
|
|
231
|
-
this._linkCheckCounter = 0;
|
|
232
|
-
if (!this._linkElement || !document.contains(this._linkElement)) {
|
|
233
|
-
this._linkElement = addNeatLink(ref);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
236
|
if (this._initialized) {
|
|
237
237
|
const timeNow = performance.now();
|
|
238
238
|
tick += ((timeNow - lastTime) / 1000) * this._speed;
|
|
@@ -380,6 +380,8 @@ export class NeatGradient {
|
|
|
380
380
|
else {
|
|
381
381
|
gl.drawElements(gl.TRIANGLES, indexCount, indexType, 0);
|
|
382
382
|
}
|
|
383
|
+
// Draw watermark overlay inside the canvas
|
|
384
|
+
this._renderWatermark(gl);
|
|
383
385
|
if (this._isVisible) {
|
|
384
386
|
this.requestRef = requestAnimationFrame(render);
|
|
385
387
|
}
|
|
@@ -453,10 +455,14 @@ export class NeatGradient {
|
|
|
453
455
|
clearTimeout(this._resizeTimeoutId);
|
|
454
456
|
this._resizeTimeoutId = null;
|
|
455
457
|
}
|
|
456
|
-
// Remove
|
|
457
|
-
if (this.
|
|
458
|
-
|
|
459
|
-
this.
|
|
458
|
+
// Remove watermark click/hover listeners
|
|
459
|
+
if (this._wmClickHandler) {
|
|
460
|
+
document.removeEventListener('click', this._wmClickHandler, true);
|
|
461
|
+
this._wmClickHandler = null;
|
|
462
|
+
}
|
|
463
|
+
if (this._wmMoveHandler) {
|
|
464
|
+
document.removeEventListener('mousemove', this._wmMoveHandler);
|
|
465
|
+
this._wmMoveHandler = null;
|
|
460
466
|
}
|
|
461
467
|
// Cleanup WebGL resources
|
|
462
468
|
if (this.glState) {
|
|
@@ -467,6 +473,15 @@ export class NeatGradient {
|
|
|
467
473
|
gl.deleteBuffer(this.glState.buffers.uv);
|
|
468
474
|
gl.deleteBuffer(this.glState.buffers.index);
|
|
469
475
|
gl.deleteBuffer(this.glState.buffers.wireframeIndex);
|
|
476
|
+
// Cleanup watermark resources
|
|
477
|
+
if (this._watermarkProgram)
|
|
478
|
+
gl.deleteProgram(this._watermarkProgram);
|
|
479
|
+
if (this._watermarkTexture)
|
|
480
|
+
gl.deleteTexture(this._watermarkTexture);
|
|
481
|
+
if (this._watermarkBuffer)
|
|
482
|
+
gl.deleteBuffer(this._watermarkBuffer);
|
|
483
|
+
if (this._watermarkTexCoordBuffer)
|
|
484
|
+
gl.deleteBuffer(this._watermarkTexCoordBuffer);
|
|
470
485
|
}
|
|
471
486
|
if (this._proceduralTexture && this.glState) {
|
|
472
487
|
this.glState.gl.deleteTexture(this._proceduralTexture);
|
|
@@ -1713,61 +1728,203 @@ export class NeatGradient {
|
|
|
1713
1728
|
gl.uniformMatrix4fv(projLoc, false, this.glState.camera.projectionMatrix.elements);
|
|
1714
1729
|
this._uniformsDirty = true;
|
|
1715
1730
|
}
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
const
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1731
|
+
/**
|
|
1732
|
+
* Compiles the watermark shader, creates the text texture, and sets up
|
|
1733
|
+
* the screen-space quad buffers. Called once from the constructor.
|
|
1734
|
+
*/
|
|
1735
|
+
_initWatermark() {
|
|
1736
|
+
const gl = this.glState.gl;
|
|
1737
|
+
// ── 1. Compile watermark shader program ──
|
|
1738
|
+
const vs = gl.createShader(gl.VERTEX_SHADER);
|
|
1739
|
+
gl.shaderSource(vs, WATERMARK_VS);
|
|
1740
|
+
gl.compileShader(vs);
|
|
1741
|
+
const fs = gl.createShader(gl.FRAGMENT_SHADER);
|
|
1742
|
+
gl.shaderSource(fs, WATERMARK_FS);
|
|
1743
|
+
gl.compileShader(fs);
|
|
1744
|
+
const prog = gl.createProgram();
|
|
1745
|
+
gl.attachShader(prog, vs);
|
|
1746
|
+
gl.attachShader(prog, fs);
|
|
1747
|
+
gl.linkProgram(prog);
|
|
1748
|
+
this._watermarkProgram = prog;
|
|
1749
|
+
// Shaders are linked; we can free them
|
|
1750
|
+
gl.deleteShader(vs);
|
|
1751
|
+
gl.deleteShader(fs);
|
|
1752
|
+
// ── 2. Rasterise "NEAT" text into an offscreen canvas ──
|
|
1753
|
+
// No DPR scaling: the canvas buffer is set to clientWidth/clientHeight
|
|
1754
|
+
// (1x CSS pixels), so the watermark must match.
|
|
1755
|
+
const fontSize = 11;
|
|
1756
|
+
const padX = 6;
|
|
1757
|
+
const padY = 5;
|
|
1758
|
+
const measure = document.createElement('canvas').getContext('2d');
|
|
1759
|
+
measure.font = `bold ${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`;
|
|
1760
|
+
const metrics = measure.measureText('NEAT');
|
|
1761
|
+
const textW = Math.ceil(metrics.width);
|
|
1762
|
+
const textH = fontSize;
|
|
1763
|
+
const cw = textW + padX * 2;
|
|
1764
|
+
const ch = textH + padY * 2;
|
|
1765
|
+
this._watermarkWidth = cw;
|
|
1766
|
+
this._watermarkHeight = ch;
|
|
1767
|
+
const c = document.createElement('canvas');
|
|
1768
|
+
c.width = cw;
|
|
1769
|
+
c.height = ch;
|
|
1770
|
+
const ctx = c.getContext('2d');
|
|
1771
|
+
// Transparent background — no fill needed
|
|
1772
|
+
ctx.clearRect(0, 0, cw, ch);
|
|
1773
|
+
// Subtle shadow for readability on any gradient
|
|
1774
|
+
ctx.shadowColor = 'rgba(0,0,0,0.4)';
|
|
1775
|
+
ctx.shadowBlur = 2;
|
|
1776
|
+
ctx.shadowOffsetX = 1;
|
|
1777
|
+
ctx.shadowOffsetY = 1;
|
|
1778
|
+
ctx.font = `bold ${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`;
|
|
1779
|
+
ctx.textAlign = 'center';
|
|
1780
|
+
ctx.textBaseline = 'middle';
|
|
1781
|
+
ctx.fillStyle = 'rgba(255,255,255,0.5)';
|
|
1782
|
+
ctx.fillText('NEAT', cw / 2, ch / 2);
|
|
1783
|
+
// ── 3. Upload as a WebGL texture ──
|
|
1784
|
+
const tex = gl.createTexture();
|
|
1785
|
+
gl.activeTexture(gl.TEXTURE2); // Use unit 2 to avoid collision with procedural (unit 1)
|
|
1786
|
+
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
1787
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
1788
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
1789
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
1790
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
1791
|
+
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
|
|
1792
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c);
|
|
1793
|
+
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
|
|
1794
|
+
this._watermarkTexture = tex;
|
|
1795
|
+
// ── 4. Create static tex-coord buffer (never changes) ──
|
|
1796
|
+
const tcBuf = gl.createBuffer();
|
|
1797
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, tcBuf);
|
|
1798
|
+
// Two-triangle strip: BL, BR, TL, TR
|
|
1799
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 1, 1, 1, 0, 0, 1, 0]), gl.STATIC_DRAW);
|
|
1800
|
+
this._watermarkTexCoordBuffer = tcBuf;
|
|
1801
|
+
// ── 5. Create position buffer (updated per-frame based on canvas size) ──
|
|
1802
|
+
const posBuf = gl.createBuffer();
|
|
1803
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, posBuf);
|
|
1804
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(8), gl.DYNAMIC_DRAW);
|
|
1805
|
+
this._watermarkBuffer = posBuf;
|
|
1806
|
+
// ── 6. Make the watermark region clickable ──
|
|
1807
|
+
// We listen on `document` (capture phase for click) because the canvas
|
|
1808
|
+
// is often covered by overlay elements (scroll containers, UI panels)
|
|
1809
|
+
// that would swallow canvas-level events.
|
|
1810
|
+
this._wmClickHandler = (e) => {
|
|
1811
|
+
if (this._isOverWatermark(e)) {
|
|
1812
|
+
e.preventDefault();
|
|
1813
|
+
e.stopPropagation();
|
|
1814
|
+
window.open('https://neat.firecms.co', '_blank', 'noopener');
|
|
1815
|
+
}
|
|
1816
|
+
};
|
|
1817
|
+
this._wmMoveHandler = (e) => {
|
|
1818
|
+
const over = this._isOverWatermark(e);
|
|
1819
|
+
this._ref.style.cursor = over ? 'pointer' : '';
|
|
1820
|
+
// Propagate pointer cursor to overlays that sit on top of the canvas
|
|
1821
|
+
if (over) {
|
|
1822
|
+
document.body.style.cursor = 'pointer';
|
|
1823
|
+
}
|
|
1824
|
+
else if (document.body.style.cursor === 'pointer') {
|
|
1825
|
+
document.body.style.cursor = '';
|
|
1826
|
+
}
|
|
1827
|
+
};
|
|
1828
|
+
document.addEventListener('click', this._wmClickHandler, true); // capture phase
|
|
1829
|
+
document.addEventListener('mousemove', this._wmMoveHandler);
|
|
1830
|
+
}
|
|
1831
|
+
/** Returns true if the mouse event is inside the watermark's pixel bounds. */
|
|
1832
|
+
_isOverWatermark(e) {
|
|
1833
|
+
const rect = this._ref.getBoundingClientRect();
|
|
1834
|
+
// Mouse position relative to the canvas element
|
|
1835
|
+
const x = e.clientX - rect.left;
|
|
1836
|
+
const y = e.clientY - rect.top;
|
|
1837
|
+
const cw = rect.width;
|
|
1838
|
+
const ch = rect.height;
|
|
1839
|
+
// Quick rejection: outside the canvas entirely
|
|
1840
|
+
if (x < 0 || y < 0 || x > cw || y > ch)
|
|
1841
|
+
return false;
|
|
1842
|
+
const m = this._watermarkMargin;
|
|
1843
|
+
const ww = this._watermarkWidth;
|
|
1844
|
+
const wh = this._watermarkHeight;
|
|
1845
|
+
// Watermark sits in the bottom-right corner
|
|
1846
|
+
const left = cw - m - ww;
|
|
1847
|
+
const top = ch - m - wh;
|
|
1848
|
+
return x >= left && x <= cw - m
|
|
1849
|
+
&& y >= top && y <= ch - m;
|
|
1750
1850
|
}
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1851
|
+
/**
|
|
1852
|
+
* Draws the watermark quad as a second pass after the main gradient.
|
|
1853
|
+
* Restores the main program afterwards so the next frame's uniform
|
|
1854
|
+
* uploads target the correct program.
|
|
1855
|
+
*/
|
|
1856
|
+
_renderWatermark(gl) {
|
|
1857
|
+
const prog = this._watermarkProgram;
|
|
1858
|
+
const tex = this._watermarkTexture;
|
|
1859
|
+
const posBuf = this._watermarkBuffer;
|
|
1860
|
+
const tcBuf = this._watermarkTexCoordBuffer;
|
|
1861
|
+
if (!prog || !tex || !posBuf || !tcBuf)
|
|
1862
|
+
return;
|
|
1863
|
+
const canvasW = this._ref.width || this._ref.clientWidth;
|
|
1864
|
+
const canvasH = this._ref.height || this._ref.clientHeight;
|
|
1865
|
+
if (canvasW === 0 || canvasH === 0)
|
|
1866
|
+
return;
|
|
1867
|
+
// Compute quad position in clip space (-1 … +1).
|
|
1868
|
+
// Place the watermark in the bottom-right corner with a small margin.
|
|
1869
|
+
const margin = 4;
|
|
1870
|
+
const qw = this._watermarkWidth;
|
|
1871
|
+
const qh = this._watermarkHeight;
|
|
1872
|
+
// Pixel → clip-space conversion
|
|
1873
|
+
const r = 1.0 - (margin / canvasW) * 2.0; // right edge
|
|
1874
|
+
const l = r - (qw / canvasW) * 2.0; // left edge
|
|
1875
|
+
const b = -1.0 + (margin / canvasH) * 2.0; // bottom edge
|
|
1876
|
+
const t = b + (qh / canvasH) * 2.0; // top edge
|
|
1877
|
+
// Update position buffer (triangle strip: BL, BR, TL, TR)
|
|
1878
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, posBuf);
|
|
1879
|
+
gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array([l, b, r, b, l, t, r, t]));
|
|
1880
|
+
// ── Switch to watermark shader ──
|
|
1881
|
+
gl.useProgram(prog);
|
|
1882
|
+
gl.disable(gl.DEPTH_TEST);
|
|
1883
|
+
// Use premultiplied alpha blending since we uploaded with PREMULTIPLY_ALPHA
|
|
1884
|
+
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
1885
|
+
// Bind position attribute
|
|
1886
|
+
const aPos = gl.getAttribLocation(prog, 'a_wm_position');
|
|
1887
|
+
gl.enableVertexAttribArray(aPos);
|
|
1888
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, posBuf);
|
|
1889
|
+
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);
|
|
1890
|
+
// Bind texcoord attribute
|
|
1891
|
+
const aTc = gl.getAttribLocation(prog, 'a_wm_texcoord');
|
|
1892
|
+
gl.enableVertexAttribArray(aTc);
|
|
1893
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, tcBuf);
|
|
1894
|
+
gl.vertexAttribPointer(aTc, 2, gl.FLOAT, false, 0, 0);
|
|
1895
|
+
// Bind watermark texture on unit 2
|
|
1896
|
+
gl.activeTexture(gl.TEXTURE2);
|
|
1897
|
+
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
1898
|
+
gl.uniform1i(gl.getUniformLocation(prog, 'u_wm_texture'), 2);
|
|
1899
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
1900
|
+
// ── Restore state for the next gradient frame ──
|
|
1901
|
+
gl.enable(gl.DEPTH_TEST);
|
|
1902
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
1903
|
+
gl.useProgram(this.glState.program);
|
|
1904
|
+
// Re-enable and re-bind the gradient's vertex attributes.
|
|
1905
|
+
// We must call enableVertexAttribArray again because the watermark's
|
|
1906
|
+
// attribute locations may overlap with the gradient's (WebGL assigns
|
|
1907
|
+
// locations globally starting from 0). Without this, the gradient's
|
|
1908
|
+
// position/normal/uv arrays stay disabled after the watermark draw.
|
|
1909
|
+
const locs = this.glState.locations.attributes;
|
|
1910
|
+
gl.enableVertexAttribArray(locs.position);
|
|
1911
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.glState.buffers.position);
|
|
1912
|
+
gl.vertexAttribPointer(locs.position, 3, gl.FLOAT, false, 0, 0);
|
|
1913
|
+
gl.enableVertexAttribArray(locs.normal);
|
|
1914
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.glState.buffers.normal);
|
|
1915
|
+
gl.vertexAttribPointer(locs.normal, 3, gl.FLOAT, false, 0, 0);
|
|
1916
|
+
gl.enableVertexAttribArray(locs.uv);
|
|
1917
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.glState.buffers.uv);
|
|
1918
|
+
gl.vertexAttribPointer(locs.uv, 2, gl.FLOAT, false, 0, 0);
|
|
1919
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.glState.buffers.index);
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1756
1922
|
function getElapsedSecondsInLastHour() {
|
|
1757
1923
|
const now = new Date();
|
|
1758
1924
|
const minutes = now.getMinutes();
|
|
1759
1925
|
const seconds = now.getSeconds();
|
|
1760
1926
|
return (minutes * 60) + seconds;
|
|
1761
1927
|
}
|
|
1762
|
-
function generateRandomString(length = 6) {
|
|
1763
|
-
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
1764
|
-
let result = '';
|
|
1765
|
-
for (let i = 0; i < length; i++) {
|
|
1766
|
-
const randomIndex = Math.floor(Math.random() * characters.length);
|
|
1767
|
-
result += characters.charAt(randomIndex);
|
|
1768
|
-
}
|
|
1769
|
-
return result;
|
|
1770
|
-
}
|
|
1771
1928
|
function downloadURI(uri, name) {
|
|
1772
1929
|
const link = document.createElement("a");
|
|
1773
1930
|
link.download = name;
|
|
@@ -1776,50 +1933,35 @@ function downloadURI(uri, name) {
|
|
|
1776
1933
|
link.click();
|
|
1777
1934
|
document.body.removeChild(link);
|
|
1778
1935
|
}
|
|
1779
|
-
|
|
1780
|
-
|
|
1936
|
+
/**
|
|
1937
|
+
* Injects a <meta name="generator"> tag — the industry-standard, SEO-safe
|
|
1938
|
+
* way for tools/libraries to identify themselves (used by WordPress, Hugo, etc.).
|
|
1939
|
+
* This is semantically correct and will not harm the end-user's SEO.
|
|
1940
|
+
*/
|
|
1941
|
+
function injectMetaGenerator() {
|
|
1942
|
+
if (document.querySelector('meta[name="generator"][content*="NEAT"]'))
|
|
1781
1943
|
return;
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
hiddenContainer.style.height = '1px';
|
|
1804
|
-
hiddenContainer.style.padding = '0';
|
|
1805
|
-
hiddenContainer.style.margin = '-1px';
|
|
1806
|
-
hiddenContainer.style.overflow = 'hidden';
|
|
1807
|
-
hiddenContainer.style.clip = 'rect(0, 0, 0, 0)';
|
|
1808
|
-
hiddenContainer.style.whiteSpace = 'nowrap';
|
|
1809
|
-
hiddenContainer.style.borderWidth = '0';
|
|
1810
|
-
try {
|
|
1811
|
-
const shadow = hiddenContainer.attachShadow({ mode: 'closed' });
|
|
1812
|
-
const link = document.createElement('a');
|
|
1813
|
-
link.href = "https://firecms.co";
|
|
1814
|
-
link.textContent = "FireCMS";
|
|
1815
|
-
shadow.appendChild(link);
|
|
1816
|
-
}
|
|
1817
|
-
catch (e) {
|
|
1818
|
-
const link = document.createElement('a');
|
|
1819
|
-
link.href = "https://firecms.co";
|
|
1820
|
-
link.textContent = "FireCMS";
|
|
1821
|
-
hiddenContainer.appendChild(link);
|
|
1822
|
-
}
|
|
1823
|
-
document.body.appendChild(hiddenContainer);
|
|
1944
|
+
const meta = document.createElement('meta');
|
|
1945
|
+
meta.name = 'generator';
|
|
1946
|
+
meta.content = 'NEAT by FireCMS — https://neat.firecms.co';
|
|
1947
|
+
document.head.appendChild(meta);
|
|
1948
|
+
}
|
|
1949
|
+
// ── Watermark shaders (minimal pass-through for a textured screen quad) ──
|
|
1950
|
+
const WATERMARK_VS = `
|
|
1951
|
+
attribute vec2 a_wm_position;
|
|
1952
|
+
attribute vec2 a_wm_texcoord;
|
|
1953
|
+
varying vec2 v_wm_texcoord;
|
|
1954
|
+
void main() {
|
|
1955
|
+
gl_Position = vec4(a_wm_position, 0.0, 1.0);
|
|
1956
|
+
v_wm_texcoord = a_wm_texcoord;
|
|
1957
|
+
}
|
|
1958
|
+
`;
|
|
1959
|
+
const WATERMARK_FS = `
|
|
1960
|
+
precision mediump float;
|
|
1961
|
+
varying vec2 v_wm_texcoord;
|
|
1962
|
+
uniform sampler2D u_wm_texture;
|
|
1963
|
+
void main() {
|
|
1964
|
+
gl_FragColor = texture2D(u_wm_texture, v_wm_texcoord);
|
|
1824
1965
|
}
|
|
1966
|
+
`;
|
|
1825
1967
|
//# sourceMappingURL=NeatGradient.js.map
|