fix particles amount
This commit is contained in:
@@ -2,6 +2,7 @@ import React, { useEffect, useRef } from 'react';
|
|||||||
|
|
||||||
const ParticlesBackground: React.FC = () => {
|
const ParticlesBackground: React.FC = () => {
|
||||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
const prevWidth = useRef(window.innerWidth);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const canvas = canvasRef.current;
|
const canvas = canvasRef.current;
|
||||||
@@ -69,11 +70,16 @@ const ParticlesBackground: React.FC = () => {
|
|||||||
const init = () => {
|
const init = () => {
|
||||||
if (!canvas) return;
|
if (!canvas) return;
|
||||||
canvas.width = window.innerWidth;
|
canvas.width = window.innerWidth;
|
||||||
|
// Use logical height that works well with the 120vh style
|
||||||
canvas.height = window.innerHeight;
|
canvas.height = window.innerHeight;
|
||||||
|
|
||||||
// Reduce particle count on mobile/portrait screens
|
// Calculate particle count based on screen area (resolution)
|
||||||
const isPortrait = canvas.height > canvas.width;
|
// Formula: sqrt(width * height) / factor
|
||||||
const particleCount = isPortrait ? 90 : 180;
|
// Desktop (1920x1080) -> ~120 particles
|
||||||
|
// Mobile (390x844) -> ~48 particles
|
||||||
|
// Mobile Landscape (844x390) -> ~48 particles (Same as portrait!)
|
||||||
|
const area = canvas.width * canvas.height;
|
||||||
|
const particleCount = Math.floor(Math.sqrt(area) / 12);
|
||||||
|
|
||||||
particles = [];
|
particles = [];
|
||||||
for (let i = 0; i < particleCount; i++) {
|
for (let i = 0; i < particleCount; i++) {
|
||||||
@@ -134,6 +140,11 @@ const ParticlesBackground: React.FC = () => {
|
|||||||
animate();
|
animate();
|
||||||
|
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
|
// Ignore vertical-only resizes (addressing mobile browser bar toggle issue)
|
||||||
|
if (window.innerWidth === prevWidth.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
prevWidth.current = window.innerWidth;
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +164,7 @@ const ParticlesBackground: React.FC = () => {
|
|||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '120vh', // Extend well below viewport to cover mobile browser bar retraction
|
||||||
zIndex: -1, // Behind everything
|
zIndex: -1, // Behind everything
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,12 +1,35 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
import { Link, useLocation } from "react-router-dom";
|
import { Link, useLocation } from "react-router-dom";
|
||||||
import "../styles/components/header.css";
|
import "../styles/components/header.css";
|
||||||
|
|
||||||
export default function FloatingHeader() {
|
export default function FloatingHeader() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
|
|
||||||
|
const toggleMenu = () => {
|
||||||
|
setIsMenuOpen(!isMenuOpen);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Close menu when route changes
|
||||||
|
useEffect(() => {
|
||||||
|
setIsMenuOpen(false);
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="floating-header">
|
<header className="floating-header">
|
||||||
<nav className="header-nav">
|
<div className="header-content">
|
||||||
|
<button
|
||||||
|
className={`mobile-toggle ${isMenuOpen ? "open" : ""}`}
|
||||||
|
onClick={toggleMenu}
|
||||||
|
aria-label="Toggle navigation"
|
||||||
|
>
|
||||||
|
<span className="hamburger-line"></span>
|
||||||
|
<span className="hamburger-line"></span>
|
||||||
|
<span className="hamburger-line"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<nav className={`header-nav ${isMenuOpen ? "is-open" : ""}`}>
|
||||||
<Link
|
<Link
|
||||||
to="/"
|
to="/"
|
||||||
className={`nav-link ${location.pathname === "/" ? "active" : ""}`}
|
className={`nav-link ${location.pathname === "/" ? "active" : ""}`}
|
||||||
@@ -37,8 +60,8 @@ export default function FloatingHeader() {
|
|||||||
>
|
>
|
||||||
Personal About Me
|
Personal About Me
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
</nav>
|
</nav>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,12 +13,13 @@
|
|||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-nav {
|
.header-nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: clamp(10px, 2vw, 40px);
|
gap: clamp(8px, 2vw, 32px);
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
}
|
}
|
||||||
@@ -61,3 +62,92 @@
|
|||||||
.nav-link.active::after {
|
.nav-link.active::after {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mobile Navigation Styles */
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-toggle {
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 30px;
|
||||||
|
height: 21px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 1001;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger-line {
|
||||||
|
width: 100%;
|
||||||
|
height: 3px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.9);
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.floating-header {
|
||||||
|
padding: 15px 20px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
justify-content: flex-end;
|
||||||
|
/* Align hamburger to right */
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-toggle {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animate Hamburger to X */
|
||||||
|
.mobile-toggle.open .hamburger-line:nth-child(1) {
|
||||||
|
transform: translateY(9px) rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-toggle.open .hamburger-line:nth-child(2) {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-toggle.open .hamburger-line:nth-child(3) {
|
||||||
|
transform: translateY(-9px) rotate(-45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-nav {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
height: 100vh;
|
||||||
|
background: rgba(10, 10, 30, 0.95);
|
||||||
|
backdrop-filter: blur(15px);
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 30px;
|
||||||
|
transform: translateX(100%);
|
||||||
|
transition: transform 0.3s ease-in-out;
|
||||||
|
z-index: 999;
|
||||||
|
/* Below toggle button */
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-nav.is-open {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link {
|
||||||
|
font-size: 24px;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user