diff --git a/scripts/build-site.sh b/scripts/build-site.sh
index 39c8338..69bf2e5 100755
--- a/scripts/build-site.sh
+++ b/scripts/build-site.sh
@@ -45,7 +45,7 @@ copy_file assets/brand/oss-singularity-mark.svg assets/brand/oss-singularity-mar
copy_file assets/projects/pdrive-control-center-v080.webp assets/projects/pdrive-control-center-v080.webp
copy_file assets/projects/chatgpt-usage-v030.webp assets/projects/chatgpt-usage-v030.webp
copy_file assets/projects/nemo-action-bar.webp assets/projects/nemo-action-bar.webp
-copy_file assets/scripts/reactive-field.js assets/scripts/reactive-field.js
+copy_file assets/scripts/reactive-field-v2.js assets/scripts/reactive-field-v2.js
copy_file assets/social/oss-singularity-social-preview.png assets/social/oss-singularity-social-preview.png
copy_file assets/styles/site-v1.css assets/styles/site-v1.css
diff --git a/scripts/check-site.py b/scripts/check-site.py
index 1f8e96d..19e20ca 100755
--- a/scripts/check-site.py
+++ b/scripts/check-site.py
@@ -77,7 +77,7 @@ def main() -> int:
"assets/projects/chatgpt-usage-v030.webp",
"assets/projects/nemo-action-bar.webp",
"assets/projects/pdrive-control-center-v080.webp",
- "assets/scripts/reactive-field.js",
+ "assets/scripts/reactive-field-v2.js",
"assets/social/oss-singularity-social-preview.png",
"assets/styles/site-v1.css",
}
@@ -146,7 +146,7 @@ def main() -> int:
css_bytes = (root / "assets/styles/site-v1.css").stat().st_size
if css_bytes > 40_000:
fail(f"CSS budget exceeded: {css_bytes} bytes")
- script_bytes = (root / "assets/scripts/reactive-field.js").stat().st_size
+ script_bytes = (root / "assets/scripts/reactive-field-v2.js").stat().st_size
if script_bytes > 8_000:
fail(f"JavaScript budget exceeded: {script_bytes} bytes")
for image in (root / "assets/projects").iterdir():
diff --git a/site/assets/scripts/reactive-field.js b/site/assets/scripts/reactive-field-v2.js
similarity index 66%
rename from site/assets/scripts/reactive-field.js
rename to site/assets/scripts/reactive-field-v2.js
index a48692e..90acc40 100644
--- a/site/assets/scripts/reactive-field.js
+++ b/site/assets/scripts/reactive-field-v2.js
@@ -32,13 +32,13 @@
})();
const createParticles = () => {
- const density = Math.floor(width * height / 26000);
- const count = Math.max(28, Math.min(72, density));
+ const density = Math.floor(width * height / 15000);
+ const count = Math.max(48, Math.min(96, density));
particles = Array.from({ length: count }, (_, index) => ({
x: random() * width,
y: random() * height,
depth: .28 + random() * .72,
- size: .45 + random() * 1.15,
+ size: .65 + random() * 1.4,
phase: random() * Math.PI * 2,
accent: index % 7 === 0,
}));
@@ -55,19 +55,19 @@
};
const positionParticle = (particle, time) => {
- let x = particle.x + pointer.easedX * particle.depth * 15;
- let y = particle.y + pointer.easedY * particle.depth * 10;
- x += Math.sin(time * .00016 + particle.phase) * 2.5 * particle.depth;
- y += Math.cos(time * .00012 + particle.phase) * 2 * particle.depth;
+ let x = particle.x + pointer.easedX * particle.depth * 28;
+ let y = particle.y + pointer.easedY * particle.depth * 20;
+ x += Math.sin(time * .00016 + particle.phase) * 4 * particle.depth;
+ y += Math.cos(time * .00012 + particle.phase) * 3 * particle.depth;
if (pointer.active) {
const offsetX = x - pointer.x;
const offsetY = y - pointer.y;
const distance = Math.hypot(offsetX, offsetY) || 1;
- const reach = 190;
+ const reach = 270;
if (distance < reach) {
- const lens = (1 - distance / reach) * 18 * particle.depth;
+ const lens = (1 - distance / reach) * 48 * particle.depth;
x += offsetX / distance * lens;
y += offsetY / distance * lens;
}
@@ -81,12 +81,45 @@
return;
}
- const glow = context.createRadialGradient(pointer.x, pointer.y, 0, pointer.x, pointer.y, 230);
- glow.addColorStop(0, "rgba(101, 229, 255, .075)");
- glow.addColorStop(.48, "rgba(255, 99, 200, .035)");
+ const glow = context.createRadialGradient(pointer.x, pointer.y, 0, pointer.x, pointer.y, 300);
+ glow.addColorStop(0, "rgba(101, 229, 255, .16)");
+ glow.addColorStop(.48, "rgba(255, 99, 200, .08)");
glow.addColorStop(1, "rgba(3, 8, 17, 0)");
context.fillStyle = glow;
- context.fillRect(pointer.x - 230, pointer.y - 230, 460, 460);
+ context.fillRect(pointer.x - 300, pointer.y - 300, 600, 600);
+ };
+
+ const drawPointerSignal = (time) => {
+ if (!pointer.active) {
+ return;
+ }
+
+ context.save();
+ context.translate(pointer.x, pointer.y);
+ context.rotate(time * .00022);
+ context.setLineDash([3, 9]);
+ context.lineCap = "round";
+
+ context.beginPath();
+ context.arc(0, 0, 58, -.2, Math.PI * 1.18);
+ context.strokeStyle = "rgba(101, 229, 255, .48)";
+ context.lineWidth = 1.1;
+ context.stroke();
+
+ context.rotate(-time * .00046);
+ context.beginPath();
+ context.arc(0, 0, 86, .35, Math.PI * 1.45);
+ context.strokeStyle = "rgba(255, 99, 200, .38)";
+ context.lineWidth = .9;
+ context.stroke();
+
+ context.setLineDash([]);
+ context.beginPath();
+ context.arc(0, 0, 4, 0, Math.PI * 2);
+ context.strokeStyle = "rgba(185, 243, 255, .72)";
+ context.lineWidth = 1;
+ context.stroke();
+ context.restore();
};
const draw = (time) => {
@@ -94,29 +127,36 @@
return;
}
- pointer.easedX += ((pointer.active ? pointer.x / width - .5 : 0) - pointer.easedX) * .045;
- pointer.easedY += ((pointer.active ? pointer.y / height - .5 : 0) - pointer.easedY) * .045;
+ pointer.easedX += ((pointer.active ? pointer.x / width - .5 : 0) - pointer.easedX) * .075;
+ pointer.easedY += ((pointer.active ? pointer.y / height - .5 : 0) - pointer.easedY) * .075;
context.clearRect(0, 0, width, height);
+ context.save();
+ context.globalCompositeOperation = "lighter";
drawGlow();
const positions = particles.map((particle) => positionParticle(particle, time));
if (pointer.active) {
- positions.forEach((point) => {
+ positions.forEach((point, index) => {
const distance = Math.hypot(point.x - pointer.x, point.y - pointer.y);
- if (distance < 165) {
+ if (distance < 260) {
context.beginPath();
context.moveTo(pointer.x, pointer.y);
context.lineTo(point.x, point.y);
- context.strokeStyle = `rgba(255, 99, 200, ${(1 - distance / 165) * .16})`;
- context.lineWidth = .7;
+ const opacity = (1 - distance / 260) * .44;
+ context.strokeStyle = particles[index].accent
+ ? `rgba(255, 99, 200, ${opacity})`
+ : `rgba(101, 229, 255, ${opacity})`;
+ context.lineWidth = 1;
context.stroke();
}
});
}
+ drawPointerSignal(time);
+
for (let first = 0; first < positions.length; first += 1) {
for (let second = first + 1; second < positions.length; second += 1) {
const distance = Math.hypot(
@@ -124,12 +164,12 @@
positions[first].y - positions[second].y,
);
- if (distance < 108) {
+ if (distance < 145) {
context.beginPath();
context.moveTo(positions[first].x, positions[first].y);
context.lineTo(positions[second].x, positions[second].y);
- context.strokeStyle = `rgba(101, 229, 255, ${(1 - distance / 108) * .085})`;
- context.lineWidth = .6;
+ context.strokeStyle = `rgba(101, 229, 255, ${(1 - distance / 145) * .19})`;
+ context.lineWidth = .75;
context.stroke();
}
}
@@ -137,15 +177,17 @@
particles.forEach((particle, index) => {
const point = positions[index];
- const pulse = .58 + Math.sin(time * .001 + particle.phase) * .22;
+ const pulse = .7 + Math.sin(time * .001 + particle.phase) * .25;
context.beginPath();
context.arc(point.x, point.y, particle.size, 0, Math.PI * 2);
context.fillStyle = particle.accent
? `rgba(255, 99, 200, ${pulse})`
- : `rgba(185, 243, 255, ${pulse * .72})`;
+ : `rgba(185, 243, 255, ${pulse * .88})`;
context.fill();
});
+ context.restore();
+
frame = window.requestAnimationFrame(draw);
};
diff --git a/site/index.html b/site/index.html
index eaca4c0..7323189 100644
--- a/site/index.html
+++ b/site/index.html
@@ -11,7 +11,7 @@
-
+