@firecms/neat 0.5.0 → 0.6.0

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