Skip to content

Instantly share code, notes, and snippets.

@aldoyh
Created May 29, 2025 11:42
Show Gist options
  • Save aldoyh/08f140774e9aaed98cd45cdb4b70cf5e to your computer and use it in GitHub Desktop.
Save aldoyh/08f140774e9aaed98cd45cdb4b70cf5e to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Snapping ShadCN-Inspired Landing Page</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" xintegrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2LwA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Cairo:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
scroll-behavior: smooth; /* Fallback for JS scrolling */
}
html[lang="ar"] body {
font-family: 'Cairo', sans-serif;
}
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
--header-height: 4rem; /* 64px for h-16 */
}
/* Snapping behavior for main sections */
html {
scroll-snap-type: y mandatory;
}
section.full-screen-section {
scroll-snap-align: start;
min-height: 100vh;
display: flex; /* To help with centering content */
flex-direction: column; /* Stack content vertically */
justify-content: center; /* Center content vertically */
padding-top: var(--header-height); /* Account for fixed header */
padding-bottom: 2rem; /* Some bottom padding */
}
/* Hero needs slightly different padding due to its nature */
#hero.full-screen-section {
padding-top: 0; /* Hero content is already centered, header overlay is fine */
}
.bg-background { background-color: hsl(var(--background)); }
.text-foreground { color: hsl(var(--foreground)); }
.bg-card { background-color: hsl(var(--card)); }
.text-card-foreground { color: hsl(var(--card-foreground)); }
.border-border { border-color: hsl(var(--border)); }
.rounded-lg { border-radius: var(--radius); }
.shadow-md { box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); }
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: var(--radius);
font-size: 0.875rem;
font-weight: 500;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
padding: 0.5rem 1rem;
border: 1px solid transparent;
user-select: none; /* Prevent text selection on rapid clicks */
}
.btn-primary { background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground)); }
.btn-primary:hover { background-color: hsl(var(--primary) / 0.9); }
.btn-secondary { background-color: hsl(var(--secondary)); color: hsl(var(--secondary-foreground)); border-color: hsl(var(--border));}
.btn-secondary:hover { background-color: hsl(var(--secondary) / 0.8); }
.btn-ghost { background-color: transparent; color: hsl(var(--primary)); }
.btn-ghost:hover { background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground)); }
.btn-sm { padding: 0.25rem 0.75rem; font-size: 0.875rem; }
#bg-canvas { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: -10; pointer-events: none; }
.animate-fadeInUp { opacity: 0; transform: translateY(20px); }
/* Confetti styles */
.confetti {
position: fixed;
width: 8px;
height: 8px;
background-color: #f00; /* Default color, will be randomized */
opacity: 1;
border-radius: 50%;
pointer-events: none; /* So they don't interfere with clicks */
z-index: 9999; /* Above everything */
animation: fall 1.5s ease-out forwards;
}
@keyframes fall {
0% { transform: translateY(0) translateX(0) rotate(0deg); opacity: 1; }
100% { transform: translateY(200px) translateX(var(--confetti-x-end)) rotate(var(--confetti-rotate-end)); opacity: 0; }
}
</style>
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
colors: {
border: 'hsl(var(--border))', input: 'hsl(var(--input))', ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))', foreground: 'hsl(var(--foreground))',
primary: { DEFAULT: 'hsl(var(--primary))', foreground: 'hsl(var(--primary-foreground))' },
secondary: { DEFAULT: 'hsl(var(--secondary))', foreground: 'hsl(var(--secondary-foreground))' },
destructive: { DEFAULT: 'hsl(var(--destructive))', foreground: 'hsl(var(--destructive-foreground))' },
muted: { DEFAULT: 'hsl(var(--muted))', foreground: 'hsl(var(--muted-foreground))' },
accent: { DEFAULT: 'hsl(var(--accent))', foreground: 'hsl(var(--accent-foreground))' },
popover: { DEFAULT: 'hsl(var(--popover))', foreground: 'hsl(var(--popover-foreground))' },
card: { DEFAULT: 'hsl(var(--card))', foreground: 'hsl(var(--card-foreground))' },
},
borderRadius: { lg: "var(--radius)", md: "calc(var(--radius) - 2px)", sm: "calc(var(--radius) - 4px)", },
height: { screen: '100vh' } // Ensure 100vh is available if needed directly in Tailwind
}
},
plugins: [
function({ addUtilities }) {
addUtilities({
'.bg-hero-pattern': { 'background-image': 'url("https://picsum.photos/seed/hero25/1920/1080?blur=1&grayscale=0.3")' },
'.bg-cta-pattern': { 'background-image': 'url("https://picsum.photos/seed/cta36/1600/900?blur=0.5")' }
}, ['responsive', 'hover'])
}
]
}
</script>
</head>
<body class="bg-background text-foreground antialiased">
<canvas id="bg-canvas"></canvas>
<header class="fixed top-0 left-0 right-0 z-50 bg-background/80 backdrop-blur-md border-b border-border h-16">
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between h-full">
<div class="flex items-center">
<a href="#hero" class="text-2xl font-bold text-primary" data-en="SnapSite" data-ar="سناپ‌سايت">SnapSite</a>
</div>
<nav class="hidden md:flex space-x-1 lg:space-x-2 items-center">
<a href="#hero" class="btn btn-ghost btn-sm nav-link" data-en="Home" data-ar="الرئيسية">Home</a>
<a href="#services" class="btn btn-ghost btn-sm nav-link" data-en="Services" data-ar="خدماتنا">Services</a>
<a href="#portfolio" class="btn btn-ghost btn-sm nav-link" data-en="Portfolio" data-ar="أعمالنا">Portfolio</a>
<a href="#cta" class="btn btn-ghost btn-sm nav-link" data-en="Contact" data-ar="اتصل بنا">Contact</a>
</nav>
<div class="flex items-center">
<button class="btn btn-secondary btn-sm lang-toggle" onclick="toggleLanguage()" data-lang-en="عربي" data-lang-ar="English">عربي</button>
<button class="md:hidden btn btn-ghost btn-sm ml-2 rtl:mr-2 rtl:ml-0" id="mobileMenuButton">
<i class="fas fa-bars text-lg"></i>
</button>
</div>
</div>
</div>
<div id="mobileMenu" class="hidden md:hidden bg-background border-t border-border shadow-lg">
<nav class="flex flex-col space-y-1 p-4">
<a href="#hero" class="block btn btn-ghost text-left nav-link" data-en="Home" data-ar="الرئيسية">Home</a>
<a href="#services" class="block btn btn-ghost text-left nav-link" data-en="Services" data-ar="خدماتنا">Services</a>
<a href="#portfolio" class="block btn btn-ghost text-left nav-link" data-en="Portfolio" data-ar="أعمالنا">Portfolio</a>
<a href="#cta" class="block btn btn-ghost text-left nav-link" data-en="Contact" data-ar="اتصل بنا">Contact</a>
</nav>
</div>
</header>
<main class="overflow-y-auto">
<section id="hero" class="full-screen-section relative flex items-center justify-center bg-hero-pattern bg-cover bg-center bg-no-repeat">
<div class="absolute inset-0 bg-black/60"></div>
<div class="relative container mx-auto px-4 text-center z-10">
<img src="https://picsum.photos/seed/mainlogo77/150/150?gravity=face&shape=circle" alt="Company Logo" class="mx-auto mb-8 rounded-full shadow-xl border-4 border-white animate-fadeInUp" style="animation-delay: 0.2s;">
<h1 class="text-4xl sm:text-5xl md:text-6xl font-bold text-white mb-6 animate-fadeInUp" data-en="Dynamic Web Experiences" data-ar="تجارب ويب ديناميكية" style="animation-delay: 0.4s;">Dynamic Web Experiences</h1>
<p class="text-lg sm:text-xl text-gray-200 mb-10 max-w-2xl mx-auto animate-fadeInUp" data-en="Crafting beautiful, snapping single-page applications that captivate and convert." data-ar="نصمم تطبيقات ويب رائعة أحادية الصفحة ذات انتقال سلس تجذب المستخدمين وتحقق الأهداف." style="animation-delay: 0.6s;">Crafting beautiful, snapping single-page applications that captivate and convert.</p>
<div class="animate-fadeInUp" style="animation-delay: 0.8s;">
<a href="#services" class="btn btn-primary px-8 py-3 text-lg nav-link" data-en="Explore Services" data-ar="اكتشف خدماتنا">Explore Services</a>
</div>
</div>
</section>
<section id="services" class="full-screen-section py-16 lg:py-24 bg-secondary">
<div class="container mx-auto px-4">
<div class="text-center mb-12 lg:mb-16">
<h2 class="text-3xl lg:text-4xl font-bold text-card-foreground mb-3" data-en="Our Expertise" data-ar="خبراتنا">Our Expertise</h2>
<p class="text-lg text-muted-foreground max-w-xl mx-auto" data-en="Delivering excellence in every line of code and pixel perfect design." data-ar="نقدم التميز في كل سطر برمجي وتصميم متقن لكل بكسل.">Delivering excellence in every line of code and pixel perfect design.</p>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 lg:gap-8">
<div class="bg-card p-6 rounded-lg shadow-md border border-border transform hover:shadow-xl transition-shadow duration-300 animate-fadeInUp">
<img src="https://picsum.photos/seed/serviceA1/400/220?random=1" alt="Web Apps" class="rounded-md mb-6 w-full h-44 object-cover">
<h3 class="text-xl font-semibold text-card-foreground mb-2" data-en="Custom Web Apps" data-ar="تطبيقات ويب مخصصة"><i class="fas fa-cogs text-primary mr-2 rtl:ml-2 rtl:mr-0"></i> Custom Web Apps</h3>
<p class="text-muted-foreground text-sm" data-en="Bespoke web application development tailored to your specific operational needs and goals." data-ar="تطوير تطبيقات ويب مصممة خصيصًا لتلبية احتياجاتك التشغيلية وأهدافك المحددة.">Bespoke web application development tailored to your specific operational needs and goals.</p>
</div>
<div class="bg-card p-6 rounded-lg shadow-md border border-border transform hover:shadow-xl transition-shadow duration-300 animate-fadeInUp" style="animation-delay: 0.1s;">
<img src="https://picsum.photos/seed/serviceB2/400/220?random=2" alt="UX Research" class="rounded-md mb-6 w-full h-44 object-cover">
<h3 class="text-xl font-semibold text-card-foreground mb-2" data-en="UX Research & Strategy" data-ar="بحث واستراتيجية تجربة المستخدم"><i class="fas fa-users text-primary mr-2 rtl:ml-2 rtl:mr-0"></i> UX Research & Strategy</h3>
<p class="text-muted-foreground text-sm" data-en="In-depth user research and strategic planning to create highly effective and user-centric designs." data-ar="بحث متعمق للمستخدم وتخطيط استراتيجي لإنشاء تصميمات فعالة للغاية تتمحور حول المستخدم.">In-depth user research and strategic planning to create highly effective and user-centric designs.</p>
</div>
<div class="bg-card p-6 rounded-lg shadow-md border border-border transform hover:shadow-xl transition-shadow duration-300 animate-fadeInUp" style="animation-delay: 0.2s;">
<img src="https://picsum.photos/seed/serviceC3/400/220?random=3" alt="API Integration" class="rounded-md mb-6 w-full h-44 object-cover">
<h3 class="text-xl font-semibold text-card-foreground mb-2" data-en="API Integration" data-ar="تكامل واجهات برمجة التطبيقات"><i class="fas fa-project-diagram text-primary mr-2 rtl:ml-2 rtl:mr-0"></i> API Integration</h3>
<p class="text-muted-foreground text-sm" data-en="Seamless integration of third-party APIs to extend functionality and streamline your workflows." data-ar="تكامل سلس لواجهات برمجة تطبيقات الطرف الثالث لتوسيع الوظائف وتبسيط سير عملك.">Seamless integration of third-party APIs to extend functionality and streamline your workflows.</p>
</div>
</div>
</div>
</section>
<section id="portfolio" class="full-screen-section py-16 lg:py-24 bg-background">
<div class="container mx-auto px-4">
<div class="text-center mb-12 lg:mb-16">
<h2 class="text-3xl lg:text-4xl font-bold text-card-foreground mb-3" data-en="Featured Projects" data-ar="مشاريع مميزة">Featured Projects</h2>
<p class="text-lg text-muted-foreground max-w-xl mx-auto" data-en="Showcasing innovation and quality in our diverse portfolio of successful projects." data-ar="نعرض الابتكار والجودة في محفظتنا المتنوعة من المشاريع الناجحة.">Showcasing innovation and quality in our diverse portfolio of successful projects.</p>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<a href="https://picsum.photos/seed/projectX1/1200/800" target="_blank" class="group block rounded-lg overflow-hidden shadow-lg animate-fadeInUp">
<img src="https://picsum.photos/seed/projectX1/600/400?random=4" alt="Project Alpha" class="w-full h-56 object-cover transform group-hover:scale-110 transition-transform duration-300">
</a>
<a href="https://picsum.photos/seed/projectY2/1200/800" target="_blank" class="group block rounded-lg overflow-hidden shadow-lg animate-fadeInUp" style="animation-delay: 0.1s;">
<img src="https://picsum.photos/seed/projectY2/600/400?random=5" alt="Project Beta" class="w-full h-56 object-cover transform group-hover:scale-110 transition-transform duration-300">
</a>
<a href="https://picsum.photos/seed/projectZ3/1200/800" target="_blank" class="group block rounded-lg overflow-hidden shadow-lg animate-fadeInUp" style="animation-delay: 0.2s;">
<img src="https://picsum.photos/seed/projectZ3/600/400?random=6" alt="Project Gamma" class="w-full h-56 object-cover transform group-hover:scale-110 transition-transform duration-300">
</a>
</div>
</div>
</section>
<section id="cta" class="full-screen-section py-20 lg:py-32 bg-cta-pattern bg-cover bg-center">
<div class="absolute inset-0 bg-primary/85"></div>
<div class="container mx-auto px-4 text-center relative z-10">
<img src="https://picsum.photos/seed/cta-icon/100/100?gravity=center&random=7" alt="CTA Icon" class="mx-auto mb-6 rounded-lg shadow-lg animate-fadeInUp" style="animation-delay: 0.2s;">
<h2 class="text-3xl lg:text-4xl font-bold text-primary-foreground mb-6 animate-fadeInUp" style="animation-delay: 0.4s;" data-en="Let's Build Something Amazing" data-ar="دعنا نبني شيئًا مذهلاً">Let's Build Something Amazing</h2>
<p class="text-lg text-primary-foreground/80 mb-10 max-w-xl mx-auto animate-fadeInUp" style="animation-delay: 0.6s;" data-en="Your vision, our expertise. Together, we can create digital solutions that make an impact." data-ar="رؤيتك وخبرتنا. معًا، يمكننا إنشاء حلول رقمية تحدث تأثيرًا.">Your vision, our expertise. Together, we can create digital solutions that make an impact.</p>
<a href="mailto:[email protected]" class="btn btn-secondary px-8 py-3 text-lg animate-fadeInUp" style="animation-delay: 0.8s;" data-en="Start a Conversation" data-ar="ابدأ محادثة">Start a Conversation</a>
</div>
</section>
</main>
<footer class="py-8 bg-card border-t border-border text-center">
<div class="container mx-auto px-4 text-muted-foreground text-sm">
<img src="https://picsum.photos/seed/footerlogo2/80/24?grayscale&random=8" alt="SnapSite Small Logo" class="mx-auto mb-3 h-6">
<p data-en="© 2025 SnapSite. Snapping to Perfection." data-ar="© 2025 سناب‌سايت. نحو الإتقان بانسيابية.">© 2025 SnapSite. Snapping to Perfection.</p>
<div class="mt-3 space-x-3 rtl:space-x-reverse">
<a href="#" class="hover:text-primary text-xs" data-en="Privacy" data-ar="الخصوصية">Privacy</a>
<span class="text-muted-foreground/50">|</span>
<a href="#" class="hover:text-primary text-xs" data-en="Terms" data-ar="الشروط">Terms</a>
</div>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ScrollTrigger.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ScrollToPlugin.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/p5.js"></script>
<script>
gsap.registerPlugin(ScrollTrigger, ScrollToPlugin);
const htmlEl = document.documentElement;
let currentLang = htmlEl.lang || 'en';
const langToggleButton = document.querySelector('.lang-toggle');
const mobileMenuButton = document.getElementById('mobileMenuButton');
const mobileMenu = document.getElementById('mobileMenu');
const navLinks = document.querySelectorAll('header nav a.nav-link[href^="#"]'); // Target specific nav links
function updateTextDirection(lang) {
htmlEl.dir = lang === 'ar' ? 'rtl' : 'ltr';
}
function translateContent(lang) {
document.querySelectorAll('[data-en]').forEach(el => {
const text = lang === 'ar' ? el.dataset.ar : el.dataset.en;
if (text) el.innerHTML = text;
});
}
function toggleLanguage() {
currentLang = currentLang === 'en' ? 'ar' : 'en';
htmlEl.lang = currentLang;
updateTextDirection(currentLang);
translateContent(currentLang);
langToggleButton.textContent = currentLang === 'ar' ? langToggleButton.dataset.langAr : langToggleButton.dataset.langEn;
ScrollTrigger.refresh(); // Recalculate ScrollTrigger positions
}
updateTextDirection(currentLang);
translateContent(currentLang);
langToggleButton.textContent = currentLang === 'ar' ? langToggleButton.dataset.langAr : langToggleButton.dataset.langEn;
mobileMenuButton.addEventListener('click', () => {
mobileMenu.classList.toggle('hidden');
const icon = mobileMenuButton.querySelector('i');
icon.classList.toggle('fa-bars');
icon.classList.toggle('fa-times');
});
mobileMenu.querySelectorAll('a.nav-link').forEach(link => {
link.addEventListener('click', () => {
mobileMenu.classList.add('hidden');
const icon = mobileMenuButton.querySelector('i');
icon.classList.remove('fa-times');
icon.classList.add('fa-bars');
});
});
// Smooth scroll for navigation links (works with CSS scroll snap)
navLinks.forEach(anchor => {
anchor.addEventListener('click', function(e) {
e.preventDefault();
const targetId = this.getAttribute('href');
const targetElement = document.querySelector(targetId);
if (targetElement) {
// GSAP ScrollToPlugin handles scrolling smoothly to the snap point
gsap.to(window, {
scrollTo: {
y: targetElement, // Target the element directly for snap
autoKill: true
},
duration: 1, // Adjust duration as needed
ease: 'power3.inOut'
});
}
});
});
function animateOnScroll() {
gsap.utils.toArray('.animate-fadeInUp').forEach(elem => {
gsap.fromTo(elem,
{ opacity: 0, y: 30 },
{
opacity: 1, y: 0, duration: 0.8, ease: 'power2.out',
delay: parseFloat(elem.style.animationDelay) || 0,
scrollTrigger: { trigger: elem, start: 'top 90%', toggleActions: 'play none none none', once: true }
}
);
});
gsap.utils.toArray('section h2, section .text-lg.text-muted-foreground, section .text-lg.text-primary-foreground\\/80').forEach(elem => {
gsap.from(elem, {
opacity:0, y:20, duration: 0.6, ease: 'power1.out',
scrollTrigger: { trigger: elem, start: 'top 90%', toggleActions: 'play none none none', once: true }
});
});
}
document.addEventListener('DOMContentLoaded', animateOnScroll);
// Confetti on click
document.addEventListener('click', function(event) {
createConfetti(event.clientX, event.clientY);
});
function createConfetti(x, y) {
const confettiCount = Math.floor(Math.random() * 3) + 3; // 3 to 5 tiny pieces
const colors = ['#FFC700', '#FF0000', '#2E86C1', '#2ECC71', '#F39C12', '#8E44AD'];
for (let i = 0; i < confettiCount; i++) {
const confettiPiece = document.createElement('div');
confettiPiece.classList.add('confetti');
const randomColor = colors[Math.floor(Math.random() * colors.length)];
confettiPiece.style.backgroundColor = randomColor;
// Position near the click
const offsetX = (Math.random() - 0.5) * 30; // Spread within 30px horizontally
const offsetY = (Math.random() - 0.5) * 30; // Spread within 30px vertically
confettiPiece.style.left = `${x + offsetX}px`;
confettiPiece.style.top = `${y + offsetY}px`;
// Randomize animation end properties
const xEnd = (Math.random() - 0.5) * 100; // Horizontal drift
const rotateEnd = (Math.random() - 0.5) * 720; // Random rotation
confettiPiece.style.setProperty('--confetti-x-end', `${xEnd}px`);
confettiPiece.style.setProperty('--confetti-rotate-end', `${rotateEnd}deg`);
document.body.appendChild(confettiPiece);
// Remove confetti after animation
confettiPiece.addEventListener('animationend', () => {
confettiPiece.remove();
});
}
}
// p5.js Background Animation
let particles = [];
function setup() {
let canvas = createCanvas(windowWidth, windowHeight);
canvas.parent('bg-canvas');
for (let i = 0; i < 60; i++) { particles.push(new Particle()); } // Reduced for performance
}
function draw() {
clear();
let particleBaseColor = document.body.classList.contains('dark') ? [200, 200, 220, 50] : [80, 80, 100, 30]; // Adjusted alpha
for (let particle of particles) {
particle.color = particleBaseColor;
particle.update();
particle.display();
particle.connect();
}
}
function windowResized() { resizeCanvas(windowWidth, windowHeight); }
class Particle {
constructor() {
this.pos = createVector(random(width), random(height));
this.vel = p5.Vector.random2D().mult(random(0.05, 0.3)); // Slower
this.size = random(1, 2); // Smaller
this.color = [80,80,100,30];
this.maxSpeed = 0.5;
this.maxForce = 0.02;
}
update() {
this.pos.add(this.vel);
this.edges();
let mouse = createVector(mouseX, mouseY);
let desired = p5.Vector.sub(mouse, this.pos);
let d = desired.mag();
if (d < 150) { // Wider mouse influence
let steer = p5.Vector.sub(desired, this.vel);
steer.limit(this.maxForce * (1 - d / 150) * 1.5);
this.applyForce(steer);
}
this.vel.limit(this.maxSpeed);
}
applyForce(force) { this.vel.add(force); }
display() {
noStroke();
fill(this.color[0], this.color[1], this.color[2], this.color[3]);
ellipse(this.pos.x, this.pos.y, this.size);
}
connect() {
particles.forEach(other => {
if (other !== this) {
let d = dist(this.pos.x, this.pos.y, other.pos.x, other.pos.y);
if (d < 120) { // Wider connection
let alpha = map(d, 0, 120, 40, 0); // Adjusted alpha
stroke(this.color[0], this.color[1], this.color[2], alpha);
strokeWeight(0.3); // Thinner lines
line(this.pos.x, this.pos.y, other.pos.x, other.pos.y);
}
}
});
}
edges() {
if (this.pos.x > width + this.size) this.pos.x = -this.size;
else if (this.pos.x < -this.size) this.pos.x = width + this.size;
if (this.pos.y > height + this.size) this.pos.y = -this.size;
else if (this.pos.y < -this.size) this.pos.y = height + this.size;
}
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment