WIP: Astrowind build with scroll animation in progress
51
package-lock.json
generated
@ -16,6 +16,7 @@
|
||||
"astro": "^5.16.7",
|
||||
"astro-embed": "^0.9.0",
|
||||
"astro-icon": "^1.1.5",
|
||||
"gsap": "^3.14.2",
|
||||
"limax": "4.1.0",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"unpic": "^4.1.3"
|
||||
@ -3192,9 +3193,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/astro": {
|
||||
"version": "5.16.7",
|
||||
"resolved": "https://registry.npmjs.org/astro/-/astro-5.16.7.tgz",
|
||||
"integrity": "sha512-Kfv7FKisFR+THvmojXWtvJGRCvQ4D9przguE9XdeUtS464ned6hvbgmyFDvPzyaNmDtkHGNpPwAQ9tgFcVqp+Q==",
|
||||
"version": "5.16.11",
|
||||
"resolved": "https://registry.npmjs.org/astro/-/astro-5.16.11.tgz",
|
||||
"integrity": "sha512-Z7kvkTTT5n6Hn5lCm6T3WU6pkxx84Hn25dtQ6dR7ATrBGq9eVa8EuB/h1S8xvaoVyCMZnIESu99Z9RJfdLRLDA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@ -3216,8 +3217,8 @@
|
||||
"cssesc": "^3.0.0",
|
||||
"debug": "^4.4.3",
|
||||
"deterministic-object-hash": "^2.0.2",
|
||||
"devalue": "^5.6.1",
|
||||
"diff": "^5.2.0",
|
||||
"devalue": "^5.6.2",
|
||||
"diff": "^8.0.3",
|
||||
"dlv": "^1.1.3",
|
||||
"dset": "^3.1.4",
|
||||
"es-module-lexer": "^1.7.0",
|
||||
@ -4735,9 +4736,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/devalue": {
|
||||
"version": "5.6.1",
|
||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.1.tgz",
|
||||
"integrity": "sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A==",
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.2.tgz",
|
||||
"integrity": "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/devlop": {
|
||||
@ -4761,9 +4762,9 @@
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
|
||||
"integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
|
||||
"version": "8.0.3",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz",
|
||||
"integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.3.1"
|
||||
@ -5805,10 +5806,16 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/gsap": {
|
||||
"version": "3.14.2",
|
||||
"resolved": "https://registry.npmjs.org/gsap/-/gsap-3.14.2.tgz",
|
||||
"integrity": "sha512-P8/mMxVLU7o4+55+1TCnQrPmgjPKnwkzkXOK1asnR9Jg2lna4tEY5qBJjMmAaOBDDZWtlRjBXjLa0w53G/uBLA==",
|
||||
"license": "Standard 'no charge' license: https://gsap.com/standard-license."
|
||||
},
|
||||
"node_modules/h3": {
|
||||
"version": "1.15.4",
|
||||
"resolved": "https://registry.npmjs.org/h3/-/h3-1.15.4.tgz",
|
||||
"integrity": "sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ==",
|
||||
"version": "1.15.5",
|
||||
"resolved": "https://registry.npmjs.org/h3/-/h3-1.15.5.tgz",
|
||||
"integrity": "sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie-es": "^1.2.2",
|
||||
@ -5816,9 +5823,9 @@
|
||||
"defu": "^6.1.4",
|
||||
"destr": "^2.0.5",
|
||||
"iron-webcrypto": "^1.2.1",
|
||||
"node-mock-http": "^1.0.2",
|
||||
"node-mock-http": "^1.0.4",
|
||||
"radix3": "^1.1.2",
|
||||
"ufo": "^1.6.1",
|
||||
"ufo": "^1.6.3",
|
||||
"uncrypto": "^0.1.3"
|
||||
}
|
||||
},
|
||||
@ -10079,9 +10086,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "7.5.2",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz",
|
||||
"integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==",
|
||||
"version": "7.5.3",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.3.tgz",
|
||||
"integrity": "sha512-ENg5JUHUm2rDD7IvKNFGzyElLXNjachNLp6RaGf4+JOgxXHkqA+gq81ZAMCUmtMtqBsoU62lcp6S27g1LCYGGQ==",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"@isaacs/fs-minipass": "^4.0.0",
|
||||
@ -10344,9 +10351,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ufo": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.2.tgz",
|
||||
"integrity": "sha512-heMioaxBcG9+Znsda5Q8sQbWnLJSl98AFDXTO80wELWEzX3hordXsTdxrIfMQoO9IY1MEnoGoPjpoKpMj+Yx0Q==",
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz",
|
||||
"integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/uhyphen": {
|
||||
|
||||
@ -30,6 +30,7 @@
|
||||
"astro": "^5.16.7",
|
||||
"astro-embed": "^0.9.0",
|
||||
"astro-icon": "^1.1.5",
|
||||
"gsap": "^3.14.2",
|
||||
"limax": "4.1.0",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"unpic": "^4.1.3"
|
||||
|
||||
BIN
public/images/benefit-bg-light.png
Normal file
|
After Width: | Height: | Size: 199 KiB |
BIN
public/images/benefit-bg.jpg
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
public/images/benefit-bg.png
Normal file
|
After Width: | Height: | Size: 165 KiB |
BIN
public/images/benefit-mobile-01-light.png
Normal file
|
After Width: | Height: | Size: 144 KiB |
BIN
public/images/benefit-mobile-01.jpg
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
public/images/benefit-mobile-02-light.png
Normal file
|
After Width: | Height: | Size: 130 KiB |
BIN
public/images/benefit-mobile-02.jpg
Normal file
|
After Width: | Height: | Size: 55 KiB |
@ -46,11 +46,7 @@ const socialLinks = [
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<!-- Tagline - Left Aligned -->
|
||||
<div class="text-left text-white/50 text-sm">
|
||||
Laconic: Offshore Hosting Made Easy
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</footer>
|
||||
183
src/components/widgets/ScrollBallAnimation.astro
Normal file
@ -0,0 +1,183 @@
|
||||
---
|
||||
// ScrollBallAnimation.astro
|
||||
// Scroll-triggered animation with glowing ball traveling along SVG path between wireframe hands
|
||||
|
||||
interface Props {
|
||||
startTrigger?: string; // Default: "#watchers"
|
||||
endTrigger?: string; // Default: "#deploy"
|
||||
}
|
||||
|
||||
const {
|
||||
startTrigger = '#watchers',
|
||||
endTrigger = '#indexer',
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<!-- Wrapper: Creates positioning context at insertion point -->
|
||||
<div class="animation-wrapper relative w-full pointer-events-none" style="height: 0;">
|
||||
|
||||
<!-- Background hands: Positioned behind text content but above page background -->
|
||||
<div class="hands-background absolute left-0 right-0 pointer-events-none" style="top: 0; height: 250vh; z-index: 1;">
|
||||
<div class="background-image absolute inset-0 w-full h-full"></div>
|
||||
</div>
|
||||
|
||||
<!-- Ball and path container: Above background hands -->
|
||||
<div class="ball-container absolute left-0 right-0 hidden md:block" style="top: 0; height: 250vh; z-index: 3;">
|
||||
<!-- SVG path for debugging (visible red line) -->
|
||||
<svg class="path-svg absolute inset-0 w-full h-full" viewBox="0 0 1920 1080" preserveAspectRatio="xMidYMid meet">
|
||||
<path
|
||||
id="ballLine"
|
||||
d="M 480,-80 C 480,0 600,250 960,280 C 1320,310 1440,400 1440,480"
|
||||
stroke="red"
|
||||
stroke-width="3"
|
||||
fill="none" />
|
||||
</svg>
|
||||
|
||||
<!-- The glowing ball -->
|
||||
<div id="scrollBall" class="ball"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.hands-background {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.ball-container {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Ball: 220px circle with multi-layer blue glow effect */
|
||||
.ball {
|
||||
width: 220px;
|
||||
height: 220px;
|
||||
border-radius: 50%;
|
||||
background: white;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
mix-blend-mode: screen; /* Creates glowing effect against dark background */
|
||||
opacity: 0.85;
|
||||
|
||||
/* Multi-layer box-shadow for realistic glow */
|
||||
box-shadow:
|
||||
0px 4px 115px 15px #0000f4, /* Outer blue glow */
|
||||
0px 0px 70px 20px #0a33ff, /* Middle glow */
|
||||
inset -39px -20px 100px rgba(0, 0, 244, 0.73); /* Inner gradient for depth */
|
||||
|
||||
/* GPU acceleration for smooth animation */
|
||||
will-change: transform;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
/* Hide entire animation on mobile for performance */
|
||||
@media (max-width: 768px) {
|
||||
.ball,
|
||||
.ball-container,
|
||||
.hands-background {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Background image: wireframe hands, theme-aware */
|
||||
.background-image {
|
||||
background-image: url('/images/benefit-bg.png'); /* Dark theme (default) */
|
||||
background-size: contain;
|
||||
background-position: center top;
|
||||
background-repeat: no-repeat;
|
||||
min-height: 100%;
|
||||
border: 2px solid lime; /* Debug: make container visible */
|
||||
}
|
||||
|
||||
/* Light theme background variant */
|
||||
:root:not(.dark) .background-image {
|
||||
background-image: url('/images/benefit-bg-light.png');
|
||||
}
|
||||
|
||||
.path-svg {
|
||||
opacity: 1; /* Visible for debugging */
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { gsap } from 'gsap';
|
||||
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||
import { MotionPathPlugin } from 'gsap/MotionPathPlugin';
|
||||
|
||||
// Register GSAP plugins
|
||||
gsap.registerPlugin(ScrollTrigger, MotionPathPlugin);
|
||||
|
||||
function initBallAnimation() {
|
||||
// Get DOM elements
|
||||
const ball = document.querySelector('#scrollBall');
|
||||
const watchersHeading = document.querySelector('#watchers h3');
|
||||
const indexerHeading = document.querySelector('#indexer h3');
|
||||
|
||||
// Safety check: ensure all required elements exist
|
||||
if (!ball || !watchersHeading || !indexerHeading) {
|
||||
console.warn('Ball animation: Required elements not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't run animation on mobile devices
|
||||
if (window.matchMedia('(max-width: 768px)').matches) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Kill any existing ScrollTrigger instances
|
||||
ScrollTrigger.getAll().forEach(st => {
|
||||
if (st.vars.id === 'ball-animation') {
|
||||
st.kill();
|
||||
}
|
||||
});
|
||||
|
||||
// Hide ball initially
|
||||
gsap.set(ball, { opacity: 0 });
|
||||
|
||||
// Create GSAP timeline with ScrollTrigger
|
||||
const timeline = gsap.timeline({
|
||||
scrollTrigger: {
|
||||
id: 'ball-animation',
|
||||
trigger: watchersHeading,
|
||||
start: 'bottom top', // Start when watchers H3 bottom leaves screen (hits top)
|
||||
endTrigger: indexerHeading,
|
||||
end: 'top center', // End when indexer H3 hits center of viewport
|
||||
scrub: 1,
|
||||
markers: true,
|
||||
onEnter: () => gsap.set(ball, { opacity: 0.85 }),
|
||||
onLeaveBack: () => gsap.set(ball, { opacity: 0 }),
|
||||
}
|
||||
});
|
||||
|
||||
// Animate ball along SVG path
|
||||
timeline.to(ball, {
|
||||
motionPath: {
|
||||
path: '#ballLine',
|
||||
align: '#ballLine',
|
||||
alignOrigin: [0.5, 0.5],
|
||||
autoRotate: false,
|
||||
},
|
||||
ease: 'none',
|
||||
duration: 1,
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize animation when DOM is ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initBallAnimation);
|
||||
} else {
|
||||
initBallAnimation();
|
||||
}
|
||||
|
||||
// Reinitialize on Astro View Transitions
|
||||
document.addEventListener('astro:page-load', initBallAnimation);
|
||||
|
||||
// Cleanup on navigation
|
||||
document.addEventListener('astro:before-preparation', () => {
|
||||
ScrollTrigger.getAll().forEach(st => {
|
||||
if (st.vars.id === 'ball-animation') {
|
||||
st.kill();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@ -15,7 +15,7 @@ const { metadata } = Astro.props;
|
||||
---
|
||||
|
||||
<Layout metadata={metadata}>
|
||||
{/* Announcement removed - not needed for Laconic site */}
|
||||
|
||||
|
||||
<slot name="header">
|
||||
<Header />
|
||||
|
||||
@ -4,6 +4,7 @@ import VideoHero from '~/components/widgets/VideoHero.astro';
|
||||
import TextSection from '~/components/widgets/TextSection.astro';
|
||||
import ButtonWhiteOutline from '~/components/widgets/ButtonWhiteOutline.astro';
|
||||
import ButtonBlue from '~/components/widgets/ButtonBlue.astro';
|
||||
import ScrollBallAnimation from '~/components/widgets/ScrollBallAnimation.astro';
|
||||
|
||||
const metadata = {
|
||||
title: 'Laconic Network',
|
||||
@ -26,7 +27,7 @@ const metadata = {
|
||||
|
||||
<!-- 2. INTRO SECTION -->
|
||||
<TextSection bgColor="bg-dark" maxWidth="max-w-4xl">
|
||||
<div id="infrastructure" class="scroll-mt-20 py-20">
|
||||
<div id="infrastructure" class="scroll-mt-20 py-20 relative z-10">
|
||||
<p class="text-lg md:text-xl mb-12 leading-relaxed text-white/90">
|
||||
Laconic is launching our flagship datacenter to participate in the Solana network.
|
||||
Host or co-locate your onchain business with Laconic to streamline development and
|
||||
@ -51,16 +52,19 @@ const metadata = {
|
||||
|
||||
<!-- 3. PRODUCTS INTRO -->
|
||||
<TextSection bgColor="bg-dark" maxWidth="max-w-5xl">
|
||||
<div class="scroll-mt-20 py-20">
|
||||
<div id="products-intro" class="scroll-mt-20 py-20 relative z-10">
|
||||
<h2 class="font-display text-4xl md:text-6xl mb-16 text-center text-white leading-tight">
|
||||
Monitor, Index, and Deploy with<br />Laconic's Premier Product Suite
|
||||
</h2>
|
||||
</div>
|
||||
</TextSection>
|
||||
|
||||
<!-- Scroll Ball Animation: Glowing ball traveling between wireframe hands -->
|
||||
<ScrollBallAnimation startTrigger="#watchers" endTrigger="#indexer" />
|
||||
|
||||
<!-- 4. WATCHERS SECTION -->
|
||||
<TextSection bgColor="bg-dark" maxWidth="max-w-4xl">
|
||||
<div id="watchers" class="scroll-mt-20 py-20">
|
||||
<div id="watchers" class="scroll-mt-20 py-20 relative z-10">
|
||||
<h3 class="font-display text-3xl md:text-5xl mb-6 text-white">
|
||||
Watchers
|
||||
</h3>
|
||||
@ -98,7 +102,7 @@ const metadata = {
|
||||
|
||||
<!-- 5. INDEXER SECTION -->
|
||||
<TextSection bgColor="bg-dark" maxWidth="max-w-4xl">
|
||||
<div id="indexer" class="scroll-mt-20 py-20">
|
||||
<div id="indexer" class="scroll-mt-20 py-20 relative z-10">
|
||||
<h3 class="font-display text-3xl md:text-5xl mb-6 text-white">
|
||||
Indexer
|
||||
</h3>
|
||||
@ -136,7 +140,7 @@ const metadata = {
|
||||
|
||||
<!-- 6. LACONIC DEPLOY SECTION -->
|
||||
<TextSection bgColor="bg-dark" maxWidth="max-w-4xl">
|
||||
<div id="deploy" class="scroll-mt-20 py-20">
|
||||
<div id="deploy" class="scroll-mt-20 py-20 relative z-10">
|
||||
<h3 class="font-display text-3xl md:text-5xl mb-6 text-white">
|
||||
Laconic Deploy
|
||||
</h3>
|
||||
|
||||