From de8331d846ce8978062be8de56119349dc980260 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 09:30:39 +0000 Subject: [PATCH 1/5] Initial plan From 897e10f68a6d321c36cdff13a283d7701f829160 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 09:36:55 +0000 Subject: [PATCH 2/5] Add ink gravity shader to Rainy Tech theme Co-authored-by: HugoGresse <662377+HugoGresse@users.noreply.github.com> --- package-lock.json | 28 +++++------ src/layouts/Layout.astro | 103 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 66da983..65498e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -126,6 +126,7 @@ "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.1.0.tgz", "integrity": "sha512-Z9IYjuXSArkAUx3N6xj6+Bnvx8OdUSHA8YoOgyepp3+zJmtVYJIl/I18GozdJVW1p5u/CNpl3Km7/gwTJK85cw==", "license": "MIT", + "peer": true, "dependencies": { "prismjs": "^1.29.0" }, @@ -770,7 +771,6 @@ "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.13.2.tgz", "integrity": "sha512-jwtMmJa1BXXDCiDx1vC6SFN/+HfYG53UkfJa6qeN5ogvOunzbFDO3wISZy5n9xgYFUrEP6M7e8EG++riHNTv9w==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@firebase/component": "0.6.18", "@firebase/logger": "0.4.4", @@ -837,7 +837,6 @@ "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.4.2.tgz", "integrity": "sha512-LssbyKHlwLeiV8GBATyOyjmHcMpX/tFjzRUCS1jnwGAew1VsBB4fJowyS5Ud5LdFbYpJeS+IQoC+RQxpK7eH3Q==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@firebase/app": "0.13.2", "@firebase/component": "0.6.18", @@ -853,8 +852,7 @@ "version": "0.9.3", "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz", "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/@firebase/auth": { "version": "1.10.8", @@ -1305,7 +1303,6 @@ "integrity": "sha512-zGlBn/9Dnya5ta9bX/fgEoNC3Cp8s6h+uYPYaDieZsFOAdHP/ExzQ/eaDgxD3GOROdPkLKpvKY0iIzr9adle0w==", "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.1.0" }, @@ -2246,6 +2243,7 @@ "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.29.2.tgz", "integrity": "sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ==", "license": "MIT", + "peer": true, "dependencies": { "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", @@ -2260,6 +2258,7 @@ "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.29.2.tgz", "integrity": "sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A==", "license": "MIT", + "peer": true, "dependencies": { "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", @@ -2271,6 +2270,7 @@ "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.29.2.tgz", "integrity": "sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA==", "license": "MIT", + "peer": true, "dependencies": { "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1" @@ -2281,6 +2281,7 @@ "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-1.29.2.tgz", "integrity": "sha512-FIBA7N3LZ+223U7cJDUYd5shmciFQlYkFXlkKVaHsCPgfVLiO+e12FmQE6Tf9vuyEsFe3dIl8qGWKXgEHL9wmQ==", "license": "MIT", + "peer": true, "dependencies": { "@shikijs/types": "1.29.2" } @@ -2290,6 +2291,7 @@ "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-1.29.2.tgz", "integrity": "sha512-i9TNZlsq4uoyqSbluIcZkmPL9Bfi3djVxRnofUHwvx/h6SRW3cwgBC5SML7vsDcWyukY0eCzVN980rqP6qNl9g==", "license": "MIT", + "peer": true, "dependencies": { "@shikijs/types": "1.29.2" } @@ -2299,6 +2301,7 @@ "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.29.2.tgz", "integrity": "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw==", "license": "MIT", + "peer": true, "dependencies": { "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" @@ -2504,7 +2507,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -3515,7 +3517,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/entities": { "version": "6.0.1", @@ -5547,6 +5550,7 @@ "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-2.3.0.tgz", "integrity": "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g==", "license": "MIT", + "peer": true, "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", @@ -5704,7 +5708,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -5759,7 +5762,6 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -5775,7 +5777,6 @@ "resolved": "https://registry.npmjs.org/prettier-plugin-astro/-/prettier-plugin-astro-0.14.1.tgz", "integrity": "sha512-RiBETaaP9veVstE4vUwSIcdATj6dKmXljouXc/DDNwBSPTp8FRkLGDSGFClKsAFeeg+13SB0Z1JZvbD76bigJw==", "license": "MIT", - "peer": true, "dependencies": { "@astrojs/compiler": "^2.9.1", "prettier": "^3.0.0", @@ -5894,6 +5895,7 @@ "resolved": "https://registry.npmjs.org/regex/-/regex-5.1.1.tgz", "integrity": "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw==", "license": "MIT", + "peer": true, "dependencies": { "regex-utilities": "^2.3.0" } @@ -5903,6 +5905,7 @@ "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-5.1.1.tgz", "integrity": "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w==", "license": "MIT", + "peer": true, "dependencies": { "regex": "^5.1.1", "regex-utilities": "^2.3.0" @@ -6202,7 +6205,6 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.2.tgz", "integrity": "sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==", "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -6382,6 +6384,7 @@ "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.29.2.tgz", "integrity": "sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg==", "license": "MIT", + "peer": true, "dependencies": { "@shikijs/core": "1.29.2", "@shikijs/engine-javascript": "1.29.2", @@ -6650,7 +6653,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7015,7 +7017,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -7641,7 +7642,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index a93b43a..7c26a92 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -174,6 +174,106 @@ const organizerTwitter = '@sunnyTech_MTP' ctx.restore() } + // Ink Gravity Shader — makes text vanish like ink washed away by rain. + // Uses SVG feTurbulence + feDisplacementMap as a fragment-shader-style + // displacement field, animated per element via requestAnimationFrame. + function applyInkGravityShader() { + if (document.getElementById('ink-gravity-styles')) return + + const styleEl = document.createElement('style') + styleEl.id = 'ink-gravity-styles' + styleEl.textContent = `.ink-dissolving { will-change: filter, transform, opacity; }` + document.head.appendChild(styleEl) + + let filterIdx = 0 + + function dissolveTextElement(el: HTMLElement) { + if (el.dataset.inkDissolving) return + el.dataset.inkDissolving = '1' + + // Inline elements need a block-level wrapper for filter to render correctly + let target: HTMLElement = el + if (window.getComputedStyle(el).display === 'inline') { + const wrapper = document.createElement('span') + wrapper.style.display = 'inline-block' + el.parentNode?.insertBefore(wrapper, el) + wrapper.appendChild(el) + target = wrapper + } + + filterIdx++ + const fid = `ink-g-${filterIdx}` + const seed = Math.floor(Math.random() * 999) + + // Each element gets its own SVG filter (unique seed & id) so + // animations are fully independent — just like per-pixel shader uniforms. + const svgEl = document.createElementNS('http://www.w3.org/2000/svg', 'svg') + svgEl.setAttribute('aria-hidden', 'true') + svgEl.style.cssText = 'position:fixed;width:0;height:0;overflow:hidden;pointer-events:none' + svgEl.innerHTML = ` + + + + + + + ` + document.body.appendChild(svgEl) + + target.style.filter = `url(#${fid})` + target.classList.add('ink-dissolving') + + const dispMap = svgEl.querySelector(`#${fid}-d`) as SVGElement | null + const start = performance.now() + const dur = 4500 + Math.random() * 1500 // 4.5 – 6 s + + function frame(now: number) { + const p = Math.min((now - start) / dur, 1) + + // Displacement grows → ink spreads/blurs as it absorbs water + dispMap?.setAttribute('scale', String(p * 40)) + + // Quadratic easing simulates gravitational acceleration + target.style.transform = `translateY(${p * p * 100}px)` + + // Ink concentration drops as it dilutes + target.style.opacity = String(Math.max(0, 1 - p * 1.5)) + + if (p < 1) { + requestAnimationFrame(frame) + } else { + target.style.visibility = 'hidden' + svgEl.remove() + } + } + + requestAnimationFrame(frame) + } + + function scheduleDissolve() { + const candidates = Array.from(document.querySelectorAll('h1, h2, h3, p, li')).filter( + (el) => + !el.dataset.inkDissolving && + !el.closest('#rainy-tech-overlay') && + !el.closest('nav') && + (el.textContent?.trim().length ?? 0) > 0 && + el.getBoundingClientRect().height > 0 + ) + + if (candidates.length === 0) return + + dissolveTextElement(candidates[Math.floor(Math.random() * candidates.length)]) + setTimeout(scheduleDissolve, 2000 + Math.random() * 3000) + } + + // Delay start: let the rain fall for a few seconds before text starts dissolving + setTimeout(scheduleDissolve, 4000) + } + function applyRainyTheme() { const now = new Date() @@ -315,6 +415,9 @@ const organizerTwitter = '@sunnyTech_MTP' document.body.appendChild(overlay) } + + // Activate ink-gravity shader: text dissolves like real ink in rain + applyInkGravityShader() } // Run on initial load and on Astro View Transitions navigation From d0e21d39808bd270247686380f3644e8a5df1b1e Mon Sep 17 00:00:00 2001 From: Hugo Gresse Date: Wed, 18 Mar 2026 10:44:09 +0100 Subject: [PATCH 3/5] Reduce timeout duration for text dissolve effect --- src/layouts/Layout.astro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index 7c26a92..fffd680 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -267,7 +267,7 @@ const organizerTwitter = '@sunnyTech_MTP' if (candidates.length === 0) return dissolveTextElement(candidates[Math.floor(Math.random() * candidates.length)]) - setTimeout(scheduleDissolve, 2000 + Math.random() * 3000) + setTimeout(scheduleDissolve, 500 + Math.random() * 1000) } // Delay start: let the rain fall for a few seconds before text starts dissolving From a61925b2a4bee58efa9d7c844161a4f46105acf6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 09:48:18 +0000 Subject: [PATCH 4/5] Increase ink dissolve parallelism and slow fade duration Co-authored-by: HugoGresse <662377+HugoGresse@users.noreply.github.com> --- src/layouts/Layout.astro | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index fffd680..c6c4f25 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -229,7 +229,7 @@ const organizerTwitter = '@sunnyTech_MTP' const dispMap = svgEl.querySelector(`#${fid}-d`) as SVGElement | null const start = performance.now() - const dur = 4500 + Math.random() * 1500 // 4.5 – 6 s + const dur = 20000 + Math.random() * 15000 // 20 – 35 s: slow ink bleed function frame(now: number) { const p = Math.min((now - start) / dur, 1) @@ -266,8 +266,11 @@ const organizerTwitter = '@sunnyTech_MTP' if (candidates.length === 0) return - dissolveTextElement(candidates[Math.floor(Math.random() * candidates.length)]) - setTimeout(scheduleDissolve, 500 + Math.random() * 1000) + // Dissolve a wave of 4–7 elements simultaneously + const waveSize = 4 + Math.floor(Math.random() * 4) + const shuffled = candidates.sort(() => Math.random() - 0.5).slice(0, waveSize) + shuffled.forEach(dissolveTextElement) + setTimeout(scheduleDissolve, 3000 + Math.random() * 4000) } // Delay start: let the rain fall for a few seconds before text starts dissolving From 2954ee9bb92edd47e23148229bc75a5eb4a03773 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 10:12:19 +0000 Subject: [PATCH 5/5] Smooth CSS-transition easing and broaden dissolve element selector Co-authored-by: HugoGresse <662377+HugoGresse@users.noreply.github.com> --- src/layouts/Layout.astro | 52 ++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index c6c4f25..5298821 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -231,36 +231,52 @@ const organizerTwitter = '@sunnyTech_MTP' const start = performance.now() const dur = 20000 + Math.random() * 15000 // 20 – 35 s: slow ink bleed + // Use CSS transitions for transform/opacity so the browser compositor + // handles interpolation — gives hardware-accelerated smooth easing + // instead of 1-px-per-frame jumps driven by JS. + const durSec = (dur / 1000).toFixed(1) + target.style.transition = [ + `transform ${durSec}s cubic-bezier(0.2, 0, 0.6, 1)`, + `opacity ${durSec}s cubic-bezier(0.1, 0, 0.5, 1)`, + ].join(', ') + + // One rAF gap so the browser registers the transition before we + // set the target values that trigger it. + requestAnimationFrame(() => { + target.style.transform = 'translateY(120px)' + target.style.opacity = '0' + }) + + // Clean up once the opacity CSS transition finishes (fires once per property; + // checking propertyName avoids acting on the transform transitionend too). + target.addEventListener('transitionend', (e: TransitionEvent) => { + if (e.propertyName !== 'opacity') return + target.style.visibility = 'hidden' + svgEl.remove() + }) + + // SVG presentation attributes are not CSS-transitionable, so the + // displacement scale still needs a rAF loop with ease-in quadratic. function frame(now: number) { const p = Math.min((now - start) / dur, 1) - - // Displacement grows → ink spreads/blurs as it absorbs water - dispMap?.setAttribute('scale', String(p * 40)) - - // Quadratic easing simulates gravitational acceleration - target.style.transform = `translateY(${p * p * 100}px)` - - // Ink concentration drops as it dilutes - target.style.opacity = String(Math.max(0, 1 - p * 1.5)) - - if (p < 1) { - requestAnimationFrame(frame) - } else { - target.style.visibility = 'hidden' - svgEl.remove() - } + // Ease-in: displacement builds slowly then accelerates + dispMap?.setAttribute('scale', String(p * p * 40)) + if (p < 1) requestAnimationFrame(frame) } requestAnimationFrame(frame) } function scheduleDissolve() { - const candidates = Array.from(document.querySelectorAll('h1, h2, h3, p, li')).filter( + const candidates = Array.from( + document.querySelectorAll( + 'h1, h2, h3, h4, h5, h6, p, li, img, picture, figure, button, blockquote, pre' + ) + ).filter( (el) => !el.dataset.inkDissolving && !el.closest('#rainy-tech-overlay') && !el.closest('nav') && - (el.textContent?.trim().length ?? 0) > 0 && el.getBoundingClientRect().height > 0 )