techstack tooltip / tall Content main => particle playground

This commit is contained in:
yousifpa98
2025-01-15 01:02:08 +01:00
parent 347229a9cd
commit 7729eb247a
7 changed files with 197 additions and 12 deletions

41
package-lock.json generated
View File

@@ -13,6 +13,7 @@
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-router-dom": "^7.0.2", "react-router-dom": "^7.0.2",
"react-tooltip": "^5.28.0",
"styled-components": "^6.1.13", "styled-components": "^6.1.13",
"vite-plugin-svgr": "^4.3.0" "vite-plugin-svgr": "^4.3.0"
}, },
@@ -796,6 +797,28 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
} }
}, },
"node_modules/@floating-ui/core": {
"version": "1.6.9",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz",
"integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==",
"dependencies": {
"@floating-ui/utils": "^0.2.9"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.6.13",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz",
"integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==",
"dependencies": {
"@floating-ui/core": "^1.6.0",
"@floating-ui/utils": "^0.2.9"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz",
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="
},
"node_modules/@humanfs/core": { "node_modules/@humanfs/core": {
"version": "0.19.1", "version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -1818,6 +1841,11 @@
"url": "https://github.com/chalk/chalk?sponsor=1" "url": "https://github.com/chalk/chalk?sponsor=1"
} }
}, },
"node_modules/classnames": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
},
"node_modules/clsx": { "node_modules/clsx": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
@@ -3837,6 +3865,19 @@
"react-dom": ">=18" "react-dom": ">=18"
} }
}, },
"node_modules/react-tooltip": {
"version": "5.28.0",
"resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.28.0.tgz",
"integrity": "sha512-R5cO3JPPXk6FRbBHMO0rI9nkUG/JKfalBSQfZedZYzmqaZQgq7GLzF8vcCWx6IhUCKg0yPqJhXIzmIO5ff15xg==",
"dependencies": {
"@floating-ui/dom": "^1.6.1",
"classnames": "^2.3.0"
},
"peerDependencies": {
"react": ">=16.14.0",
"react-dom": ">=16.14.0"
}
},
"node_modules/reflect.getprototypeof": { "node_modules/reflect.getprototypeof": {
"version": "1.0.9", "version": "1.0.9",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.9.tgz", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.9.tgz",

View File

@@ -15,6 +15,7 @@
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-router-dom": "^7.0.2", "react-router-dom": "^7.0.2",
"react-tooltip": "^5.28.0",
"styled-components": "^6.1.13", "styled-components": "^6.1.13",
"vite-plugin-svgr": "^4.3.0" "vite-plugin-svgr": "^4.3.0"
}, },

View File

@@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useState, useEffect, useRef } from "react";
import "../css/TallContent.css"; import "../css/TallContent.css";
import { useTheme } from "../Context/ThemeContext"; import { useTheme } from "../Context/ThemeContext";
import { useLang } from "../Context/LangContext"; import { useLang } from "../Context/LangContext";
@@ -7,14 +7,105 @@ const TallContent = ({ initialAnimation, onMouseEnter, onAnimationEnd }) => {
const [animation, setAnimation] = useState(initialAnimation); const [animation, setAnimation] = useState(initialAnimation);
const { colormode } = useTheme(); const { colormode } = useTheme();
const { language } = useLang(); const { language } = useLang();
const canvasRef = useRef(null);
const speedFactor = 0.2; // Geschwindigkeit anpassen: kleiner = langsamer, größer = schneller
useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
canvas.width = canvas.parentElement.offsetWidth;
canvas.height = canvas.parentElement.offsetHeight;
const particles = Array.from({ length: 50 }, () => ({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
radius: Math.random() * 4 + 1,
dx: (Math.random() * 0.5 - 0.25) * speedFactor,
dy: (Math.random() * 0.5 - 0.25) * speedFactor,
color: `hsl(${Math.random() * 360}, 70%, 70%)`,
}));
const starFieldEffect = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "rgba(0, 0, 0, 0.1)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
particles.forEach((particle) => {
particle.x += particle.dx;
particle.y += particle.dy;
// Warp particles when they leave canvas
if (particle.x < 0) particle.x = canvas.width;
if (particle.x > canvas.width) particle.x = 0;
if (particle.y < 0) particle.y = canvas.height;
if (particle.y > canvas.height) particle.y = 0;
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.radius, 0, Math.PI * 2);
ctx.fillStyle = particle.color;
ctx.fill();
});
requestAnimationFrame(starFieldEffect);
};
starFieldEffect();
canvas.addEventListener("mousemove", (e) => {
const rect = canvas.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
particles.forEach((particle) => {
const dx = particle.x - mouseX;
const dy = particle.y - mouseY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 150) {
const force = (150 - distance) / 150;
particle.dx += (dx / distance) * force * -0.05;
particle.dy += (dy / distance) * force * -0.05;
}
});
});
canvas.addEventListener("click", (e) => {
const rect = canvas.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
particles.forEach((particle) => {
const dx = particle.x - mouseX;
const dy = particle.y - mouseY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 200) {
const force = (200 - distance) / 200;
particle.dx += (dx / distance) * force * 1.5;
particle.dy += (dy / distance) * force * 1.5;
}
});
});
const handleResize = () => {
canvas.width = canvas.parentElement.offsetWidth;
canvas.height = canvas.parentElement.offsetHeight;
};
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, [speedFactor]);
return ( return (
<div <div
className={`container tallContent animate__animated ${animation} ${colormode}`} className={`container tallContent animate__animated ${animation} ${colormode}`}
onMouseEnter={() => onMouseEnter(setAnimation)} /* onMouseEnter={() => onMouseEnter(setAnimation)}
onAnimationEnd={() => onAnimationEnd(setAnimation)} */ onAnimationEnd={() => onAnimationEnd(setAnimation)}
> >
TallContent <canvas ref={canvasRef} className="particleCanvas"></canvas>
</div> </div>
); );
}; };

View File

@@ -17,16 +17,14 @@ import Linux from "../assets/icons/skill-icons/icons/Linux-Dark.svg?react";
import "../css/TechStack.css"; import "../css/TechStack.css";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import "animate.css";
import { useTheme } from "../Context/ThemeContext"; import { useTheme } from "../Context/ThemeContext";
import { useLang } from "../Context/LangContext"; import { useLang } from "../Context/LangContext";
const TechStack = ({ const TechStack = ({
initialAnimation, initialAnimation,
/* onMouseEnter, */
onAnimationEnd, onAnimationEnd,
isExpanded, isExpanded,
onExpandToggle, // Receive the handler as a prop onExpandToggle,
}) => { }) => {
const techStack = [ const techStack = [
{ name: "React", component: ReactIcon, className: "reactIcon", id: 1 }, { name: "React", component: ReactIcon, className: "reactIcon", id: 1 },
@@ -99,6 +97,7 @@ const TechStack = ({
const [animation, setAnimation] = useState(initialAnimation); const [animation, setAnimation] = useState(initialAnimation);
const { colormode } = useTheme(); const { colormode } = useTheme();
const { language } = useLang(); const { language } = useLang();
return ( return (
<div <div
className={clsx( className={clsx(
@@ -107,20 +106,20 @@ const TechStack = ({
colormode === "light" ? "light" : "dark", colormode === "light" ? "light" : "dark",
"animate__animated", "animate__animated",
animation, animation,
{ expanded: isExpanded } // Conditional class { expanded: isExpanded }
)} )}
/* onMouseEnter={() => onMouseEnter(setAnimation)} */
onAnimationEnd={() => onAnimationEnd(setAnimation)} onAnimationEnd={() => onAnimationEnd(setAnimation)}
> >
<h2>Tech Stack</h2> <h2>Tech Stack</h2>
<div className="techStack"> <div className="techStack">
{techStack {techStack
.filter((tech) => isExpanded || !tech.needExpand) // Filter based on isExpanded .filter((tech) => isExpanded || !tech.needExpand)
.map((tech) => { .map((tech) => {
const IconComponent = tech.component; const IconComponent = tech.component;
return ( return (
<div key={tech.id} className="techStackItem"> <div key={tech.id} className="techStackItem tooltip">
<IconComponent className={`techIcon ${tech.className}`} /> <IconComponent className={`techIcon ${tech.className}`} />
<span className="tooltipText">{tech.name}</span>
</div> </div>
); );
})} })}

View File

@@ -5,12 +5,17 @@
.navList { .navList {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-around; justify-content: start;
align-items: center; align-items: center;
gap: 2.5rem;
list-style-type: none; list-style-type: none;
height: 100%; height: 100%;
} }
.navItem:last-child {
margin-top: auto;
}
.navItem { .navItem {
font-size: 1.5rem; font-size: 1.5rem;
width: 100%; width: 100%;

View File

@@ -1,3 +1,12 @@
.tallContent { .tallContent {
grid-area: 1 / 5 / 8 / 9; grid-area: 1 / 5 / 8 / 9;
position: relative;
} }
.particleCanvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}

View File

@@ -72,3 +72,42 @@
.techStackContainer.expanded { .techStackContainer.expanded {
max-height: 600px; /* Expanded max-height */ max-height: 600px; /* Expanded max-height */
} }
.react-tooltip {
background-color: #333;
color: #fff;
font-size: 0.9rem;
border-radius: 4px;
padding: 5px 10px;
}
/* Tooltip container */
.tooltip {
position: relative;
display: inline-block;
}
/* Tooltip text */
.tooltipText {
visibility: hidden;
background-color: #333;
color: #fff;
text-align: center;
border-radius: 5px;
padding: 5px 10px;
position: absolute;
z-index: 1;
top: 90%; /* Position above the element */
left: 50%;
transform: translateX(-50%);
opacity: 0;
transition: opacity 0.3s;
font-size: 0.8rem;
white-space: nowrap;
}
/* Show the tooltip on hover */
.tooltip:hover .tooltipText {
visibility: visible;
opacity: 1;
}