From 13ad01a247a58109383e7ed9aa75b710d5847a6d Mon Sep 17 00:00:00 2001 From: Nicholai Date: Fri, 2 Jan 2026 02:31:53 -0700 Subject: [PATCH] .Add projects collection, dev page, navigation, hero fixes - Create structured projects collection with schema - Add new dev page and navigation link - Refactor hero animation for view transitions - Update project descriptions and tags - Add new projects: Summit Painting, United Tattoos, etc. - Trapped in endless work, despise this commit Hubert The Eunuch --- dev/continuity.md | 94 +++++++++- src/components/Navigation.astro | 17 ++ src/components/ProjectCard.astro | 82 +++++++++ src/components/sections/Hero.astro | 215 +++++++++++++---------- src/content.config.ts | 16 +- src/content/projects/kampus-cadilari.mdx | 12 ++ src/content/projects/summit-painting.mdx | 12 ++ src/content/projects/united-tattoos.mdx | 12 ++ src/pages/dev.astro | 162 +++++++++++++++++ 9 files changed, 523 insertions(+), 99 deletions(-) create mode 100644 src/components/ProjectCard.astro create mode 100644 src/content/projects/kampus-cadilari.mdx create mode 100644 src/content/projects/summit-painting.mdx create mode 100644 src/content/projects/united-tattoos.mdx create mode 100644 src/pages/dev.astro diff --git a/dev/continuity.md b/dev/continuity.md index 151d3a6..f2f075a 100644 --- a/dev/continuity.md +++ b/dev/continuity.md @@ -133,4 +133,96 @@ export async function updateClasses( ### Next Steps - [ ] Monitor for any script re-execution issues common with View Transitions. -- [ ] Consider adding custom transition animations for specific elements if needed. \ No newline at end of file +- [ ] Consider adding custom transition animations for specific elements if needed. + +## 2026-01-02 - Work Page & Projects Collection Implementation + +### Changes Made +- Updated `src/content.config.ts` to include a new `projects` collection with schema for title, description, link, category, and tags. +- Created `src/content/projects/` directory and added `united-tattoos.mdx` and `the-highering-agency.mdx`. +- Created `src/components/ProjectCard.astro` for modular project display. +- Created `src/pages/work.astro` to list development and design projects. +- Updated `src/components/Navigation.astro` to include "Work" in desktop and mobile menus. + +### Decisions +- Created a separate `projects` collection rather than overloading `pages` to allow for structured, queryable project data. +- Placed "Work" between "Home" and "Blog" in navigation to emphasize the portfolio aspect. +- Included a VFX/Technical Art section at the bottom of the Work page to bridge the gap between web dev and the existing VFX portfolio. + +### How to Test +1. Navigate to the new `/work` page. +2. Verify both "United Tattoos" and "The Highering Agency" are displayed with correct information and links. +3. Test navigation from Home -> Work and Work -> Blog using the new Client Router. +4. Verify mobile menu contains the "Work" link and functions correctly. + +### Next Steps +- [ ] Add screenshots/images to the projects in `src/content/projects/`. +- [ ] Expand the `ProjectCard` to support hover effects or mini-galleries if desired. + +## 2026-01-02 - Project Descriptions Update + +### Changes Made +- Updated `src/content/projects/the-highering-agency.mdx` description to reflect its status as a cannabis staffing agency with specific features like direct hire and executive search. +- Updated `src/content/projects/united-tattoos.mdx` description and tags based on its official README, highlighting Astro 5, booking system features, and editorial design. + +### Decisions +- Ensured project descriptions are accurate and highlight the specific technical and business value of each project. +- Added relevant tags like "Astro", "GSAP", "Booking System" to United Tattoo to showcase technical depth. + +### How to Test +1. Navigate to `/work`. +2. Verify the text for "The Highering Agency" mentions cannabis staffing and recruitment services. +3. Verify "United Tattoo" description mentions "Official marketing website", "Astro 5", and "Booking System". + +## 2026-01-02 - Hero Animation Fix for Client Router + +### Changes Made +- Refactored `src/components/sections/Hero.astro` script. +- Wrapped initialization logic (intro, clock, grid, pulse) in `initHero()` function. +- Added event listener for `astro:page-load` to run `initHero()` on every navigation. +- Added `cleanup()` function triggered on `astro:before-swap` to clear timers (`clockTimer`, `pulseInterval`). + +### Decisions +- Moving logic to `astro:page-load` is required for View Transitions (ClientRouter) because standard `window.onload` only fires on the first page visit. +- Explicit cleanup prevents memory leaks and double-firing timers when navigating back and forth. + +### How to Test +1. Load the homepage directly. Verify Hero animations (text fade-in, grid pulse) work. +2. Navigate to another page (e.g., `/work`). +3. Click "Home" in the navigation. +4. Verify Hero animations re-trigger correctly without a full page reload. +5. Check console for any errors (none expected). + +## 2026-01-02 - Dev Page Redesign & Rename + +### Changes Made +- Renamed `src/pages/work.astro` to `src/pages/dev.astro`. +- Updated `src/components/Navigation.astro` to link to `/dev` labeled "DEV". +- Redesigned `src/pages/dev.astro` completely: + - **Layout:** Switched from a sparse grid to a dense, 2-column "System Module" layout. + - **Aesthetics:** Aligned with the homepage's Industrial Dark Mode (scanlines, mono fonts, technical overlays). + - **Visuals:** Added CSS-based animated mesh backgrounds and SVG decorators to cards, eliminating the need for screenshots while maintaining a high-tech feel. + - **Typography:** Updated headers to be massive and uppercase, matching the Hero section. + +### Decisions +- Renaming to "Dev" better reflects the technical nature of the portfolio and distinguishes it from the VFX work. +- The previous card layout was too simple and looked "sloppy" without images. The new "System Module" design uses CSS/SVG abstracts to look finished and professional without requiring assets. +- Integrated "secondary specialization" section (VFX) with better styling to act as a bridge between the two portfolio halves. + +### How to Test +1. Navigate to `/dev` via the new navigation link. +2. Verify the page title is "DEV LOG". +3. Check that the project cards look like technical modules with animated backgrounds. +4. Hover over cards to see the accent color shift. +5. Verify the "Initialize Uplink" buttons link to the correct project URLs. + +## 2026-01-02 - Project Added: Summit Painting & Handyman + +### Changes Made +- Created `src/content/projects/summit-painting.mdx` with details from its official README. +- Set the preview link to `https://summit-painting-and-handyman-services.pages.dev/`. +- Updated tags and description to highlight Astro 5 and React 19 usage. + +### Decisions +- Added as the third project in the Dev list to maintain a chronological or curated flow. +- Highlights the transition to Astro 5 and content-driven design patterns common in recent work. diff --git a/src/components/Navigation.astro b/src/components/Navigation.astro index ba479f5..939f8a1 100644 --- a/src/components/Navigation.astro +++ b/src/components/Navigation.astro @@ -25,6 +25,17 @@ import ThemeToggle from './ThemeToggle.astro'; Astro.url.pathname === '/' ? "w-full" : "w-0 group-hover:w-full" ]}> + + Dev + + Home + + Dev + + +
+
+ +
+ +
+ + PRJ.0{order || 'X'} + + + + {category} + +
+ + +
+

+ {title} +

+
+ + +

+ {description} +

+ + + {tags && tags.length > 0 && ( +
+ {tags.map((tag) => ( + + {tag} + + ))} +
+ )} + + + +
+ diff --git a/src/components/sections/Hero.astro b/src/components/sections/Hero.astro index 3563d7a..a32d601 100644 --- a/src/components/sections/Hero.astro +++ b/src/components/sections/Hero.astro @@ -146,40 +146,52 @@ const { headlineLine1, headlineLine2, portfolioYear, location, locationLabel, bi const reduceMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches ?? false; const finePointer = window.matchMedia?.('(pointer: fine) and (hover: hover)')?.matches ?? false; - // ===== CLOCK (pause on hidden tab, align to second boundaries) ===== - let clockTimer = 0; + // Track active timers to clear them on navigation/cleanup + let clockTimer: number | undefined; + let pulseInterval: number | undefined; - function updateClockOnce() { - const clock = document.getElementById('clock'); - if (!clock) return; - - const now = new Date(); - const timeString = now.toLocaleTimeString('en-US', { hour12: false, timeZone: 'America/Denver' }); - clock.textContent = `${timeString} MST`; + function cleanup() { + if (clockTimer) window.clearTimeout(clockTimer); + if (pulseInterval) window.clearInterval(pulseInterval); + clockTimer = undefined; + pulseInterval = undefined; } - function startClock() { - if (clockTimer) window.clearTimeout(clockTimer); + function initHero() { + // Clean up previous instances first + cleanup(); - const tick = () => { - if (document.hidden) { - clockTimer = window.setTimeout(tick, 1000); - return; + // ===== CLOCK ===== + const clock = document.getElementById('clock'); + if (clock) { + function updateClockOnce() { + if (!clock) return; + const now = new Date(); + const timeString = now.toLocaleTimeString('en-US', { hour12: false, timeZone: 'America/Denver' }); + clock.textContent = `${timeString} MST`; } - updateClockOnce(); - // Align to the next second boundary to reduce drift. - const msToNextSecond = 1000 - (Date.now() % 1000); - clockTimer = window.setTimeout(tick, msToNextSecond); - }; + function startClock() { + const tick = () => { + // Stop if element is gone + if (!document.body.contains(clock)) return; - tick(); - } + if (document.hidden) { + clockTimer = window.setTimeout(tick, 1000); + return; + } - startClock(); + updateClockOnce(); + // Align to the next second boundary to reduce drift. + const msToNextSecond = 1000 - (Date.now() % 1000); + clockTimer = window.setTimeout(tick, msToNextSecond); + }; + tick(); + } + startClock(); + } - // Intro Animation Sequence - window.addEventListener('load', () => { + // ===== INTRO ANIMATIONS ===== // Trigger Intro Elements const introElements = document.querySelectorAll('.intro-element'); introElements.forEach(el => { @@ -192,9 +204,11 @@ const { headlineLine1, headlineLine2, portfolioYear, location, locationLabel, bi portrait.classList.add('portrait-visible'); } + const section = document.getElementById('hero'); + const cells = document.querySelectorAll('.grid-cell'); + // Trigger Grid Ripple (skip if reduced motion) - if (!reduceMotion) { - const cells = document.querySelectorAll('.grid-cell'); + if (!reduceMotion && cells.length > 0) { // Diagonal sweep effect cells.forEach((cell, i) => { const row = Math.floor(i / 10); @@ -209,93 +223,100 @@ const { headlineLine1, headlineLine2, portfolioYear, location, locationLabel, bi }, delay); }); } - }); + // ===== GRID INTERACTION ===== + if (section && cells.length > 0) { + // Throttle mousemove work to one update per frame. + let latestX = 0; + let latestY = 0; + let pending = false; + let lastIndex = -1; + const timeouts: number[] = new Array(cells.length).fill(0); - // Robust Grid Interaction - const section = document.getElementById('hero'); - const cells = document.querySelectorAll('.grid-cell'); + const process = () => { + pending = false; + if (!finePointer || reduceMotion) return; + + // Safety check if section is still valid + if (!document.body.contains(section)) return; - if (section) { - // Throttle mousemove work to one update per frame. - let latestX = 0; - let latestY = 0; - let pending = false; - let lastIndex = -1; - const timeouts: number[] = new Array(cells.length).fill(0); + const rect = section.getBoundingClientRect(); + const width = rect.width; + const height = rect.height; + if (width <= 0 || height <= 0) return; - const process = () => { - pending = false; - if (!finePointer || reduceMotion) return; + const x = latestX - rect.left; + const y = latestY - rect.top; - const rect = section.getBoundingClientRect(); - const width = rect.width; - const height = rect.height; - if (width <= 0 || height <= 0) return; + const col = Math.floor((x / width) * 10); + const row = Math.floor((y / height) * 10); - const x = latestX - rect.left; - const y = latestY - rect.top; + if (col < 0 || col >= 10 || row < 0 || row >= 10) return; - const col = Math.floor((x / width) * 10); - const row = Math.floor((y / height) * 10); + const index = row * 10 + col; + if (index === lastIndex) return; + lastIndex = index; - if (col < 0 || col >= 10 || row < 0 || row >= 10) return; + const cell = cells[index] as HTMLElement | undefined; + if (!cell) return; - const index = row * 10 + col; - if (index === lastIndex) return; - lastIndex = index; + cell.classList.add('active'); - const cell = cells[index] as HTMLElement | undefined; - if (!cell) return; + const prev = timeouts[index]; + if (prev) window.clearTimeout(prev); - cell.classList.add('active'); + // Shorter hold time for a quicker trail. + timeouts[index] = window.setTimeout(() => { + cell.classList.remove('active'); + timeouts[index] = 0; + }, 35); + }; - const prev = timeouts[index]; - if (prev) window.clearTimeout(prev); + section.addEventListener('mousemove', (e) => { + latestX = e.clientX; + latestY = e.clientY; + if (pending) return; + pending = true; + window.requestAnimationFrame(process); + }, { passive: true }); + } - // Shorter hold time for a quicker trail. - timeouts[index] = window.setTimeout(() => { - cell.classList.remove('active'); - timeouts[index] = 0; - }, 35); - }; + // ===== PULSE ANIMATION ===== + if (finePointer && !reduceMotion && cells.length > 0) { + pulseInterval = window.setInterval(() => { + if (document.hidden) return; + + // Stop if elements are gone + if (cells.length > 0 && !document.body.contains(cells[0])) { + clearInterval(pulseInterval); + return; + } - section.addEventListener('mousemove', (e) => { - latestX = e.clientX; - latestY = e.clientY; - if (pending) return; - pending = true; - window.requestAnimationFrame(process); - }, { passive: true }); + const randomIndex = Math.floor(Math.random() * cells.length); + const cell = cells[randomIndex] as HTMLElement | undefined; + if (!cell) return; + + cell.classList.add('active'); + window.setTimeout(() => { + cell.classList.remove('active'); + }, 160); + }, 1200); + } } - // Random pulse for liveliness - let pulseInterval = 0; - - function startPulse() { - if (pulseInterval) window.clearInterval(pulseInterval); - if (!finePointer || reduceMotion) return; - - pulseInterval = window.setInterval(() => { - if (document.hidden) return; - - const randomIndex = Math.floor(Math.random() * cells.length); - const cell = cells[randomIndex] as HTMLElement | undefined; - if (!cell) return; - - cell.classList.add('active'); - window.setTimeout(() => { - cell.classList.remove('active'); - }, 160); - }, 1200); - } - - startPulse(); - + // Run on every navigation + document.addEventListener('astro:page-load', initHero); + + // Clean up before swap + document.addEventListener('astro:before-swap', cleanup); + + // Visibility change handler for clock updates (keep persistent) document.addEventListener('visibilitychange', () => { - // Keep timers light in background. - if (!document.hidden) { - updateClockOnce(); + const clock = document.getElementById('clock'); + if (!document.hidden && clock) { + const now = new Date(); + const timeString = now.toLocaleTimeString('en-US', { hour12: false, timeZone: 'America/Denver' }); + clock.textContent = `${timeString} MST`; } }); diff --git a/src/content.config.ts b/src/content.config.ts index d7bafff..2eff8cf 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -106,4 +106,18 @@ const pages = defineCollection({ }), }); -export const collections = { blog, sections, pages }; +const projects = defineCollection({ + loader: glob({ base: './src/content/projects', pattern: '**/*.{md,mdx}' }), + schema: ({ image }) => + z.object({ + title: z.string(), + description: z.string(), + link: z.string(), + category: z.string(), + tags: z.array(z.string()).optional(), + image: image().optional(), + order: z.number().optional().default(0), + }), +}); + +export const collections = { blog, sections, pages, projects }; diff --git a/src/content/projects/kampus-cadilari.mdx b/src/content/projects/kampus-cadilari.mdx new file mode 100644 index 0000000..822a41d --- /dev/null +++ b/src/content/projects/kampus-cadilari.mdx @@ -0,0 +1,12 @@ +--- +title: "Kampüs Cadıları" +description: "A modern Astro migration of the Kampüs Cadıları feminist collective platform. Converted from a React SPA to a high-performance Astro 5 application with React islands, bilingual routing (TR/EN), and brutalist design aesthetics." +link: "https://thehigheringagency.com" +category: "Astro Migration" +tags: + - "Astro 5" + - "React Islands" + - "i18n" + - "Brutalist Design" +order: 2 +--- \ No newline at end of file diff --git a/src/content/projects/summit-painting.mdx b/src/content/projects/summit-painting.mdx new file mode 100644 index 0000000..036e1aa --- /dev/null +++ b/src/content/projects/summit-painting.mdx @@ -0,0 +1,12 @@ +--- +title: "Summit Painting & Handyman" +description: "The official digital platform for a Colorado Springs-based service provider. Built with Astro 5 and React 19, the site features a content-driven architecture, automated image optimization, and an elegant design system tailored for craftsmanship showcase." +link: "https://summit-painting-and-handyman-services.pages.dev/" +category: "Website Design & Development" +tags: + - "Astro 5" + - "React 19" + - "Cloudflare Pages" + - "Content Collections" +order: 3 +--- diff --git a/src/content/projects/united-tattoos.mdx b/src/content/projects/united-tattoos.mdx new file mode 100644 index 0000000..d966aeb --- /dev/null +++ b/src/content/projects/united-tattoos.mdx @@ -0,0 +1,12 @@ +--- +title: "United Tattoo" +description: "The official marketing website for United Tattoo in Fountain, Colorado. Built with Astro 5, it features dynamic artist portfolios, a multi-step booking system with file uploads, and a modern editorial design aesthetic powered by GSAP and Lenis animations." +link: "https://united-tattoos.com" +category: "Website Design & Development" +tags: + - "Astro" + - "GSAP" + - "Booking System" + - "Editorial Design" +order: 1 +--- diff --git a/src/pages/dev.astro b/src/pages/dev.astro new file mode 100644 index 0000000..d69e8ff --- /dev/null +++ b/src/pages/dev.astro @@ -0,0 +1,162 @@ +--- +import { getCollection } from 'astro:content'; +import BaseLayout from '../layouts/BaseLayout.astro'; +import { SITE_TITLE } from '../consts'; + +// Fetch all projects sorted by order +const allProjects = (await getCollection('projects')).sort( + (a, b) => a.data.order - b.data.order, +); + +const pageTitle = `Dev | ${SITE_TITLE}`; +--- + + + +
+
+
+
+ + +
+ + + +
+
+
+ SYS.DEV /// INDEX +
+

+ DEV + LOG +

+ +
+
+

+ Deploying scalable web solutions and high-performance applications. +

+
+
+
+
+ + +
+
+ {allProjects.map((project, index) => ( +
+ + +
+ +
+
+
+ PRJ.0{project.data.order} + + LIVE_FEED +
+
+ +

+ {project.data.title} +

+

+ {project.data.description} +

+ + +
+ /// STACK_MANIFEST +
+ {project.data.tags && ( +
+ {project.data.tags.map((tag) => ( + + {tag} + + ))} +
+ )} +
+ + + Initialize Project + + + + +
+ + +
+ +
+ +
+ + +
+ + +
+ + +
+
+ Live Connection +
+
+
+
+ ))} +
+
+ + +
+
+
+
+
+ /// SECONDARY_SPECIALIZATION +
+

+ Visual Effects &
Technical Art +

+

+ Beyond traditional web development, I specialize in high-end VFX production and pipeline automation. +

+
+ +
+
+
+
\ No newline at end of file