diff --git a/src/components/homepage/AnimatedButton.astro b/src/components/homepage/AnimatedButton.astro
new file mode 100644
index 0000000..34fcbab
--- /dev/null
+++ b/src/components/homepage/AnimatedButton.astro
@@ -0,0 +1,94 @@
+---
+const buttonText = 'Analyze URL';
+const buttonType = 'submit';
+---
+
+
+
+
+
+
diff --git a/src/components/homepage/AnimatedInput.astro b/src/components/homepage/AnimatedInput.astro
new file mode 100644
index 0000000..d4488d6
--- /dev/null
+++ b/src/components/homepage/AnimatedInput.astro
@@ -0,0 +1,186 @@
+---
+const placeholders = [
+ 'duck.com',
+ 'github.com',
+ 'google.com',
+ 'x.com',
+ 'bbc.co.uk',
+ 'wikipedia.org',
+ 'openai.com',
+];
+---
+
+
+
+
+
+
+
+
diff --git a/src/components/homepage/HeroForm.astro b/src/components/homepage/HeroForm.astro
new file mode 100644
index 0000000..f5d2e38
--- /dev/null
+++ b/src/components/homepage/HeroForm.astro
@@ -0,0 +1,128 @@
+---
+import AnimatedButton from "./AnimatedButton.astro"
+import AnimatedInput from "./AnimatedInput.astro"
+---
+
+
+
+
+ Web
+ Check
+
+
+
We give you X-Ray
Vision for your Website
+
+ In just 20 seconds, you can see
+ what attackers already know
+
+
+
+
X
+
+
+
+
+
diff --git a/src/components/homepage/HomeBackground.tsx b/src/components/homepage/HomeBackground.tsx
new file mode 100644
index 0000000..7102072
--- /dev/null
+++ b/src/components/homepage/HomeBackground.tsx
@@ -0,0 +1,174 @@
+import { useState, useEffect } from 'react';
+import { motion } from 'framer-motion';
+import styled from '@emotion/styled';
+
+// Global Animation Configuration Constants
+const dotSpacing = 32; // Number of px between each dot
+const meteorCount = 4; // Number of meteors to display at any given time
+const tailLength = 80; // Length of the meteor tail in px
+const distanceBase = 5; // Base distance for meteor to travel in grid units
+const distanceVariance = 5; // Variance for randomization to append to travel in grid units
+const durationBase = 1.5; // Base duration for meteor to travel in seconds
+const durationVariance = 1; // Variance for randomization to append to travel in seconds
+const delayBase = 500; // Base delay for meteor to respawn in milliseconds
+const delayVariance = 1500; // Variance for randomization to append to respawn in milliseconds
+const tailDuration = 0.25; // Duration for meteor tail to retract in seconds
+const headEasing = [0.8, 0.6, 1, 1]; // Easing for meteor head
+const tailEasing = [0.5, 0.6, 0.6, 1]; // Easing for meteor tail
+
+const MeteorContainer = styled(motion.div)`
+ position: absolute;
+ width: 4px;
+ height: 4px;
+ border-radius: 50%;
+ background-color: #9fef00;
+ top: 1px;
+`;
+
+const Tail = styled(motion.div)`
+ position: absolute;
+ top: -80px;
+ left: 1px;
+ width: 2px;
+ height: 80px;
+ background: linear-gradient(to bottom, transparent, #9fef00);
+`;
+
+const StyledSvg = styled.svg`
+ pointer-events: none;
+ position: absolute;
+ inset: 0;
+ height: 100%;
+ width: 100%;
+ fill: rgba(100, 100, 100, 0.5);
+ defs pattern circle {
+ z-index: 1;
+ }
+`;
+
+const StyledRect = styled.rect`
+ width: 100%;
+ height: 100%;
+ stroke-width: 0;
+`;
+
+const Container = styled.div`
+ pointer-events: none;
+ position: absolute;
+ height: 100vh;
+ width: 100vw;
+ z-index: 1;
+ top: 0;
+ left: 0;
+ background: radial-gradient(circle at center top, transparent, transparent 60%, var(--background) 100%);
+`;
+
+const generateMeteor = (id: number, gridSizeX: number, gridSizeY: number) => {
+ const column = Math.floor(Math.random() * gridSizeX);
+ const startRow = Math.floor(Math.random() * (gridSizeY - 12));
+ const travelDistance = distanceBase + Math.floor(Math.random() * distanceVariance);
+ const duration = durationBase + Math.floor(Math.random() * durationVariance);
+
+ return {
+ id,
+ column,
+ startRow,
+ endRow: startRow + travelDistance,
+ duration,
+ tailVisible: true,
+ animationStage: 'traveling',
+ opacity: 1, // Initial opacity
+ };
+};
+
+const generateInitialMeteors = (gridSizeX: number, gridSizeY: number) => {
+ const seen = new Set();
+ return Array.from({ length: meteorCount }, (_, index) => generateMeteor(index, gridSizeX, gridSizeY))
+ .filter(item => !seen.has(item.column) && seen.add(item.column));
+};
+
+const WebCheckHomeBackground = () => {
+ const [gridSizeX, setGridSizeX] = useState(Math.floor(window.innerWidth / dotSpacing));
+ const [gridSizeY, setGridSizeY] = useState(Math.floor(window.innerHeight / dotSpacing));
+ const [meteors, setMeteors] = useState(() => generateInitialMeteors(gridSizeX, gridSizeY));
+
+ const handleAnimationComplete = (id: number) => {
+ setMeteors(current =>
+ current.map(meteor => {
+ if (meteor.id === id) {
+ if (meteor.animationStage === 'traveling') {
+ // Transition to retracting tail
+ return { ...meteor, tailVisible: false, animationStage: 'retractingTail' };
+ } else if (meteor.animationStage === 'retractingTail') {
+ // Set to resetting and make invisible
+ return { ...meteor, animationStage: 'resetting', opacity: 0 };
+ } else if (meteor.animationStage === 'resetting') {
+ // Respawn the meteor after a delay
+ setTimeout(() => {
+ setMeteors(current =>
+ current.map(m => m.id === id ? generateMeteor(id, gridSizeX, gridSizeY) : m)
+ );
+ }, delayBase + Math.random() * delayVariance);
+ }
+ }
+ return meteor;
+ })
+ );
+ };
+
+ useEffect(() => {
+ const handleResize = () => {
+ setGridSizeX(Math.floor(window.innerWidth / dotSpacing));
+ setGridSizeY(Math.floor(window.innerHeight / dotSpacing));
+ };
+ window.addEventListener('resize', handleResize);
+ return () => window.removeEventListener('resize', handleResize);
+ }, []);
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ {meteors.map(({ id, column, startRow, endRow, duration, tailVisible, animationStage, opacity }) => {
+ return (
+ handleAnimationComplete(id)}
+ >
+
+
+ );
+ })}
+ >
+ );
+};
+
+export default WebCheckHomeBackground;