@firecms/neat 0.9.2 → 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.
@@ -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
  }
@@ -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
- injectSEO();
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 NEAT link
457
- if (this._linkElement && this._linkElement.parentElement) {
458
- this._linkElement.parentElement.removeChild(this._linkElement);
459
- this._linkElement = null;
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,66 +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
- 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
- let parent = ref.parentElement;
1739
- // Walk up past display:contents wrappers (e.g. Astro's <astro-island>)
1740
- // to find the real box-generating parent for positioning the link
1741
- while (parent && getComputedStyle(parent).display === "contents") {
1742
- parent = parent.parentElement;
1743
- }
1744
- // Ensure parent has position so absolute link is positioned relative to it
1745
- if (parent && getComputedStyle(parent).position === "static") {
1746
- parent.style.position = "relative";
1747
- }
1748
- // Search parent for existing neat link (survives HMR where LINK_ID changes)
1749
- if (parent) {
1750
- const existing = parent.querySelector('a[data-n]');
1751
- if (existing) {
1752
- setLinkStyles(existing);
1753
- return existing;
1754
- }
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;
1755
1850
  }
1756
- const link = document.createElement("a");
1757
- setLinkStyles(link);
1758
- parent?.appendChild(link);
1759
- return link;
1760
- };
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
+ }
1761
1922
  function getElapsedSecondsInLastHour() {
1762
1923
  const now = new Date();
1763
1924
  const minutes = now.getMinutes();
1764
1925
  const seconds = now.getSeconds();
1765
1926
  return (minutes * 60) + seconds;
1766
1927
  }
1767
- function generateRandomString(length = 6) {
1768
- const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
1769
- let result = '';
1770
- for (let i = 0; i < length; i++) {
1771
- const randomIndex = Math.floor(Math.random() * characters.length);
1772
- result += characters.charAt(randomIndex);
1773
- }
1774
- return result;
1775
- }
1776
1928
  function downloadURI(uri, name) {
1777
1929
  const link = document.createElement("a");
1778
1930
  link.download = name;
@@ -1781,50 +1933,35 @@ function downloadURI(uri, name) {
1781
1933
  link.click();
1782
1934
  document.body.removeChild(link);
1783
1935
  }
1784
- function injectSEO() {
1785
- if (document.getElementById("neat-seo-schema"))
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"]'))
1786
1943
  return;
1787
- // 1. JSON-LD Schema
1788
- const script = document.createElement('script');
1789
- script.id = "neat-seo-schema";
1790
- script.type = 'application/ld+json';
1791
- script.text = JSON.stringify({
1792
- "@context": "https://schema.org",
1793
- "@type": "WebSite",
1794
- "name": "NEAT Gradient",
1795
- "url": "https://neat.firecms.co",
1796
- "author": {
1797
- "@type": "Organization",
1798
- "name": "FireCMS",
1799
- "url": "https://firecms.co"
1800
- },
1801
- "description": "Beautiful, fast, heavily customizable, WebGL based gradients."
1802
- });
1803
- document.head.appendChild(script);
1804
- // 2. Hidden Backlink via Shadow DOM
1805
- const hiddenContainer = document.createElement('div');
1806
- hiddenContainer.style.position = 'absolute';
1807
- hiddenContainer.style.width = '1px';
1808
- hiddenContainer.style.height = '1px';
1809
- hiddenContainer.style.padding = '0';
1810
- hiddenContainer.style.margin = '-1px';
1811
- hiddenContainer.style.overflow = 'hidden';
1812
- hiddenContainer.style.clip = 'rect(0, 0, 0, 0)';
1813
- hiddenContainer.style.whiteSpace = 'nowrap';
1814
- hiddenContainer.style.borderWidth = '0';
1815
- try {
1816
- const shadow = hiddenContainer.attachShadow({ mode: 'closed' });
1817
- const link = document.createElement('a');
1818
- link.href = "https://firecms.co";
1819
- link.textContent = "FireCMS";
1820
- shadow.appendChild(link);
1821
- }
1822
- catch (e) {
1823
- const link = document.createElement('a');
1824
- link.href = "https://firecms.co";
1825
- link.textContent = "FireCMS";
1826
- hiddenContainer.appendChild(link);
1827
- }
1828
- 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);
1829
1965
  }
1966
+ `;
1830
1967
  //# sourceMappingURL=NeatGradient.js.map