astro-component-kit 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +139 -27
- package/bin/cli.js +67 -0
- package/bin/installer.js +179 -0
- package/package.json +35 -5
- package/src/components/index.ts +2 -0
- package/src/components/lib/Galaxy3D.astro +249 -137
- package/src/components/lib/app/AppSidebar.astro +249 -0
- package/src/components/lib/app/AppTopbar.astro +284 -0
- package/src/components/lib/app/ArrivalsBoard.astro +176 -0
- package/src/components/lib/app/AuthCard.astro +213 -0
- package/src/components/lib/app/DataTable.astro +346 -0
- package/src/components/lib/app/GuestProfileCard.astro +133 -0
- package/src/components/lib/app/HousekeepingBoard.astro +189 -0
- package/src/components/lib/app/PageHeader.astro +157 -0
- package/src/components/lib/app/PlanUsageCard.astro +112 -0
- package/src/components/lib/app/RoomRack.astro +207 -0
- package/src/components/lib/app/SettingsSection.astro +96 -0
- package/src/components/lib/app/SwitchRow.astro +71 -0
- package/src/components/lib/app/TeamMembers.astro +190 -0
- package/src/components/lib/app/app-icons.ts +52 -0
- package/src/components/lib/app/index.ts +19 -0
- package/src/components/lib/charts/AreaChart.astro +4 -4
- package/src/components/lib/charts/BarChart.astro +3 -3
- package/src/components/lib/charts/ChartFrame.astro +10 -10
- package/src/components/lib/charts/DonutChart.astro +2 -2
- package/src/components/lib/charts/DumbbellChart.astro +1 -1
- package/src/components/lib/charts/ForecastLineChart.astro +1 -1
- package/src/components/lib/charts/GaugeChart.astro +1 -1
- package/src/components/lib/charts/HeatmapChart.astro +1 -1
- package/src/components/lib/charts/HorizontalBarChart.astro +2 -2
- package/src/components/lib/charts/KpiStatTile.astro +4 -4
- package/src/components/lib/charts/LinearProgressGauge.astro +4 -4
- package/src/components/lib/charts/MetricDeltaTable.astro +3 -3
- package/src/components/lib/charts/RadarMetricChart.astro +4 -4
- package/src/components/lib/charts/RadialBarChart.astro +1 -1
- package/src/components/lib/charts/RetentionCohortTable.astro +2 -2
- package/src/components/lib/charts/TreemapChart.astro +1 -1
- package/src/components/lib/ecommerce/CartDrawer.astro +483 -0
- package/src/components/lib/ecommerce/CartPageList.astro +290 -0
- package/src/components/lib/ecommerce/CartToast.astro +276 -0
- package/src/components/lib/ecommerce/CategoryTiles.astro +143 -0
- package/src/components/lib/ecommerce/CheckoutForm.astro +423 -0
- package/src/components/lib/ecommerce/CountdownBanner.astro +230 -0
- package/src/components/lib/ecommerce/FilterBar.astro +278 -0
- package/src/components/lib/ecommerce/FilterSidebar.astro +405 -0
- package/src/components/lib/ecommerce/LiveActivityToast.astro +213 -0
- package/src/components/lib/ecommerce/OrderSummary.astro +295 -0
- package/src/components/lib/ecommerce/ProductBuyBox.astro +247 -0
- package/src/components/lib/ecommerce/ProductCardMinimal.astro +229 -0
- package/src/components/lib/ecommerce/ProductGallery.astro +145 -0
- package/src/components/lib/ecommerce/ProductGrid.astro +118 -0
- package/src/components/lib/ecommerce/ProductMedia.astro +113 -0
- package/src/components/lib/ecommerce/ProductRating.astro +78 -0
- package/src/components/lib/ecommerce/ProductToolbar.astro +93 -0
- package/src/components/lib/ecommerce/PromoBar.astro +145 -0
- package/src/components/lib/ecommerce/StockIndicator.astro +127 -0
- package/src/components/lib/ecommerce/StoreHeader.astro +446 -0
- package/src/components/lib/ecommerce/StoreHero.astro +260 -0
- package/src/components/lib/ecommerce/StoreProductCard.astro +307 -0
- package/src/components/lib/ecommerce/TrustBadges.astro +101 -0
- package/src/components/lib/ecommerce/VariantPicker.astro +145 -0
- package/src/components/lib/ecommerce/cart-store.ts +151 -0
- package/src/components/lib/ecommerce/catalog-filter.ts +62 -0
- package/src/components/lib/ecommerce/index.ts +34 -0
- package/src/components/lib/ecommerce/product-icons.ts +28 -0
- package/src/components/lib/ecommerce/types.ts +70 -0
- package/src/components/lib/forms/AnimatedInput.astro +4 -2
- package/src/components/lib/forms/CustomRadioGroup.astro +3 -1
- package/src/components/lib/index.ts +2 -0
- package/src/components/lib/layout/BentoGrid3.astro +6 -4
- package/src/components/lib/layout/CenterFixedBox.astro +2 -2
- package/src/components/lib/layout/CenteredContainer.astro +4 -2
- package/src/components/lib/layout/ClusterHorizontal.astro +2 -2
- package/src/components/lib/layout/FeatureHero.astro +4 -2
- package/src/components/lib/layout/FooterCenteredMinimal.astro +4 -2
- package/src/components/lib/layout/FullBleedSection.astro +2 -2
- package/src/components/lib/layout/GalleryGrid4.astro +2 -2
- package/src/components/lib/layout/HolyGrailGrid.astro +11 -9
- package/src/components/lib/layout/LandingSectionSplit.astro +5 -3
- package/src/components/lib/layout/MasonryGrid.astro +2 -2
- package/src/components/lib/layout/MinimalFooterGrid.astro +1 -1
- package/src/components/lib/layout/ScrollSnapSection.astro +1 -1
- package/src/components/lib/layout/SidebarLayoutWrapper.astro +4 -2
- package/src/components/lib/layout/SplitScreenLayout.astro +3 -3
- package/src/components/lib/layout/StepLayoutVertical.astro +1 -1
- package/src/components/lib/layout/StickySidebarWrapper.astro +4 -2
- package/src/components/lib/sections/AgencyContactBrief.astro +194 -0
- package/src/components/lib/sections/AgencyFooterCta.astro +94 -0
- package/src/components/lib/sections/AgencyHeader.astro +161 -0
- package/src/components/lib/sections/AgencyHeroKinetic.astro +172 -0
- package/src/components/lib/sections/AgencyServicesList.astro +113 -0
- package/src/components/lib/sections/AgencyTeamGrid.astro +111 -0
- package/src/components/lib/sections/AgencyWorkGrid.astro +147 -0
- package/src/components/lib/sections/BlogGridSection.astro +9 -7
- package/src/components/lib/sections/ButtonsShowcase.astro +20 -18
- package/src/components/lib/sections/CardsShowcase.astro +28 -26
- package/src/components/lib/sections/CareersOpenings.astro +10 -8
- package/src/components/lib/sections/ChangelogSection.astro +20 -7
- package/src/components/lib/sections/ContactGlassForm.astro +10 -8
- package/src/components/lib/sections/ContactSplitMap.astro +11 -9
- package/src/components/lib/sections/CtaAppDownload.astro +13 -11
- package/src/components/lib/sections/CtaGlowBanner.astro +12 -10
- package/src/components/lib/sections/CtaNewsletterSection.astro +13 -11
- package/src/components/lib/sections/CtaSplitImage.astro +13 -11
- package/src/components/lib/sections/DataShowcase.astro +23 -21
- package/src/components/lib/sections/FaqAccordionSection.astro +8 -8
- package/src/components/lib/sections/FaqTwoColumn.astro +9 -7
- package/src/components/lib/sections/FeatureAlternatingRows.astro +10 -8
- package/src/components/lib/sections/FeatureBentoGrid.astro +13 -11
- package/src/components/lib/sections/FeatureIconGrid.astro +6 -6
- package/src/components/lib/sections/FeatureTabsShowcase.astro +13 -11
- package/src/components/lib/sections/FeatureTimelineFlow.astro +8 -6
- package/src/components/lib/sections/FeedbackShowcase.astro +15 -13
- package/src/components/lib/sections/FooterMegaSection.astro +13 -11
- package/src/components/lib/sections/FormsShowcase.astro +22 -20
- package/src/components/lib/sections/HeroDashboardPreview.astro +11 -9
- package/src/components/lib/sections/HeroGlass.astro +22 -20
- package/src/components/lib/sections/HeroGradient.astro +15 -13
- package/src/components/lib/sections/HeroMinimalCentered.astro +10 -10
- package/src/components/lib/sections/HeroShowcaseSplit.astro +18 -16
- package/src/components/lib/sections/HeroSplit.astro +13 -11
- package/src/components/lib/sections/HeroTerminal.astro +14 -12
- package/src/components/lib/sections/HeroWaitlist.astro +10 -8
- package/src/components/lib/sections/HowItWorksSteps.astro +12 -10
- package/src/components/lib/sections/IntegrationsGrid.astro +7 -7
- package/src/components/lib/sections/LayoutShowcase.astro +21 -19
- package/src/components/lib/sections/LogoCloudSection.astro +4 -4
- package/src/components/lib/sections/LogoWallGrid.astro +6 -4
- package/src/components/lib/sections/NavigationShowcase.astro +15 -13
- package/src/components/lib/sections/OverlaysShowcase.astro +21 -19
- package/src/components/lib/sections/PricingComparisonMatrix.astro +15 -13
- package/src/components/lib/sections/PricingSinglePlan.astro +13 -11
- package/src/components/lib/sections/PricingTierCards.astro +11 -9
- package/src/components/lib/sections/PricingToggleCards.astro +13 -13
- package/src/components/lib/sections/ShopCategoryHero.astro +103 -0
- package/src/components/lib/sections/ShopCollectionMosaic.astro +131 -0
- package/src/components/lib/sections/ShopFooterWordmark.astro +100 -0
- package/src/components/lib/sections/ShopHeaderEditorial.astro +396 -0
- package/src/components/lib/sections/ShopHeroEditorial.astro +151 -0
- package/src/components/lib/sections/ShopLookbook.astro +250 -0
- package/src/components/lib/sections/ShopNewsletterEditorial.astro +128 -0
- package/src/components/lib/sections/ShopProductRail.astro +150 -0
- package/src/components/lib/sections/ShopReviewsEditorial.astro +133 -0
- package/src/components/lib/sections/ShopValuesMarquee.astro +91 -0
- package/src/components/lib/sections/StatsCounterBand.astro +9 -7
- package/src/components/lib/sections/StatsDashboardSection.astro +4 -2
- package/src/components/lib/sections/StatsMetricShowcase.astro +9 -7
- package/src/components/lib/sections/TeamGridSection.astro +12 -10
- package/src/components/lib/sections/TestimonialCarousel.astro +10 -8
- package/src/components/lib/sections/TestimonialFeatured.astro +7 -5
- package/src/components/lib/sections/TestimonialGridSection.astro +7 -7
- package/src/components/lib/sections/TestimonialWallGlass.astro +11 -9
- package/src/components/lib/sections/TypographyShowcase.astro +10 -8
- package/src/components/lib/sections/index.ts +10 -0
- package/src/components/lib/templates/ecommerce/StoreCategoryPage.astro +160 -0
- package/src/components/lib/templates/ecommerce/StoreCheckoutPage.astro +98 -0
- package/src/components/lib/templates/ecommerce/StoreHomePage.astro +191 -0
- package/src/components/lib/templates/ecommerce/StoreLayout.astro +169 -0
- package/src/components/lib/templates/ecommerce/StoreProductPage.astro +165 -0
- package/src/components/lib/templates/ecommerce/data.ts +187 -0
- package/src/components/lib/templates/ecommerce/index.ts +14 -0
- package/src/components/lib/templates/ecommerce-editorial/EditorialBagPage.astro +52 -0
- package/src/components/lib/templates/ecommerce-editorial/EditorialCheckoutPage.astro +123 -0
- package/src/components/lib/templates/ecommerce-editorial/EditorialHomePage.astro +102 -0
- package/src/components/lib/templates/ecommerce-editorial/EditorialLayout.astro +216 -0
- package/src/components/lib/templates/ecommerce-editorial/EditorialProductPage.astro +205 -0
- package/src/components/lib/templates/ecommerce-editorial/EditorialShopPage.astro +112 -0
- package/src/components/lib/templates/ecommerce-editorial/data.ts +125 -0
- package/src/components/lib/templates/ecommerce-editorial/index.ts +16 -0
- package/src/components/lib/templates/saas-hotel/AppLayout.astro +83 -0
- package/src/components/lib/templates/saas-hotel/DashboardPage.astro +66 -0
- package/src/components/lib/templates/saas-hotel/GuestsPage.astro +65 -0
- package/src/components/lib/templates/saas-hotel/LandingPage.astro +213 -0
- package/src/components/lib/templates/saas-hotel/ReportsPage.astro +63 -0
- package/src/components/lib/templates/saas-hotel/ReservationsPage.astro +49 -0
- package/src/components/lib/templates/saas-hotel/RoomsPage.astro +48 -0
- package/src/components/lib/templates/saas-hotel/SettingsPage.astro +115 -0
- package/src/components/lib/templates/saas-hotel/SignInPage.astro +50 -0
- package/src/components/lib/templates/saas-hotel/data.ts +229 -0
- package/src/components/lib/templates/saas-hotel/index.ts +19 -0
- package/src/components/lib/templates/saas-hotel/lobby-theme.css +90 -0
- package/src/data/registry/app.ts +93 -0
- package/src/data/registry/buttons.ts +3279 -0
- package/src/data/registry/cards.ts +1753 -0
- package/src/data/registry/charts-svg.ts +557 -0
- package/src/data/registry/charts.ts +500 -0
- package/src/data/registry/data.ts +986 -0
- package/src/data/registry/ecommerce.ts +269 -0
- package/src/data/registry/feedback.ts +1824 -0
- package/src/data/registry/forms.ts +2513 -0
- package/src/data/registry/index.ts +64 -0
- package/src/data/registry/layout.ts +206 -0
- package/src/data/registry/navigation.ts +936 -0
- package/src/data/registry/overlays.ts +1133 -0
- package/src/data/registry/sections-extra.ts +397 -0
- package/src/data/registry/sections.ts +361 -0
- package/src/data/registry/sources.ts +41 -0
- package/src/data/registry/templates.ts +414 -0
- package/src/data/registry/types.ts +30 -0
- package/src/data/registry/typography.ts +777 -0
- package/src/mcp/http-server.ts +324 -0
- package/src/mcp/index.ts +67 -0
- package/src/mcp/installer.ts +178 -0
- package/src/mcp/registry-adapter.ts +278 -0
- package/src/mcp/stdio-server.ts +11 -0
- package/src/mcp/tools.ts +247 -0
- package/src/mcp/types.ts +53 -0
- package/src/styles/global.css +95 -0
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
/**
|
|
3
|
-
* Galaxy3D — Interactive 3D galaxy background with mouse-reactive particle
|
|
3
|
+
* Galaxy3D — Interactive 3D galaxy background with a fluid, mouse-reactive particle field.
|
|
4
4
|
*
|
|
5
|
-
* Renders a spiral galaxy with thousands of particles using Three.js.
|
|
6
|
-
*
|
|
5
|
+
* Renders a spiral galaxy with thousands of particles using Three.js. All particle motion
|
|
6
|
+
* (rotation, twinkle and cursor interaction) runs on the GPU in a vertex shader:
|
|
7
|
+
* the cursor parts the stars with a soft gaussian falloff, a gentle swirl and a wake
|
|
8
|
+
* that follows the pointer's direction, and nearby stars glow slightly brighter.
|
|
9
|
+
*
|
|
10
|
+
* Pointer tracking is frame-rate independent and listens on `window`, so overlays on top
|
|
11
|
+
* of the canvas (titles, buttons) don't create dead zones. Rendering pauses when the
|
|
12
|
+
* canvas is off-screen or the tab is hidden, and `prefers-reduced-motion` renders a
|
|
13
|
+
* static frame without interaction.
|
|
7
14
|
*
|
|
8
15
|
* @param {string} className - Additional CSS class
|
|
9
16
|
*/
|
|
@@ -14,7 +21,7 @@ interface Props {
|
|
|
14
21
|
const { className = '' } = Astro.props;
|
|
15
22
|
---
|
|
16
23
|
|
|
17
|
-
<div class=
|
|
24
|
+
<div class={`galaxy-3d-container ${className}`} id="galaxy-3d-wrapper" aria-hidden="true">
|
|
18
25
|
<canvas id="galaxy-3d-canvas"></canvas>
|
|
19
26
|
</div>
|
|
20
27
|
|
|
@@ -45,28 +52,135 @@ const { className = '' } = Astro.props;
|
|
|
45
52
|
const container = canvas.parentElement;
|
|
46
53
|
if (!container) return;
|
|
47
54
|
|
|
55
|
+
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
56
|
+
|
|
48
57
|
// ===== SCENE =====
|
|
49
58
|
const scene = new THREE.Scene();
|
|
50
|
-
const
|
|
59
|
+
const FOV = 75;
|
|
60
|
+
const camera = new THREE.PerspectiveCamera(FOV, 1, 0.1, 100);
|
|
51
61
|
camera.position.set(0, 0, 5);
|
|
52
62
|
|
|
53
63
|
const renderer = new THREE.WebGLRenderer({ canvas, alpha: true, antialias: true });
|
|
54
64
|
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
|
55
65
|
|
|
66
|
+
// ===== PARTICLE SHADER =====
|
|
67
|
+
// Shared by the galaxy and the background stars. Positions are static; the shader
|
|
68
|
+
// rotates them, projects them, and displaces them away from the cursor in view space
|
|
69
|
+
// so the effect lines up with the pointer at any depth.
|
|
70
|
+
const vertexShader = /* glsl */ `
|
|
71
|
+
uniform float uTime;
|
|
72
|
+
uniform float uRotation;
|
|
73
|
+
uniform float uSize;
|
|
74
|
+
uniform float uScale;
|
|
75
|
+
uniform float uAspect;
|
|
76
|
+
uniform float uTanHalfFov;
|
|
77
|
+
uniform vec2 uMouse;
|
|
78
|
+
uniform vec2 uMouseVel;
|
|
79
|
+
uniform float uStrength;
|
|
80
|
+
uniform float uRadius;
|
|
81
|
+
uniform float uPush;
|
|
82
|
+
|
|
83
|
+
attribute vec3 color;
|
|
84
|
+
attribute float aRandom;
|
|
85
|
+
|
|
86
|
+
varying vec3 vColor;
|
|
87
|
+
varying float vAlpha;
|
|
88
|
+
varying float vGlow;
|
|
89
|
+
|
|
90
|
+
void main() {
|
|
91
|
+
// Spin around the galaxy's own axis.
|
|
92
|
+
float c = cos(uRotation);
|
|
93
|
+
float s = sin(uRotation);
|
|
94
|
+
vec3 p = vec3(position.x * c - position.z * s, position.y, position.x * s + position.z * c);
|
|
95
|
+
|
|
96
|
+
vec4 view = viewMatrix * modelMatrix * vec4(p, 1.0);
|
|
97
|
+
vec4 clip = projectionMatrix * view;
|
|
98
|
+
vec2 ndc = clip.xy / clip.w;
|
|
99
|
+
|
|
100
|
+
// Distance to the cursor in aspect-corrected screen units.
|
|
101
|
+
vec2 d = (ndc - uMouse) * vec2(uAspect, 1.0);
|
|
102
|
+
float dist = length(d);
|
|
103
|
+
float falloff = exp(-(dist * dist) / (uRadius * uRadius)) * uStrength;
|
|
104
|
+
vec2 dir = d / max(dist, 1e-4);
|
|
105
|
+
vec2 swirl = vec2(-dir.y, dir.x);
|
|
106
|
+
|
|
107
|
+
// Soft repulsion + a little swirl + a wake in the direction of travel.
|
|
108
|
+
// Each star reacts slightly differently so the field never moves as a rigid disc.
|
|
109
|
+
float personal = 0.75 + aRandom * 0.5;
|
|
110
|
+
vec2 push = (dir * 0.85 + swirl * 0.35) * falloff * personal + uMouseVel * falloff * 0.9;
|
|
111
|
+
|
|
112
|
+
// Screen units -> view units at this particle's depth.
|
|
113
|
+
// (d is measured in NDC-y units on both axes, and one NDC-y unit = tan(fov/2) * depth.)
|
|
114
|
+
view.xy += push * uPush * uTanHalfFov * -view.z;
|
|
115
|
+
gl_Position = projectionMatrix * view;
|
|
116
|
+
|
|
117
|
+
// Per-star twinkle with its own speed and phase.
|
|
118
|
+
float twinkle = 0.7 + 0.3 * sin(uTime * (0.6 + aRandom * 1.8) + aRandom * 6.2831);
|
|
119
|
+
|
|
120
|
+
vColor = color;
|
|
121
|
+
vGlow = falloff;
|
|
122
|
+
vAlpha = twinkle;
|
|
123
|
+
gl_PointSize = max(uSize * (0.55 + aRandom * 1.0) * (1.0 + falloff * 0.6) * uScale / -view.z, 1.0);
|
|
124
|
+
}
|
|
125
|
+
`;
|
|
126
|
+
|
|
127
|
+
const fragmentShader = /* glsl */ `
|
|
128
|
+
uniform float uOpacity;
|
|
129
|
+
varying vec3 vColor;
|
|
130
|
+
varying float vAlpha;
|
|
131
|
+
varying float vGlow;
|
|
132
|
+
|
|
133
|
+
void main() {
|
|
134
|
+
// Round, soft-edged star instead of a hard square.
|
|
135
|
+
float r = length(gl_PointCoord - 0.5);
|
|
136
|
+
float a = smoothstep(0.5, 0.05, r);
|
|
137
|
+
if (a <= 0.0) discard;
|
|
138
|
+
vec3 col = vColor * (1.0 + vGlow * 0.9);
|
|
139
|
+
gl_FragColor = vec4(col, a * vAlpha * uOpacity);
|
|
140
|
+
}
|
|
141
|
+
`;
|
|
142
|
+
|
|
143
|
+
// Pointer state shared by every particle layer.
|
|
144
|
+
const pointerUniforms = {
|
|
145
|
+
uMouse: { value: new THREE.Vector2(0, 0) },
|
|
146
|
+
uMouseVel: { value: new THREE.Vector2(0, 0) },
|
|
147
|
+
uStrength: { value: 0 },
|
|
148
|
+
uAspect: { value: 1 },
|
|
149
|
+
uScale: { value: 1 },
|
|
150
|
+
uTanHalfFov: { value: Math.tan(THREE.MathUtils.degToRad(FOV / 2)) },
|
|
151
|
+
uTime: { value: 0 },
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
function particleMaterial({ size, opacity, radius, push }) {
|
|
155
|
+
return new THREE.ShaderMaterial({
|
|
156
|
+
vertexShader,
|
|
157
|
+
fragmentShader,
|
|
158
|
+
transparent: true,
|
|
159
|
+
depthWrite: false,
|
|
160
|
+
blending: THREE.AdditiveBlending,
|
|
161
|
+
uniforms: {
|
|
162
|
+
...pointerUniforms,
|
|
163
|
+
uRotation: { value: 0 },
|
|
164
|
+
uSize: { value: size },
|
|
165
|
+
uOpacity: { value: opacity },
|
|
166
|
+
uRadius: { value: radius },
|
|
167
|
+
uPush: { value: push },
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
56
172
|
function resize() {
|
|
57
173
|
const w = container.clientWidth;
|
|
58
174
|
const h = container.clientHeight;
|
|
59
175
|
if (w === 0 || h === 0) return;
|
|
60
|
-
renderer.setSize(w, h);
|
|
176
|
+
renderer.setSize(w, h, false);
|
|
61
177
|
camera.aspect = w / h;
|
|
62
178
|
camera.updateProjectionMatrix();
|
|
179
|
+
pointerUniforms.uAspect.value = w / h;
|
|
180
|
+
pointerUniforms.uScale.value = renderer.domElement.height * 0.5;
|
|
181
|
+
if (reduceMotion) renderer.render(scene, camera);
|
|
63
182
|
}
|
|
64
183
|
|
|
65
|
-
requestAnimationFrame(() => resize());
|
|
66
|
-
setTimeout(resize, 100);
|
|
67
|
-
setTimeout(resize, 500);
|
|
68
|
-
window.addEventListener('resize', resize);
|
|
69
|
-
|
|
70
184
|
// ===== COLORS =====
|
|
71
185
|
const palette = [
|
|
72
186
|
new THREE.Color('#fef3c7'),
|
|
@@ -91,7 +205,7 @@ const { className = '' } = Astro.props;
|
|
|
91
205
|
const galaxyGeometry = new THREE.BufferGeometry();
|
|
92
206
|
const galaxyPositions = new Float32Array(GALAXY_COUNT * 3);
|
|
93
207
|
const galaxyColors = new Float32Array(GALAXY_COUNT * 3);
|
|
94
|
-
const
|
|
208
|
+
const galaxyRandom = new Float32Array(GALAXY_COUNT);
|
|
95
209
|
|
|
96
210
|
for (let i = 0; i < GALAXY_COUNT; i++) {
|
|
97
211
|
const i3 = i * 3;
|
|
@@ -108,70 +222,56 @@ const { className = '' } = Astro.props;
|
|
|
108
222
|
const angle = branchAngle + spinAngle;
|
|
109
223
|
const spiralOffset = radius * galaxyParams.inwardDrop;
|
|
110
224
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
galaxyPositions[i3] = x;
|
|
116
|
-
galaxyPositions[i3 + 1] = y;
|
|
117
|
-
galaxyPositions[i3 + 2] = z;
|
|
118
|
-
|
|
119
|
-
galaxyOriginalPositions[i3] = x;
|
|
120
|
-
galaxyOriginalPositions[i3 + 1] = y;
|
|
121
|
-
galaxyOriginalPositions[i3 + 2] = z;
|
|
225
|
+
galaxyPositions[i3] = Math.cos(angle) * radius + randomX + Math.cos(angle + Math.PI / 2) * spiralOffset;
|
|
226
|
+
galaxyPositions[i3 + 1] = randomY;
|
|
227
|
+
galaxyPositions[i3 + 2] = Math.sin(angle) * radius + randomZ + Math.sin(angle + Math.PI / 2) * spiralOffset;
|
|
122
228
|
|
|
123
229
|
const colorMix = radius / galaxyParams.radius;
|
|
124
230
|
const baseColor = palette[Math.floor(Math.random() * 3)];
|
|
125
231
|
const outerColor = palette[3 + Math.floor(Math.random() * 2)];
|
|
126
232
|
const finalColor = baseColor.clone().lerp(outerColor, colorMix);
|
|
127
|
-
|
|
128
233
|
galaxyColors[i3] = finalColor.r;
|
|
129
234
|
galaxyColors[i3 + 1] = finalColor.g;
|
|
130
235
|
galaxyColors[i3 + 2] = finalColor.b;
|
|
236
|
+
|
|
237
|
+
galaxyRandom[i] = Math.random();
|
|
131
238
|
}
|
|
132
239
|
|
|
133
240
|
galaxyGeometry.setAttribute('position', new THREE.BufferAttribute(galaxyPositions, 3));
|
|
134
241
|
galaxyGeometry.setAttribute('color', new THREE.BufferAttribute(galaxyColors, 3));
|
|
242
|
+
galaxyGeometry.setAttribute('aRandom', new THREE.BufferAttribute(galaxyRandom, 1));
|
|
135
243
|
|
|
136
|
-
const galaxyMaterial =
|
|
137
|
-
size: 0.008,
|
|
138
|
-
sizeAttenuation: true,
|
|
139
|
-
depthWrite: false,
|
|
140
|
-
blending: THREE.AdditiveBlending,
|
|
141
|
-
vertexColors: true,
|
|
142
|
-
transparent: true,
|
|
143
|
-
opacity: 0.95,
|
|
144
|
-
});
|
|
145
|
-
|
|
244
|
+
const galaxyMaterial = particleMaterial({ size: 0.011, opacity: 0.95, radius: 0.19, push: 0.12 });
|
|
146
245
|
const galaxyPoints = new THREE.Points(galaxyGeometry, galaxyMaterial);
|
|
147
246
|
galaxyPoints.rotation.x = -0.15;
|
|
148
247
|
galaxyPoints.rotation.z = 0.05;
|
|
149
248
|
scene.add(galaxyPoints);
|
|
150
249
|
|
|
151
250
|
// ===== BACKGROUND STARS =====
|
|
251
|
+
// Same shader with a much weaker push, so the deep field reacts subtly (parallax feel).
|
|
152
252
|
const STAR_COUNT = 2000;
|
|
153
253
|
const starGeometry = new THREE.BufferGeometry();
|
|
154
254
|
const starPositions = new Float32Array(STAR_COUNT * 3);
|
|
255
|
+
const starColors = new Float32Array(STAR_COUNT * 3);
|
|
256
|
+
const starRandom = new Float32Array(STAR_COUNT);
|
|
257
|
+
const white = new THREE.Color('#ffffff');
|
|
155
258
|
|
|
156
259
|
for (let i = 0; i < STAR_COUNT; i++) {
|
|
157
260
|
const i3 = i * 3;
|
|
158
261
|
starPositions[i3] = (Math.random() - 0.5) * 20;
|
|
159
262
|
starPositions[i3 + 1] = (Math.random() - 0.5) * 20;
|
|
160
263
|
starPositions[i3 + 2] = (Math.random() - 0.5) * 10 - 5;
|
|
264
|
+
starColors[i3] = white.r;
|
|
265
|
+
starColors[i3 + 1] = white.g;
|
|
266
|
+
starColors[i3 + 2] = white.b;
|
|
267
|
+
starRandom[i] = Math.random();
|
|
161
268
|
}
|
|
162
269
|
|
|
163
270
|
starGeometry.setAttribute('position', new THREE.BufferAttribute(starPositions, 3));
|
|
271
|
+
starGeometry.setAttribute('color', new THREE.BufferAttribute(starColors, 3));
|
|
272
|
+
starGeometry.setAttribute('aRandom', new THREE.BufferAttribute(starRandom, 1));
|
|
164
273
|
|
|
165
|
-
const starMaterial =
|
|
166
|
-
size: 0.008,
|
|
167
|
-
sizeAttenuation: true,
|
|
168
|
-
depthWrite: false,
|
|
169
|
-
transparent: true,
|
|
170
|
-
opacity: 0.5,
|
|
171
|
-
color: new THREE.Color('#ffffff'),
|
|
172
|
-
blending: THREE.AdditiveBlending,
|
|
173
|
-
});
|
|
174
|
-
|
|
274
|
+
const starMaterial = particleMaterial({ size: 0.012, opacity: 0.5, radius: 0.3, push: 0.05 });
|
|
175
275
|
const stars = new THREE.Points(starGeometry, starMaterial);
|
|
176
276
|
scene.add(stars);
|
|
177
277
|
|
|
@@ -236,100 +336,91 @@ const { className = '' } = Astro.props;
|
|
|
236
336
|
ring.position.z = -0.3;
|
|
237
337
|
scene.add(ring);
|
|
238
338
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
339
|
+
requestAnimationFrame(() => resize());
|
|
340
|
+
setTimeout(resize, 100);
|
|
341
|
+
setTimeout(resize, 500);
|
|
342
|
+
window.addEventListener('resize', resize);
|
|
343
|
+
|
|
344
|
+
// Reduced motion: one static frame, no interaction or animation loop.
|
|
345
|
+
if (reduceMotion) {
|
|
346
|
+
renderer.render(scene, camera);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
246
349
|
|
|
247
|
-
|
|
248
|
-
|
|
350
|
+
// ===== POINTER TRACKING =====
|
|
351
|
+
// Listen on window so the title/buttons layered over the canvas don't block it.
|
|
352
|
+
const target = { x: 0, y: 0, inside: false };
|
|
353
|
+
const smooth = { x: 0, y: 0 };
|
|
354
|
+
const vel = { x: 0, y: 0 };
|
|
355
|
+
let hasPointer = false;
|
|
356
|
+
let lastMove = 0;
|
|
249
357
|
|
|
250
|
-
|
|
358
|
+
window.addEventListener('pointermove', (e) => {
|
|
251
359
|
const rect = canvas.getBoundingClientRect();
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
360
|
+
const inside = e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom;
|
|
361
|
+
target.inside = inside && e.pointerType !== 'touch';
|
|
362
|
+
if (!target.inside) return;
|
|
363
|
+
target.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
|
|
364
|
+
target.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
|
|
365
|
+
if (!hasPointer) {
|
|
366
|
+
// First contact: start from the pointer instead of sliding in from the centre.
|
|
367
|
+
smooth.x = target.x;
|
|
368
|
+
smooth.y = target.y;
|
|
369
|
+
hasPointer = true;
|
|
370
|
+
}
|
|
371
|
+
lastMove = performance.now();
|
|
372
|
+
}, { passive: true });
|
|
255
373
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
targetMouse.y = 0;
|
|
259
|
-
});
|
|
374
|
+
document.addEventListener('pointerleave', () => { target.inside = false; });
|
|
375
|
+
window.addEventListener('blur', () => { target.inside = false; });
|
|
260
376
|
|
|
261
377
|
// ===== ANIMATION =====
|
|
262
378
|
const clock = new THREE.Clock();
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
303
|
-
|
|
304
|
-
let pushX = 0, pushY = 0, pushZ = 0;
|
|
305
|
-
|
|
306
|
-
if (dist < MOUSE_RADIUS && dist > 0.01) {
|
|
307
|
-
const force = (1 - dist / MOUSE_RADIUS) * MOUSE_FORCE;
|
|
308
|
-
const nx = dx / dist;
|
|
309
|
-
const ny = dy / dist;
|
|
310
|
-
pushX = nx * force;
|
|
311
|
-
pushY = ny * force;
|
|
312
|
-
pushZ = (nx * ny) * force * 0.15;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
const targetX = baseX + pushX;
|
|
316
|
-
const targetY = baseY + pushY;
|
|
317
|
-
const targetZ = baseZ + pushZ;
|
|
318
|
-
|
|
319
|
-
const spring = 0.03;
|
|
320
|
-
pos[i3] += (targetX - pos[i3]) * spring;
|
|
321
|
-
pos[i3 + 1] += (targetY - pos[i3 + 1]) * spring;
|
|
322
|
-
pos[i3 + 2] += (targetZ - pos[i3 + 2]) * spring;
|
|
323
|
-
}
|
|
324
|
-
galaxyGeometry.attributes.position.needsUpdate = true;
|
|
325
|
-
|
|
326
|
-
// Animate dust
|
|
379
|
+
let elapsed = 0;
|
|
380
|
+
let rafId = 0;
|
|
381
|
+
let visible = true;
|
|
382
|
+
|
|
383
|
+
// Frame-rate independent exponential smoothing.
|
|
384
|
+
const damp = (current, goal, lambda, dt) => current + (goal - current) * (1 - Math.exp(-lambda * dt));
|
|
385
|
+
|
|
386
|
+
function frame() {
|
|
387
|
+
rafId = requestAnimationFrame(frame);
|
|
388
|
+
const dt = Math.min(clock.getDelta(), 1 / 20);
|
|
389
|
+
elapsed += dt;
|
|
390
|
+
pointerUniforms.uTime.value = elapsed;
|
|
391
|
+
|
|
392
|
+
// Pointer: follows quickly but without jitter; velocity drives the wake.
|
|
393
|
+
const px = smooth.x;
|
|
394
|
+
const py = smooth.y;
|
|
395
|
+
smooth.x = damp(smooth.x, target.x, 9, dt);
|
|
396
|
+
smooth.y = damp(smooth.y, target.y, 9, dt);
|
|
397
|
+
const aspect = pointerUniforms.uAspect.value;
|
|
398
|
+
const rawVx = ((smooth.x - px) / Math.max(dt, 1e-4)) * aspect * 0.05;
|
|
399
|
+
const rawVy = ((smooth.y - py) / Math.max(dt, 1e-4)) * 0.05;
|
|
400
|
+
vel.x = damp(vel.x, rawVx, 6, dt);
|
|
401
|
+
vel.y = damp(vel.y, rawVy, 6, dt);
|
|
402
|
+
const vLen = Math.hypot(vel.x, vel.y);
|
|
403
|
+
const vMax = 0.6;
|
|
404
|
+
const vk = vLen > vMax ? vMax / vLen : 1;
|
|
405
|
+
pointerUniforms.uMouse.value.set(smooth.x, smooth.y);
|
|
406
|
+
pointerUniforms.uMouseVel.value.set(vel.x * vk, vel.y * vk);
|
|
407
|
+
|
|
408
|
+
// Influence eases in on hover, relaxes a little when the pointer rests, and fades out on leave.
|
|
409
|
+
const idle = performance.now() - lastMove > 1800;
|
|
410
|
+
const goalStrength = target.inside ? (idle ? 0.55 : 1) : 0;
|
|
411
|
+
pointerUniforms.uStrength.value = damp(pointerUniforms.uStrength.value, goalStrength, target.inside ? 3.5 : 1.8, dt);
|
|
412
|
+
|
|
413
|
+
// Galaxy spin (on the GPU) and a much slower drift for the deep field.
|
|
414
|
+
galaxyMaterial.uniforms.uRotation.value = elapsed * 0.025;
|
|
415
|
+
starMaterial.uniforms.uRotation.value = elapsed * 0.004;
|
|
416
|
+
|
|
417
|
+
// Dust drift.
|
|
327
418
|
const dustPos = dustGeometry.attributes.position.array;
|
|
328
419
|
for (let i = 0; i < DUST_COUNT; i++) {
|
|
329
420
|
const i3 = i * 3;
|
|
330
|
-
dustPos[i3] += dustVelocities[i3] * 0.
|
|
331
|
-
dustPos[i3 + 1] += dustVelocities[i3 + 1] * 0.
|
|
332
|
-
dustPos[i3 + 2] += dustVelocities[i3 + 2] * 0.
|
|
421
|
+
dustPos[i3] += dustVelocities[i3] * 0.12 * dt + Math.sin(elapsed * 0.3 + i) * 0.006 * dt;
|
|
422
|
+
dustPos[i3 + 1] += dustVelocities[i3 + 1] * 0.12 * dt + Math.cos(elapsed * 0.3 + i) * 0.006 * dt;
|
|
423
|
+
dustPos[i3 + 2] += dustVelocities[i3 + 2] * 0.12 * dt;
|
|
333
424
|
|
|
334
425
|
if (dustPos[i3] > 4) dustPos[i3] = -4;
|
|
335
426
|
if (dustPos[i3] < -4) dustPos[i3] = 4;
|
|
@@ -338,23 +429,44 @@ const { className = '' } = Astro.props;
|
|
|
338
429
|
}
|
|
339
430
|
dustGeometry.attributes.position.needsUpdate = true;
|
|
340
431
|
|
|
341
|
-
// Central glow pulse
|
|
342
|
-
const
|
|
432
|
+
// Central glow pulse.
|
|
433
|
+
const pulse = Math.sin(elapsed * 1.2);
|
|
434
|
+
const pulseScale = 1 + pulse * 0.04;
|
|
343
435
|
glow.scale.set(pulseScale, pulseScale, pulseScale);
|
|
344
|
-
glowMat.opacity = 0.12 +
|
|
436
|
+
glowMat.opacity = 0.12 + pulse * 0.02;
|
|
345
437
|
|
|
346
|
-
// Ring rotation
|
|
438
|
+
// Ring rotation.
|
|
347
439
|
ring.rotation.z = elapsed * 0.08;
|
|
348
440
|
ringMat.opacity = 0.06 + Math.sin(elapsed * 1.5) * 0.015;
|
|
349
441
|
|
|
350
|
-
// Gentle camera
|
|
351
|
-
|
|
352
|
-
camera.position.
|
|
442
|
+
// Gentle camera parallax that follows the pointer only while it is over the scene.
|
|
443
|
+
const s = pointerUniforms.uStrength.value;
|
|
444
|
+
camera.position.x = damp(camera.position.x, smooth.x * 0.18 * s, 2.2, dt);
|
|
445
|
+
camera.position.y = damp(camera.position.y, smooth.y * 0.12 * s, 2.2, dt);
|
|
353
446
|
camera.lookAt(0, 0, 0);
|
|
354
447
|
|
|
355
448
|
renderer.render(scene, camera);
|
|
356
449
|
}
|
|
357
450
|
|
|
358
|
-
|
|
451
|
+
function start() {
|
|
452
|
+
if (rafId || !visible || document.hidden) return;
|
|
453
|
+
clock.getDelta(); // drop the paused interval
|
|
454
|
+
rafId = requestAnimationFrame(frame);
|
|
455
|
+
}
|
|
456
|
+
function stop() {
|
|
457
|
+
cancelAnimationFrame(rafId);
|
|
458
|
+
rafId = 0;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Pause when off-screen or when the tab is hidden.
|
|
462
|
+
if ('IntersectionObserver' in window) {
|
|
463
|
+
new IntersectionObserver((entries) => {
|
|
464
|
+
visible = entries[0].isIntersecting;
|
|
465
|
+
visible ? start() : stop();
|
|
466
|
+
}).observe(container);
|
|
467
|
+
}
|
|
468
|
+
document.addEventListener('visibilitychange', () => (document.hidden ? stop() : start()));
|
|
469
|
+
|
|
470
|
+
start();
|
|
359
471
|
})();
|
|
360
|
-
</script>
|
|
472
|
+
</script>
|