Prettier format global

This commit is contained in:
2024-12-18 14:43:32 -06:00
parent 05a6d18858
commit c9b240c6b4
20 changed files with 1300 additions and 997 deletions

View File

@@ -1,19 +1,33 @@
import {Html, Head, Main, NextScript} from 'next/document'
import { Html, Head, Main, NextScript } from "next/document";
export default function Document() {
return (
<Html>
<Head>
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="manifest" href="/site.webmanifest" />
<meta name="theme-color" content="#000000" />
</Head>
<body className="bg-black">
<Main/>
<NextScript/>
</body>
</Html>
)
}
return (
<Html>
<Head>
<link
rel="apple-touch-icon"
sizes="180x180"
href="/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon-16x16.png"
/>
<link rel="manifest" href="/site.webmanifest" />
<meta name="theme-color" content="#000000" />
</Head>
<body className="bg-black">
<Main />
<NextScript />
</body>
</Html>
);
}

View File

@@ -1,46 +1,56 @@
import {type NextPage} from "next";
import { type NextPage } from "next";
import AppWrapper from "../components/AppWrapper";
import {BsDiscord, BsGithub} from "react-icons/bs";
import {AiFillMail} from "react-icons/ai";
import { BsDiscord, BsGithub } from "react-icons/bs";
import { AiFillMail } from "react-icons/ai";
import Link from "next/link";
import type {IconType} from "react-icons";
import type { IconType } from "react-icons";
import Tippy from "@tippyjs/react";
import 'tippy.js/dist/tippy.css';
import "tippy.js/dist/tippy.css";
const socials: { icon: IconType, href?: string, hint?: string, hideHint?: boolean }[] = [
{
icon: BsGithub,
href: "https://github.com/Xevion/"
},
{
icon: AiFillMail,
href: "mailto:xevion@xevion.dev",
hint: "xevion@xevion.dev"
},
{
icon: BsDiscord,
hint: "Xevion#8506"
}
]
const socials: {
icon: IconType;
href?: string;
hint?: string;
hideHint?: boolean;
}[] = [
{
icon: BsGithub,
href: "https://github.com/Xevion/",
},
{
icon: AiFillMail,
href: "mailto:xevion@xevion.dev",
hint: "xevion@xevion.dev",
},
{
icon: BsDiscord,
hint: "Xevion#8506",
},
];
const ContactPage: NextPage = () => {
return <AppWrapper current='contact'>
<div className="w-full my-10 flex flex-col items-center">
<div
className="bg-zinc-800/50 border border-zinc-800 rounded-md max-w-[23rem] sm:max-w-[25rem] lg:max-w-[30rem] mx-3 w-full p-5 flex flex-col">
<div className="flex justify-center gap-x-5 text-center">
{socials.map(({icon: Icon, href, hint, hideHint}, index) => {
const inner = <Icon className="w-8 h-8"/>;
return <Tippy key={index} disabled={hideHint} content={hint ?? href}>
{
href != undefined ? <Link href={href}>{inner}</Link> : <span>{inner}</span>
}
</Tippy>
})}
</div>
</div>
return (
<AppWrapper current="contact">
<div className="my-10 flex w-full flex-col items-center">
<div className="mx-3 flex w-full max-w-[23rem] flex-col rounded-md border border-zinc-800 bg-zinc-800/50 p-5 sm:max-w-[25rem] lg:max-w-[30rem]">
<div className="flex justify-center gap-x-5 text-center">
{socials.map(({ icon: Icon, href, hint, hideHint }, index) => {
const inner = <Icon className="h-8 w-8" />;
return (
<Tippy key={index} disabled={hideHint} content={hint ?? href}>
{href != undefined ? (
<Link href={href}>{inner}</Link>
) : (
<span>{inner}</span>
)}
</Tippy>
);
})}
</div>
</div>
</div>
</AppWrapper>
}
);
};
export default ContactPage;
export default ContactPage;

View File

@@ -1,77 +1,102 @@
import type {NextPage} from "next";
import type { NextPage } from "next";
import Head from "next/head";
import Image from "next/image";
import {BsGithub} from "react-icons/bs";
import {RxOpenInNewWindow} from "react-icons/rx";
import { BsGithub } from "react-icons/bs";
import { RxOpenInNewWindow } from "react-icons/rx";
import Link from "next/link";
import AppWrapper from "../components/AppWrapper";
import type {ReactNode} from "react";
import type { ReactNode } from "react";
type Screenshot = [string, null | string | ReactNode];
type ScreenshotWithQuality = [string, null | string | ReactNode, number];
const images: (Screenshot | ScreenshotWithQuality)[] = [
["/grain/index.jpg", null, 100],
["/grain/hidden.jpg", null, 100]
]
["/grain/index.jpg", null, 100],
["/grain/hidden.jpg", null, 100],
];
const GrainPage: NextPage = () => {
return <>
<Head>
<title>Grain | Xevion.dev</title>
</Head>
<AppWrapper>
<div className="w-full overflow-auto h-full min-h-screen flex justify-center">
<div className="relative my-10 p-3 px-6 w-full max-w-screen-md">
<div className="pb-2 flex justify-between">
<div className="text-3xl font-semibold">
Grain
</div>
<div className="flex items-center justify-end space-x-1.5">
<Link href="https://grain.xevion.dev" target="_blank">
<RxOpenInNewWindow className="w-6 h-6 hover:text-zinc-200"/>
</Link>
<Link href="https://github.com/Xevion/grain" target="_blank">
<BsGithub className="w-6 h-6 hover:text-zinc-200"/>
</Link>
</div>
</div>
<div className="relative">
<Link href="https://grain.xevion.dev/">
<Image fill quality={100} sizes="100vw" src="/grain/banner.jpeg" alt=""
className="!relative pointer-events-none min-h-[10rem] rounded-md object-cover"/>
</Link>
</div>
<div className="mt-3 w-full prose prose-invert prose-lg">
<p>
After seeing an online post with beautiful noise patterns & gradients, I decided to
try and recreate it. The result was Grain, a simple web app that generates beautiful noise.
Under the hood, this app uses multiple layers of SVGs that automatically rescale with the browsers viewport.
That way, the noise is always crisp and clear, no matter the screen size.
</p>
<ul className="md:columns-2">
<li>Performant - SVG generation and layering is optimized</li>
<li>Small - Builds in less than 16 seconds</li>
<li>Open Source - Want to use my gradients? Check it out on <Link href="https://github.com/Xevion/grain" target="_blank">GitHub</Link>.</li>
</ul>
<h3>Screenshots</h3>
<div className="relative space-y-8">
{images.map(([src, description, quality]) => {
return <div key={src} className="flex flex-col justify-center w-full">
<Image fill sizes="100vw" src={src} alt="" quality={quality ?? 75}
className="shadow-lg !my-1 !relative pointer-events-none min-h-[10rem] rounded-md object-cover"/>
{description != null ?
<span
className="text-center text-base">{description}</span> : null}
</div>
}
)}
</div>
</div>
</div>
return (
<>
<Head>
<title>Grain | Xevion.dev</title>
</Head>
<AppWrapper>
<div className="flex h-full min-h-screen w-full justify-center overflow-auto">
<div className="relative my-10 w-full max-w-screen-md p-3 px-6">
<div className="flex justify-between pb-2">
<div className="text-3xl font-semibold">Grain</div>
<div className="flex items-center justify-end space-x-1.5">
<Link href="https://grain.xevion.dev" target="_blank">
<RxOpenInNewWindow className="h-6 w-6 hover:text-zinc-200" />
</Link>
<Link href="https://github.com/Xevion/grain" target="_blank">
<BsGithub className="h-6 w-6 hover:text-zinc-200" />
</Link>
</div>
</div>
</AppWrapper>
<div className="relative">
<Link href="https://grain.xevion.dev/">
<Image
fill
quality={100}
sizes="100vw"
src="/grain/banner.jpeg"
alt=""
className="pointer-events-none !relative min-h-[10rem] rounded-md object-cover"
/>
</Link>
</div>
<div className="prose prose-lg prose-invert mt-3 w-full">
<p>
After seeing an online post with beautiful noise patterns &
gradients, I decided to try and recreate it. The result was
Grain, a simple web app that generates beautiful noise. Under
the hood, this app uses multiple layers of SVGs that
automatically rescale with the browsers viewport. That way, the
noise is always crisp and clear, no matter the screen size.
</p>
<ul className="md:columns-2">
<li>Performant - SVG generation and layering is optimized</li>
<li>Small - Builds in less than 16 seconds</li>
<li>
Open Source - Want to use my gradients? Check it out on{" "}
<Link href="https://github.com/Xevion/grain" target="_blank">
GitHub
</Link>
.
</li>
</ul>
<h3>Screenshots</h3>
<div className="relative space-y-8">
{images.map(([src, description, quality]) => {
return (
<div
key={src}
className="flex w-full flex-col justify-center"
>
<Image
fill
sizes="100vw"
src={src}
alt=""
quality={quality ?? 75}
className="pointer-events-none !relative !my-1 min-h-[10rem] rounded-md object-cover shadow-lg"
/>
{description != null ? (
<span className="text-center text-base">
{description}
</span>
) : null}
</div>
);
})}
</div>
</div>
</div>
</div>
</AppWrapper>
</>
}
);
};
export default GrainPage;
export default GrainPage;

View File

@@ -104,7 +104,7 @@ export async function getStaticProps() {
...project,
bannerBlur: base64,
};
})
}),
),
},
};
@@ -145,7 +145,7 @@ const Home: NextPage<HomeStaticProps> = ({
{buttons.map(({ text, href }) => (
<Link
key={href}
className="text-sm text-zinc-500 duration-500 hover:text-zinc-300"
className="text-sm text-zinc-500 duration-500 hover:text-zinc-300"
href={href}
>
{text}

View File

@@ -1,11 +1,13 @@
import {type NextPage} from "next";
import { type NextPage } from "next";
import AppWrapper from "../components/AppWrapper";
import WorkInProgress from "../components/WorkInProgress";
const PathsPage: NextPage = () => {
return <AppWrapper>
<WorkInProgress />
return (
<AppWrapper>
<WorkInProgress />
</AppWrapper>
}
);
};
export default PathsPage;
export default PathsPage;

View File

@@ -1,59 +1,65 @@
import {NextPage} from "next";
import { NextPage } from "next";
import Head from "next/head";
import Image from "next/image";
import {BsGithub} from "react-icons/bs";
import { BsGithub } from "react-icons/bs";
import Link from "next/link";
import AppWrapper from "../components/AppWrapper";
const PhototagPage: NextPage = () => {
return <>
<Head>
<title>Phototag | Xevion.dev</title>
</Head>
<AppWrapper>
<div className="w-full overflow-auto h-full min-h-screen flex justify-center">
<div className="relative my-10 p-3 px-6 w-full max-w-screen-md">
<div className="pb-2 flex justify-between">
<div className="text-2xl font-semibold">
Phototag
</div>
<div className="flex items-center justify-end space-x-1.5">
<Link href="https://github.com/Xevion/phototag" target="_blank">
<BsGithub className="w-5 h-5 hover:text-zinc-200"/>
</Link>
</div>
</div>
<div className="relative">
<Image fill sizes="100vw" src="/phototag.png" alt=""
className="!relative pointer-events-none min-h-[10rem] rounded-md object-cover"/>
</div>
<div className="mt-3 w-full prose prose-invert prose-lg">
<p>
Phototag is a powerful tool that helps you quickly and easily add rich, descriptive tags to
your photos. Using Google&apos;s Vision API, Phototag automatically generates tags based on
the visual content of your photos, making it easier than ever to organize and find your
photos.
</p>
<p>
With support for IPTC metadata and Adobe XMP Sidecar files, you can easily integrate
Phototag into your existing workflow on Windows. Whether you&apos;re a professional
photographer or a casual snapshot taker, Phototag is the perfect tool for adding clarity and
context to your photos.
</p>
<ul className="md:columns-2">
<li>Simple, but configurable</li>
<li>Fully automatic</li>
<li>Leverages compression to reduce network load</li>
<li>Supports JPEG, PNG, GIF etc.</li>
<li>Supports IPTC metadata</li>
<li>Supports Adobe XMP sidecar files</li>
</ul>
</div>
</div>
return (
<>
<Head>
<title>Phototag | Xevion.dev</title>
</Head>
<AppWrapper>
<div className="flex h-full min-h-screen w-full justify-center overflow-auto">
<div className="relative my-10 w-full max-w-screen-md p-3 px-6">
<div className="flex justify-between pb-2">
<div className="text-2xl font-semibold">Phototag</div>
<div className="flex items-center justify-end space-x-1.5">
<Link href="https://github.com/Xevion/phototag" target="_blank">
<BsGithub className="h-5 w-5 hover:text-zinc-200" />
</Link>
</div>
</div>
</AppWrapper>
<div className="relative">
<Image
fill
sizes="100vw"
src="/phototag.png"
alt=""
className="pointer-events-none !relative min-h-[10rem] rounded-md object-cover"
/>
</div>
<div className="prose prose-lg prose-invert mt-3 w-full">
<p>
Phototag is a powerful tool that helps you quickly and easily
add rich, descriptive tags to your photos. Using Google&apos;s
Vision API, Phototag automatically generates tags based on the
visual content of your photos, making it easier than ever to
organize and find your photos.
</p>
<p>
With support for IPTC metadata and Adobe XMP Sidecar files, you
can easily integrate Phototag into your existing workflow on
Windows. Whether you&apos;re a professional photographer or a
casual snapshot taker, Phototag is the perfect tool for adding
clarity and context to your photos.
</p>
<ul className="md:columns-2">
<li>Simple, but configurable</li>
<li>Fully automatic</li>
<li>Leverages compression to reduce network load</li>
<li>Supports JPEG, PNG, GIF etc.</li>
<li>Supports IPTC metadata</li>
<li>Supports Adobe XMP sidecar files</li>
</ul>
</div>
</div>
</div>
</AppWrapper>
</>
}
);
};
export default PhototagPage;
export default PhototagPage;

View File

@@ -1,81 +1,114 @@
import type {NextPage} from "next";
import type { NextPage } from "next";
import Head from "next/head";
import Image from "next/image";
import {BsGithub} from "react-icons/bs";
import { BsGithub } from "react-icons/bs";
import Link from "next/link";
import AppWrapper from "../components/AppWrapper";
import type {ReactNode} from "react";
import type { ReactNode } from "react";
const images: [string, string | ReactNode][] = [
["/portal/home.jpeg", "The home page."],
["/portal/events.png", <> A page listing all current events. <br/> Initial data is cached for performance, but
becomes
dynamic when filtered.</>],
["/portal/admin.png", "A secure admin panel for our officers to view, filter & edit members & events."],
["/portal/event.png", "The view of a specific event."],
["/portal/checkin.png", "The check-in view."],
["/portal/filters.png", "Organization filtering options. Dynamic semester filtering & event sorting is also available."],
["/portal/login.png", "The login. Fast form validation, seamless login."],
["/portal/profile.png", <>The member profile view; fully editable on both desktop & mobile. <br/> Seamless editing
of profiles for users. Full validation available.</>],
["/portal/status.png", "Members can check their progress towards becoming full members & view what events they attended."],
]
["/portal/home.jpeg", "The home page."],
[
"/portal/events.png",
<>
{" "}
A page listing all current events. <br /> Initial data is cached for
performance, but becomes dynamic when filtered.
</>,
],
[
"/portal/admin.png",
"A secure admin panel for our officers to view, filter & edit members & events.",
],
["/portal/event.png", "The view of a specific event."],
["/portal/checkin.png", "The check-in view."],
[
"/portal/filters.png",
"Organization filtering options. Dynamic semester filtering & event sorting is also available.",
],
["/portal/login.png", "The login. Fast form validation, seamless login."],
[
"/portal/profile.png",
<>
The member profile view; fully editable on both desktop & mobile. <br />{" "}
Seamless editing of profiles for users. Full validation available.
</>,
],
[
"/portal/status.png",
"Members can check their progress towards becoming full members & view what events they attended.",
],
];
const PortalPage: NextPage = () => {
return <>
<Head>
<title>Portal | Xevion.dev</title>
</Head>
<AppWrapper>
<div className="w-full overflow-auto h-full min-h-screen flex justify-center">
<div className="relative my-10 p-3 px-6 w-full max-w-screen-md">
<div className="pb-2 flex justify-between">
<div className="text-3xl font-semibold">
Portal
</div>
<div className="flex items-center justify-end space-x-1.5">
<Link href="https://github.com/acmutsa/Portal" target="_blank">
<BsGithub className="w-6 h-6 hover:text-zinc-200"/>
</Link>
</div>
</div>
<div className="relative">
<Link href="https://portal.acmutsa.org/">
<Image fill sizes="100vw" src="/portal/banner.jpeg" alt=""
className="!relative pointer-events-none min-h-[10rem] rounded-md object-cover"/>
</Link>
</div>
<div className="mt-3 w-full prose prose-invert prose-lg">
<p>
Created in service of our membership, Portal was designed as a approachable membership
portal
for our users so we could <b>track membership</b>, <b>advertise events</b> and replace our
existing <b>database solution</b>.
</p>
<ul className="md:columns-2">
<li>Fast - built to serve thousands</li>
<li>Cheap - minimize costs</li>
<li>Open Source - help us improve</li>
<li>Cutting Edge - the latest technology</li>
</ul>
<h3>Screenshots</h3>
<div className="relative space-y-8">
{images.map(([src, description]) => {
return <div key={src} className="flex flex-col justify-center w-full">
<Image fill sizes="100vw" src={src} alt=""
className="shadow-lg !my-1 !relative pointer-events-none min-h-[10rem] rounded-md object-cover"/>
<span
className="text-center text-base">{description}</span>
</div>
}
)}
</div>
</div>
</div>
return (
<>
<Head>
<title>Portal | Xevion.dev</title>
</Head>
<AppWrapper>
<div className="flex h-full min-h-screen w-full justify-center overflow-auto">
<div className="relative my-10 w-full max-w-screen-md p-3 px-6">
<div className="flex justify-between pb-2">
<div className="text-3xl font-semibold">Portal</div>
<div className="flex items-center justify-end space-x-1.5">
<Link href="https://github.com/acmutsa/Portal" target="_blank">
<BsGithub className="h-6 w-6 hover:text-zinc-200" />
</Link>
</div>
</div>
</AppWrapper>
<div className="relative">
<Link href="https://portal.acmutsa.org/">
<Image
fill
sizes="100vw"
src="/portal/banner.jpeg"
alt=""
className="pointer-events-none !relative min-h-[10rem] rounded-md object-cover"
/>
</Link>
</div>
<div className="prose prose-lg prose-invert mt-3 w-full">
<p>
Created in service of our membership, Portal was designed as a
approachable membership portal for our users so we could{" "}
<b>track membership</b>, <b>advertise events</b> and replace our
existing <b>database solution</b>.
</p>
<ul className="md:columns-2">
<li>Fast - built to serve thousands</li>
<li>Cheap - minimize costs</li>
<li>Open Source - help us improve</li>
<li>Cutting Edge - the latest technology</li>
</ul>
<h3>Screenshots</h3>
<div className="relative space-y-8">
{images.map(([src, description]) => {
return (
<div
key={src}
className="flex w-full flex-col justify-center"
>
<Image
fill
sizes="100vw"
src={src}
alt=""
className="pointer-events-none !relative !my-1 min-h-[10rem] rounded-md object-cover shadow-lg"
/>
<span className="text-center text-base">
{description}
</span>
</div>
);
})}
</div>
</div>
</div>
</div>
</AppWrapper>
</>
}
);
};
export default PortalPage;
export default PortalPage;

View File

@@ -1,142 +1,158 @@
import {type NextPage} from "next";
import { type NextPage } from "next";
import AppWrapper from "../components/AppWrapper";
import {RiMagicLine} from "react-icons/ri";
import {BiBus, BiHash, BiNetworkChart} from "react-icons/bi";
import { RiMagicLine } from "react-icons/ri";
import { BiBus, BiHash, BiNetworkChart } from "react-icons/bi";
import Link from "next/link";
import {IconType} from "react-icons";
import {PiPlantBold} from "react-icons/pi";
import {HiOutlineRss} from "react-icons/hi";
import {GiHummingbird, GiPathDistance} from "react-icons/gi";
import {MdOutlineGrain, MdOutlineDns, MdOutlineLeaderboard} from "react-icons/md";
import {FiLayers} from "react-icons/fi";
import {FaReact} from "react-icons/fa";
import {SiPowershell} from "react-icons/si";
import {RxTimer} from "react-icons/rx";
import { IconType } from "react-icons";
import { PiPlantBold } from "react-icons/pi";
import { HiOutlineRss } from "react-icons/hi";
import { GiHummingbird, GiPathDistance } from "react-icons/gi";
import {
MdOutlineGrain,
MdOutlineDns,
MdOutlineLeaderboard,
} from "react-icons/md";
import { FiLayers } from "react-icons/fi";
import { FaReact } from "react-icons/fa";
import { SiPowershell } from "react-icons/si";
import { RxTimer } from "react-icons/rx";
const ProjectsPage: NextPage = () => {
const projects: { name: string, description: string, url?: string, icon: IconType }[] = [
{
name: "Portal",
description: "ACM Membership & Event System",
url: "https://portal.acmutsa.org",
icon: RiMagicLine
},
{
name: "Runnerspace",
description: "Hackathon MySpace Clone",
url: "https://runnerspace.xevion.dev",
icon: RiMagicLine
},
{
name: "v6 Place",
description: "Paint Images with IPv6 Addresses",
url: "https://github.com/Xevion/v6-place",
icon: BiNetworkChart
},
{
name: "Phototag",
description: "Effortlessly Tag Photos",
url: "/phototag",
icon: BiHash
},
{
name: "Paths",
description: "Graph Traversal Algorithms",
url: "https://github.com/Xevion/Paths",
icon: GiPathDistance
},
{
name: "undefined behaviors",
description: "Astro-based Blog",
url: "https://undefined.behavio.rs",
icon: HiOutlineRss
},
{
name: "v2.xevion.dev",
description: "Jekyll-based Blog",
url: "https://v2.xevion.dev",
icon: HiOutlineRss
},
{
name: "Grain",
description: "Pretty SVG-based Noise",
url: "https://grain.xevion.dev",
icon: MdOutlineGrain
},
{
name: "Hydroponics Expanded",
description: "A RimWorld Mod for Hydroponics",
url: "https://github.com/Xevion/RimWorld-Hydroponics-Expanded",
icon: PiPlantBold
},
{
name: "Time Banner",
description: "Instant PNG Timestamps in Rust",
url: "https://github.com/Xevion/time-banner",
icon: RxTimer
},
{
name: "The Office",
description: "View Quotes from The Office",
url: "https://the-office.xevion.dev",
icon: FiLayers
},
{
name: "Boids",
description: "Flocking Simulation",
url: "https://github.com/Xevion/Boids",
icon: GiHummingbird
},
{
name: "bus-reminder",
description: "Last Bus Departure Reminder",
url: "http://github.com/Xevion/bus-reminder",
icon: BiBus
},
{
name: "rdap",
description: "Javascript RDAP Client",
url: "https://rdap.xevion.dev",
icon: MdOutlineDns
},
{
name: "icons",
description: "Dynamic React-Icons Loading",
url: "https://icons.xevion.dev",
icon: FaReact
},
{
name: "trivia",
description: "CLI + Flask Leaderboard",
url: "http://github.com/Xevion/trivia",
icon: MdOutlineLeaderboard
},
{
name: "Powershell",
description: "Scripts & Guides",
url: "https://powershell.xevion.dev",
icon: SiPowershell
}
]
return <AppWrapper current='projects'>
<div className="mt-20 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-x-20 gap-y-14 h-full py-2 max-w-500 w-max mx-auto flex ">
{projects.map(({name, description, url, icon: Icon}) => <Link
key={name}
className="relative flex flex-shrink items-center opacity-75 hover:opacity-100 transition-opacity"
href={url ?? ""}
target="_blank"
rel="noreferrer"
title={name}>
<div className="pt-2 pr-5">
<Icon className="text-3xl saturate-0 opacity-80"/>
</div>
<div className="flex-auto">
<div className="text-normal">{name}</div>
<div className="text-sm opacity-70 font-normal">{description}</div>
</div>
</Link>)}
</div>
const projects: {
name: string;
description: string;
url?: string;
icon: IconType;
}[] = [
{
name: "Portal",
description: "ACM Membership & Event System",
url: "https://portal.acmutsa.org",
icon: RiMagicLine,
},
{
name: "Runnerspace",
description: "Hackathon MySpace Clone",
url: "https://runnerspace.xevion.dev",
icon: RiMagicLine,
},
{
name: "v6 Place",
description: "Paint Images with IPv6 Addresses",
url: "https://github.com/Xevion/v6-place",
icon: BiNetworkChart,
},
{
name: "Phototag",
description: "Effortlessly Tag Photos",
url: "/phototag",
icon: BiHash,
},
{
name: "Paths",
description: "Graph Traversal Algorithms",
url: "https://github.com/Xevion/Paths",
icon: GiPathDistance,
},
{
name: "undefined behaviors",
description: "Astro-based Blog",
url: "https://undefined.behavio.rs",
icon: HiOutlineRss,
},
{
name: "v2.xevion.dev",
description: "Jekyll-based Blog",
url: "https://v2.xevion.dev",
icon: HiOutlineRss,
},
{
name: "Grain",
description: "Pretty SVG-based Noise",
url: "https://grain.xevion.dev",
icon: MdOutlineGrain,
},
{
name: "Hydroponics Expanded",
description: "A RimWorld Mod for Hydroponics",
url: "https://github.com/Xevion/RimWorld-Hydroponics-Expanded",
icon: PiPlantBold,
},
{
name: "Time Banner",
description: "Instant PNG Timestamps in Rust",
url: "https://github.com/Xevion/time-banner",
icon: RxTimer,
},
{
name: "The Office",
description: "View Quotes from The Office",
url: "https://the-office.xevion.dev",
icon: FiLayers,
},
{
name: "Boids",
description: "Flocking Simulation",
url: "https://github.com/Xevion/Boids",
icon: GiHummingbird,
},
{
name: "bus-reminder",
description: "Last Bus Departure Reminder",
url: "http://github.com/Xevion/bus-reminder",
icon: BiBus,
},
{
name: "rdap",
description: "Javascript RDAP Client",
url: "https://rdap.xevion.dev",
icon: MdOutlineDns,
},
{
name: "icons",
description: "Dynamic React-Icons Loading",
url: "https://icons.xevion.dev",
icon: FaReact,
},
{
name: "trivia",
description: "CLI + Flask Leaderboard",
url: "http://github.com/Xevion/trivia",
icon: MdOutlineLeaderboard,
},
{
name: "Powershell",
description: "Scripts & Guides",
url: "https://powershell.xevion.dev",
icon: SiPowershell,
},
];
return (
<AppWrapper current="projects">
<div className="max-w-500 mx-auto mt-20 flex grid h-full w-max grid-cols-1 gap-x-20 gap-y-14 py-2 md:grid-cols-2 lg:grid-cols-3">
{projects.map(({ name, description, url, icon: Icon }) => (
<Link
key={name}
className="relative flex flex-shrink items-center opacity-75 transition-opacity hover:opacity-100"
href={url ?? ""}
target="_blank"
rel="noreferrer"
title={name}
>
<div className="pr-5 pt-2">
<Icon className="text-3xl opacity-80 saturate-0" />
</div>
<div className="flex-auto">
<div className="text-normal">{name}</div>
<div className="text-sm font-normal opacity-70">
{description}
</div>
</div>
</Link>
))}
</div>
</AppWrapper>
);
};
export default ProjectsPage;
export default ProjectsPage;