296 lines
8.1 KiB
Svelte
296 lines
8.1 KiB
Svelte
<script lang="ts">
|
|
import EMail from "$lib/assets/email.svg";
|
|
import Git from "$lib/assets/git.svg";
|
|
import GitHub from "$lib/assets/github.svg";
|
|
import Instagram from "$lib/assets/instagram.svg";
|
|
import LinkedIn from "$lib/assets/linkedin.svg";
|
|
import Star from "$lib/assets/star.svg";
|
|
import { onMount } from "svelte";
|
|
|
|
$effect(() => {
|
|
const indices = Array(50)
|
|
.fill(0)
|
|
.map((_, i) => i);
|
|
|
|
const points: [number, number][] = indices.map(() => [
|
|
Math.random(),
|
|
Math.random(),
|
|
]);
|
|
const sizes = indices.map(() => Math.max(Math.random(), 0.3) * 2);
|
|
|
|
const stars = points.map(([x, y], i) => {
|
|
const star = document.createElement("img");
|
|
star.src = Star;
|
|
star.classList.add("star");
|
|
|
|
star.style.width = `${sizes[i]}vw`;
|
|
star.style.position = "absolute";
|
|
star.style.left = `${100 * x}vw`;
|
|
star.style.top = `${100 * y}vh`;
|
|
|
|
return star;
|
|
});
|
|
|
|
function chooseRandom<T>(arr: T[]): T {
|
|
return arr[Math.floor(Math.random() * arr.length)];
|
|
}
|
|
|
|
const connections = stars.map((_, i) => {
|
|
let others: number[] = [];
|
|
|
|
const otherCount = Math.max(3, Math.floor(Math.random() * 10));
|
|
for (let i = 0; i < otherCount; i++) {
|
|
let other = i;
|
|
while (other === i || others.includes(other)) {
|
|
other = chooseRandom(indices);
|
|
}
|
|
others.push(other);
|
|
}
|
|
|
|
return others;
|
|
});
|
|
|
|
let lidx: number = 0;
|
|
const lineInterval = setInterval(() => {
|
|
if (Math.random() < 0.3) return;
|
|
if (Math.random() < 0.05) lidx = chooseRandom(indices);
|
|
|
|
let iter = 0;
|
|
while (iter++ < 300) {
|
|
const nidx = chooseRandom(indices);
|
|
if (lidx === nidx) continue;
|
|
|
|
const [lx, ly] = points[lidx];
|
|
const [rx, ry] = points[nidx];
|
|
const d = Math.sqrt(
|
|
Math.pow(lx - rx, 2) + Math.pow(ly - ry, 2),
|
|
);
|
|
if ((d > 0.4 && Math.random() < 0.8) || d > 0.5) continue;
|
|
|
|
const line = document.getElementById(`${lidx}-${nidx}`);
|
|
if (!line) continue;
|
|
|
|
line.setAttribute("opacity", "1");
|
|
setTimeout(
|
|
() => line.setAttribute("opacity", "0"),
|
|
4500 + Math.random() * 2500,
|
|
);
|
|
|
|
lidx = nidx;
|
|
return;
|
|
}
|
|
|
|
// nothing found -> choose next random
|
|
lidx = chooseRandom(indices);
|
|
}, 650);
|
|
|
|
document.getElementById("stars")?.append(...stars);
|
|
|
|
const linesSVG = document.getElementById("stars-lines");
|
|
|
|
stars.forEach((_, i) => {
|
|
const [x1, y1] = points[i];
|
|
|
|
connections[i].forEach((conn) => {
|
|
const [x2, y2] = points[conn];
|
|
const line = document.createElementNS(
|
|
"http://www.w3.org/2000/svg",
|
|
"line",
|
|
);
|
|
|
|
line.setAttribute("x1", `${x1 * 100}%`);
|
|
line.setAttribute("y1", `${y1 * 100}%`);
|
|
line.setAttribute("x2", `${x2 * 100}%`);
|
|
line.setAttribute("y2", `${y2 * 100}%`);
|
|
|
|
line.setAttribute("stroke", "white");
|
|
line.setAttribute("stroke-width", "1.5");
|
|
|
|
line.id = `${i}-${conn}`;
|
|
|
|
line.setAttribute("opacity", "0");
|
|
|
|
linesSVG?.append(line);
|
|
});
|
|
});
|
|
|
|
const enterListeners = stars.map((_, i) => () => {
|
|
connections[i].forEach((conn) => {
|
|
const line = document.getElementById(`${i}-${conn}`);
|
|
line?.setAttribute("opacity", "1");
|
|
});
|
|
});
|
|
|
|
const leaveListeners = stars.map((_, i) => () => {
|
|
connections[i].forEach((conn) => {
|
|
const line = document.getElementById(`${i}-${conn}`);
|
|
line?.setAttribute("opacity", "0");
|
|
});
|
|
});
|
|
|
|
stars.forEach((star, i) => {
|
|
star.addEventListener("mouseenter", enterListeners[i]);
|
|
star.addEventListener("mouseleave", leaveListeners[i]);
|
|
});
|
|
|
|
const svgResizeListener = () => {
|
|
linesSVG?.setAttribute(
|
|
"viewBox",
|
|
`0 0 ${window.innerWidth} ${window.innerHeight}`,
|
|
);
|
|
};
|
|
|
|
svgResizeListener();
|
|
|
|
window.addEventListener("resize", svgResizeListener);
|
|
|
|
return () => {
|
|
clearInterval(lineInterval);
|
|
|
|
stars.forEach((star, i) => {
|
|
star.removeEventListener("mouseenter", enterListeners[i]);
|
|
star.removeEventListener("mouseleave", leaveListeners[i]);
|
|
});
|
|
|
|
window.removeEventListener("resize", svgResizeListener);
|
|
};
|
|
});
|
|
</script>
|
|
|
|
<svg
|
|
id="stars-lines"
|
|
aria-hidden="true"
|
|
style="position: absolute; left: 0; top: 0; height: 100%; width: 100%; z-index: 0;"
|
|
></svg>
|
|
|
|
<div id="stars" style="position: absolute; left: 0; top: 0; z-index: 2;"></div>
|
|
|
|
<main style="position: relative; z-index: 1;">
|
|
<h1>Hey, I'm Ludwig 🚀</h1>
|
|
<p>Welcome to my digital playground.</p>
|
|
|
|
<div class="social-links">
|
|
<a target="_blank" href="https://instagram.com/lehlud">
|
|
<img src={Instagram} alt="" width="25" />
|
|
</a>
|
|
<a target="_blank" href="https://linkedin.com/in/ludwig-lehnert">
|
|
<img src={LinkedIn} alt="" width="25" />
|
|
</a>
|
|
<a target="_blank" href="mailto:info@lehnert.dev">
|
|
<img src={EMail} alt="" width="25" />
|
|
</a>
|
|
<a target="_blank" href="https://gitea.cloud.lehnert.dev/explore/repos">
|
|
<img src={Git} alt="" width="25" />
|
|
</a>
|
|
<a target="_blank" href="https://github.com/lehlud">
|
|
<img src={GitHub} alt="" width="25" />
|
|
</a>
|
|
</div>
|
|
|
|
<div class="legal-links">
|
|
<a href="/imprint">Imprint</a>
|
|
<a href="/privacy">Privacy Policy</a>
|
|
</div>
|
|
</main>
|
|
|
|
<div style="height: 1000px;"></div>
|
|
|
|
<style>
|
|
h1 {
|
|
font-size: 3.5rem;
|
|
background: linear-gradient(90deg, #00ffff, #ff00ff);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
animation: glow 3s ease-in-out infinite alternate;
|
|
}
|
|
|
|
@keyframes glow {
|
|
from {
|
|
text-shadow: 0 0 10px #00ffff;
|
|
}
|
|
to {
|
|
text-shadow: 0 0 20px #ff00ff;
|
|
}
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from {
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
:global(.star) {
|
|
animation: fadeIn 1s forwards;
|
|
transition-duration: 500ms;
|
|
translate: -50% -50%;
|
|
}
|
|
|
|
:global(#stars-lines line) {
|
|
transition-duration: 500ms;
|
|
}
|
|
|
|
:global(#stars-lines line[opacity="1"]) {
|
|
animation: strokeAnim 3s ease-in-out forwards;
|
|
}
|
|
|
|
@keyframes strokeAnim {
|
|
from {
|
|
stroke: #00ffff;
|
|
}
|
|
to {
|
|
stroke: #ff00ff;
|
|
}
|
|
}
|
|
|
|
p {
|
|
font-size: 1.2rem;
|
|
max-width: 600px;
|
|
margin-top: 1rem;
|
|
color: #ccc;
|
|
}
|
|
|
|
.social-links {
|
|
display: flex;
|
|
gap: 0.75rem;
|
|
margin-top: 1.5rem;
|
|
}
|
|
|
|
.social-links a {
|
|
padding: 0.75rem;
|
|
font-size: 1rem;
|
|
background: #00ffff;
|
|
color: #1e1e2f;
|
|
border: none;
|
|
border-radius: 999px;
|
|
cursor: pointer;
|
|
transition: background 0.3s ease;
|
|
|
|
display: grid;
|
|
place-items: center;
|
|
}
|
|
|
|
.social-links a:hover {
|
|
background: #ff00ff;
|
|
color: white;
|
|
}
|
|
|
|
.legal-links {
|
|
margin-top: 1rem;
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.legal-links a {
|
|
color: rgb(255, 255, 255);
|
|
cursor: pointer;
|
|
transition: background 0.3s ease;
|
|
}
|
|
|
|
.legal-links a:hover {
|
|
opacity: 0.8;
|
|
}
|
|
</style>
|