This commit is contained in:
2026-08-08 18:30:36 -04:00
parent 03e82380db
commit cc6e00d777
11 changed files with 182 additions and 135 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
@import "tailwindcss";
body {
font-family: "Pathway Gothic One", sans-serif;
font-family: "Open Sans", sans-serif;
background-color: black;
::-webkit-scrollbar {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

+14
View File
@@ -0,0 +1,14 @@
export default function Footer() {
return (
<div className="w-full flex justify-between">
<a
href="#"
onClick={() => navigator.clipboard.writeText("alex@alexav.gg")}
className="text-xs opacity-85 hover:underline"
>
alex@alexav.gg
</a>
<p className="text-xs opacity-85">&copy; 2026 Alex Frantz</p>
</div>
);
}
+25
View File
@@ -0,0 +1,25 @@
export default function Header() {
return (
<div className="flex justify-between py-4">
<h1 className="font-bold">
Alex <span className="font-normal text-blue-500">/</span> dev + QA
</h1>
<div className="space-x-2">
<a
href="https://github.com/azpha"
target="_blank"
className="hover:underline"
>
GitHub
</a>
<a
href="#"
className="hover:underline"
onClick={() => navigator.clipboard.writeText("alex@alexav.gg")}
>
alex@alexav.gg
</a>
</div>
</div>
);
}
+3 -1
View File
@@ -1,8 +1,10 @@
import type { ReactNode } from "react";
import Header from "./Header";
export default function Layout({ children }: { children: ReactNode }) {
return (
<div className="bg-neutral-800 text-white min-h-screen flex justify-center items-center select-none">
<div className="bg-black text-white min-h-screen sm:w-[95%] md:w-[75%] lg:w-[50%] xl:w-[25%] mx-auto select-none px-4 md:px-0">
<Header />
<div className="relative">{children}</div>
</div>
);
+46
View File
@@ -0,0 +1,46 @@
interface ProjectCardProps {
title: string;
description: string;
link: string;
pills: string[];
}
interface PillProps {
name: string;
}
export default function ProjectCard({
title,
description,
link,
pills,
}: ProjectCardProps) {
const Pill = ({ name }: PillProps) => {
return (
<div className="bg-zinc-500 rounded-lg max-w-fit text-xs p-1 inline-block font-semibold">
{name}
</div>
);
};
return (
<div className="bg-neutral-800 rounded-lg p-2 border border-white">
<div className="flex justify-between">
<h1 className="text-1xl font-bold">{title}</h1>
<a href={link} target="_blank">
<button className="bg-gray-500 border border-zinc-500 rounded-lg px-2 py-1 text-sm">
View &gt;
</button>
</a>
</div>
<div className="space-y-2 w-[75%]">
<p className="text-sm">{description}</p>
<div className="space-x-2">
{pills.map((v, k) => {
return <Pill key={k} name={v} />;
})}
</div>
</div>
</div>
);
}
-28
View File
@@ -1,28 +0,0 @@
import { useState, useEffect } from "react";
export default function Clock() {
const [clock, setClock] = useState(
new Date().toLocaleTimeString("en-GB", {
hour: "2-digit",
minute: "2-digit",
}),
);
useEffect(() => {
setInterval(() => {
const date = new Date().toLocaleTimeString("en-GB", {
hour: "2-digit",
minute: "2-digit",
});
if (clock != date) {
setClock(date);
}
}, 500);
}, []);
return (
<h1 className="text-center bg-white text-black max-w-fit mx-auto px-1">
{clock}
</h1>
);
}
@@ -1,68 +0,0 @@
import { useEffect, useRef } from "react";
export default function CountdownModule() {
const countdownRef = useRef<HTMLParagraphElement | null>(null);
const audioRef = useRef<HTMLAudioElement | null>(null);
useEffect(() => {
const dateOfStart = new Date("Sep 7, 2025 20:15:00").getTime();
setInterval(() => {
const currentDate = new Date().getTime();
const difference = dateOfStart - currentDate;
const days = Math.floor(difference / (1000 * 60 * 60 * 24));
const hours = Math.floor(
(difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)
);
const minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((difference % (1000 * 60)) / 1000);
if (countdownRef?.current) {
let timerString = "";
timerString += `${days || "0"}d, `;
timerString += `${hours || "0"}h, `;
timerString += `${minutes || "0"}m, `;
timerString += `${seconds || "0"}s`;
if (
days <= 0 &&
hours <= 0 &&
minutes <= 0 &&
seconds <= 0 &&
timerString === ""
) {
timerString = "GO BILLS";
}
countdownRef.current.innerText = timerString;
}
}, 1000);
}, []);
return (
<div className="border border-white bg-blue-800 border-solid rounded-lg w-full mb-4">
<div className="flex items-center justify-between p-2">
<img
onClick={() => {
if (audioRef?.current) {
audioRef.current.play();
}
}}
width="50"
height="50"
className="hover:cursor-pointer"
src={"https://storage.alexav.gg/content/bills.png"}
/>
<p className="text-2xl font-bold" ref={countdownRef}></p>
</div>
<audio
ref={audioRef}
autoPlay={false}
src={
"https://storage.alexav.gg/content/0f4ce103-62d0-4ff1-8b8b-e38becced35d.mp3"
}
/>
</div>
);
}
+92 -36
View File
@@ -5,6 +5,12 @@ import Button from "../components/Button";
import Home from "./Home";
import PicturesPage from "./Pictures";
import Sports from "./Sports";
import ProjectCard from "../components/ProjectCard";
import FantasyStatus from "../components/FantasyStatus";
import GameComponent from "../components/info/GameComponent";
import MusicComponent from "../components/info/MusicComponent";
import PictureCarousel from "../components/modules/PictureCarousel";
import Footer from "../components/Footer";
export default function App() {
const [tab, setTab] = useState<"home" | "scores" | "sports" | "pictures">(
@@ -13,44 +19,94 @@ export default function App() {
return (
<Layout>
<div className="border-3 bg-zinc-700 border-black w-full sm:w-[500px]">
<div className="w-full">
<div className="grid grid-cols-3 gap-2 p-2 text-2xl">
<h1 className="font-semibold text-white">welcome</h1>
<Clock />
<h1 className="text-right font-bold">welcome</h1>
<main className="my-24">
<div className="py-2">
<p className="opacity-85 text-sm text-left">HI, I'M ALEX</p>
<h1 className="w-full whitespace-break-spaces text-4xl py-2">
I write software sometimes, then I spend twice as long breaking it
</h1>
</div>
<div className="py-2 space-y-6">
<p>
My day job is software QA at{" "}
<a
className="text-lime-300 hover:underline"
href="https://medal.tv/u/alexav"
target="_blank"
>
Medal.tv
</a>{" "}
(
<a
href="https://medal.tv/careers"
target="_blank"
className="underline"
>
we're hiring!
</a>
), then by night I play with my homelab rack or write some mediocre
JavaScript.
</p>
<p>
Based in Western New York (go bills, go sabres). Working on learning
Java, C#. Pointless software for funzies is my passion.
</p>
</div>
<hr className="my-4 border-zinc-800" />
<p className="opacity-85 text-sm text-left pb-2">THINGS I'VE BUILT</p>
<div className="my-2 space-y-2">
<ProjectCard
title={"LED Matrix Scoreboard"}
description={
"A physical scoreboard driven by an LED matrix, pulls live sports data and renders pixel by pixel."
}
link="https://git.alexav.gg/alex/scoreboard"
pills={["Python", "Raspberry Pi", "Hardware"]}
/>
<ProjectCard
title={"Subber"}
description={
"Keeps track on your subscriptions and generates clear metrics for you to manage."
}
link="https://github.com/azpha/subber"
pills={["TypeScript", "Tailwind", "Prisma"]}
/>
<ProjectCard
title={"Snapchat Memory Extractor"}
description={
"Extracts your Snapchat memories from your data export in a cleaner format than their horrible index.html."
}
link="https://github.com/azpha/snapchat-memory-extractor"
pills={["Python", "Automation"]}
/>
<ProjectCard
title={"Homelab"}
description={
"My tinkering sandbox. Full CI/CD pipeline for people to learn & take inspiration from."
}
link="https://git.alexav.gg/alex/homelab"
pills={["Docker", "Ansible", "Python", "Automation"]}
/>
<hr className="my-4 border-zinc-800" />
<p className="opacity-85 text-sm text-left pb-2">FUN STUFF</p>
<div>
<h1 className="text-sm opacity-85 font-semibold">Fantasy</h1>
<FantasyStatus />
</div>
<div>
<h1 className="text-sm opacity-85 font-semibold">
My Life, Pictured
</h1>
<PictureCarousel />
</div>
<Footer />
</div>
<img
className="h-[100px] object-[25%_35%] w-full object-cover border-b-black border-b-2 border-t-2 border-t-black"
style={{
objectPosition: "",
}}
src="https://storage.alexav.gg/content/59c4bbeba39eb04cf91b81aa0c2e519ad6cf6f95725f9bd9cc8238523aea9529.webp"
/>
<div className="flex flex-row space-x-2 justify-center text-2xl my-2">
<Button active={tab === "home"} onClick={() => setTab("home")}>
home
</Button>
<Button
active={tab === "pictures"}
onClick={() => setTab("pictures")}
>
pictures
</Button>
<Button active={tab === "sports"} onClick={() => setTab("sports")}>
sports
</Button>
</div>
<hr className="border-black border-1" />
{tab === "home" && <Home />}
{tab === "pictures" && <PicturesPage />}
{tab === "sports" && <Sports />}
</div>
</main>
</Layout>
);
}