diff --git a/components/sections/HeroDotCanvas.tsx b/components/sections/HeroDotCanvas.tsx
index e8a938f..108f127 100644
--- a/components/sections/HeroDotCanvas.tsx
+++ b/components/sections/HeroDotCanvas.tsx
@@ -105,6 +105,8 @@ export function HeroDotCanvas({ sectionRef }: HeroDotCanvasProps) {
spotCtx = spotCanvas.getContext("2d");
}
+
+
// Bottom curved cutout path from line 1145 of hero_back.svg (covers bottom corners)
const bottomPath =
typeof Path2D !== "undefined"
@@ -116,8 +118,9 @@ export function HeroDotCanvas({ sectionRef }: HeroDotCanvasProps) {
const draw = () => {
ctx.clearRect(0, 0, width, height);
- // 1. Draw all dots with their dynamic radius, using ONLY the single standard dot fill
- // No color change, no alpha change: only the scale changes!
+ // 1. Single-pass dot rendering:
+ // Every dot is drawn EXACTLY ONCE with its dynamic radius r
+ // Eliminates any duplicate dot layers completely!
ctx.fillStyle = "rgba(255, 255, 255, 0.18)";
ctx.beginPath();
for (let i = 0; i < dots.length; i++) {
@@ -140,7 +143,7 @@ export function HeroDotCanvas({ sectionRef }: HeroDotCanvasProps) {
ctx.fill();
// 2. Apply vertical fade gradient mask matching the SVG's dotFadeMask
- // Invisible dots on top stay 100% invisible; transparent dots stay transparent!
+ // Invisible dots on top stay invisible; transparent dots stay transparent!
ctx.globalCompositeOperation = "destination-in";
const fadeGrad = ctx.createLinearGradient(0, 45 * scale, 0, 566 * scale);
fadeGrad.addColorStop(0, "rgba(255, 255, 255, 0)");
@@ -150,8 +153,8 @@ export function HeroDotCanvas({ sectionRef }: HeroDotCanvasProps) {
ctx.fillStyle = fadeGrad;
ctx.fillRect(0, 0, width, height);
- // 3. Cut out the bottom curved shape matching line 1145 of hero_back.svg
- // Dots on bottom right and left with black background stay invisible at rest
+ // 3. Cut out the bottom curved shape matching line 1145 of hero_back.svg:
+ // Preserves the authentic curved arch of the dots pattern (matching previous commits)
if (bottomPath) {
ctx.globalCompositeOperation = "destination-out";
ctx.save();
@@ -163,49 +166,21 @@ export function HeroDotCanvas({ sectionRef }: HeroDotCanvasProps) {
ctx.globalCompositeOperation = "source-over";
- // 4. Subtle hover interaction:
- // When hovered, scaled dots receive a gentle, soft brightness boost (~0.13 * ratio):
- // - If a dot is invisible as default (on top or in dark corners): it softly emerges at ~0.13 alpha (never fully white!)
- // - If a dot is already visible (0.18): it gently brightens to ~0.29 alpha
- // - When cursor leaves: it smoothly fades back to its resting state
- if (currentStrength > 0.005) {
- const minCol = Math.max(0, Math.floor((currentX - maxRadius - minX) / rowsize));
- const maxCol = Math.min(numCols - 1, Math.ceil((currentX + maxRadius - minX) / rowsize));
- const minRow = Math.max(0, Math.floor((currentY - maxRadius - minY) / rowsize));
- const maxRow = Math.min(numRows - 1, Math.ceil((currentY + maxRadius - minY) / rowsize));
-
- for (let r = minRow; r <= maxRow; r++) {
- const dotY = minY + r * rowsize;
- const screenY = dotY * scale;
-
- for (let c = minCol; c <= maxCol; c++) {
- const dotX = minX + c * rowsize;
- const screenX = dotX * scale + offsetX;
-
- if (screenX < -10 || screenX > width + 10) continue;
-
- const scaler = Math.hypot((currentX - dotX) / rowsize, (currentY - dotY) / rowsize);
- const addedSize = Math.max(0, (dotsizebase - dotmin) - scaler * decay);
-
- if (addedSize > 0) {
- const radius = dotmin + addedSize * currentStrength;
- const ratio = (addedSize / (dotsizebase - dotmin)) * currentStrength;
-
- // Smooth top atmospheric fade: tapers smoothly from y=150 down to y=15
- // ensuring dots never hit a sharp ceiling or sudden cut off
- const t = Math.max(0, Math.min(1, (dotY - 15) / 135));
- const topFade = t * t * (3 - 2 * t);
-
- const alpha = DOT_HOVER_BRIGHTNESS * ratio * topFade;
- if (alpha > 0.002) {
- ctx.fillStyle = `rgba(255, 255, 255, ${alpha.toFixed(3)})`;
- ctx.beginPath();
- ctx.arc(screenX, screenY, radius, 0, Math.PI * 2);
- ctx.fill();
- }
- }
- }
- }
+ // 4. Subtle hover illumination on dots:
+ // Uses source-atop so it ONLY gently highlights the already-drawn & masked dots,
+ // strictly guaranteeing that NO dots outside the bottom curve are ever illuminated!
+ if (DOT_HOVER_BRIGHTNESS > 0.001 && currentStrength > 0.005) {
+ ctx.globalCompositeOperation = "source-atop";
+ const scX = currentX * scale + offsetX;
+ const scY = currentY * scale;
+ const pRad = maxRadius * scale;
+ const dotLight = ctx.createRadialGradient(scX, scY, 0, scX, scY, pRad);
+ dotLight.addColorStop(0, `rgba(255, 255, 255, ${(DOT_HOVER_BRIGHTNESS * 6 * currentStrength).toFixed(3)})`);
+ dotLight.addColorStop(0.6, `rgba(255, 255, 255, ${(DOT_HOVER_BRIGHTNESS * 2 * currentStrength).toFixed(3)})`);
+ dotLight.addColorStop(1, "rgba(255, 255, 255, 0)");
+ ctx.fillStyle = dotLight;
+ ctx.fillRect(scX - pRad, scY - pRad, pRad * 2, pRad * 2);
+ ctx.globalCompositeOperation = "source-over";
}
// 5. Pixel-precise radial spotlight for the TeamBy logo patterns:
diff --git a/public/assets/images/hero_back.svg b/public/assets/images/hero_back.svg
index 82cff53..b504266 100644
--- a/public/assets/images/hero_back.svg
+++ b/public/assets/images/hero_back.svg
@@ -1030,7 +1030,6 @@
-
@@ -1181,9 +1180,6 @@
-
-
-
@@ -1209,6 +1205,5 @@
-