Feat/ui update #5

Merged
LiamPietralla merged 2 commits from feat/ui-update into main 2026-10-15 17:28:50 +11:00
7 changed files with 193 additions and 55 deletions
Showing only changes of commit f061457d7f - Show all commits

No files matched your search

+1
View File
@@ -27,6 +27,7 @@ export default function RootLayout({
<body <body
className={`${geistSans.variable} ${geistMono.variable} antialiased px-4`} className={`${geistSans.variable} ${geistMono.variable} antialiased px-4`}
> >
<div className="aurora" />
{children} {children}
</body> </body>
</html> </html>
+19 -11
View File
@@ -10,34 +10,42 @@ const IndexPage = async () => {
const home = await getHome(); const home = await getHome();
return ( return (
<div className="flex flex-col gap-4 justify-center items-center h-screen"> <div className="flex flex-col gap-5 justify-center items-center h-screen">
<Image className="rounded-full max-w-[200px]" src={ProfileImage} alt="Liam Pietralla" /> <div className="glow-ring animate-fade-in" style={{ animationDelay: "0ms" }}>
<h1 className="text-5xl font-bold">Liam Pietralla</h1> <Image className="rounded-full max-w-[200px]" src={ProfileImage} alt="Liam Pietralla" />
<div className="flex flex-col md:flex-row gap-0 md:gap-[7px]"> </div>
<h1 className="text-5xl font-bold animate-fade-in-up" style={{ animationDelay: "120ms" }}>
Liam Pietralla
</h1>
<div className="flex flex-col md:flex-row gap-0 md:gap-[7px] animate-fade-in-up" style={{ animationDelay: "240ms" }}>
<h2 className="text-xl text-center">Enthusiastic Software Developer</h2> <h2 className="text-xl text-center">Enthusiastic Software Developer</h2>
<h2 className="text-xl text-center mt-[-5px] md:mt-0">& DevOps Engineer</h2> <h2 className="text-xl text-center mt-[-5px] md:mt-0">& DevOps Engineer</h2>
</div> </div>
<Rule className="w-[300px]" /> <Rule className="w-[300px] animate-fade-in" style={{ animationDelay: "320ms" }} />
<div className="flex flex-row gap-4"> <div className="flex flex-row gap-4 animate-fade-in-up" style={{ animationDelay: "400ms" }}>
{home.mainLinks.map(link => ( {home.mainLinks.map(link => (
<IndexLink key={link.id} {...link} /> <IndexLink key={link.id} {...link} />
))} ))}
</div> </div>
<div className="flex flex-row gap-4"> <div className="flex flex-row gap-4 animate-fade-in-up relative z-20" style={{ animationDelay: "480ms" }}>
{home.popoverLinks.map(link => ( {home.popoverLinks.map(link => (
<IndexLink key={link.id} {...link} isPopover={true} /> <IndexLink key={link.id} {...link} isPopover={true} />
))} ))}
</div> </div>
<Rule className="w-[300px]" /> <Rule className="w-[300px] animate-fade-in" style={{ animationDelay: "560ms" }} />
<a href="mailto:me@liampietralla.com" className="group leading-relaxed"> <a
href="mailto:me@liampietralla.com"
className="group leading-relaxed animate-fade-in-up"
style={{ animationDelay: "640ms" }}
>
<span className="flex flex-row gap-2"> <span className="flex flex-row gap-2">
<Mail /> <Mail />
me@liampietralla.com me@liampietralla.com
</span> </span>
<span className="block max-w-0 group-hover:max-w-full transition-all duration-500 h-0.5 bg-white"></span> <span className="block max-w-0 group-hover:max-w-full transition-all duration-500 h-0.5 bg-accent"></span>
</a> </a>
</div> </div>
); );
} }
export default IndexPage; export default IndexPage;
+10 -11
View File
@@ -1,9 +1,7 @@
import ProjectCard from "@/components/project-card"; import ProjectCard from "@/components/project-card";
import Rule from "@/components/horizontal-rule";
import { getProjects } from "@/services/projects-service"; import { getProjects } from "@/services/projects-service";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { Fragment } from "react";
import ProfileImage from "../../../../public/images/liam_pietralla.jpg"; import ProfileImage from "../../../../public/images/liam_pietralla.jpg";
@@ -11,25 +9,26 @@ const ProjectsPage = async () => {
const projects = await getProjects(); const projects = await getProjects();
return ( return (
<div className="flex flex-col gap-4 justify-center items-center my-15"> <div className="flex flex-col gap-4 justify-center items-center my-15">
<div className="flex flex-row items-center gap-2 my-2"> <div className="flex flex-row items-center gap-2 my-2 animate-fade-in-up">
<Image src={ProfileImage} alt="Liam Pietralla" className="rounded-full max-w-[50px]" /> <Image src={ProfileImage} alt="Liam Pietralla" className="rounded-full max-w-[50px]" />
<Link href="/" className="group leading-relaxed font-semi-bold"> <Link href="/" className="group leading-relaxed font-semi-bold">
Liam Pietralla Liam Pietralla
<span className="block max-w-0 group-hover:max-w-full transition-all duration-500 h-0.5 bg-white"></span> <span className="block max-w-0 group-hover:max-w-full transition-all duration-500 h-0.5 bg-accent"></span>
</Link> </Link>
</div> </div>
<h1 className="text-5xl font-bold">Projects</h1> <h1 className="text-5xl font-bold animate-fade-in-up" style={{ animationDelay: "120ms" }}>Projects</h1>
<h2>A collection of interesting projects that I am working on currently or have worked on in the past.</h2> <h2 className="animate-fade-in-up text-center max-w-2xl" style={{ animationDelay: "240ms" }}>
<div className="flex flex-col gap-4"> A collection of interesting projects that I am working on currently or have worked on in the past.
</h2>
<div className="flex flex-col gap-4 w-full">
{projects.docs.map((project, index) => ( {projects.docs.map((project, index) => (
<Fragment key={index}> <div key={index} className="animate-fade-in-up" style={{ animationDelay: `${320 + index * 120}ms` }}>
<ProjectCard {...project} /> <ProjectCard {...project} />
{index < projects.docs.length - 1 && <Rule />} </div>
</Fragment>
))} ))}
</div> </div>
</div> </div>
) )
} }
export default ProjectsPage; export default ProjectsPage;
+119 -2
View File
@@ -3,11 +3,13 @@
:root { :root {
--background: #0a0a0a; --background: #0a0a0a;
--foreground: #ededed; --foreground: #ededed;
--accent: #7c9cff;
} }
@theme inline { @theme inline {
--color-background: var(--background); --color-background: var(--background);
--color-foreground: var(--foreground); --color-foreground: var(--foreground);
--color-accent: var(--accent);
--font-sans: var(--font-geist-sans); --font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono); --font-mono: var(--font-geist-mono);
} }
@@ -15,5 +17,120 @@
body { body {
background: var(--background); background: var(--background);
color: var(--foreground); color: var(--foreground);
font-family: Arial, Helvetica, sans-serif; font-family: var(--font-geist-sans), Arial, Helvetica, sans-serif;
} overflow-x: hidden;
}
.aurora {
position: fixed;
inset: 0;
z-index: -1;
overflow: hidden;
pointer-events: none;
}
.aurora::before,
.aurora::after {
content: "";
position: absolute;
width: 60vw;
height: 60vw;
border-radius: 9999px;
filter: blur(120px);
opacity: 0.22;
will-change: transform;
}
.aurora::before {
top: -15%;
left: -10%;
background: radial-gradient(circle at center, var(--accent), transparent 70%);
animation: drift-1 18s ease-in-out infinite alternate;
}
.aurora::after {
bottom: -20%;
right: -10%;
background: radial-gradient(circle at center, #b07cff, transparent 70%);
animation: drift-2 22s ease-in-out infinite alternate;
}
@keyframes drift-1 {
0% { transform: translate(0, 0) scale(1); }
100% { transform: translate(15vw, 10vh) scale(1.15); }
}
@keyframes drift-2 {
0% { transform: translate(0, 0) scale(1); }
100% { transform: translate(-12vw, -8vh) scale(1.2); }
}
@keyframes fade-in-up {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-fade-in-up {
opacity: 0;
animation: fade-in-up 0.7s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
.animate-fade-in {
opacity: 0;
animation: fade-in 1.2s ease forwards;
}
.glow-ring {
position: relative;
}
.glow-ring::before {
content: "";
position: absolute;
inset: -6px;
border-radius: 9999px;
background: conic-gradient(from 0deg, var(--accent), #b07cff, var(--accent));
filter: blur(8px);
opacity: 0.55;
z-index: -1;
animation: spin-slow 8s linear infinite;
}
@keyframes spin-slow {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.glass {
background: rgba(255, 255, 255, 0.04);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
border: 1px solid rgba(255, 255, 255, 0.08);
transition: background 0.3s ease, border-color 0.3s ease, transform 0.3s ease, box-shadow 0.3s ease;
}
.glass-hover:hover {
background: rgba(255, 255, 255, 0.08);
border-color: rgba(124, 156, 255, 0.4);
transform: translateY(-2px);
box-shadow: 0 8px 30px -10px rgba(124, 156, 255, 0.35);
}
@media (prefers-reduced-motion: reduce) {
*,
.aurora::before,
.aurora::after,
.glow-ring::before {
animation: none !important;
}
.animate-fade-in-up,
.animate-fade-in {
opacity: 1;
animation: none !important;
}
}
+28 -17
View File
@@ -15,42 +15,53 @@ const HomePageLink = ({ title, icon, url, isPopover }: HomePageLinkProps) => {
const IconComponent = lucidIconMap[icon as IconName]; const IconComponent = lucidIconMap[icon as IconName];
if (isPopover) { if (isPopover) {
const LinkContent = (
<span className="flex items-center space-x-2 glass glass-hover rounded-lg px-3 py-2 transition-all duration-300">
{icon && <IconComponent className="size-4" />}
</span>
);
const Popover = (
<div className="absolute left-1/2 -translate-x-1/2 top-full mt-2 z-10 hidden p-2 px-4 text-sm text-black bg-white rounded-md shadow-lg group-hover:block text-center whitespace-nowrap">
{title}
</div>
);
if (isRelative) { if (isRelative) {
return ( return (
<div className="relative group"> <div className="relative group">
<Link href={url} className="flex items-center space-x-2"> <Link href={url}>{LinkContent}</Link>
{icon && <IconComponent />} {Popover}
</Link>
<div className="absolute left-1/2 -translate-x-1/2 z-10 hidden p-2 px-4 text-sm text-black bg-white rounded-md group-hover:block text-center">
{title}
</div>
</div> </div>
) )
} else { } else {
return ( return (
<div className="relative group"> <div className="relative group">
<a href={url} className="flex items-center space-x-2"> <a href={url}>{LinkContent}</a>
{icon && <IconComponent />} {Popover}
</a>
<div className="absolute left-1/2 -translate-x-1/2 z-10 hidden p-2 px-4 text-sm text-black bg-white rounded-md group-hover:block text-center">
{title}
</div>
</div> </div>
) )
} }
} else { } else {
const Content = (
<>
<span className="flex flex-row gap-2 group-hover:text-accent transition-colors duration-300">
{icon && <IconComponent className="size-4" />} {title}
</span>
<span className="block max-w-0 group-hover:max-w-full transition-all duration-500 h-0.5 bg-accent"></span>
</>
);
if (isRelative) { if (isRelative) {
return ( return (
<Link href={url} className="group leading-relaxed"> <Link href={url} className="group leading-relaxed">
<span className="flex flex-row gap-2">{icon && <IconComponent />} {title}</span> {Content}
<span className="block max-w-0 group-hover:max-w-full transition-all duration-500 h-0.5 bg-white"></span>
</Link> </Link>
) )
} else { } else {
return ( return (
<a href={url} className="group leading-relaxed"> <a href={url} className="group leading-relaxed">
<span className="flex flex-row gap-2">{icon && <IconComponent />} {title}</span> {Content}
<span className="block max-w-0 group-hover:max-w-full transition-all duration-500 h-0.5 bg-white"></span>
</a> </a>
) )
} }
@@ -58,4 +69,4 @@ const HomePageLink = ({ title, icon, url, isPopover }: HomePageLinkProps) => {
} }
export default HomePageLink; export default HomePageLink;
+5 -3
View File
@@ -1,13 +1,15 @@
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { CSSProperties } from "react";
interface RuleProps { interface RuleProps {
className?: string; className?: string;
style?: CSSProperties;
} }
const HorizontalRule = ({ className }: RuleProps) => { const HorizontalRule = ({ className, style }: RuleProps) => {
return ( return (
<div className={cn("border-t-1 border-accent", className)} /> <div style={style} className={cn("border-t-1 border-accent", className)} />
) )
} }
export default HorizontalRule; export default HorizontalRule;
+11 -11
View File
@@ -5,16 +5,16 @@ import { Project } from "@/payload-types";
const ProjectCard = (project: Project) => { const ProjectCard = (project: Project) => {
return ( return (
<div className="border border-white rounded-md p-4 flex flex-col gap-2 max-w-[700px] mx-auto"> <div className="glass glass-hover rounded-lg p-5 flex flex-col gap-3 max-w-[700px] mx-auto">
<h2 className="text-2xl font-bold">{project.title}</h2> <h2 className="text-2xl font-bold">{project.title}</h2>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
{project.featuredImage && typeof project.featuredImage === "object" && ( {project.featuredImage && typeof project.featuredImage === "object" && (
<Image src={project.featuredImage.url!} alt={project.title} width={project.featuredImage.width!} height={project.featuredImage.height!} className="rounded-md border border-gray-600" /> <Image src={project.featuredImage.url!} alt={project.title} width={project.featuredImage.width!} height={project.featuredImage.height!} className="rounded-md border border-white/10" />
)} )}
<p className="whitespace-pre-wrap">{project.description}</p> <p className="whitespace-pre-wrap">{project.description}</p>
<div className="flex flex-row gap-2 my-2"> <div className="flex flex-row flex-wrap gap-2 my-2">
{(project?.tags || []).map((tag) => ( {(project?.tags || []).map((tag) => (
<span key={tag.id} className="bg-white text-black rounded-md px-1 py-0.5 text-sm hover:bg-gray-200"> <span key={tag.id} className="bg-white/10 text-foreground rounded-md px-2 py-0.5 text-sm border border-white/10 transition-colors hover:bg-white/20">
{tag.tag} {tag.tag}
</span> </span>
))} ))}
@@ -24,20 +24,20 @@ const ProjectCard = (project: Project) => {
<div className="flex flex-row gap-4"> <div className="flex flex-row gap-4">
{project.viewLink && ( {project.viewLink && (
<a href={project.viewLink} target="_blank" className="group"> <a href={project.viewLink} target="_blank" className="group">
<span className="flex flex-row gap-2"> <span className="flex flex-row gap-2 group-hover:text-accent transition-colors duration-300">
<ExternalLink /> <ExternalLink className="size-4" />
View Project View Project
</span> </span>
<span className="block max-w-0 group-hover:max-w-full transition-all duration-500 h-0.5 bg-white"></span> <span className="block max-w-0 group-hover:max-w-full transition-all duration-500 h-0.5 bg-accent"></span>
</a> </a>
)} )}
{project.repositoryLink && ( {project.repositoryLink && (
<a href={project.repositoryLink} target="_blank" className="group"> <a href={project.repositoryLink} target="_blank" className="group">
<span className="flex flex-row gap-2"> <span className="flex flex-row gap-2 group-hover:text-accent transition-colors duration-300">
<Code /> <Code className="size-4" />
View Source View Source
</span> </span>
<span className="block max-w-0 group-hover:max-w-full transition-all duration-500 h-0.5 bg-white"></span> <span className="block max-w-0 group-hover:max-w-full transition-all duration-500 h-0.5 bg-accent"></span>
</a> </a>
)} )}
</div> </div>
@@ -45,4 +45,4 @@ const ProjectCard = (project: Project) => {
) )
} }
export default ProjectCard; export default ProjectCard;