@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.
@@ -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
  }
@@ -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
- 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;");
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
- injectSEO();
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 NEAT link
457
- if (this._linkElement && this._linkElement.parentElement) {
458
- this._linkElement.parentElement.removeChild(this._linkElement);
459
- this._linkElement = null;
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
- const setLinkStyles = (link) => {
1718
- link.id = LINK_ID;
1719
- link.href = "https://neat.firecms.co";
1720
- link.target = "_blank";
1721
- link.style.position = "absolute";
1722
- link.style.display = "block";
1723
- link.style.bottom = "0";
1724
- link.style.right = "0";
1725
- link.style.padding = "10px";
1726
- link.style.color = "#dcdcdc";
1727
- link.style.opacity = "0.8";
1728
- link.style.fontFamily = "sans-serif";
1729
- link.style.fontSize = "16px";
1730
- link.style.fontWeight = "bold";
1731
- link.style.textDecoration = "none";
1732
- link.style.zIndex = "10000";
1733
- link.style.pointerEvents = "auto";
1734
- link.setAttribute("data-n", "1");
1735
- link.innerHTML = "NEAT";
1736
- };
1737
- const addNeatLink = (ref) => {
1738
- const parent = ref.parentElement;
1739
- // Ensure parent has position so absolute link is positioned relative to it
1740
- if (parent && getComputedStyle(parent).position === "static") {
1741
- parent.style.position = "relative";
1742
- }
1743
- // Search parent for existing neat link (survives HMR where LINK_ID changes)
1744
- if (parent) {
1745
- const existing = parent.querySelector('a[data-n]');
1746
- if (existing) {
1747
- setLinkStyles(existing);
1748
- return existing;
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
- const link = document.createElement("a");
1752
- setLinkStyles(link);
1753
- parent?.appendChild(link);
1754
- return link;
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
- function injectSEO() {
1780
- if (document.getElementById("neat-seo-schema"))
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
- // 1. JSON-LD Schema
1783
- const script = document.createElement('script');
1784
- script.id = "neat-seo-schema";
1785
- script.type = 'application/ld+json';
1786
- script.text = JSON.stringify({
1787
- "@context": "https://schema.org",
1788
- "@type": "WebSite",
1789
- "name": "NEAT Gradient",
1790
- "url": "https://neat.firecms.co",
1791
- "author": {
1792
- "@type": "Organization",
1793
- "name": "FireCMS",
1794
- "url": "https://firecms.co"
1795
- },
1796
- "description": "Beautiful, fast, heavily customizable, WebGL based gradients."
1797
- });
1798
- document.head.appendChild(script);
1799
- // 2. Hidden Backlink via Shadow DOM
1800
- const hiddenContainer = document.createElement('div');
1801
- hiddenContainer.style.position = 'absolute';
1802
- hiddenContainer.style.width = '1px';
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