Feat/ui update #5
No files matched your search
@@ -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>
|
||||||
|
|||||||
+18
-10
@@ -10,31 +10,39 @@ 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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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,21 +9,22 @@ 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>
|
||||||
|
|||||||
+118
-1
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
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)} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in new issue
Block a user