techstack tooltip / tall Content main => particle playground
This commit is contained in:
41
package-lock.json
generated
41
package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -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%;
|
||||||
|
|||||||
@@ -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%;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user