Add background to content; update layouts; add more content:

This commit is contained in:
Timothy Pidashev
2025-01-08 14:51:51 -08:00
parent 5681e4b1ad
commit 5f06079b5b
16 changed files with 151 additions and 128 deletions

View File

@@ -4,22 +4,22 @@ import { Code2, BookOpen, RocketIcon, Compass } from 'lucide-react';
export default function CurrentFocus() { export default function CurrentFocus() {
const recentProjects = [ const recentProjects = [
{ {
title: "Project Name 1", title: "Darkbox",
description: "Short description of the project", description: "My gruvbox theme, with a pure black background",
href: "/projects/project-1", href: "/projects/darkbox",
tech: ["React", "TypeScript", "Node.js"] tech: ["Neovim", "Lua"]
}, },
{ {
title: "Project Name 2", title: "Revive Auto Parts",
description: "Short description of the project", description: "A car parts listing site built for a client",
href: "/projects/project-2", href: "/projects/reviveauto",
tech: ["Python", "Django", "PostgreSQL"] tech: ["Tanstack", "React Query", "Fastapi"]
}, },
{ {
title: "Project Name 3", title: "Fhccenter",
description: "Short description of the project", description: "Website made for a private school",
href: "/projects/project-3", href: "/projects/fhccenter",
tech: ["Next.js", "Tailwind", "Prisma"] tech: ["Nextjs", "Typescript", "Prisma"]
} }
]; ];

View File

@@ -24,13 +24,22 @@ interface Grid {
offsetY: number; offsetY: number;
} }
interface BackgroundProps {
layout?: 'index' | 'sidebar';
position?: 'left' | 'right';
}
const CELL_SIZE = 25; const CELL_SIZE = 25;
const TRANSITION_SPEED = 0.1; const TRANSITION_SPEED = 0.1;
const SCALE_SPEED = 0.15; const SCALE_SPEED = 0.15;
const CYCLE_FRAMES = 120; const CYCLE_FRAMES = 120;
const INITIAL_DENSITY = 0.15; const INITIAL_DENSITY = 0.15;
const SIDEBAR_WIDTH = 240;
const Background: React.FC = () => { const Background: React.FC<BackgroundProps> = ({
layout = 'index',
position = 'left'
}) => {
const canvasRef = useRef<HTMLCanvasElement>(null); const canvasRef = useRef<HTMLCanvasElement>(null);
const gridRef = useRef<Grid>(); const gridRef = useRef<Grid>();
const animationFrameRef = useRef<number>(); const animationFrameRef = useRef<number>();
@@ -50,14 +59,10 @@ const Background: React.FC = () => {
}; };
const calculateGridDimensions = (width: number, height: number) => { const calculateGridDimensions = (width: number, height: number) => {
// Calculate number of complete cells that fit in the viewport
const cols = Math.floor(width / CELL_SIZE); const cols = Math.floor(width / CELL_SIZE);
const rows = Math.floor(height / CELL_SIZE); const rows = Math.floor(height / CELL_SIZE);
// Calculate offsets to center the grid
const offsetX = Math.floor((width - (cols * CELL_SIZE)) / 2); const offsetX = Math.floor((width - (cols * CELL_SIZE)) / 2);
const offsetY = Math.floor((height - (rows * CELL_SIZE)) / 2); const offsetY = Math.floor((height - (rows * CELL_SIZE)) / 2);
return { cols, rows, offsetX, offsetY }; return { cols, rows, offsetX, offsetY };
}; };
@@ -138,7 +143,6 @@ const Background: React.FC = () => {
}; };
const computeNextState = (grid: Grid) => { const computeNextState = (grid: Grid) => {
// First pass: compute next state without applying it
for (let i = 0; i < grid.cols; i++) { for (let i = 0; i < grid.cols; i++) {
for (let j = 0; j < grid.rows; j++) { for (let j = 0; j < grid.rows; j++) {
const cell = grid.cells[i][j]; const cell = grid.cells[i][j];
@@ -153,11 +157,9 @@ const Background: React.FC = () => {
} }
} }
// Mark cells that need to transition
if (cell.alive !== cell.next && !cell.transitioning) { if (cell.alive !== cell.next && !cell.transitioning) {
cell.transitioning = true; cell.transitioning = true;
cell.transitionComplete = false; cell.transitionComplete = false;
// For dying cells, start the shrinking animation
if (!cell.next) { if (!cell.next) {
cell.targetScale = 0; cell.targetScale = 0;
cell.targetOpacity = 0; cell.targetOpacity = 0;
@@ -172,13 +174,10 @@ const Background: React.FC = () => {
for (let j = 0; j < grid.rows; j++) { for (let j = 0; j < grid.rows; j++) {
const cell = grid.cells[i][j]; const cell = grid.cells[i][j];
// Update animation properties
cell.opacity += (cell.targetOpacity - cell.opacity) * TRANSITION_SPEED; cell.opacity += (cell.targetOpacity - cell.opacity) * TRANSITION_SPEED;
cell.scale += (cell.targetScale - cell.scale) * SCALE_SPEED; cell.scale += (cell.targetScale - cell.scale) * SCALE_SPEED;
// Handle transition states
if (cell.transitioning) { if (cell.transitioning) {
// Check if shrinking animation is complete for dying cells
if (!cell.next && cell.scale < 0.05) { if (!cell.next && cell.scale < 0.05) {
cell.alive = false; cell.alive = false;
cell.transitioning = false; cell.transitioning = false;
@@ -186,7 +185,6 @@ const Background: React.FC = () => {
cell.scale = 0; cell.scale = 0;
cell.opacity = 0; cell.opacity = 0;
} }
// Check if growing animation is complete for new cells
else if (cell.next && !cell.alive && !cell.transitionComplete) { else if (cell.next && !cell.alive && !cell.transitionComplete) {
cell.alive = true; cell.alive = true;
cell.transitioning = false; cell.transitioning = false;
@@ -194,7 +192,6 @@ const Background: React.FC = () => {
cell.targetScale = 1; cell.targetScale = 1;
cell.targetOpacity = 1; cell.targetOpacity = 1;
} }
// Start growing animation for new cells once old cells have shrunk
else if (cell.next && !cell.alive && cell.transitionComplete) { else if (cell.next && !cell.alive && cell.transitionComplete) {
cell.transitioning = true; cell.transitioning = true;
cell.targetScale = 1; cell.targetScale = 1;
@@ -214,17 +211,21 @@ const Background: React.FC = () => {
const resizeCanvas = () => { const resizeCanvas = () => {
const dpr = window.devicePixelRatio || 1; const dpr = window.devicePixelRatio || 1;
const displayWidth = window.innerWidth; let displayWidth: number;
const displayHeight = window.innerHeight; let displayHeight: number;
if (layout === 'index') {
displayWidth = window.innerWidth;
displayHeight = window.innerHeight;
} else {
displayWidth = SIDEBAR_WIDTH;
displayHeight = window.innerHeight;
}
// Set canvas size accounting for device pixel ratio
canvas.width = displayWidth * dpr; canvas.width = displayWidth * dpr;
canvas.height = displayHeight * dpr; canvas.height = displayHeight * dpr;
// Scale the context to ensure correct drawing operations
ctx.scale(dpr, dpr); ctx.scale(dpr, dpr);
// Set CSS size
canvas.style.width = `${displayWidth}px`; canvas.style.width = `${displayWidth}px`;
canvas.style.height = `${displayHeight}px`; canvas.style.height = `${displayHeight}px`;
@@ -232,7 +233,6 @@ const Background: React.FC = () => {
gridRef.current = initGrid(displayWidth, displayHeight); gridRef.current = initGrid(displayWidth, displayHeight);
isInitialized.current = true; isInitialized.current = true;
} else if (gridRef.current) { } else if (gridRef.current) {
// Update grid dimensions and offsets on resize
const { cols, rows, offsetX, offsetY } = calculateGridDimensions(displayWidth, displayHeight); const { cols, rows, offsetX, offsetY } = calculateGridDimensions(displayWidth, displayHeight);
gridRef.current.cols = cols; gridRef.current.cols = cols;
gridRef.current.rows = rows; gridRef.current.rows = rows;
@@ -264,7 +264,6 @@ const Background: React.FC = () => {
const xOffset = (cellSize - scaledSize) / 2; const xOffset = (cellSize - scaledSize) / 2;
const yOffset = (cellSize - scaledSize) / 2; const yOffset = (cellSize - scaledSize) / 2;
// Add grid offsets to center the animation
const x = grid.offsetX + i * CELL_SIZE + (CELL_SIZE - cellSize) / 2 + xOffset; const x = grid.offsetX + i * CELL_SIZE + (CELL_SIZE - cellSize) / 2 + xOffset;
const y = grid.offsetY + j * CELL_SIZE + (CELL_SIZE - cellSize) / 2 + yOffset; const y = grid.offsetY + j * CELL_SIZE + (CELL_SIZE - cellSize) / 2 + yOffset;
const scaledRoundness = roundness * cell.scale; const scaledRoundness = roundness * cell.scale;
@@ -312,10 +311,21 @@ const Background: React.FC = () => {
cancelAnimationFrame(animationFrameRef.current); cancelAnimationFrame(animationFrameRef.current);
} }
}; };
}, []); }, [layout]);
const getContainerClasses = () => {
if (layout === 'index') {
return 'fixed inset-0 -z-10';
}
const baseClasses = 'fixed top-0 bottom-0 hidden lg:block -z-10';
return position === 'left'
? `${baseClasses} left-0`
: `${baseClasses} right-0`;
};
return ( return (
<div className="fixed inset-0 -z-10"> <div className={getContainerClasses()}>
<canvas <canvas
ref={canvasRef} ref={canvasRef}
className="w-full h-full bg-black" className="w-full h-full bg-black"

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef } from "react"; import React, { useState, useEffect } from "react";
import { Links } from "@/components/header/links"; import { Links } from "@/components/header/links";
export default function Header() { export default function Header() {
@@ -8,11 +8,9 @@ export default function Header() {
const [currentPath, setCurrentPath] = useState(""); const [currentPath, setCurrentPath] = useState("");
const [shouldAnimate, setShouldAnimate] = useState(false); const [shouldAnimate, setShouldAnimate] = useState(false);
// Handle client-side initialization
useEffect(() => { useEffect(() => {
setIsClient(true); setIsClient(true);
setCurrentPath(document.location.pathname); setCurrentPath(document.location.pathname);
// Trigger initial animation after a brief delay
setTimeout(() => setShouldAnimate(true), 50); setTimeout(() => setShouldAnimate(true), 50);
}, []); }, []);
@@ -44,7 +42,11 @@ export default function Header() {
return ( return (
<div <div
key={link.id} key={link.id}
className={`relative inline-block ${link.color}`} className={`
relative inline-block
${link.color}
${!isIndexPage ? 'bg-black rounded' : ''}
`}
> >
<a <a
href={link.href} href={link.href}
@@ -87,11 +89,17 @@ export default function Header() {
font-bold font-bold
transition-transform duration-300 transition-transform duration-300
${visible ? "translate-y-0" : "-translate-y-full"} ${visible ? "translate-y-0" : "-translate-y-full"}
${!isIndexPage ? "bg-black" : ""}
`} `}
> >
<div className="flex flex-row pt-1 px-2 text-lg lg:pt-2 lg:text-3xl md:text-2xl items-center justify-between md:justify-center space-x-2 md:space-x-10 lg:space-x-20"> <div className="flex flex-row items-center justify-center h-full">
{headerLinks} <div className={`
flex flex-row pt-1 px-2 text-lg lg:pt-2 lg:text-3xl md:text-2xl
items-center justify-between md:justify-center
space-x-2 md:space-x-10 lg:space-x-20
${!isIndexPage ? 'bg-black' : ''}
`}>
{headerLinks}
</div>
</div> </div>
</header> </header>
); );

View File

@@ -1,8 +1,58 @@
--- ---
title: "Iridescent" title: "Iridescent"
description: "An open-source graphics engine." description: "An open-source graphics engine"
githubUrl: "https://github.com/timmypidashev/iridescent" githubUrl: "https://github.com/timmypidashev/iridescent"
techStack: ["Cmake", "Glad", "Imgui"] techStack: ["Cmake", "Glad", "Imgui"]
date: "2024-05-03" date: "2024-05-03"
image: "/projects/iridescent/thumbnail.jpeg" image: "/projects/iridescent/thumbnail.jpeg"
--- ---
## Overview
A open-source graphics engine concept created for my highschool senior project.
Built to expand my understanding of the low level programming world, and further
my reach of graphics and c++ programming.
## Key Features
* **Dockable Windows**: A quality of life feature utilizing imgui which allows
to efficiently use monitor space, moving logging and certain tools into their own
windows outside of the engines viewspace.
* **Layer Stack**: Deterministic and fast stack which determines the order in which
all layers of the application are drawn, from the ui all the way to the polygons making
up the scene.
* **Input Polling**: A realtime implementation polling all input events on the keyboard
and mouse for an extremely low latency between input events and scene events.
* **Detailed Logging**: A detailed logging system which logs events from the scene all the
way down to the system.
* **Shading**: The crème de la crème of the renderer, adding shading to the entire scene.
## Development Highlights
Some of the best highlights for me during this project was quite literally every time I managed
to fix a compilation issue. The exhileration after each successful step forward was just mesmerizing,
and I knew the moment I began my obsession wouldn't end until the engine was in a complete state.
Since I was going into this blind during my senior year at highschool, I knew almost nothing about
c++ or the build systems that accompany it, so learning all of that at once was both a breath of fresh
air and a frustration. However, after several days of work, I became very comfortable with cmake, my build
system of choice, and most basic concepts of c++, allowing me to begin working on the engine itself.
It was at this point where I started hitting some real knowledge barriers and ended up going to multiple
resources to learn and understand the inner workings of a graphics renderer. Resources such as the
[Hazel Engine Series](https://www.youtube.com/watch?v=JxIZbV_XjAs&list=PLlrATfBNZ98dC-V-N3m0Go4deliWHPFwT),
[Learn C++ Website](https://www.learncpp.com/learn-cpp-site-index/), and [OpenGL Tutorials](https://www.opengl.org/sdk/docs/tutorials/)
helped me tremendously here.
## Challenges and Roadblocks
Some of the biggest challenges I faced was overcoming the knowledge barrier I had on the inner workings
of graphics rendering and low level programming overall. Thankfuly, being stubborn and obsessed can
sometimes help, and in this case after numerous attempts, I began meticoulouly learning every aspect
of the engine as I developed it, and over time it began paying off, snowballing into a very fun experience.
Some food for thought, after you overcome the biggest barrier in your journey, the rest seems laughably easy.
## Summary
Looking back, working on Iridescent was some of the most fun I have ever had programming as of yet,
and I am still craving for the next project to top this one. It was only by working on this engine
that I realized just how much I love working on the little details, obsessing over each little system
in the engine. Definitely something I will have to do again, maybe write a little game, but thats for
future me to decide :D

View File

@@ -1,6 +1,6 @@
--- ---
title: "Revive Auto Parts" title: "Revive Auto Parts"
description: "A car parts listing site built for a client." description: "A car parts listing site built for a client"
demoUrl: "https://reviveauto.parts" demoUrl: "https://reviveauto.parts"
techStack: ["Tanstack", "React Query", "Fastapi"] techStack: ["Tanstack", "React Query", "Fastapi"]
date: "2025-01-04" date: "2025-01-04"

View File

@@ -2,6 +2,7 @@
import "@/style/globals.css"; import "@/style/globals.css";
import Header from "@/components/header"; import Header from "@/components/header";
import Footer from "@/components/footer"; import Footer from "@/components/footer";
import Background from "@/components/background";
export interface Props { export interface Props {
title: string; title: string;
@@ -23,7 +24,9 @@ const { title, description, permalink, current } = Astro.props;
<Header client:load /> <Header client:load />
<main> <main>
<div class="max-w-5xl mx-auto pt-12 px-4 py-8"> <div class="max-w-5xl mx-auto pt-12 px-4 py-8">
<Background layout="content" position="right" client:only="react" />
<slot /> <slot />
<Background layout="content" position="left" client:only="react" />
</div> </div>
</main> </main>
<Footer client:load /> <Footer client:load />

View File

@@ -5,20 +5,21 @@ import "@/style/globals.css";
import Header from "@/components/header"; import Header from "@/components/header";
import Footer from "@/components/footer"; import Footer from "@/components/footer";
import Background from "@/components/hero/background"; import Background from "@/components/background";
--- ---
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="sitemap" href="/sitemap-index.xml" />
<title>{content.title}</title> <title>{content.title}</title>
</head> </head>
<body class="bg-background text-foreground"> <body class="bg-background text-foreground">
<Header client:load /> <Header client:load />
<main> <main>
<Background client:only="react" /> <Background layout="index" client:only="react" />
<slot /> <slot />
</main> </main>
<Footer client:load fixed=true /> <Footer client:load fixed=true />

View File

@@ -1,25 +0,0 @@
---
const { content } = Astro.props;
import "@/style/globals.css";
import Header from "@/components/header";
import Footer from "@/components/footer";
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="sitemap" href="/sitemap-index.xml" />
<title>{content.title}</title>
</head>
<body class="bg-background text-foreground">
<Header client:load />
<main>
<slot />
</main>
<Footer client:load />
</body>
</html>

View File

@@ -1,33 +0,0 @@
---
import "@/style/globals.css";
import Header from "@/components/header";
import Footer from "@/components/footer";
export interface Props {
title: string;
description?: string;
permalink?: string;
}
const { title, description, permalink } = Astro.props;
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>{title}</title>
{description && <meta name="description" content={description} />}
{permalink && <link rel="canonical" href={permalink} />}
</head>
<body class="bg-background text-foreground">
<Header client:load />
<main>
<div class="max-w-5xl mx-auto pt-12 px-4 py-8">
<slot />
</div>
</main>
<Footer client:load />
</body>
</html>

View File

@@ -1,9 +1,18 @@
--- ---
import MainLayout from "@/layouts/main.astro"; import IndexLayout from "@/layouts/index.astro";
const title = "404 Not Found"; const title = "404 Not Found";
--- ---
<MainLayout content={{ title: "404 | Timothy Pidashev" }}> <IndexLayout content={{ title: "404 | Timothy Pidashev" }}>
<main>404 not found</main> <main class="min-h-screen flex flex-col items-center justify-center p-4 text-center">
</MainLayout> <h1 class="text-6xl font-bold mb-4">404</h1>
<p class="text-xl mb-8">Whoops! This page doesn't exist.</p>
<button
onclick="window.history.back()"
class="underline hover:opacity-70 transition-opacity"
>
go back
</button>
</main>
</IndexLayout>

View File

@@ -1,12 +1,12 @@
--- ---
import "@/style/globals.css" import "@/style/globals.css"
import MainLayout from "@/layouts/main.astro"; import ContentLayout from "@/layouts/content.astro";
import Intro from "@/components/about/intro"; import Intro from "@/components/about/intro";
import Timeline from "@/components/about/timeline"; import Timeline from "@/components/about/timeline";
import CurrentFocus from "@/components/about/current-focus"; import CurrentFocus from "@/components/about/current-focus";
import OutsideCoding from "@/components/about/outside-coding"; import OutsideCoding from "@/components/about/outside-coding";
--- ---
<MainLayout content={{ title: "About | Timothy Pidashev" }}> <ContentLayout content={{ title: "About | Timothy Pidashev" }}>
<div class="min-h-screen"> <div class="min-h-screen">
<section class="h-screen flex items-center justify-center"> <section class="h-screen flex items-center justify-center">
<Intro client:load /> <Intro client:load />

View File

@@ -1,7 +1,7 @@
--- ---
import { CollectionEntry, getCollection } from "astro:content"; import { CollectionEntry, getCollection } from "astro:content";
import { Image } from "astro:assets"; import { Image } from "astro:assets";
import BlogLayout from "@/layouts/blog.astro"; import ContentLayout from "@/layouts/content.astro";
import { getArticleSchema } from "@/lib/structuredData"; import { getArticleSchema } from "@/lib/structuredData";
import { blogWebsite } from "@/lib/structuredData"; import { blogWebsite } from "@/lib/structuredData";
@@ -43,7 +43,7 @@ const jsonLd = {
}; };
--- ---
<BlogLayout> <ContentLayout>
<script type="application/ld+json" set:html={JSON.stringify(jsonLd)} /> <script type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
<div class="relative max-w-8xl mx-auto"> <div class="relative max-w-8xl mx-auto">
<article class="prose prose-lg mx-auto max-w-4xl"> <article class="prose prose-lg mx-auto max-w-4xl">
@@ -74,4 +74,4 @@ const jsonLd = {
<Content /> <Content />
</article> </article>
</div> </div>
</BlogLayout> </ContentLayout>

View File

@@ -1,6 +1,6 @@
--- ---
import { getCollection } from "astro:content"; import { getCollection } from "astro:content";
import MainLayout from "@/layouts/main.astro"; import ContentLayout from "@/layouts/content.astro";
import { BlogPostList } from "@/components/blog/post-list"; import { BlogPostList } from "@/components/blog/post-list";
@@ -11,6 +11,6 @@ const posts = (await getCollection("blog", ({ data }) => {
); );
--- ---
<MainLayout content={{ title: "Blog | Timothy Pidashev" }}> <ContentLayout content={{ title: "Blog | Timothy Pidashev" }}>
<BlogPostList posts={posts} client:load /> <BlogPostList posts={posts} client:load />
</MainLayout> </ContentLayout>

View File

@@ -1,6 +1,6 @@
--- ---
import { getCollection } from "astro:content"; import { getCollection } from "astro:content";
import ProjectsLayout from "@/layouts/projects.astro"; import ContentLayout from "@/layouts/content.astro";
export async function getStaticPaths() { export async function getStaticPaths() {
const projects = await getCollection("projects"); const projects = await getCollection("projects");
@@ -14,7 +14,7 @@ const { project } = Astro.props;
const { Content } = await project.render(); const { Content } = await project.render();
--- ---
<ProjectsLayout title={`${project.data.title} | Timothy Pidashev`}> <ContentLayout title={`${project.data.title} | Timothy Pidashev`}>
<article class="w-full mx-auto px-4 pt-6 sm:pt-12"> <article class="w-full mx-auto px-4 pt-6 sm:pt-12">
{/* Image Section */} {/* Image Section */}
{project.data.image && ( {project.data.image && (
@@ -60,4 +60,4 @@ const { Content } = await project.render();
<Content /> <Content />
</div> </div>
</article> </article>
</ProjectsLayout> </ContentLayout>

View File

@@ -1,6 +1,6 @@
--- ---
import { getCollection } from "astro:content"; import { getCollection } from "astro:content";
import ProjectsLayout from "@/layouts/projects.astro"; import ContentLayout from "@/layouts/content.astro";
import { ProjectList } from "@/components/projects/project-list"; import { ProjectList } from "@/components/projects/project-list";
const projects = (await getCollection("projects", ({ data }) => { const projects = (await getCollection("projects", ({ data }) => {
@@ -10,6 +10,6 @@ const projects = (await getCollection("projects", ({ data }) => {
); );
--- ---
<ProjectsLayout title="Projects | Timothy Pidashev"> <ContentLayout title="Projects | Timothy Pidashev">
<ProjectList projects={projects} client:load /> <ProjectList projects={projects} client:load />
</ProjectsLayout> </ContentLayout>

View File

@@ -1,12 +1,12 @@
--- ---
import "@/style/globals.css" import "@/style/globals.css"
import MainLayout from "@/layouts/main.astro"; import ContentLayout from "@/layouts/content.astro";
--- ---
<MainLayout content={{ title: "Resume | Timothy Pidashev" }}> <ContentLayout content={{ title: "Resume | Timothy Pidashev" }}>
<div class="flex items-center justify-center h-screen w-full"> <div class="flex items-center justify-center h-screen w-full">
<h1 class="text-4xl text-blue font-bold">Resume</h1> <h1 class="text-4xl text-blue font-bold">Resume</h1>
</div> </div>
</MainLayout> </ContentLayout>