fix(logo-pulse): threads properly visible (solid + glow) (#19)

* fix(pulse): split halves now meet simultaneously + no backtracking

Two bugs fixed after Linus feedback:

1. Path-form: top/bottom split halves used Bezier curves that could
   loop back on themselves when entry.x lay outside box X-range.
   Now use clamped startX/endX (max(xL+r, min(xR-r, entry.x))) so
   pathen alltid rör sig framåt (entry.x → exit.x), aldrig vänder.

2. Synchronized meeting: previously each blob moved at fixed velocity
   along its own path → shorter path arrived first and waited.
   Now both blobs share a phase2T (0→1) progress fraction, so they
   meet at exit-point simultaneously. The path with different length
   simply moves at a slightly different speed (averaged). Phase2
   duration based on avg(topLen,bottomLen)/velocity so total animation
   time stays consistent.

* feat: logo-pulse with pink->green color transition

Adds a 4th pulse target: the IMAGES BY OLOFSSON hero-text logo.
20% chance per pulse-slot when logo is visible (footer scrolled in).

Flow:
1. New purple wave-thread on bottom-left (mirror of cyan wave1 on right)
2. Pulse glides in along purple thread to logo-left edge
3. Pulse enters logo + tints smoky-path stroke pink (#e879f9) + adds
   pink text-shadow halo on h1
4. Mid-passage: tint transitions to green (#4fd1c5) — matches default
   smoke + cyan glow
5. Pulse exits logo via wave1 (existing cyan thread on right)
6. Logo tint reverts to default (white smokeShift animation)

CSS-side: data-logo-tint attribute on #text-wrapper drives stroke +
text-shadow overrides with 0.6-0.8s transitions for smooth fade.

* fix(logo-pulse): dynamic paths anchored to logo + 50% probability

Previous version tried to use existing wave1+threadPurple paths for
logo-pulse glide-in/out, but those paths are in SVG-fixed coordinates
that don't align with logo's viewport position when hero is scrolled
into view. wave1 has translateX(2283) which pushes it off-screen for
ultrawide viewports.

Fix: build dynamic Bezier paths anchored to the logo's actual
getBoundingClientRect() position at pulse-time:
- Entry path: from (offLeft, logoY+dipOffset) curves up into logo-left
- Exit path: from logo-right curves up to (offRight, logoY-riseOffset)

Paths are appended to box-trail-group, used for one pulse, then
removed in cleanup. No reliance on fixed wave-thread coordinates.

Also raises LOGO_PROBABILITY 20%->50% so the effect is visible more
often when hero is in view.

* fix(logo-pulse): visible entry/exit threads + restore pulse color after

Two bugs fixed:

1. Entry+exit paths were stroke=transparent — pulse appeared to spawn
   out of nothing. Now stroke=url(#gradPurple) on entry (subtle pink
   thread visible from off-screen left) and stroke=url(#gradMain) on
   exit (cyan thread visible going off-screen right). Both at
   stroke-width 0.8 + opacity 0.55 to match other wave-thread style.

2. After logo-pulse completed, pulse-gradient stops were left in pink
   state — next regular wave-pulse rendered as stale pink instead of
   cyan/theme color. Now updatePulseColors(false) is called in cleanup
   to restore default cyan-tema.

* fix(logo-pulse): entry/exit threads now properly visible

Previous version used gradient strokes (gradPurple/gradMain) which
have stop-opacity 0 at ends + 0.45 mid, multiplied by path-opacity
0.55 = ~0.25 effective opacity. Practically invisible.

Now solid stroke colors (#e879f9 pink, #4fd1c5 cyan), stroke-width 2
(was 0.8), opacity 0.85 (was 0.55), plus pulseGlowMid filter for soft
halo. Should be clearly visible from off-screen edges to logo.
This commit is contained in:
Linus Olofsson
2026-05-21 04:39:26 +02:00
committed by GitHub
parent 57767a112e
commit 23880f746b
+9 -8
View File
@@ -447,23 +447,24 @@ const pulseCore = theme === 'purple' ? '#fdf4ff' : '#f0fdfa';
const NS_local = 'http://www.w3.org/2000/svg'; const NS_local = 'http://www.w3.org/2000/svg';
// Entry path (lila): från off-screen vänster (dipY) kurvar upp till logo-left // Entry path (lila): från off-screen vänster (dipY) kurvar upp till logo-left
// SYNLIG via gradPurple stroke så användaren ser varifrån pulsen kommer // TYDLIG SOLID stroke + glow filter så användaren ser tråden ordentligt
const entryPath = document.createElementNS(NS_local, 'path'); const entryPath = document.createElementNS(NS_local, 'path');
entryPath.setAttribute('d', `M ${offLeft} ${dipY} C ${logoLeftX - 200} ${dipY}, ${logoLeftX - 80} ${logoY}, ${logoLeftX} ${logoY}`); entryPath.setAttribute('d', `M ${offLeft} ${dipY} C ${logoLeftX - 200} ${dipY}, ${logoLeftX - 80} ${logoY}, ${logoLeftX} ${logoY}`);
entryPath.setAttribute('fill', 'none'); entryPath.setAttribute('fill', 'none');
entryPath.setAttribute('stroke', 'url(#gradPurple)'); entryPath.setAttribute('stroke', '#e879f9');
entryPath.setAttribute('stroke-width', '0.8'); entryPath.setAttribute('stroke-width', '2');
entryPath.setAttribute('opacity', '0.55'); entryPath.setAttribute('opacity', '0.85');
entryPath.setAttribute('filter', 'url(#pulseGlowMid)');
boxGroup.appendChild(entryPath); boxGroup.appendChild(entryPath);
// Exit path (cyan): från logo-right kurvar upp till off-screen höger (riseY) // Exit path (cyan): från logo-right kurvar upp till off-screen höger (riseY)
// SYNLIG via gradMain (default cyan) så användaren ser varthän pulsen går
const exitPath = document.createElementNS(NS_local, 'path'); const exitPath = document.createElementNS(NS_local, 'path');
exitPath.setAttribute('d', `M ${logoRightX} ${logoY} C ${logoRightX + 80} ${logoY}, ${logoRightX + 200} ${riseY}, ${offRight} ${riseY}`); exitPath.setAttribute('d', `M ${logoRightX} ${logoY} C ${logoRightX + 80} ${logoY}, ${logoRightX + 200} ${riseY}, ${offRight} ${riseY}`);
exitPath.setAttribute('fill', 'none'); exitPath.setAttribute('fill', 'none');
exitPath.setAttribute('stroke', 'url(#gradMain)'); exitPath.setAttribute('stroke', '#4fd1c5');
exitPath.setAttribute('stroke-width', '0.8'); exitPath.setAttribute('stroke-width', '2');
exitPath.setAttribute('opacity', '0.55'); exitPath.setAttribute('opacity', '0.85');
exitPath.setAttribute('filter', 'url(#pulseGlowMid)');
boxGroup.appendChild(exitPath); boxGroup.appendChild(exitPath);
const entryLen = entryPath.getTotalLength(); const entryLen = entryPath.getTotalLength();