Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/blade/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,12 @@
"lucide-react": "^0.575.0",
"luxon": "^3.7.2",
"monaco-editor": "^0.56.0",
"motion": "^13.2.0",
"next": "^16.2.11",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"recharts": "^3.7.0",
"simple-icons": "^16.30.0",
"superjson": "2.2.6",
"tw-animate-css": "^1.4.0",
"zod": "catalog:"
Expand Down
Binary file added apps/blade/public/adrian-garced/Resume.pdf
Binary file not shown.
15 changes: 15 additions & 0 deletions apps/blade/src/app/_components/adrian-garced/footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default function Footer() {
return (
<footer
id="contact"
className="border-t border-neutral-800 bg-[#0a0a0a]"
>
<div className="border-t border-neutral-900">
<div className="text-sm mx-auto max-w-6xl px-6 py-6 flex flex-col gap-2 text-neutral-400 sm:flex-row sm:items-center sm:justify-between">
<p>© {new Date().getFullYear()} Adrian</p>
<p>Built with Next.js, Tailwind, and Motion.</p>
</div>
</div>
</footer>
);
}
63 changes: 63 additions & 0 deletions apps/blade/src/app/_components/adrian-garced/navbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { FileText, Mail } from "lucide-react";

import { Tooltip, TooltipContent, TooltipTrigger } from "@forge/ui/tooltip";

import { CodebergIcon, GithubIcon } from "~/app/adrian-garced/icons";

const NAVICONS = [
{
tooltip: "Github",
icon: GithubIcon,
url: "https://github.com/pinkytoefoo",
},
{
tooltip: "Codeberg",
icon: CodebergIcon,
url: "https://codeberg.org/pinkytoefoo",
},
{
tooltip: "Resume",
icon: FileText,
url: "/adrian-garced/resume.pdf",
},
{
tooltip: "Email",
icon: Mail,
url: "mailto:agadrian1331@gmail.com",
},
] as const;

export default function Navbar() {
return (
<nav
className="sticky top-0 z-40 w-full border-b backdrop-blur"
style={{ backgroundColor: "rgba(var(--background), 0.8)" }}
>
<div className="mx-auto flex max-w-4xl items-center justify-between px-6 py-4">
<a href="#" className="font-semibold">
adrian-g
</a>

<div className="flex items-center gap-8">
{NAVICONS.map((icons) => (
<Tooltip key={icons.tooltip}>
<TooltipTrigger asChild>
<a
href={icons.url}
className="flex items-center gap-2"
target="_blank"
rel="noopener noreferrer"
>
<icons.icon className="size-6" />
</a>
</TooltipTrigger>
<TooltipContent className="bg-accent">
<p>{icons.tooltip}</p>
</TooltipContent>
</Tooltip>
))}
</div>
</div>
</nav>
);
}
162 changes: 162 additions & 0 deletions apps/blade/src/app/_components/adrian-garced/playground.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
"use client";

import { useEffect, useRef } from "react";
import {
siCplusplus,
siRust,
siTypescript,
siPython,
siHono,
siSolid,
siTailwindcss,
siShadcnui
} from "simple-icons";

const ICONS = [
siCplusplus,
siRust,
siTypescript,
siPython,
siHono,
siSolid,
siTailwindcss,
siShadcnui
];

const SIZE = 60;

export default function Playground() {
const canvasRef = useRef<HTMLCanvasElement>(null);
const containerRef = useRef<HTMLDivElement>(null);

useEffect(() => {
const canvas = canvasRef.current;
const container = containerRef.current;
const ctx = canvas?.getContext("2d");

if (!canvas || !container || !ctx) return;

const icon = new Image();

let x = 40;
let y = 40;
let vx = 100;
let vy = 100;
let width = 0;
let height = 0;
let iconIndex = 0;
let frame = 0;
let previous = performance.now();

const loadIcon = () => {
const currentIcon = ICONS[iconIndex];

if(!currentIcon)
return;

icon.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="#${currentIcon.hex}" d="${currentIcon.path}"/>
</svg>
`)}`;
};

const nextIcon = () => {
let next = iconIndex;

while (next === iconIndex) {
next = Math.floor(Math.random() * ICONS.length);
}

iconIndex = next;
loadIcon();
};

const resize = () => {
const { width: w, height: h } = container.getBoundingClientRect();
const dpr = window.devicePixelRatio || 1;

width = w;
height = h;

canvas.width = w * dpr;
canvas.height = h * dpr;
canvas.style.width = `${w}px`;
canvas.style.height = `${h}px`;

ctx.setTransform(dpr, 0, 0, dpr, 0, 0);

x = Math.max(0, Math.min(x, width - SIZE));
y = Math.max(0, Math.min(y, height - SIZE));
};

const draw = (now: number) => {
const dt = Math.min((now - previous) / 1000, 0.05);
previous = now;

x += vx * dt;
y += vy * dt;

let hit = false;

if (x <= 0 && vx < 0) {
x = 0;
vx *= -1;
hit = true;
} else if (x + SIZE >= width && vx > 0) {
x = width - SIZE;
vx *= -1;
hit = true;
}

if (y <= 0 && vy < 0) {
y = 0;
vy *= -1;
hit = true;
} else if (y + SIZE >= height && vy > 0) {
y = height - SIZE;
vy *= -1;
hit = true;
}

if (hit) nextIcon();

ctx.clearRect(0, 0, width, height);

ctx.beginPath();
ctx.roundRect(x, y, SIZE, SIZE, 8);
ctx.fillStyle = "rgba(245, 245, 245, 0.9)";
ctx.fill();
ctx.strokeStyle = "rgba(64, 64, 64, 0.8)";
ctx.stroke();

if (icon.complete) {
ctx.drawImage(icon, x + 7, y + 7, SIZE - 14, SIZE - 14);
}

frame = requestAnimationFrame(draw);
};

resize();
loadIcon();
frame = requestAnimationFrame(draw);

window.addEventListener("resize", resize);

return () => {
cancelAnimationFrame(frame);
window.removeEventListener("resize", resize);
};
}, []);

return (
<section className="mt-1 scroll-mt-24">
<div
ref={containerRef}
className="mt-2 h-72 overflow-hidden rounded-lg border border-neutral-800 bg-neutral-950"
>
<canvas ref={canvasRef} />
</div>
</section>
);
}
51 changes: 51 additions & 0 deletions apps/blade/src/app/_components/adrian-garced/reveal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"use client";

import { useEffect, useRef, useState } from "react";

interface RevealProps {
children: React.ReactNode;
className?: string;
}

export default function Reveal({
children,
className = "",
}: RevealProps) {
const ref = useRef<HTMLDivElement>(null);
const [visible, setVisible] = useState(false);

useEffect(() => {
const element = ref.current;

if (!element) return;

const observer = new IntersectionObserver(
([entry]) => {
if (entry?.isIntersecting) {
setVisible(true);
observer.disconnect();
}
},
{
threshold: 0.1,
}
);

observer.observe(element);

return () => observer.disconnect();
}, []);

return (
<div
ref={ref}
className={`transition-all duration-700 ease-out ${
visible
? "translate-y-0 opacity-100"
: "translate-y-6 opacity-0"
} ${className}`}
>
{children}
</div>
);
}
Loading