Skip to content

Commit 7b6970b

Browse files
Add dark/light theme toggle with localStorage, particle animation, and hover lift effects
1 parent 782d4d4 commit 7b6970b

3 files changed

Lines changed: 195 additions & 17 deletions

File tree

index.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,18 @@
2525
<li><a href="#team">Team</a></li>
2626
<li><a href="#contact" class="nav-cta">Contact Us</a></li>
2727
</ul>
28+
<button class="theme-toggle" id="themeToggle" aria-label="Toggle theme">
29+
<svg class="icon-moon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
30+
<svg class="icon-sun" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
31+
</button>
2832
<div class="hamburger" aria-label="Menu">
2933
<span></span><span></span><span></span>
3034
</div>
3135
</nav>
3236

3337
<!-- ── HERO ── -->
3438
<section id="hero">
39+
<canvas id="particles"></canvas>
3540
<div class="hero-grid">
3641
<div class="hero-content">
3742
<span class="hero-badge">Software Development Company</span>

script.js

Lines changed: 88 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
// ── THEME ──
2+
const themeToggle = document.getElementById('themeToggle');
3+
const savedTheme = localStorage.getItem('theme');
4+
if (savedTheme === null || savedTheme === 'dark') document.body.classList.add('dark');
5+
6+
themeToggle.addEventListener('click', () => {
7+
document.body.classList.toggle('dark');
8+
localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light');
9+
});
10+
111
// Nav scroll effect
212
const nav = document.querySelector('nav');
313
window.addEventListener('scroll', () => {
@@ -25,15 +35,82 @@ const observer = new IntersectionObserver((entries) => {
2535

2636
document.querySelectorAll('.fade-up').forEach(el => observer.observe(el));
2737

28-
// Contact form
29-
document.querySelector('.contact-form').addEventListener('submit', (e) => {
30-
e.preventDefault();
31-
const btn = e.target.querySelector('.btn-send');
32-
btn.textContent = 'Message Sent ✓';
33-
btn.style.background = '#e8e8e8';
34-
setTimeout(() => {
35-
btn.textContent = 'Send Message';
36-
btn.style.background = '';
37-
e.target.reset();
38-
}, 3000);
38+
// ── PARTICLE BACKGROUND ──
39+
const canvas = document.getElementById('particles');
40+
const ctx = canvas.getContext('2d');
41+
42+
let particles = [];
43+
const PARTICLE_COUNT = 80;
44+
const MAX_DIST = 140;
45+
46+
function resize() {
47+
canvas.width = canvas.offsetWidth;
48+
canvas.height = canvas.offsetHeight;
49+
}
50+
51+
function createParticles() {
52+
particles = [];
53+
for (let i = 0; i < PARTICLE_COUNT; i++) {
54+
particles.push({
55+
x: Math.random() * canvas.width,
56+
y: Math.random() * canvas.height,
57+
vx: (Math.random() - 0.5) * 0.5,
58+
vy: (Math.random() - 0.5) * 0.5,
59+
r: Math.random() * 2 + 1,
60+
});
61+
}
62+
}
63+
64+
function drawParticles() {
65+
ctx.clearRect(0, 0, canvas.width, canvas.height);
66+
67+
for (let i = 0; i < particles.length; i++) {
68+
const p = particles[i];
69+
70+
// Move
71+
p.x += p.vx;
72+
p.y += p.vy;
73+
74+
// Bounce
75+
if (p.x < 0 || p.x > canvas.width) p.vx *= -1;
76+
if (p.y < 0 || p.y > canvas.height) p.vy *= -1;
77+
78+
const isDark = document.body.classList.contains('dark');
79+
const color = isDark ? '255,255,255' : '10,10,10';
80+
81+
// Draw dot
82+
ctx.beginPath();
83+
ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
84+
ctx.fillStyle = `rgba(${color},0.25)`;
85+
ctx.fill();
86+
87+
// Draw connecting lines
88+
for (let j = i + 1; j < particles.length; j++) {
89+
const q = particles[j];
90+
const dx = p.x - q.x;
91+
const dy = p.y - q.y;
92+
const dist = Math.sqrt(dx * dx + dy * dy);
93+
94+
if (dist < MAX_DIST) {
95+
const alpha = (1 - dist / MAX_DIST) * 0.12;
96+
ctx.beginPath();
97+
ctx.moveTo(p.x, p.y);
98+
ctx.lineTo(q.x, q.y);
99+
ctx.strokeStyle = `rgba(${color},${alpha})`;
100+
ctx.lineWidth = 1;
101+
ctx.stroke();
102+
}
103+
}
104+
}
105+
106+
requestAnimationFrame(drawParticles);
107+
}
108+
109+
window.addEventListener('resize', () => {
110+
resize();
111+
createParticles();
39112
});
113+
114+
resize();
115+
createParticles();
116+
drawParticles();

styles.css

Lines changed: 102 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,17 @@ nav.scrolled { box-shadow: 0 2px 20px rgba(0,0,0,0.08); }
7474
overflow: hidden;
7575
}
7676

77+
#particles {
78+
position: absolute;
79+
inset: 0;
80+
width: 100%;
81+
height: 100%;
82+
z-index: 0;
83+
pointer-events: none;
84+
}
85+
86+
.hero-grid { position: relative; z-index: 1; }
87+
7788
.hero-grid {
7889
display: grid;
7990
grid-template-columns: 1fr 1fr;
@@ -340,8 +351,8 @@ section { padding: 6rem 5%; }
340351
}
341352
.service-card:hover {
342353
border-color: var(--black);
343-
transform: translateY(-3px);
344-
box-shadow: 0 8px 30px rgba(0,0,0,0.08);
354+
transform: translateY(-10px);
355+
box-shadow: 0 20px 40px rgba(0,0,0,0.12);
345356
}
346357

347358
.service-icon {
@@ -381,8 +392,8 @@ section { padding: 6rem 5%; }
381392
transition: var(--transition);
382393
}
383394
.portfolio-card:hover {
384-
transform: translateY(-4px);
385-
box-shadow: 0 12px 40px rgba(0,0,0,0.1);
395+
transform: translateY(-10px);
396+
box-shadow: 0 20px 40px rgba(0,0,0,0.12);
386397
}
387398

388399
.portfolio-cover {
@@ -475,7 +486,7 @@ section { padding: 6rem 5%; }
475486
border-radius: 10px;
476487
transition: var(--transition);
477488
}
478-
.review-card:hover { border-color: var(--black); transform: translateY(-3px); box-shadow: 0 8px 30px rgba(0,0,0,0.07); }
489+
.review-card:hover { border-color: var(--black); transform: translateY(-10px); box-shadow: 0 20px 40px rgba(0,0,0,0.12); }
479490

480491
.review-top { display: flex; align-items: flex-start; gap: 0.875rem; margin-bottom: 1rem; }
481492

@@ -528,7 +539,7 @@ section { padding: 6rem 5%; }
528539
text-align: center;
529540
transition: var(--transition);
530541
}
531-
.team-card:hover { border-color: var(--black); transform: translateY(-3px); }
542+
.team-card:hover { border-color: var(--black); transform: translateY(-10px); box-shadow: 0 20px 40px rgba(0,0,0,0.12); }
532543

533544
.team-avatar {
534545
width: 72px; height: 72px;
@@ -635,6 +646,91 @@ footer {
635646
.footer-links a { color: #555; text-decoration: none; font-size: 0.8rem; transition: color var(--transition); }
636647
.footer-links a:hover { color: #999; }
637648

649+
/* ── THEME TOGGLE BUTTON ── */
650+
.theme-toggle {
651+
background: none;
652+
border: 1.5px solid var(--gray-200);
653+
border-radius: 8px;
654+
width: 38px; height: 38px;
655+
display: flex; align-items: center; justify-content: center;
656+
cursor: pointer;
657+
color: var(--black);
658+
transition: var(--transition);
659+
flex-shrink: 0;
660+
}
661+
.theme-toggle:hover { border-color: var(--black); background: var(--gray-100); }
662+
.icon-sun { display: none; }
663+
.icon-moon { display: block; }
664+
665+
/* ── DARK THEME ── */
666+
body.dark { background: #0a0a0a; color: #ffffff; }
667+
668+
body.dark .nav-logo img { filter: invert(1); }
669+
670+
body.dark nav {
671+
background: rgba(10,10,10,0.95);
672+
border-bottom-color: #1a1a1a;
673+
}
674+
body.dark .nav-links a { color: #888; }
675+
body.dark .nav-links a:hover { color: #fff; }
676+
body.dark .nav-cta { background: #fff; color: #000 !important; }
677+
body.dark .nav-cta:hover { background: #e8e8e8; }
678+
body.dark .hamburger span { background: #fff; }
679+
body.dark .theme-toggle { border-color: #333; color: #fff; }
680+
body.dark .theme-toggle:hover { background: #1a1a1a; border-color: #555; }
681+
body.dark .icon-sun { display: block; }
682+
body.dark .icon-moon { display: none; }
683+
body.dark .nav-links.open { background: #0a0a0a; border-bottom-color: #1a1a1a; }
684+
685+
body.dark #hero { background: #0a0a0a; }
686+
body.dark .hero-badge { color: #666; }
687+
body.dark .hero-title span { color: #555; }
688+
body.dark .hero-desc { color: #888; }
689+
body.dark .btn-secondary { border-color: #333; color: #fff; }
690+
body.dark .btn-secondary:hover { border-color: #fff; }
691+
body.dark .hero-card { background: #111; border-color: #2a2a2a; box-shadow: 0 22px 60px rgba(0,0,0,0.4); }
692+
body.dark .hero-card-header { border-bottom-color: #2a2a2a; }
693+
body.dark .hero-card-header span { color: #555; }
694+
body.dark .hero-card-header strong { background: #fff; color: #000; }
695+
body.dark .stat-item { background: #1a1a1a; border-color: #2a2a2a; }
696+
body.dark .stat-num { color: #fff; }
697+
body.dark .stat-label { color: #555; }
698+
body.dark .hero-card-note { background: #fff; color: #000; }
699+
700+
body.dark #about { background: #111; }
701+
body.dark .pillar { background: #1a1a1a; }
702+
body.dark .section-subtitle { color: #888; }
703+
704+
body.dark #services { background: #0a0a0a; }
705+
body.dark .service-card { background: #111; border-color: #2a2a2a; }
706+
body.dark .service-card:hover { border-color: #fff; box-shadow: 0 20px 40px rgba(0,0,0,0.4); }
707+
body.dark .service-card p { color: #888; }
708+
709+
body.dark #portfolio { background: #111; }
710+
body.dark .portfolio-card { background: #1a1a1a; border-color: #2a2a2a; }
711+
body.dark .portfolio-card:hover { box-shadow: 0 20px 40px rgba(0,0,0,0.4); }
712+
body.dark .portfolio-category { color: #666; }
713+
body.dark .portfolio-body p { color: #888; }
714+
body.dark .p-stat-label { color: #666; }
715+
716+
body.dark #reviews { background: #0a0a0a; }
717+
body.dark .reviews-stats { background: #111; border: 1.5px solid #2a2a2a; }
718+
body.dark .rs-divider { background: #2a2a2a; }
719+
body.dark .rs-label { color: #555; }
720+
body.dark .review-card { background: #111; border-color: #2a2a2a; }
721+
body.dark .review-card:hover { border-color: #fff; box-shadow: 0 20px 40px rgba(0,0,0,0.4); }
722+
body.dark .review-time { color: #555; }
723+
body.dark .review-project { color: #666; }
724+
body.dark .review-text { color: #888; }
725+
726+
body.dark #team { background: #0a0a0a; }
727+
body.dark .team-card { background: #111; border-color: #2a2a2a; }
728+
body.dark .team-card:hover { border-color: #fff; box-shadow: 0 20px 40px rgba(0,0,0,0.4); }
729+
body.dark .team-card p { color: #888; }
730+
731+
body.dark footer { background: #050505; }
732+
body.dark .footer-logo img { filter: invert(0); opacity: 0.5; }
733+
638734
/* ── SCROLL ANIMATIONS ── */
639735
.fade-up {
640736
opacity: 0;

0 commit comments

Comments
 (0)