From fb80bd0f2b30394f2f44e344fb70847a9c121d52 Mon Sep 17 00:00:00 2001
From: Linus Olofsson <61837116+LinusOlofsson-maker@users.noreply.github.com>
Date: Thu, 21 May 2026 05:34:27 +0200
Subject: [PATCH] feat(logo-threads): bracket-anchored paths with x^2 / -x^2
curves (#24)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Threads now lock to logo bracket pin endpoints (where vertical legs
of ::before / ::after bracket-corners end):
- Rosa tråd: från (0, viewport.height) → (logoLeft, logoTop + 40% height)
Quadratic Bezier with cp=(endX, startY) = x^2 curve, går rakt först
och accelererar upp i slutet
- Cyan tråd: från (logoRight, logoBottom - 40% height) → (viewport.width, 0)
Quadratic Bezier with cp=(startX, endY) = -x^2 curve, accelererar upp
i början och decelererar mot slutet
Permanent paths som uppdateras vid resize + scroll + 500ms/1500ms
post-load. runLogoPulse triggar update före varje pulse.
---
src/components/WaveBackground.astro | 48 +++++++++++++++++++++++++++--
1 file changed, 46 insertions(+), 2 deletions(-)
diff --git a/src/components/WaveBackground.astro b/src/components/WaveBackground.astro
index ec14fb1..fdb0585 100644
--- a/src/components/WaveBackground.astro
+++ b/src/components/WaveBackground.astro
@@ -72,8 +72,8 @@ const pulseCore = theme === 'purple' ? '#fdf4ff' : '#f0fdfa';
-
-
+
+
@@ -422,6 +422,47 @@ const pulseCore = theme === 'purple' ? '#fdf4ff' : '#f0fdfa';
return el;
}
+ function updateLogoThreadPaths() {
+ const logo = document.querySelector('[data-logo-target]');
+ const threadIn = document.getElementById('threadLogoIn') as SVGPathElement | null;
+ const threadOut = document.getElementById('threadLogoOut') as SVGPathElement | null;
+ if (!logo || !threadIn || !threadOut) return;
+ const rect = logo.getBoundingClientRect();
+ const vw = window.innerWidth;
+ const vh = window.innerHeight;
+ // Bracket-pinnarnas SLUT (där vertikala benen tar slut)
+ // ::before: top-left bracket, height=40% av text-wrapper. Vänstra benet går nedåt 40% av höjden
+ // Lås-punkt: (rect.left, rect.top + 0.40 * rect.height) — pinnens nedre spets
+ const leftPinX = rect.left;
+ const leftPinY = rect.top + rect.height * 0.40;
+ // ::after: bottom-right bracket. Höger ben börjar 40% från botten
+ // Lås-punkt: (rect.right, rect.bottom - 0.40 * rect.height) — pinnens övre spets
+ const rightPinX = rect.right;
+ const rightPinY = rect.bottom - rect.height * 0.40;
+
+ // Rosa tråd: från (0, vh) → (leftPinX, leftPinY)
+ // x² curve: kontrollpunkt (endX, startY) = rakt fram, sedan upp = accelererande lutning
+ const inStartX = 0, inStartY = vh;
+ const inEndX = leftPinX, inEndY = leftPinY;
+ const inCpX = inEndX, inCpY = inStartY;
+ threadIn.setAttribute('d', `M ${inStartX} ${inStartY} Q ${inCpX} ${inCpY}, ${inEndX} ${inEndY}`);
+
+ // Cyan tråd: från (rightPinX, rightPinY) → (vw, 0)
+ // -x² curve: kontrollpunkt (startX, endY) = upp först, sedan rakt = decelererande lutning
+ const outStartX = rightPinX, outStartY = rightPinY;
+ const outEndX = vw, outEndY = 0;
+ const outCpX = outStartX, outCpY = outEndY;
+ threadOut.setAttribute('d', `M ${outStartX} ${outStartY} Q ${outCpX} ${outCpY}, ${outEndX} ${outEndY}`);
+ }
+
+ // Uppdatera vid load + resize + scroll (logo-position beror på alla tre)
+ updateLogoThreadPaths();
+ window.addEventListener('resize', updateLogoThreadPaths);
+ window.addEventListener('scroll', updateLogoThreadPaths, { passive: true });
+ // Re-run efter en kort delay för att Astro layout ska vara klar
+ setTimeout(updateLogoThreadPaths, 500);
+ setTimeout(updateLogoThreadPaths, 1500);
+
function runLogoPulse(durationMs: number) {
if (!blob1) return;
const logo = isLogoTargetVisible();
@@ -434,6 +475,9 @@ const pulseCore = theme === 'purple' ? '#fdf4ff' : '#f0fdfa';
isPulsing = true;
updatePulseColors(false);
+ // Uppdatera path-d till EXAKT nuvarande logo-position innan pulse börjar
+ updateLogoThreadPaths();
+
const rect = logo.getBoundingClientRect();
const logoLeftX = rect.left + 8;
const logoRightX = rect.right - 8;