@firecms/neat 0.9.1 → 0.9.3-canary.20260614235729
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 +23 -2
- package/dist/NeatGradient.js +268 -110
- package/dist/NeatGradient.js.map +1 -1
- package/dist/index.es.js +584 -488
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +27 -12
- package/dist/index.umd.js.map +1 -1
- package/dist/license.d.ts +32 -0
- package/dist/license.js +119 -0
- package/dist/license.js.map +1 -0
- package/dist/types.d.ts +6 -0
- package/package.json +4 -2
- package/src/NeatGradient.ts +299 -115
- package/src/license.ts +154 -0
- package/src/types.ts +6 -0
package/dist/NeatGradient.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export interface WebGLState {
|
|
|
21
21
|
import { NeatConfig, NeatColor, NeatController } from "./types";
|
|
22
22
|
export declare class NeatGradient implements NeatController {
|
|
23
23
|
private _ref;
|
|
24
|
+
private _licensed;
|
|
24
25
|
private _speed;
|
|
25
26
|
private _horizontalPressure;
|
|
26
27
|
private _verticalPressure;
|
|
@@ -107,7 +108,6 @@ export declare class NeatGradient implements NeatController {
|
|
|
107
108
|
private requestRef;
|
|
108
109
|
private sizeObserver;
|
|
109
110
|
private _initialized;
|
|
110
|
-
private _linkElement;
|
|
111
111
|
private _cachedColorRgb;
|
|
112
112
|
private _yOffset;
|
|
113
113
|
private _yOffsetWaveMultiplier;
|
|
@@ -119,7 +119,6 @@ export declare class NeatGradient implements NeatController {
|
|
|
119
119
|
private _maskedCtx;
|
|
120
120
|
private _resizeTimeoutId;
|
|
121
121
|
private _textureNeedsUpdate;
|
|
122
|
-
private _linkCheckCounter;
|
|
123
122
|
private _colorsChanged;
|
|
124
123
|
private _uniformsDirty;
|
|
125
124
|
private _textureDirty;
|
|
@@ -128,6 +127,15 @@ export declare class NeatGradient implements NeatController {
|
|
|
128
127
|
private _isVisible;
|
|
129
128
|
private _visibilityObserver;
|
|
130
129
|
private _visibilityHandler;
|
|
130
|
+
private _watermarkProgram;
|
|
131
|
+
private _watermarkTexture;
|
|
132
|
+
private _watermarkBuffer;
|
|
133
|
+
private _watermarkTexCoordBuffer;
|
|
134
|
+
private _watermarkWidth;
|
|
135
|
+
private _watermarkHeight;
|
|
136
|
+
private _watermarkMargin;
|
|
137
|
+
private _wmClickHandler;
|
|
138
|
+
private _wmMoveHandler;
|
|
131
139
|
constructor(config: NeatConfig & {
|
|
132
140
|
ref: HTMLCanvasElement;
|
|
133
141
|
resolution?: number;
|
|
@@ -326,4 +334,17 @@ export declare class NeatGradient implements NeatController {
|
|
|
326
334
|
get cameraZoom(): number;
|
|
327
335
|
set cameraZoom(val: number);
|
|
328
336
|
_updateCameraFrustum(): void;
|
|
337
|
+
/**
|
|
338
|
+
* Compiles the watermark shader, creates the text texture, and sets up
|
|
339
|
+
* the screen-space quad buffers. Called once from the constructor.
|
|
340
|
+
*/
|
|
341
|
+
private _initWatermark;
|
|
342
|
+
/** Returns true if the mouse event is inside the watermark's pixel bounds. */
|
|
343
|
+
private _isOverWatermark;
|
|
344
|
+
/**
|
|
345
|
+
* Draws the watermark quad as a second pass after the main gradient.
|
|
346
|
+
* Restores the main program afterwards so the next frame's uniform
|
|
347
|
+
* uploads target the correct program.
|
|
348
|
+
*/
|
|
349
|
+
private _renderWatermark;
|
|
329
350
|
}
|
package/dist/NeatGradient.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { buildColorFunctions, buildNoise, buildVertUniforms, buildFragUniforms, fragmentShaderSource, vertexShaderSource } from "./shaders";
|
|
2
2
|
import { generatePlaneGeometry, generateSphereGeometry, generateTorusGeometry, generateCylinderGeometry, generateRibbonGeometry, OrthographicCamera, updateCamera, Matrix4 } from "./math";
|
|
3
|
-
|
|
3
|
+
import { verifyLicenseKey } from "./license";
|
|
4
|
+
function _logBranding() {
|
|
5
|
+
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;");
|
|
6
|
+
}
|
|
4
7
|
const PLANE_WIDTH = 50;
|
|
5
8
|
const PLANE_HEIGHT = 80;
|
|
6
9
|
const COLORS_COUNT = 6;
|
|
7
|
-
const LINK_ID = generateRandomString();
|
|
8
10
|
export class NeatGradient {
|
|
9
11
|
_ref;
|
|
12
|
+
_licensed = false;
|
|
10
13
|
_speed = -1;
|
|
11
14
|
_horizontalPressure = -1;
|
|
12
15
|
_verticalPressure = -1;
|
|
@@ -98,7 +101,6 @@ export class NeatGradient {
|
|
|
98
101
|
requestRef = -1;
|
|
99
102
|
sizeObserver;
|
|
100
103
|
_initialized = false;
|
|
101
|
-
_linkElement = null;
|
|
102
104
|
_cachedColorRgb = [];
|
|
103
105
|
_yOffset = 0;
|
|
104
106
|
_yOffsetWaveMultiplier = 0.004;
|
|
@@ -112,7 +114,6 @@ export class NeatGradient {
|
|
|
112
114
|
// Performance optimizations
|
|
113
115
|
_resizeTimeoutId = null;
|
|
114
116
|
_textureNeedsUpdate = false;
|
|
115
|
-
_linkCheckCounter = 0;
|
|
116
117
|
_colorsChanged = true;
|
|
117
118
|
_uniformsDirty = true;
|
|
118
119
|
_textureDirty = true;
|
|
@@ -121,6 +122,16 @@ export class NeatGradient {
|
|
|
121
122
|
_isVisible = true;
|
|
122
123
|
_visibilityObserver = null;
|
|
123
124
|
_visibilityHandler = null;
|
|
125
|
+
// Watermark overlay (rendered inside the canvas via a separate WebGL pass)
|
|
126
|
+
_watermarkProgram = null;
|
|
127
|
+
_watermarkTexture = null;
|
|
128
|
+
_watermarkBuffer = null;
|
|
129
|
+
_watermarkTexCoordBuffer = null;
|
|
130
|
+
_watermarkWidth = 0;
|
|
131
|
+
_watermarkHeight = 0;
|
|
132
|
+
_watermarkMargin = 4;
|
|
133
|
+
_wmClickHandler = null;
|
|
134
|
+
_wmMoveHandler = null;
|
|
124
135
|
constructor(config) {
|
|
125
136
|
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
137
|
// Flow field parameters
|
|
@@ -130,7 +141,7 @@ export class NeatGradient {
|
|
|
130
141
|
// Camera configuration
|
|
131
142
|
cameraLock = false, cameraX = 0, cameraY = 0, cameraZ = 0, cameraRotationX = 0, cameraRotationY = 0, cameraRotationZ = 0, cameraZoom = 1.0,
|
|
132
143
|
// 3D shapes default
|
|
133
|
-
shapeType = 'plane', shapeRotationX = 0, shapeRotationY = 0, shapeRotationZ = 0, shapeAutoRotateSpeedX = 0, shapeAutoRotateSpeedY = 0, sphereRadius = 15, torusRadius = 15, torusTube = 5, cylinderRadius = 10, cylinderHeight = 40, planeBend = 0, planeTwist = 0, } = config;
|
|
144
|
+
shapeType = 'plane', shapeRotationX = 0, shapeRotationY = 0, shapeRotationZ = 0, shapeAutoRotateSpeedX = 0, shapeAutoRotateSpeedY = 0, sphereRadius = 15, torusRadius = 15, torusTube = 5, cylinderRadius = 10, cylinderHeight = 40, planeBend = 0, planeTwist = 0, licenseKey, } = config;
|
|
134
145
|
this._ref = ref;
|
|
135
146
|
this.destroy = this.destroy.bind(this);
|
|
136
147
|
this._initScene = this._initScene.bind(this);
|
|
@@ -220,19 +231,21 @@ export class NeatGradient {
|
|
|
220
231
|
this._planeBend = planeBend;
|
|
221
232
|
this._planeTwist = planeTwist;
|
|
222
233
|
this.glState = this._initScene(resolution);
|
|
223
|
-
|
|
234
|
+
this._initWatermark();
|
|
235
|
+
injectMetaGenerator();
|
|
236
|
+
// License verification — async, watermark renders until verified
|
|
237
|
+
if (licenseKey) {
|
|
238
|
+
verifyLicenseKey(licenseKey).then((result) => {
|
|
239
|
+
this._licensed = result.valid;
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
_logBranding();
|
|
244
|
+
}
|
|
224
245
|
let tick = seed !== undefined ? seed : getElapsedSecondsInLastHour();
|
|
225
246
|
let lastTime = performance.now();
|
|
226
247
|
const render = () => {
|
|
227
248
|
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
249
|
if (this._initialized) {
|
|
237
250
|
const timeNow = performance.now();
|
|
238
251
|
tick += ((timeNow - lastTime) / 1000) * this._speed;
|
|
@@ -380,6 +393,8 @@ export class NeatGradient {
|
|
|
380
393
|
else {
|
|
381
394
|
gl.drawElements(gl.TRIANGLES, indexCount, indexType, 0);
|
|
382
395
|
}
|
|
396
|
+
// Draw watermark overlay inside the canvas
|
|
397
|
+
this._renderWatermark(gl);
|
|
383
398
|
if (this._isVisible) {
|
|
384
399
|
this.requestRef = requestAnimationFrame(render);
|
|
385
400
|
}
|
|
@@ -453,10 +468,14 @@ export class NeatGradient {
|
|
|
453
468
|
clearTimeout(this._resizeTimeoutId);
|
|
454
469
|
this._resizeTimeoutId = null;
|
|
455
470
|
}
|
|
456
|
-
// Remove
|
|
457
|
-
if (this.
|
|
458
|
-
|
|
459
|
-
this.
|
|
471
|
+
// Remove watermark click/hover listeners
|
|
472
|
+
if (this._wmClickHandler) {
|
|
473
|
+
document.removeEventListener('click', this._wmClickHandler, true);
|
|
474
|
+
this._wmClickHandler = null;
|
|
475
|
+
}
|
|
476
|
+
if (this._wmMoveHandler) {
|
|
477
|
+
document.removeEventListener('mousemove', this._wmMoveHandler);
|
|
478
|
+
this._wmMoveHandler = null;
|
|
460
479
|
}
|
|
461
480
|
// Cleanup WebGL resources
|
|
462
481
|
if (this.glState) {
|
|
@@ -467,6 +486,15 @@ export class NeatGradient {
|
|
|
467
486
|
gl.deleteBuffer(this.glState.buffers.uv);
|
|
468
487
|
gl.deleteBuffer(this.glState.buffers.index);
|
|
469
488
|
gl.deleteBuffer(this.glState.buffers.wireframeIndex);
|
|
489
|
+
// Cleanup watermark resources
|
|
490
|
+
if (this._watermarkProgram)
|
|
491
|
+
gl.deleteProgram(this._watermarkProgram);
|
|
492
|
+
if (this._watermarkTexture)
|
|
493
|
+
gl.deleteTexture(this._watermarkTexture);
|
|
494
|
+
if (this._watermarkBuffer)
|
|
495
|
+
gl.deleteBuffer(this._watermarkBuffer);
|
|
496
|
+
if (this._watermarkTexCoordBuffer)
|
|
497
|
+
gl.deleteBuffer(this._watermarkTexCoordBuffer);
|
|
470
498
|
}
|
|
471
499
|
if (this._proceduralTexture && this.glState) {
|
|
472
500
|
this.glState.gl.deleteTexture(this._proceduralTexture);
|
|
@@ -1713,61 +1741,206 @@ export class NeatGradient {
|
|
|
1713
1741
|
gl.uniformMatrix4fv(projLoc, false, this.glState.camera.projectionMatrix.elements);
|
|
1714
1742
|
this._uniformsDirty = true;
|
|
1715
1743
|
}
|
|
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
|
-
|
|
1744
|
+
/**
|
|
1745
|
+
* Compiles the watermark shader, creates the text texture, and sets up
|
|
1746
|
+
* the screen-space quad buffers. Called once from the constructor.
|
|
1747
|
+
*/
|
|
1748
|
+
_initWatermark() {
|
|
1749
|
+
const gl = this.glState.gl;
|
|
1750
|
+
// ── 1. Compile watermark shader program ──
|
|
1751
|
+
const vs = gl.createShader(gl.VERTEX_SHADER);
|
|
1752
|
+
gl.shaderSource(vs, WATERMARK_VS);
|
|
1753
|
+
gl.compileShader(vs);
|
|
1754
|
+
const fs = gl.createShader(gl.FRAGMENT_SHADER);
|
|
1755
|
+
gl.shaderSource(fs, WATERMARK_FS);
|
|
1756
|
+
gl.compileShader(fs);
|
|
1757
|
+
const prog = gl.createProgram();
|
|
1758
|
+
gl.attachShader(prog, vs);
|
|
1759
|
+
gl.attachShader(prog, fs);
|
|
1760
|
+
gl.linkProgram(prog);
|
|
1761
|
+
this._watermarkProgram = prog;
|
|
1762
|
+
// Shaders are linked; we can free them
|
|
1763
|
+
gl.deleteShader(vs);
|
|
1764
|
+
gl.deleteShader(fs);
|
|
1765
|
+
// ── 2. Rasterise "NEAT" text into an offscreen canvas ──
|
|
1766
|
+
// No DPR scaling: the canvas buffer is set to clientWidth/clientHeight
|
|
1767
|
+
// (1x CSS pixels), so the watermark must match.
|
|
1768
|
+
const fontSize = 11;
|
|
1769
|
+
const padX = 6;
|
|
1770
|
+
const padY = 5;
|
|
1771
|
+
const measure = document.createElement('canvas').getContext('2d');
|
|
1772
|
+
measure.font = `bold ${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`;
|
|
1773
|
+
const metrics = measure.measureText('NEAT');
|
|
1774
|
+
const textW = Math.ceil(metrics.width);
|
|
1775
|
+
const textH = fontSize;
|
|
1776
|
+
const cw = textW + padX * 2;
|
|
1777
|
+
const ch = textH + padY * 2;
|
|
1778
|
+
this._watermarkWidth = cw;
|
|
1779
|
+
this._watermarkHeight = ch;
|
|
1780
|
+
const c = document.createElement('canvas');
|
|
1781
|
+
c.width = cw;
|
|
1782
|
+
c.height = ch;
|
|
1783
|
+
const ctx = c.getContext('2d');
|
|
1784
|
+
// Transparent background — no fill needed
|
|
1785
|
+
ctx.clearRect(0, 0, cw, ch);
|
|
1786
|
+
// Subtle shadow for readability on any gradient
|
|
1787
|
+
ctx.shadowColor = 'rgba(0,0,0,0.4)';
|
|
1788
|
+
ctx.shadowBlur = 2;
|
|
1789
|
+
ctx.shadowOffsetX = 1;
|
|
1790
|
+
ctx.shadowOffsetY = 1;
|
|
1791
|
+
ctx.font = `bold ${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`;
|
|
1792
|
+
ctx.textAlign = 'center';
|
|
1793
|
+
ctx.textBaseline = 'middle';
|
|
1794
|
+
ctx.fillStyle = 'rgba(255,255,255,0.5)';
|
|
1795
|
+
ctx.fillText('NEAT', cw / 2, ch / 2);
|
|
1796
|
+
// ── 3. Upload as a WebGL texture ──
|
|
1797
|
+
const tex = gl.createTexture();
|
|
1798
|
+
gl.activeTexture(gl.TEXTURE2); // Use unit 2 to avoid collision with procedural (unit 1)
|
|
1799
|
+
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
1800
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
1801
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
1802
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
1803
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
1804
|
+
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
|
|
1805
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c);
|
|
1806
|
+
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
|
|
1807
|
+
this._watermarkTexture = tex;
|
|
1808
|
+
// ── 4. Create static tex-coord buffer (never changes) ──
|
|
1809
|
+
const tcBuf = gl.createBuffer();
|
|
1810
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, tcBuf);
|
|
1811
|
+
// Two-triangle strip: BL, BR, TL, TR
|
|
1812
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 1, 1, 1, 0, 0, 1, 0]), gl.STATIC_DRAW);
|
|
1813
|
+
this._watermarkTexCoordBuffer = tcBuf;
|
|
1814
|
+
// ── 5. Create position buffer (updated per-frame based on canvas size) ──
|
|
1815
|
+
const posBuf = gl.createBuffer();
|
|
1816
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, posBuf);
|
|
1817
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(8), gl.DYNAMIC_DRAW);
|
|
1818
|
+
this._watermarkBuffer = posBuf;
|
|
1819
|
+
// ── 6. Make the watermark region clickable ──
|
|
1820
|
+
// We listen on `document` (capture phase for click) because the canvas
|
|
1821
|
+
// is often covered by overlay elements (scroll containers, UI panels)
|
|
1822
|
+
// that would swallow canvas-level events.
|
|
1823
|
+
this._wmClickHandler = (e) => {
|
|
1824
|
+
if (this._isOverWatermark(e)) {
|
|
1825
|
+
e.preventDefault();
|
|
1826
|
+
e.stopPropagation();
|
|
1827
|
+
window.open('https://neat.firecms.co', '_blank', 'noopener');
|
|
1828
|
+
}
|
|
1829
|
+
};
|
|
1830
|
+
this._wmMoveHandler = (e) => {
|
|
1831
|
+
const over = this._isOverWatermark(e);
|
|
1832
|
+
this._ref.style.cursor = over ? 'pointer' : '';
|
|
1833
|
+
// Propagate pointer cursor to overlays that sit on top of the canvas
|
|
1834
|
+
if (over) {
|
|
1835
|
+
document.body.style.cursor = 'pointer';
|
|
1836
|
+
}
|
|
1837
|
+
else if (document.body.style.cursor === 'pointer') {
|
|
1838
|
+
document.body.style.cursor = '';
|
|
1839
|
+
}
|
|
1840
|
+
};
|
|
1841
|
+
document.addEventListener('click', this._wmClickHandler, true); // capture phase
|
|
1842
|
+
document.addEventListener('mousemove', this._wmMoveHandler);
|
|
1843
|
+
}
|
|
1844
|
+
/** Returns true if the mouse event is inside the watermark's pixel bounds. */
|
|
1845
|
+
_isOverWatermark(e) {
|
|
1846
|
+
const rect = this._ref.getBoundingClientRect();
|
|
1847
|
+
// Mouse position relative to the canvas element
|
|
1848
|
+
const x = e.clientX - rect.left;
|
|
1849
|
+
const y = e.clientY - rect.top;
|
|
1850
|
+
const cw = rect.width;
|
|
1851
|
+
const ch = rect.height;
|
|
1852
|
+
// Quick rejection: outside the canvas entirely
|
|
1853
|
+
if (x < 0 || y < 0 || x > cw || y > ch)
|
|
1854
|
+
return false;
|
|
1855
|
+
const m = this._watermarkMargin;
|
|
1856
|
+
const ww = this._watermarkWidth;
|
|
1857
|
+
const wh = this._watermarkHeight;
|
|
1858
|
+
// Watermark sits in the bottom-right corner
|
|
1859
|
+
const left = cw - m - ww;
|
|
1860
|
+
const top = ch - m - wh;
|
|
1861
|
+
return x >= left && x <= cw - m
|
|
1862
|
+
&& y >= top && y <= ch - m;
|
|
1750
1863
|
}
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1864
|
+
/**
|
|
1865
|
+
* Draws the watermark quad as a second pass after the main gradient.
|
|
1866
|
+
* Restores the main program afterwards so the next frame's uniform
|
|
1867
|
+
* uploads target the correct program.
|
|
1868
|
+
*/
|
|
1869
|
+
_renderWatermark(gl) {
|
|
1870
|
+
// Skip watermark for licensed users
|
|
1871
|
+
if (this._licensed)
|
|
1872
|
+
return;
|
|
1873
|
+
const prog = this._watermarkProgram;
|
|
1874
|
+
const tex = this._watermarkTexture;
|
|
1875
|
+
const posBuf = this._watermarkBuffer;
|
|
1876
|
+
const tcBuf = this._watermarkTexCoordBuffer;
|
|
1877
|
+
if (!prog || !tex || !posBuf || !tcBuf)
|
|
1878
|
+
return;
|
|
1879
|
+
const canvasW = this._ref.width || this._ref.clientWidth;
|
|
1880
|
+
const canvasH = this._ref.height || this._ref.clientHeight;
|
|
1881
|
+
if (canvasW === 0 || canvasH === 0)
|
|
1882
|
+
return;
|
|
1883
|
+
// Compute quad position in clip space (-1 … +1).
|
|
1884
|
+
// Place the watermark in the bottom-right corner with a small margin.
|
|
1885
|
+
const margin = 4;
|
|
1886
|
+
const qw = this._watermarkWidth;
|
|
1887
|
+
const qh = this._watermarkHeight;
|
|
1888
|
+
// Pixel → clip-space conversion
|
|
1889
|
+
const r = 1.0 - (margin / canvasW) * 2.0; // right edge
|
|
1890
|
+
const l = r - (qw / canvasW) * 2.0; // left edge
|
|
1891
|
+
const b = -1.0 + (margin / canvasH) * 2.0; // bottom edge
|
|
1892
|
+
const t = b + (qh / canvasH) * 2.0; // top edge
|
|
1893
|
+
// Update position buffer (triangle strip: BL, BR, TL, TR)
|
|
1894
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, posBuf);
|
|
1895
|
+
gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array([l, b, r, b, l, t, r, t]));
|
|
1896
|
+
// ── Switch to watermark shader ──
|
|
1897
|
+
gl.useProgram(prog);
|
|
1898
|
+
gl.disable(gl.DEPTH_TEST);
|
|
1899
|
+
// Use premultiplied alpha blending since we uploaded with PREMULTIPLY_ALPHA
|
|
1900
|
+
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
1901
|
+
// Bind position attribute
|
|
1902
|
+
const aPos = gl.getAttribLocation(prog, 'a_wm_position');
|
|
1903
|
+
gl.enableVertexAttribArray(aPos);
|
|
1904
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, posBuf);
|
|
1905
|
+
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);
|
|
1906
|
+
// Bind texcoord attribute
|
|
1907
|
+
const aTc = gl.getAttribLocation(prog, 'a_wm_texcoord');
|
|
1908
|
+
gl.enableVertexAttribArray(aTc);
|
|
1909
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, tcBuf);
|
|
1910
|
+
gl.vertexAttribPointer(aTc, 2, gl.FLOAT, false, 0, 0);
|
|
1911
|
+
// Bind watermark texture on unit 2
|
|
1912
|
+
gl.activeTexture(gl.TEXTURE2);
|
|
1913
|
+
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
1914
|
+
gl.uniform1i(gl.getUniformLocation(prog, 'u_wm_texture'), 2);
|
|
1915
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
1916
|
+
// ── Restore state for the next gradient frame ──
|
|
1917
|
+
gl.enable(gl.DEPTH_TEST);
|
|
1918
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
1919
|
+
gl.useProgram(this.glState.program);
|
|
1920
|
+
// Re-enable and re-bind the gradient's vertex attributes.
|
|
1921
|
+
// We must call enableVertexAttribArray again because the watermark's
|
|
1922
|
+
// attribute locations may overlap with the gradient's (WebGL assigns
|
|
1923
|
+
// locations globally starting from 0). Without this, the gradient's
|
|
1924
|
+
// position/normal/uv arrays stay disabled after the watermark draw.
|
|
1925
|
+
const locs = this.glState.locations.attributes;
|
|
1926
|
+
gl.enableVertexAttribArray(locs.position);
|
|
1927
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.glState.buffers.position);
|
|
1928
|
+
gl.vertexAttribPointer(locs.position, 3, gl.FLOAT, false, 0, 0);
|
|
1929
|
+
gl.enableVertexAttribArray(locs.normal);
|
|
1930
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.glState.buffers.normal);
|
|
1931
|
+
gl.vertexAttribPointer(locs.normal, 3, gl.FLOAT, false, 0, 0);
|
|
1932
|
+
gl.enableVertexAttribArray(locs.uv);
|
|
1933
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.glState.buffers.uv);
|
|
1934
|
+
gl.vertexAttribPointer(locs.uv, 2, gl.FLOAT, false, 0, 0);
|
|
1935
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.glState.buffers.index);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1756
1938
|
function getElapsedSecondsInLastHour() {
|
|
1757
1939
|
const now = new Date();
|
|
1758
1940
|
const minutes = now.getMinutes();
|
|
1759
1941
|
const seconds = now.getSeconds();
|
|
1760
1942
|
return (minutes * 60) + seconds;
|
|
1761
1943
|
}
|
|
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
1944
|
function downloadURI(uri, name) {
|
|
1772
1945
|
const link = document.createElement("a");
|
|
1773
1946
|
link.download = name;
|
|
@@ -1776,50 +1949,35 @@ function downloadURI(uri, name) {
|
|
|
1776
1949
|
link.click();
|
|
1777
1950
|
document.body.removeChild(link);
|
|
1778
1951
|
}
|
|
1779
|
-
|
|
1780
|
-
|
|
1952
|
+
/**
|
|
1953
|
+
* Injects a <meta name="generator"> tag — the industry-standard, SEO-safe
|
|
1954
|
+
* way for tools/libraries to identify themselves (used by WordPress, Hugo, etc.).
|
|
1955
|
+
* This is semantically correct and will not harm the end-user's SEO.
|
|
1956
|
+
*/
|
|
1957
|
+
function injectMetaGenerator() {
|
|
1958
|
+
if (document.querySelector('meta[name="generator"][content*="NEAT"]'))
|
|
1781
1959
|
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);
|
|
1960
|
+
const meta = document.createElement('meta');
|
|
1961
|
+
meta.name = 'generator';
|
|
1962
|
+
meta.content = 'NEAT by FireCMS — https://neat.firecms.co';
|
|
1963
|
+
document.head.appendChild(meta);
|
|
1964
|
+
}
|
|
1965
|
+
// ── Watermark shaders (minimal pass-through for a textured screen quad) ──
|
|
1966
|
+
const WATERMARK_VS = `
|
|
1967
|
+
attribute vec2 a_wm_position;
|
|
1968
|
+
attribute vec2 a_wm_texcoord;
|
|
1969
|
+
varying vec2 v_wm_texcoord;
|
|
1970
|
+
void main() {
|
|
1971
|
+
gl_Position = vec4(a_wm_position, 0.0, 1.0);
|
|
1972
|
+
v_wm_texcoord = a_wm_texcoord;
|
|
1973
|
+
}
|
|
1974
|
+
`;
|
|
1975
|
+
const WATERMARK_FS = `
|
|
1976
|
+
precision mediump float;
|
|
1977
|
+
varying vec2 v_wm_texcoord;
|
|
1978
|
+
uniform sampler2D u_wm_texture;
|
|
1979
|
+
void main() {
|
|
1980
|
+
gl_FragColor = texture2D(u_wm_texture, v_wm_texcoord);
|
|
1824
1981
|
}
|
|
1982
|
+
`;
|
|
1825
1983
|
//# sourceMappingURL=NeatGradient.js.map
|