Updated layout and background
This commit is contained in:
@@ -1,290 +0,0 @@
|
||||
@keyframes move {
|
||||
100% {
|
||||
transform: translate3d(0, 0, 1px) rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.background {
|
||||
position: absolute;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: #000000;
|
||||
overflow: hidden;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.background span {
|
||||
width: 24vmin;
|
||||
height: 24vmin;
|
||||
border-radius: 24vmin;
|
||||
backface-visibility: hidden;
|
||||
position: absolute;
|
||||
animation: move;
|
||||
animation-duration: 6;
|
||||
animation-timing-function: linear;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
.background span:nth-child(0) {
|
||||
color: #f5304c;
|
||||
top: 24%;
|
||||
left: 18%;
|
||||
animation-duration: 220s;
|
||||
animation-delay: -272s;
|
||||
transform-origin: -5vw -4vh;
|
||||
box-shadow: 48vmin 0 6.421535280295322vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(1) {
|
||||
color: #f5304c;
|
||||
top: 86%;
|
||||
left: 77%;
|
||||
animation-duration: 91s;
|
||||
animation-delay: -322s;
|
||||
transform-origin: 14vw 0vh;
|
||||
box-shadow: -48vmin 0 6.323515398437237vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(2) {
|
||||
color: #f5304c;
|
||||
top: 17%;
|
||||
left: 44%;
|
||||
animation-duration: 248s;
|
||||
animation-delay: -181s;
|
||||
transform-origin: 0vw -7vh;
|
||||
box-shadow: -48vmin 0 6.116084464270985vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(3) {
|
||||
color: #f5304c;
|
||||
top: 43%;
|
||||
left: 15%;
|
||||
animation-duration: 98s;
|
||||
animation-delay: -211s;
|
||||
transform-origin: -21vw 8vh;
|
||||
box-shadow: 48vmin 0 6.907246782805462vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(4) {
|
||||
color: #f5304c;
|
||||
top: 66%;
|
||||
left: 60%;
|
||||
animation-duration: 241s;
|
||||
animation-delay: -75s;
|
||||
transform-origin: 15vw -9vh;
|
||||
box-shadow: -48vmin 0 6.832728853871303vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(5) {
|
||||
color: #f5304c;
|
||||
top: 79%;
|
||||
left: 87%;
|
||||
animation-duration: 207s;
|
||||
animation-delay: -234s;
|
||||
transform-origin: -19vw 4vh;
|
||||
box-shadow: -48vmin 0 6.062609002101319vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(6) {
|
||||
color: #f5304c;
|
||||
top: 45%;
|
||||
left: 79%;
|
||||
animation-duration: 243s;
|
||||
animation-delay: -285s;
|
||||
transform-origin: 11vw 25vh;
|
||||
box-shadow: -48vmin 0 6.42616777258573vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(7) {
|
||||
color: #f5304c;
|
||||
top: 43%;
|
||||
left: 51%;
|
||||
animation-duration: 227s;
|
||||
animation-delay: -181s;
|
||||
transform-origin: -23vw 1vh;
|
||||
box-shadow: 48vmin 0 6.831515741167314vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(8) {
|
||||
color: #f5304c;
|
||||
top: 21%;
|
||||
left: 59%;
|
||||
animation-duration: 228s;
|
||||
animation-delay: -196s;
|
||||
transform-origin: -23vw 23vh;
|
||||
box-shadow: 48vmin 0 6.824335114512845vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(9) {
|
||||
color: #f5304c;
|
||||
top: 95%;
|
||||
left: 81%;
|
||||
animation-duration: 202s;
|
||||
animation-delay: -141s;
|
||||
transform-origin: 17vw 24vh;
|
||||
box-shadow: 48vmin 0 6.5732268320821685vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(10) {
|
||||
color: #f5304c;
|
||||
top: 23%;
|
||||
left: 90%;
|
||||
animation-duration: 43s;
|
||||
animation-delay: -406s;
|
||||
transform-origin: -7vw -12vh;
|
||||
box-shadow: 48vmin 0 6.1955583991481vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(11) {
|
||||
color: #f5304c;
|
||||
top: 6%;
|
||||
left: 94%;
|
||||
animation-duration: 422s;
|
||||
animation-delay: -38s;
|
||||
transform-origin: -5vw 16vh;
|
||||
box-shadow: 48vmin 0 6.212234588070589vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(12) {
|
||||
color: #f5304c;
|
||||
top: 76%;
|
||||
left: 22%;
|
||||
animation-duration: 258s;
|
||||
animation-delay: -58s;
|
||||
transform-origin: -23vw -6vh;
|
||||
box-shadow: 48vmin 0 6.165111619626152vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(13) {
|
||||
color: #f5304c;
|
||||
top: 20%;
|
||||
left: 80%;
|
||||
animation-duration: 363s;
|
||||
animation-delay: -411s;
|
||||
transform-origin: 22vw 25vh;
|
||||
box-shadow: -48vmin 0 6.179675889584371vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(14) {
|
||||
color: #f5304c;
|
||||
top: 88%;
|
||||
left: 36%;
|
||||
animation-duration: 12s;
|
||||
animation-delay: -69s;
|
||||
transform-origin: -11vw -4vh;
|
||||
box-shadow: -48vmin 0 6.07188338021629vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(15) {
|
||||
color: #f5304c;
|
||||
top: 97%;
|
||||
left: 11%;
|
||||
animation-duration: 351s;
|
||||
animation-delay: -130s;
|
||||
transform-origin: -16vw -18vh;
|
||||
box-shadow: 48vmin 0 6.558182158209241vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(16) {
|
||||
color: #f5304c;
|
||||
top: 60%;
|
||||
left: 99%;
|
||||
animation-duration: 193s;
|
||||
animation-delay: -67s;
|
||||
transform-origin: 10vw 25vh;
|
||||
box-shadow: 48vmin 0 6.570051729134467vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(17) {
|
||||
color: #f5304c;
|
||||
top: 12%;
|
||||
left: 8%;
|
||||
animation-duration: 166s;
|
||||
animation-delay: -69s;
|
||||
transform-origin: 7vw -1vh;
|
||||
box-shadow: -48vmin 0 6.742868690500271vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(18) {
|
||||
color: #f5304c;
|
||||
top: 44%;
|
||||
left: 38%;
|
||||
animation-duration: 96s;
|
||||
animation-delay: -279s;
|
||||
transform-origin: 11vw 1vh;
|
||||
box-shadow: -48vmin 0 6.261014277195781vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(19) {
|
||||
color: #f5304c;
|
||||
top: 96%;
|
||||
left: 77%;
|
||||
animation-duration: 122s;
|
||||
animation-delay: -355s;
|
||||
transform-origin: 6vw 3vh;
|
||||
box-shadow: -48vmin 0 6.438018625575824vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(20) {
|
||||
color: #f5304c;
|
||||
top: 2%;
|
||||
left: 81%;
|
||||
animation-duration: 331s;
|
||||
animation-delay: -355s;
|
||||
transform-origin: 11vw -18vh;
|
||||
box-shadow: -48vmin 0 6.555921470976377vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(21) {
|
||||
color: #f5304c;
|
||||
top: 42%;
|
||||
left: 9%;
|
||||
animation-duration: 439s;
|
||||
animation-delay: -233s;
|
||||
transform-origin: -20vw -24vh;
|
||||
box-shadow: -48vmin 0 6.570997236391078vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(22) {
|
||||
color: #f5304c;
|
||||
top: 69%;
|
||||
left: 23%;
|
||||
animation-duration: 400s;
|
||||
animation-delay: -390s;
|
||||
transform-origin: -18vw -21vh;
|
||||
box-shadow: 48vmin 0 6.279087258882428vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(23) {
|
||||
color: #f5304c;
|
||||
top: 33%;
|
||||
left: 95%;
|
||||
animation-duration: 18s;
|
||||
animation-delay: -98s;
|
||||
transform-origin: 22vw 8vh;
|
||||
box-shadow: -48vmin 0 6.088381708804739vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(24) {
|
||||
color: #f5304c;
|
||||
top: 39%;
|
||||
left: 69%;
|
||||
animation-duration: 337s;
|
||||
animation-delay: -83s;
|
||||
transform-origin: 15vw -5vh;
|
||||
box-shadow: -48vmin 0 6.515991392427649vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(25) {
|
||||
color: #f5304c;
|
||||
top: 57%;
|
||||
left: 17%;
|
||||
animation-duration: 126s;
|
||||
animation-delay: -93s;
|
||||
transform-origin: -7vw 9vh;
|
||||
box-shadow: 48vmin 0 6.431063119719202vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(26) {
|
||||
color: #f5304c;
|
||||
top: 91%;
|
||||
left: 76%;
|
||||
animation-duration: 131s;
|
||||
animation-delay: -256s;
|
||||
transform-origin: -4vw 23vh;
|
||||
box-shadow: 48vmin 0 6.370776053425152vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(27) {
|
||||
color: #f5304c;
|
||||
top: 44%;
|
||||
left: 65%;
|
||||
animation-duration: 160s;
|
||||
animation-delay: -235s;
|
||||
transform-origin: -23vw 12vh;
|
||||
box-shadow: 48vmin 0 6.34935032141241vmin currentColor;
|
||||
}
|
||||
.background span:nth-child(28) {
|
||||
color: #f5304c;
|
||||
top: 89%;
|
||||
left: 36%;
|
||||
animation-duration: 298s;
|
||||
animation-delay: -400s;
|
||||
transform-origin: -7vw 2vh;
|
||||
box-shadow: -48vmin 0 6.935061205772426vmin currentColor;
|
||||
}
|
||||
+184
-34
@@ -1,39 +1,189 @@
|
||||
import "./Background.css";
|
||||
'use client';
|
||||
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
|
||||
export default function Background({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
||||
const animationRef = useRef<number>(0);
|
||||
const particlesRef = useRef<Particle[]>([]);
|
||||
const [dimensions, setDimensions] = useState<{ width: number; height: number }>({
|
||||
width: 0,
|
||||
height: 0,
|
||||
});
|
||||
|
||||
// Particle configuration
|
||||
const config = {
|
||||
particleCount: 80,
|
||||
maxDistance: 120,
|
||||
particleSpeed: 0.5,
|
||||
particleSize: 2,
|
||||
lineOpacity: 0.6,
|
||||
particleOpacity: 0.8,
|
||||
colors: {
|
||||
particles: "#f5304c", // blue-400
|
||||
connections: "#f5304c", // blue-500
|
||||
},
|
||||
};
|
||||
|
||||
// Particle class
|
||||
class Particle {
|
||||
x: number;
|
||||
y: number;
|
||||
vx: number;
|
||||
vy: number;
|
||||
size: number;
|
||||
|
||||
constructor(width: number, height: number) {
|
||||
this.x = Math.random() * width;
|
||||
this.y = Math.random() * height;
|
||||
this.vx = (Math.random() - 0.5) * config.particleSpeed;
|
||||
this.vy = (Math.random() - 0.5) * config.particleSpeed;
|
||||
this.size = config.particleSize + Math.random() * 2;
|
||||
}
|
||||
|
||||
update(width: number, height: number) {
|
||||
this.x += this.vx;
|
||||
this.y += this.vy;
|
||||
|
||||
// Bounce off edges
|
||||
if (this.x < 0 || this.x > width) {
|
||||
this.vx = -this.vx;
|
||||
this.x = Math.max(0, Math.min(width, this.x));
|
||||
}
|
||||
if (this.y < 0 || this.y > height) {
|
||||
this.vy = -this.vy;
|
||||
this.y = Math.max(0, Math.min(height, this.y));
|
||||
}
|
||||
}
|
||||
|
||||
draw(ctx: CanvasRenderingContext2D) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
|
||||
ctx.fillStyle = `${config.colors.particles}${Math.round(
|
||||
config.particleOpacity * 255
|
||||
)
|
||||
.toString(16)
|
||||
.padStart(2, "0")}`;
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize particles
|
||||
const initParticles = (width: number, height: number) => {
|
||||
particlesRef.current = [];
|
||||
for (let i = 0; i < config.particleCount; i++) {
|
||||
particlesRef.current.push(new Particle(width, height));
|
||||
}
|
||||
};
|
||||
|
||||
// Draw connections between nearby particles
|
||||
const drawConnections = (ctx: CanvasRenderingContext2D, particles: Particle[]) => {
|
||||
for (let i = 0; i < particles.length; i++) {
|
||||
for (let j = i + 1; j < particles.length; j++) {
|
||||
const dx = particles[i].x - particles[j].x;
|
||||
const dy = particles[i].y - particles[j].y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < config.maxDistance) {
|
||||
const opacity = 1 - distance / config.maxDistance;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(particles[i].x, particles[i].y);
|
||||
ctx.lineTo(particles[j].x, particles[j].y);
|
||||
ctx.strokeStyle = `${config.colors.connections}${Math.round(
|
||||
opacity * config.lineOpacity * 255
|
||||
)
|
||||
.toString(16)
|
||||
.padStart(2, "0")}`;
|
||||
ctx.lineWidth = 1;
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Animation loop
|
||||
const animate = () => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return;
|
||||
|
||||
const { width, height } = dimensions;
|
||||
|
||||
// Clear canvas
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
// Update and draw particles
|
||||
particlesRef.current.forEach((particle) => {
|
||||
particle.update(width, height);
|
||||
particle.draw(ctx);
|
||||
});
|
||||
|
||||
// Draw connections
|
||||
drawConnections(ctx, particlesRef.current);
|
||||
|
||||
animationRef.current = requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
// Handle window resize
|
||||
const handleResize = () => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas || !canvas.parentElement) return;
|
||||
|
||||
const rect = canvas.parentElement.getBoundingClientRect();
|
||||
const width = rect.width;
|
||||
const height = rect.height;
|
||||
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
canvas.width = width * dpr;
|
||||
canvas.height = height * dpr;
|
||||
canvas.style.width = `${width}px`;
|
||||
canvas.style.height = `${height}px`;
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (ctx) {
|
||||
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset scaling before re-scaling
|
||||
ctx.scale(dpr, dpr);
|
||||
}
|
||||
|
||||
setDimensions({ width, height });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
handleResize();
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", handleResize);
|
||||
if (animationRef.current) {
|
||||
cancelAnimationFrame(animationRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (dimensions.width && dimensions.height) {
|
||||
initParticles(dimensions.width, dimensions.height);
|
||||
animate();
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (animationRef.current) {
|
||||
cancelAnimationFrame(animationRef.current);
|
||||
}
|
||||
};
|
||||
}, [dimensions]);
|
||||
|
||||
const Background = () => {
|
||||
return (
|
||||
<div className="background blur-2xl">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<div className="relative w-full min-h-screen overflow-hidden bg-black">
|
||||
<canvas ref={canvasRef} className="absolute inset-0 w-full h-full" />
|
||||
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Background;
|
||||
|
||||
@@ -28,15 +28,6 @@ export default function Navbar() {
|
||||
>
|
||||
Pricing
|
||||
</Link>
|
||||
<Link
|
||||
href="/blog"
|
||||
className={
|
||||
"hover:text-red transition-colors duration-300" +
|
||||
(pathname === "/blog" ? " text-red" : "")
|
||||
}
|
||||
>
|
||||
Blog
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { stringify } from "querystring";
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
type PinnedRepo = {
|
||||
author: string;
|
||||
@@ -33,8 +35,7 @@ export function Repo({
|
||||
hover:shadow-md
|
||||
transition-shadow
|
||||
duration-300
|
||||
bg-transparent-ish
|
||||
backdrop-blur-xl
|
||||
backdrop-blur-[2px]
|
||||
`}
|
||||
>
|
||||
<div className="w-full">
|
||||
@@ -103,7 +104,8 @@ export function Repo({
|
||||
<span className="text-gray-300">{forks}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
{lang && (
|
||||
<div
|
||||
className="inline-flex items-center rounded-md border px-2.5 py-0.5 font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 text-foreground text-xs bg-red-600/10 border-red-600"
|
||||
data-sentry-component="Badge"
|
||||
data-sentry-source-file="badge.tsx"
|
||||
@@ -126,6 +128,7 @@ export function Repo({
|
||||
</svg>
|
||||
{lang}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -134,11 +137,21 @@ export function Repo({
|
||||
);
|
||||
}
|
||||
|
||||
export default async function PinnedRepos() {
|
||||
export default function PinnedRepos() {
|
||||
try {
|
||||
const pinned: PinnedRepo[] = await (
|
||||
await fetch("https://pinned.berrysauce.dev/get/kleo-dev")
|
||||
).json();
|
||||
const [pinned, setPinned] = useState<PinnedRepo[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetch("https://pinned.berrysauce.dev/get/kleo-dev").then(r => {
|
||||
r.json().then(p => {
|
||||
setPinned(p);
|
||||
}).catch(e => {
|
||||
console.error(e)
|
||||
})
|
||||
}).catch(e => {
|
||||
console.error(e)
|
||||
})
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-6 bg-transparent">
|
||||
|
||||
@@ -4,16 +4,27 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
export default function Skill({
|
||||
skill,
|
||||
icon,
|
||||
mt,
|
||||
classInsert,
|
||||
}: {
|
||||
skill: string;
|
||||
icon: IconProp;
|
||||
mt?: string;
|
||||
classInsert?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="skill">
|
||||
<FontAwesomeIcon icon={icon} width={40} className={`text-red ${mt}`} />
|
||||
<span className="tooltip absolute bg-transparent-ish">{skill}</span>
|
||||
<div className="relative flex items-center justify-center group">
|
||||
<span
|
||||
className="absolute bottom-full mb-2 left-1/2 -translate-x-1/2
|
||||
bg-black text-white text-sm px-2 py-1 rounded
|
||||
opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-nowrap"
|
||||
>
|
||||
Skilled in {" "} {skill}
|
||||
</span>
|
||||
|
||||
<FontAwesomeIcon
|
||||
icon={icon}
|
||||
width={40}
|
||||
className={`text-red ${classInsert} cursor-pointer`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user