feat(projects): update project links to showcase page and redesign dedicated project layout
This commit is contained in:
1 parent
363fa64faf
commit
64cc41e919
3 files changed
+395
-462
No files matched your search
@@ -1,43 +1,49 @@
|
||||
<!-- - testimonial section stars add in sanity studio
|
||||
- accessibility dropdown background color in light mode it has transparent
|
||||
- add a photo gallery in gallery page -> uploads from sanity title(optional), photo upload/url, description (optional), category(optional) -> hover overlay blur + show title + show description + click show full image in a modal with cross button in top right -> add filter by category in top right -> display all the photos in a grid layout. -> responsive grid layout for mobile, tablet and desktop -> animation for the photo grid when loaded from the bottom
|
||||
- optimize the low end mobile color theme -> dark mode, light mode -> colors, background, buttons, text, links, icons, everything minimize the blur effect
|
||||
- chat bot remove from navigation bar and its page -> turn the chat bot into FAB -> keyword-matching search assistant for chat -> dont use api, dont label it as ai chat bot label it as Portfolio Assistant
|
||||
- footer make the spotify like on messenger/instagram chat music disk spinning when playing if not its not sppining disk -> layout the socials that is not blocked by bring to top button
|
||||
- link page -> optimize the theme colors, layout, animation for low end mobile -> make it linktree style that no header and footer
|
||||
- navigation bar in mobile -> fix the background colors theme -> fix not working minimize or bring back icon
|
||||
-->
|
||||
|
||||
<!-- - fix certifications page title and numbering it should be 3, tech stack is 2
|
||||
- redesign the card of certificates large picture, badge/cert code, issuing org, and title only -> hover and when click show all the information of the certificate in a modal -> dont make verify button only on credly or other platform
|
||||
- in about page it has certifications section -> remove it because it is already in certifications page -->
|
||||
|
||||
<!-- - in about section bento cell 2 make the image optimize load [About.tsx#L99-103](textBlock;file:///f%3A/WebDevelopment/Portfolio/src/components/About.tsx#L99-103)
|
||||
- link page cv url /CV-Cris-Charles-Garcia.pdf -> available work chip fetch from sanity same from home page
|
||||
- in cards of certificate/badges can be previewed if the picture is a certificate not a badge so make it large and can be previewed
|
||||
- certifications page title remove the ```03 // Credentials & Badges
|
||||
|
||||
Certifications & Badges
|
||||
Verified digital credentials from Cisco and industry partners. Each badge represents completed coursework, assessments, and demonstrated competency
|
||||
<!-- - background shapes in landscape the right side shapes collides
|
||||
- in portrait make the different approach of parallex and layout of the shapes -->
|
||||
<!-- - Lite mode default the toggle is disabled
|
||||
- hide the magnifier lens settings if detected mobile
|
||||
- limit portfolio assistant pre selections, add feature that shows their ip and location when prompts like a using AI chatbot or if they try to prompt in the portfolio assistant like its a AI -->
|
||||
|
||||
<!-- - fix the lite mode only hero section loads when i reload that enabled lite mode
|
||||
- make the UI in mobile same as on pc but when lagging notify to turn on the lite mode -->
|
||||
|
||||
<!-- - remove title description per sections
|
||||
- in project section remove the screenshot picture frame like its a browser in localhost, just make it simple
|
||||
- do not use emoji
|
||||
- certifications section -> all categories use simple word and short for title category
|
||||
- only fetch spotify widget when modal is open, fix recent play its pausing
|
||||
- in tech stack technology add icon fontawesome and update the sanity io -->
|
||||
|
||||
<!-- # TECHNOLOGIES & TOOLS
|
||||
- make the per categories simple and easy to read, remove some uneccessary components, remove the per skills has its box
|
||||
|
||||
# CONTACT SECTION
|
||||
- fix the "Let's create something amazing." is under lapping to paragraph element
|
||||
- in ultra fast mode the form still has label animation, fix the label to be static and dont move or animate, always show the label above the input field -->
|
||||
<!-- - testimonial section stars add in sanity studio
|
||||
- accessibility dropdown background color in light mode it has transparent
|
||||
- add a photo gallery in gallery page -> uploads from sanity title(optional), photo upload/url, description (optional), category(optional) -> hover overlay blur + show title + show description + click show full image in a modal with cross button in top right -> add filter by category in top right -> display all the photos in a grid layout. -> responsive grid layout for mobile, tablet and desktop -> animation for the photo grid when loaded from the bottom
|
||||
- optimize the low end mobile color theme -> dark mode, light mode -> colors, background, buttons, text, links, icons, everything minimize the blur effect
|
||||
- chat bot remove from navigation bar and its page -> turn the chat bot into FAB -> keyword-matching search assistant for chat -> dont use api, dont label it as ai chat bot label it as Portfolio Assistant
|
||||
- footer make the spotify like on messenger/instagram chat music disk spinning when playing if not its not sppining disk -> layout the socials that is not blocked by bring to top button
|
||||
- link page -> optimize the theme colors, layout, animation for low end mobile -> make it linktree style that no header and footer
|
||||
- navigation bar in mobile -> fix the background colors theme -> fix not working minimize or bring back icon
|
||||
-->
|
||||
|
||||
<!-- - fix certifications page title and numbering it should be 3, tech stack is 2
|
||||
- redesign the card of certificates large picture, badge/cert code, issuing org, and title only -> hover and when click show all the information of the certificate in a modal -> dont make verify button only on credly or other platform
|
||||
- in about page it has certifications section -> remove it because it is already in certifications page -->
|
||||
|
||||
<!-- - in about section bento cell 2 make the image optimize load [About.tsx#L99-103](textBlock;file:///f%3A/WebDevelopment/Portfolio/src/components/About.tsx#L99-103)
|
||||
- link page cv url /CV-Cris-Charles-Garcia.pdf -> available work chip fetch from sanity same from home page
|
||||
- in cards of certificate/badges can be previewed if the picture is a certificate not a badge so make it large and can be previewed
|
||||
- certifications page title remove the ```03 // Credentials & Badges
|
||||
|
||||
Certifications & Badges
|
||||
Verified digital credentials from Cisco and industry partners. Each badge represents completed coursework, assessments, and demonstrated competency
|
||||
<!-- - background shapes in landscape the right side shapes collides
|
||||
- in portrait make the different approach of parallex and layout of the shapes -->
|
||||
<!-- - Lite mode default the toggle is disabled
|
||||
- hide the magnifier lens settings if detected mobile
|
||||
- limit portfolio assistant pre selections, add feature that shows their ip and location when prompts like a using AI chatbot or if they try to prompt in the portfolio assistant like its a AI -->
|
||||
|
||||
<!-- - fix the lite mode only hero section loads when i reload that enabled lite mode
|
||||
- make the UI in mobile same as on pc but when lagging notify to turn on the lite mode -->
|
||||
|
||||
<!-- - remove title description per sections
|
||||
- in project section remove the screenshot picture frame like its a browser in localhost, just make it simple
|
||||
- do not use emoji
|
||||
- certifications section -> all categories use simple word and short for title category
|
||||
- only fetch spotify widget when modal is open, fix recent play its pausing
|
||||
- in tech stack technology add icon fontawesome and update the sanity io -->
|
||||
|
||||
<!-- # TECHNOLOGIES & TOOLS
|
||||
- make the per categories simple and easy to read, remove some uneccessary components, remove the per skills has its box
|
||||
|
||||
# CONTACT SECTION
|
||||
- fix the "Let's create something amazing." is under lapping to paragraph element
|
||||
- in ultra fast mode the form still has label animation, fix the label to be static and dont move or animate, always show the label above the input field -->
|
||||
|
||||
<!-- Project Section
|
||||
- make the inpect overview update to open dedicated show case page, use basic words for all buttons,
|
||||
|
||||
Project Dedicated page
|
||||
- redesign the layout, remove the background border design, spread all the information, make it responsive, organize the contents -->␍
|
||||
+63
-281
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import type { Project } from '../lib/data';
|
||||
import ScrollReveal from './ScrollReveal';
|
||||
|
||||
@@ -16,32 +16,6 @@ export default function Projects({
|
||||
sectionTag = '02 // Projects'
|
||||
}: ProjectsProps) {
|
||||
const [filter, setFilter] = useState<string>('All');
|
||||
const [selectedProject, setSelectedProject] = useState<Project | null>(null);
|
||||
const [activeImage, setActiveImage] = useState<string>('');
|
||||
|
||||
// Synchronize active preview image and lock body scroll when modal opens
|
||||
useEffect(() => {
|
||||
if (selectedProject) {
|
||||
setActiveImage(selectedProject.coverImage || '');
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else {
|
||||
document.body.style.overflow = 'unset';
|
||||
}
|
||||
return () => {
|
||||
document.body.style.overflow = 'unset';
|
||||
};
|
||||
}, [selectedProject]);
|
||||
|
||||
// Handle ESC key to close modal
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
setSelectedProject(null);
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, []);
|
||||
|
||||
// Extract unique categories
|
||||
const categories = Array.from(
|
||||
@@ -59,11 +33,6 @@ export default function Projects({
|
||||
const flagshipProject = isAllView && filteredProjects.length > 0 ? filteredProjects[0] : null;
|
||||
const secondaryProjects = isAllView && filteredProjects.length > 0 ? filteredProjects.slice(1) : filteredProjects;
|
||||
|
||||
// Compile all images for the selected project (cover + gallery)
|
||||
const modalImages = selectedProject
|
||||
? [selectedProject.coverImage, ...(selectedProject.gallery || [])].filter(Boolean) as string[]
|
||||
: [];
|
||||
|
||||
return (
|
||||
<section id="projects" className="py-20 relative">
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 relative z-10">
|
||||
@@ -111,19 +80,20 @@ export default function Projects({
|
||||
</ScrollReveal>
|
||||
|
||||
{/* ============================================================== */}
|
||||
{/* FLAGSHIP HERO PROJECT (Option 1 - Full Width Showcase) */}
|
||||
{/* FLAGSHIP HERO PROJECT */}
|
||||
{/* ============================================================== */}
|
||||
{flagshipProject && (
|
||||
<ScrollReveal variant="fade-up" className="mb-8">
|
||||
<article
|
||||
onClick={() => setSelectedProject(flagshipProject)}
|
||||
className="bento-card p-6 sm:p-8 lg:p-10 border border-border-custom hover:border-primary-custom/40 transition-all duration-300 cursor-pointer group/hero"
|
||||
>
|
||||
<article className="bento-card p-6 sm:p-8 lg:p-10 border border-border-custom hover:border-primary-custom/40 transition-all duration-300 group/hero">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 items-center">
|
||||
|
||||
{/* Left: Project Screenshot Viewport */}
|
||||
<div className="lg:col-span-7 flex flex-col justify-between">
|
||||
<div className="relative aspect-[16/10] w-full rounded-2xl overflow-hidden border border-border-custom bg-muted-custom/20 shadow-lg group/mockup">
|
||||
<a
|
||||
href={`/projects/${flagshipProject.slug}`}
|
||||
className="relative aspect-[16/10] w-full rounded-2xl overflow-hidden border border-border-custom bg-muted-custom/20 shadow-lg group/mockup block"
|
||||
aria-label={`View ${flagshipProject.title}`}
|
||||
>
|
||||
{flagshipProject.status && (
|
||||
<div className="absolute top-3.5 right-3.5 z-20 px-2.5 py-1 rounded-full bg-background-custom/85 backdrop-blur-md border border-border-custom text-[10px] font-mono font-medium text-emerald-500 dark:text-emerald-400 flex items-center gap-1.5 shadow-sm">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse" />
|
||||
@@ -143,16 +113,16 @@ export default function Projects({
|
||||
<span className="font-mono text-xs text-muted-foreground-custom">Preview Interface</span>
|
||||
</div>
|
||||
)}
|
||||
{/* Click to inspect overlay */}
|
||||
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover/hero:opacity-100 transition-opacity duration-300 flex items-center justify-center pointer-events-none">
|
||||
{/* Hover overlay link hint */}
|
||||
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover/mockup:opacity-100 transition-opacity duration-300 flex items-center justify-center pointer-events-none">
|
||||
<span className="px-3.5 py-1.5 rounded-full bg-background-custom/90 text-foreground-custom font-mono text-xs font-semibold backdrop-blur-sm shadow-md flex items-center gap-1.5">
|
||||
<svg className="w-3.5 h-3.5 text-primary-custom" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607zM10.5 7.5v6m3-3h-6" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3" />
|
||||
</svg>
|
||||
Click to View Details
|
||||
View Project
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{/* Highlight Impact Pills (Below screenshot) */}
|
||||
{flagshipProject.highlights && flagshipProject.highlights.length > 0 && (
|
||||
@@ -178,7 +148,7 @@ export default function Projects({
|
||||
{/* Eyebrow & Meta */}
|
||||
<div className="flex items-center justify-between gap-2 mb-3">
|
||||
<span className="px-2.5 py-1 rounded-md bg-primary-custom/10 border border-primary-custom/20 text-[10px] font-mono font-semibold uppercase tracking-wider text-primary-custom">
|
||||
{flagshipProject.category || 'Flagship Project'}
|
||||
{flagshipProject.category || 'Featured'}
|
||||
</span>
|
||||
<span className="text-xs font-mono text-muted-foreground-custom">
|
||||
{flagshipProject.publishedAt || '2026'}
|
||||
@@ -186,9 +156,14 @@ export default function Projects({
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h3 className="text-2xl sm:text-3xl font-bold font-heading text-foreground-custom group-hover/hero:text-primary-custom transition-colors tracking-tight leading-snug">
|
||||
{flagshipProject.title}
|
||||
</h3>
|
||||
<a
|
||||
href={`/projects/${flagshipProject.slug}`}
|
||||
className="block group/title"
|
||||
>
|
||||
<h3 className="text-2xl sm:text-3xl font-bold font-heading text-foreground-custom group-hover/title:text-primary-custom transition-colors tracking-tight leading-snug">
|
||||
{flagshipProject.title}
|
||||
</h3>
|
||||
</a>
|
||||
|
||||
{/* Summary */}
|
||||
<p className="text-sm text-muted-foreground-custom leading-relaxed mt-3 mb-6 font-normal">
|
||||
@@ -198,16 +173,13 @@ export default function Projects({
|
||||
{/* Technologies Tag Group */}
|
||||
<div className="mb-6">
|
||||
<span className="text-[11px] font-mono text-muted-foreground-custom block mb-2 font-medium">
|
||||
Technologies Deployed
|
||||
Technologies
|
||||
</span>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{flagshipProject.technologies.map((tech) => (
|
||||
<button
|
||||
key={tech}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setFilter(tech);
|
||||
}}
|
||||
onClick={() => setFilter(tech)}
|
||||
className="tech-tag hover:border-primary-custom cursor-pointer"
|
||||
title={`Filter by ${tech}`}
|
||||
>
|
||||
@@ -218,34 +190,29 @@ export default function Projects({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Flagship Actions Footer */}
|
||||
{/* Flagship Actions Footer with basic words */}
|
||||
<div className="flex flex-wrap items-center gap-3 pt-5 border-t border-border-custom">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedProject(flagshipProject);
|
||||
}}
|
||||
className="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl bg-primary-custom text-white text-xs font-semibold hover:bg-primary-custom/90 transition-all shadow-sm hover:shadow hover:-translate-y-0.5 active:translate-y-0 cursor-pointer"
|
||||
<a
|
||||
href={`/projects/${flagshipProject.slug}`}
|
||||
className="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl bg-primary-custom text-white text-xs font-semibold hover:bg-primary-custom/90 transition-all shadow-sm hover:shadow hover:-translate-y-0.5 active:translate-y-0"
|
||||
>
|
||||
Inspect Overview
|
||||
View Project
|
||||
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2.5" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3" />
|
||||
</svg>
|
||||
</button>
|
||||
</a>
|
||||
|
||||
{flagshipProject.repositoryUrl && (
|
||||
<a
|
||||
href={flagshipProject.repositoryUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="inline-flex items-center gap-1.5 px-3.5 py-2.5 rounded-xl border border-border-custom hover:border-primary-custom/40 text-xs font-mono font-medium text-foreground-custom hover:text-primary-custom transition-all"
|
||||
>
|
||||
<svg className="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
||||
</svg>
|
||||
Source
|
||||
Code
|
||||
</a>
|
||||
)}
|
||||
|
||||
@@ -254,10 +221,9 @@ export default function Projects({
|
||||
href={flagshipProject.liveUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="inline-flex items-center gap-1 text-xs font-mono font-medium text-primary-custom hover:underline ml-auto"
|
||||
>
|
||||
Live Demo ↗
|
||||
Demo ↗
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
@@ -279,14 +245,15 @@ export default function Projects({
|
||||
|
||||
return (
|
||||
<ScrollReveal key={project.slug} variant="fade-up" delay={idx * 80}>
|
||||
<article
|
||||
onClick={() => setSelectedProject(project)}
|
||||
className="bento-card p-6 sm:p-7 flex flex-col justify-between h-full group hover:border-primary-custom/40 transition-all duration-300 cursor-pointer"
|
||||
>
|
||||
<article className="bento-card p-6 sm:p-7 flex flex-col justify-between h-full group hover:border-primary-custom/40 transition-all duration-300">
|
||||
<div>
|
||||
{/* Project Screenshot Viewport for Secondary Cards */}
|
||||
{project.coverImage ? (
|
||||
<div className="relative aspect-[16/9] w-full rounded-2xl overflow-hidden border border-border-custom bg-muted-custom/20 mb-5 group/submockup shadow-xs">
|
||||
<a
|
||||
href={`/projects/${project.slug}`}
|
||||
className="relative aspect-[16/9] w-full rounded-2xl overflow-hidden border border-border-custom bg-muted-custom/20 mb-5 group/submockup shadow-xs block"
|
||||
aria-label={`View ${project.title}`}
|
||||
>
|
||||
{project.status && (
|
||||
<div className="absolute top-2.5 right-2.5 z-20 px-2 py-0.5 rounded-full bg-background-custom/85 backdrop-blur-md border border-border-custom text-[9px] font-mono font-medium text-emerald-500 dark:text-emerald-400 flex items-center gap-1 shadow-sm">
|
||||
<span className="w-1 h-1 rounded-full bg-emerald-500 animate-pulse" />
|
||||
@@ -301,12 +268,15 @@ export default function Projects({
|
||||
className="w-full h-full object-cover group-hover/submockup:scale-[1.03] transition-transform duration-500 ease-out"
|
||||
/>
|
||||
{/* Hover overlay hint */}
|
||||
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center pointer-events-none">
|
||||
<span className="px-3 py-1 rounded-full bg-background-custom/90 text-foreground-custom font-mono text-[11px] font-medium backdrop-blur-sm shadow-sm">
|
||||
View Details ↗
|
||||
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover/submockup:opacity-100 transition-opacity duration-300 flex items-center justify-center pointer-events-none">
|
||||
<span className="px-3 py-1 rounded-full bg-background-custom/90 text-foreground-custom font-mono text-[11px] font-medium backdrop-blur-sm shadow-sm flex items-center gap-1.5">
|
||||
<svg className="w-3 h-3 text-primary-custom" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3" />
|
||||
</svg>
|
||||
View Project
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
) : null}
|
||||
|
||||
{/* Header Meta */}
|
||||
@@ -327,9 +297,14 @@ export default function Projects({
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h3 className="text-xl font-bold font-heading text-foreground-custom group-hover:text-primary-custom transition-colors tracking-tight leading-snug mt-1">
|
||||
{project.title}
|
||||
</h3>
|
||||
<a
|
||||
href={`/projects/${project.slug}`}
|
||||
className="block group/title"
|
||||
>
|
||||
<h3 className="text-xl font-bold font-heading text-foreground-custom group-hover/title:text-primary-custom transition-colors tracking-tight leading-snug mt-1">
|
||||
{project.title}
|
||||
</h3>
|
||||
</a>
|
||||
|
||||
{/* Summary */}
|
||||
<p className="text-sm text-muted-foreground-custom leading-relaxed mt-2.5 mb-4 font-normal">
|
||||
@@ -354,10 +329,7 @@ export default function Projects({
|
||||
{project.technologies.map((tech) => (
|
||||
<button
|
||||
key={tech}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setFilter(tech);
|
||||
}}
|
||||
onClick={() => setFilter(tech)}
|
||||
className="tech-tag hover:border-primary-custom cursor-pointer"
|
||||
title={`Filter by ${tech}`}
|
||||
>
|
||||
@@ -366,21 +338,17 @@ export default function Projects({
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Action Links */}
|
||||
{/* Action Links with basic words */}
|
||||
<div className="flex items-center justify-between gap-3 pt-3 border-t border-border-custom">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedProject(project);
|
||||
}}
|
||||
className="inline-flex items-center gap-1 text-xs font-semibold text-foreground-custom hover:text-primary-custom transition-colors cursor-pointer"
|
||||
<a
|
||||
href={`/projects/${project.slug}`}
|
||||
className="inline-flex items-center gap-1 text-xs font-semibold text-foreground-custom hover:text-primary-custom transition-colors"
|
||||
>
|
||||
Quick View
|
||||
View Project
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
|
||||
</svg>
|
||||
</button>
|
||||
</a>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
{project.repositoryUrl && (
|
||||
@@ -388,9 +356,8 @@ export default function Projects({
|
||||
href={project.repositoryUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="text-xs font-mono text-muted-foreground-custom hover:text-foreground-custom transition-colors inline-flex items-center gap-1"
|
||||
title="View source repository"
|
||||
title="View source code"
|
||||
>
|
||||
<svg className="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
||||
@@ -404,7 +371,6 @@ export default function Projects({
|
||||
href={project.liveUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="text-xs font-mono font-medium text-primary-custom hover:underline"
|
||||
>
|
||||
Demo ↗
|
||||
@@ -430,7 +396,7 @@ export default function Projects({
|
||||
onClick={() => setFilter('All')}
|
||||
className="px-4 py-2 rounded-xl bg-primary-custom text-white text-xs font-semibold cursor-pointer"
|
||||
>
|
||||
Reset Filters
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -453,7 +419,7 @@ export default function Projects({
|
||||
href="/projects"
|
||||
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-xl bg-background-custom border border-border-custom hover:border-primary-custom/40 text-foreground-custom text-xs font-semibold hover:text-primary-custom transition-all shadow-xs hover:shadow whitespace-nowrap"
|
||||
>
|
||||
Explore Full Directory
|
||||
All Projects
|
||||
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3" />
|
||||
</svg>
|
||||
@@ -463,190 +429,6 @@ export default function Projects({
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
{/* ============================================================== */}
|
||||
{/* SPLIT MODAL: Left Screenshot Overview | Right Project Details */}
|
||||
{/* ============================================================== */}
|
||||
{selectedProject && (
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-center justify-center p-3 sm:p-5 md:p-8 bg-black/80 backdrop-blur-md animate-modal-fade"
|
||||
onClick={() => setSelectedProject(null)}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label={`${selectedProject.title} Details`}
|
||||
>
|
||||
<div
|
||||
className="relative w-full max-w-5xl max-h-[90vh] bg-background-custom border border-border-custom shadow-2xl rounded-2xl md:rounded-3xl overflow-hidden flex flex-col md:flex-row animate-modal-scale"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* Close button (top right) */}
|
||||
<button
|
||||
onClick={() => setSelectedProject(null)}
|
||||
className="absolute top-4 right-4 z-30 w-8 h-8 rounded-full bg-background-custom/90 border border-border-custom hover:border-primary-custom/40 text-foreground-custom flex items-center justify-center transition-all cursor-pointer shadow-md hover:scale-105 active:scale-95"
|
||||
aria-label="Close modal"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2.5" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* LEFT COLUMN: Screenshot Overview & Gallery */}
|
||||
<div className="w-full md:w-7/12 bg-muted-custom/25 border-b md:border-b-0 md:border-r border-border-custom p-5 sm:p-6 flex flex-col justify-between overflow-y-auto max-h-[45vh] md:max-h-[90vh]">
|
||||
<div>
|
||||
{/* Clean Project Screenshot Frame */}
|
||||
<div className="relative aspect-[16/10] w-full rounded-2xl overflow-hidden border border-border-custom bg-black/5 mb-4 shadow-md">
|
||||
{selectedProject.status && (
|
||||
<div className="absolute top-3 right-3 z-20 px-2.5 py-1 rounded-full bg-background-custom/85 backdrop-blur-md border border-border-custom text-[10px] font-mono font-medium text-emerald-500 dark:text-emerald-400 flex items-center gap-1.5 shadow-sm">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse" />
|
||||
<span>{selectedProject.status}</span>
|
||||
</div>
|
||||
)}
|
||||
{activeImage ? (
|
||||
<img
|
||||
src={activeImage}
|
||||
alt={selectedProject.title}
|
||||
className="w-full h-full object-cover transition-all duration-300"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center p-6 text-center text-muted-foreground-custom font-mono text-xs">
|
||||
No Screenshot Preview Available
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Gallery Thumbnails Carousel if multiple images exist */}
|
||||
{modalImages.length > 1 && (
|
||||
<div className="mt-3">
|
||||
<span className="text-[10px] font-mono uppercase tracking-wider text-muted-foreground-custom block mb-2">
|
||||
Gallery Previews ({modalImages.length})
|
||||
</span>
|
||||
<div className="flex items-center gap-2 overflow-x-auto pb-1">
|
||||
{modalImages.map((imgUrl, i) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => setActiveImage(imgUrl)}
|
||||
className={`relative w-16 h-11 rounded-lg overflow-hidden border-2 flex-shrink-0 transition-all cursor-pointer ${
|
||||
activeImage === imgUrl
|
||||
? 'border-primary-custom scale-105 shadow-md'
|
||||
: 'border-border-custom opacity-70 hover:opacity-100'
|
||||
}`}
|
||||
>
|
||||
<img src={imgUrl} alt={`Thumbnail ${i + 1}`} className="w-full h-full object-cover" />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Bottom metadata tags */}
|
||||
<div className="mt-4 pt-3 border-t border-border-custom/50 flex items-center justify-between text-xs text-muted-foreground-custom">
|
||||
<span className="font-mono text-[11px]">Published: {selectedProject.publishedAt || '2026'}</span>
|
||||
<span className="font-mono text-[11px] text-primary-custom">{selectedProject.order ? `Order #${selectedProject.order}` : ''}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT COLUMN: Project Information Details */}
|
||||
<div className="w-full md:w-5/12 p-6 sm:p-8 flex flex-col justify-between overflow-y-auto max-h-[50vh] md:max-h-[90vh]">
|
||||
<div>
|
||||
{/* Header Category & Published Year */}
|
||||
<div className="flex items-center justify-between gap-2 mb-3">
|
||||
<span className="px-2.5 py-1 rounded-md bg-primary-custom/10 border border-primary-custom/20 text-[10px] font-mono font-semibold uppercase tracking-wider text-primary-custom">
|
||||
{selectedProject.category || 'Project Showcase'}
|
||||
</span>
|
||||
<span className="text-xs font-mono text-muted-foreground-custom">
|
||||
{selectedProject.publishedAt || '2026'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h3 className="text-2xl sm:text-3xl font-bold font-heading text-foreground-custom tracking-tight leading-snug">
|
||||
{selectedProject.title}
|
||||
</h3>
|
||||
|
||||
{/* Detailed Description */}
|
||||
<div className="text-sm text-muted-foreground-custom leading-relaxed mt-4 mb-6">
|
||||
<p>{selectedProject.description || selectedProject.summary}</p>
|
||||
</div>
|
||||
|
||||
{/* Key Highlights / Impact Metrics */}
|
||||
{selectedProject.highlights && selectedProject.highlights.length > 0 && (
|
||||
<div className="mb-6">
|
||||
<span className="text-[11px] font-mono uppercase tracking-wider text-muted-foreground-custom block mb-2 font-medium">
|
||||
Key Architectural Highlights
|
||||
</span>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{selectedProject.highlights.map((h) => (
|
||||
<span key={h} className="impact-pill text-[10px] py-1 px-2.5">
|
||||
<svg className="w-3 h-3 text-primary-custom flex-shrink-0" fill="none" stroke="currentColor" strokeWidth="2.5" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="m4.5 12.75 6 6 9-13.5" />
|
||||
</svg>
|
||||
{h}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Technologies Stack */}
|
||||
<div className="mb-6">
|
||||
<span className="text-[11px] font-mono uppercase tracking-wider text-muted-foreground-custom block mb-2 font-medium">
|
||||
Technologies Stack
|
||||
</span>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{selectedProject.technologies.map((tech) => (
|
||||
<span key={tech} className="tech-tag text-xs">
|
||||
{tech}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons Footer */}
|
||||
<div className="pt-5 border-t border-border-custom space-y-3">
|
||||
<div className="flex items-center gap-2.5">
|
||||
{selectedProject.liveUrl && (
|
||||
<a
|
||||
href={selectedProject.liveUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex-1 py-2.5 px-4 rounded-xl bg-primary-custom text-white text-xs font-semibold hover:bg-primary-custom/90 transition-all text-center shadow-sm flex items-center justify-center gap-1.5"
|
||||
>
|
||||
Launch Demo
|
||||
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2.5" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
|
||||
</svg>
|
||||
</a>
|
||||
)}
|
||||
{selectedProject.repositoryUrl && (
|
||||
<a
|
||||
href={selectedProject.repositoryUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex-1 py-2.5 px-4 rounded-xl border border-border-custom hover:border-primary-custom/40 text-xs font-mono font-medium text-foreground-custom hover:text-primary-custom transition-all text-center flex items-center justify-center gap-1.5"
|
||||
>
|
||||
<svg className="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
||||
</svg>
|
||||
Repository
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
<a
|
||||
href={`/projects/${selectedProject.slug}`}
|
||||
className="block w-full py-2 text-center text-xs font-mono text-muted-foreground-custom hover:text-primary-custom transition-colors"
|
||||
>
|
||||
Open Dedicated Showcase Page →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
+283
-138
@@ -6,13 +6,18 @@ import { getProjects, getSiteSettings, getSocialLinks } from '../../lib/data';
|
||||
|
||||
const { slug } = Astro.params;
|
||||
const allProjects = await getProjects();
|
||||
const project = allProjects.find(p => p.slug === slug);
|
||||
const currentIndex = allProjects.findIndex(p => p.slug === slug);
|
||||
const project = allProjects[currentIndex];
|
||||
const settings = await getSiteSettings();
|
||||
const socialLinks = await getSocialLinks();
|
||||
|
||||
if (!project) {
|
||||
return Astro.redirect('/404');
|
||||
}
|
||||
|
||||
// Adjacent projects for bottom navigation
|
||||
const prevProject = currentIndex > 0 ? allProjects[currentIndex - 1] : null;
|
||||
const nextProject = currentIndex < allProjects.length - 1 ? allProjects[currentIndex + 1] : null;
|
||||
---
|
||||
|
||||
<Layout
|
||||
@@ -22,164 +27,304 @@ if (!project) {
|
||||
>
|
||||
<Navbar fullName={settings.fullName} logoImage={settings.logoImage} client:load />
|
||||
|
||||
<main class="flex-1 pt-6 sm:pt-10 pb-16 relative overflow-hidden">
|
||||
<!-- Background elements -->
|
||||
<div className="absolute inset-0 z-0 overflow-hidden pointer-events-none">
|
||||
<div className="absolute -top-40 left-1/4 w-[500px] h-[500px] rounded-full bg-primary-custom/5 blur-3xl" />
|
||||
<div className="absolute bottom-20 right-1/4 w-80 h-80 rounded-full bg-secondary-custom/5 blur-3xl" />
|
||||
<main class="flex-1 pt-6 sm:pt-10 pb-20 relative overflow-hidden">
|
||||
<!-- Subtle ambient background glows (no enclosing border) -->
|
||||
<div class="absolute inset-0 z-0 overflow-hidden pointer-events-none">
|
||||
<div class="absolute -top-32 left-1/3 w-[600px] h-[500px] rounded-full bg-primary-custom/5 blur-3xl"></div>
|
||||
<div class="absolute top-1/2 right-10 w-96 h-96 rounded-full bg-secondary-custom/5 blur-3xl"></div>
|
||||
</div>
|
||||
|
||||
<div class="container mx-auto px-6 max-w-3xl relative z-10">
|
||||
<!-- Back to projects -->
|
||||
<a
|
||||
href="/projects"
|
||||
class="inline-flex items-center gap-1.5 text-xs font-bold text-muted-foreground-custom hover:text-primary-custom transition-colors mb-10"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2.5" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" />
|
||||
</svg>
|
||||
Back to projects
|
||||
</a>
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 max-w-6xl relative z-10">
|
||||
|
||||
{/* Top Navigation & Meta Breadcrumb */}
|
||||
<div class="flex flex-wrap items-center justify-between gap-4 mb-8">
|
||||
<a
|
||||
href="/projects"
|
||||
class="inline-flex items-center gap-2 text-xs font-mono font-medium text-muted-foreground-custom hover:text-foreground-custom transition-colors group"
|
||||
>
|
||||
<svg class="w-4 h-4 group-hover:-translate-x-1 transition-transform" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" />
|
||||
</svg>
|
||||
Back to Projects
|
||||
</a>
|
||||
|
||||
<article class="glass-card p-8 md:p-12 rounded-[32px] border border-border/20 shadow-2xl relative overflow-hidden">
|
||||
{/* Header banner */}
|
||||
<div class="absolute top-0 left-0 right-0 h-[3px] bg-gradient-to-r from-primary-custom via-secondary-custom to-primary-custom" />
|
||||
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<span class="text-[10px] uppercase tracking-widest font-bold text-primary-custom bg-primary-custom/10 px-3 py-1 rounded-full">
|
||||
Project Showcase
|
||||
</span>
|
||||
<span class="text-xs font-semibold text-muted-foreground-custom">
|
||||
Published: {project.publishedAt}
|
||||
</span>
|
||||
<div class="flex items-center gap-2.5">
|
||||
{project.category && (
|
||||
<span class="text-xs font-mono font-semibold text-primary-custom bg-primary-custom/10 border border-primary-custom/20 px-3 py-1 rounded-full">
|
||||
{project.category}
|
||||
</span>
|
||||
)}
|
||||
{project.status && (
|
||||
<span class="text-xs font-mono font-medium text-emerald-500 dark:text-emerald-400 bg-emerald-500/10 border border-emerald-500/20 px-3 py-1 rounded-full inline-flex items-center gap-1.5">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse" />
|
||||
{project.status}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 class="text-3xl md:text-5xl font-bold font-heading mb-6 text-foreground-custom text-glow">
|
||||
{/* Project Hero Header */}
|
||||
<header class="mb-10">
|
||||
<h1 class="text-3xl sm:text-5xl lg:text-6xl font-extrabold font-heading text-foreground-custom tracking-tight leading-[1.1] mb-6">
|
||||
{project.title}
|
||||
</h1>
|
||||
|
||||
<p class="text-base md:text-lg font-medium text-muted-foreground-custom/95 leading-relaxed mb-8 border-l-2 border-primary-custom/30 pl-4 italic">
|
||||
<p class="text-base sm:text-lg lg:text-xl text-muted-foreground-custom leading-relaxed max-w-4xl font-normal mb-8">
|
||||
{project.summary}
|
||||
</p>
|
||||
|
||||
{/* Project Cover & Gallery */}
|
||||
{project.coverImage ? (
|
||||
<div class="aspect-video w-full rounded-2xl overflow-hidden border border-border-custom mb-10 shadow-lg relative group">
|
||||
<img
|
||||
src={project.coverImage}
|
||||
alt={project.title}
|
||||
class="w-full h-full object-cover group-hover:scale-[1.01] transition-transform duration-500"
|
||||
/>
|
||||
{project.status && (
|
||||
<div class="absolute top-4 left-4 px-3 py-1 rounded-full bg-black/70 backdrop-blur-md text-xs font-mono text-emerald-400 flex items-center gap-1.5 shadow">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse" />
|
||||
{project.status}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div class="aspect-video w-full rounded-2xl bg-gradient-to-tr from-primary-custom/10 to-secondary-custom/10 border border-border/10 flex items-center justify-center p-6 mb-10 shadow-inner group overflow-hidden">
|
||||
<div class="text-center group-hover:scale-105 transition-transform duration-300">
|
||||
<div class="w-16 h-16 rounded-full bg-background-custom/80 flex items-center justify-center text-primary-custom mx-auto mb-4 border border-border/20 shadow">
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M6.429 9.75 2.25 12l4.179 2.25m11.142 0L21.75 12l-4.179-2.25M12 21.75V2.25" />
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-xs font-semibold text-muted-foreground-custom/90">{project.title} Interface</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Gallery images if present */}
|
||||
{project.gallery && project.gallery.length > 0 && (
|
||||
<div class="mb-10">
|
||||
<h3 class="text-xs font-bold text-muted-foreground-custom uppercase tracking-wider mb-4">
|
||||
Project Gallery & Previews
|
||||
</h3>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{project.gallery.map((imgUrl, i) => (
|
||||
<div key={i} class="rounded-xl overflow-hidden border border-border-custom aspect-video shadow-xs">
|
||||
<img src={imgUrl} alt={`${project.title} screenshot ${i+1}`} class="w-full h-full object-cover" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class="prose dark:prose-invert max-w-none text-sm md:text-base text-muted-foreground-custom/90 leading-relaxed mb-10">
|
||||
<h3 class="text-lg font-bold text-foreground-custom mb-3">Project Description</h3>
|
||||
<p>{project.description}</p>
|
||||
</div>
|
||||
|
||||
{/* Key Highlights / Impact */}
|
||||
{project.highlights && project.highlights.length > 0 && (
|
||||
<div class="mb-8 pt-6 border-t border-border/10">
|
||||
<h3 class="text-xs font-bold text-muted-foreground-custom uppercase tracking-wider mb-3">
|
||||
Key Architectural Highlights & Metrics
|
||||
</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{project.highlights.map((h) => (
|
||||
<span
|
||||
key={h}
|
||||
class="impact-pill"
|
||||
>
|
||||
<svg class="w-3 h-3 text-primary-custom flex-shrink-0" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
|
||||
</svg>
|
||||
{h}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tech Stack List */}
|
||||
<div class="mb-10 pt-6 border-t border-border/10">
|
||||
<h3 class="text-xs font-bold text-muted-foreground-custom uppercase tracking-wider mb-4">
|
||||
Technologies Employed
|
||||
</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{project.technologies.map((t) => (
|
||||
<span
|
||||
key={t}
|
||||
class="text-xs font-semibold px-3.5 py-1.5 rounded-xl bg-muted-custom/45 text-muted-foreground-custom border border-border/10 font-mono"
|
||||
>
|
||||
{t}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Launch Links */}
|
||||
<div class="flex flex-col sm:flex-row items-center gap-4 pt-6 border-t border-border/10">
|
||||
{project.repositoryUrl && (
|
||||
<a
|
||||
href={project.repositoryUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="w-full sm:w-auto px-6 py-3.5 rounded-xl glass-card text-foreground-custom font-semibold text-xs border border-border/15 hover:bg-muted-custom/30 hover:scale-[1.01] active:scale-[0.99] transition-transform flex items-center justify-center gap-2"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
||||
</svg>
|
||||
Source Repository
|
||||
</a>
|
||||
)}
|
||||
{/* Primary Quick Action Buttons */}
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
{project.liveUrl && (
|
||||
<a
|
||||
href={project.liveUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="w-full sm:w-auto px-6 py-3.5 rounded-xl bg-primary-custom text-primary-foreground font-semibold text-xs shadow-md hover:scale-[1.01] active:scale-[0.99] transition-transform flex items-center justify-center gap-2 sm:ml-auto"
|
||||
class="inline-flex items-center gap-2 px-5 py-2.5 rounded-xl bg-primary-custom text-white text-xs sm:text-sm font-semibold hover:bg-primary-custom/90 transition-all shadow-sm hover:shadow hover:-translate-y-0.5 active:translate-y-0"
|
||||
>
|
||||
Launch Live Demo
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2.5" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
|
||||
Demo
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2.5" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
|
||||
</svg>
|
||||
</a>
|
||||
)}
|
||||
|
||||
{project.repositoryUrl && (
|
||||
<a
|
||||
href={project.repositoryUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="inline-flex items-center gap-2 px-5 py-2.5 rounded-xl border border-border-custom hover:border-primary-custom/40 bg-card-custom text-foreground-custom text-xs sm:text-sm font-mono font-medium hover:text-primary-custom transition-all"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
||||
</svg>
|
||||
Code
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</article>
|
||||
</header>
|
||||
|
||||
{/* Featured Hero Cover Image Viewport */}
|
||||
{project.coverImage && (
|
||||
<div class="relative aspect-[16/9] w-full rounded-2xl md:rounded-3xl overflow-hidden border border-border-custom bg-muted-custom/20 mb-12 shadow-xl group">
|
||||
<img
|
||||
src={project.coverImage}
|
||||
alt={project.title}
|
||||
class="w-full h-full object-cover group-hover:scale-[1.01] transition-transform duration-500"
|
||||
/>
|
||||
{project.status && (
|
||||
<div class="absolute top-4 left-4 px-3 py-1 rounded-full bg-background-custom/85 backdrop-blur-md border border-border-custom text-xs font-mono font-medium text-emerald-500 dark:text-emerald-400 flex items-center gap-1.5 shadow-sm">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse" />
|
||||
{project.status}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Spread Multi-Column Responsive Layout */}
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-10 lg:gap-12 items-start">
|
||||
|
||||
{/* LEFT / MAIN COLUMN (8 cols): Overview, Highlights, Gallery */}
|
||||
<div class="lg:col-span-8 space-y-12">
|
||||
|
||||
{/* Section: Overview / Description */}
|
||||
<section>
|
||||
<h2 class="text-xl sm:text-2xl font-bold font-heading text-foreground-custom mb-4 flex items-center gap-2">
|
||||
<span class="w-2 h-2 rounded-full bg-primary-custom" />
|
||||
Overview
|
||||
</h2>
|
||||
<div class="text-base sm:text-lg text-muted-foreground-custom leading-relaxed space-y-4 font-normal">
|
||||
<p>{project.description || project.summary}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Section: Key Highlights & Features */}
|
||||
{project.highlights && project.highlights.length > 0 && (
|
||||
<section>
|
||||
<h2 class="text-xl sm:text-2xl font-bold font-heading text-foreground-custom mb-6 flex items-center gap-2">
|
||||
<span class="w-2 h-2 rounded-full bg-primary-custom" />
|
||||
Key Highlights
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{project.highlights.map((highlight) => (
|
||||
<div
|
||||
key={highlight}
|
||||
class="p-4 sm:p-5 rounded-2xl border border-border-custom bg-card-custom hover:border-primary-custom/40 transition-all flex items-start gap-3.5 shadow-xs"
|
||||
>
|
||||
<div class="w-6 h-6 rounded-lg bg-primary-custom/10 text-primary-custom flex items-center justify-center flex-shrink-0 mt-0.5">
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2.5" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="m4.5 12.75 6 6 9-13.5" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-foreground-custom leading-snug">
|
||||
{highlight}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Section: Gallery Previews */}
|
||||
{project.gallery && project.gallery.length > 0 && (
|
||||
<section>
|
||||
<h2 class="text-xl sm:text-2xl font-bold font-heading text-foreground-custom mb-6 flex items-center gap-2">
|
||||
<span class="w-2 h-2 rounded-full bg-primary-custom" />
|
||||
Gallery & Screenshots
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{project.gallery.map((imgUrl, i) => (
|
||||
<div
|
||||
key={i}
|
||||
class="relative aspect-[16/10] rounded-2xl overflow-hidden border border-border-custom bg-muted-custom/20 shadow-sm group"
|
||||
>
|
||||
<img
|
||||
src={imgUrl}
|
||||
alt={`${project.title} screenshot ${i + 1}`}
|
||||
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
{/* RIGHT / SIDEBAR COLUMN (4 cols): Sticky Specifications & Tech Stack */}
|
||||
<aside class="lg:col-span-4 space-y-6 lg:sticky lg:top-24">
|
||||
|
||||
{/* Project Details Specifications Card */}
|
||||
<div class="bento-card p-6 border border-border-custom space-y-5">
|
||||
<h3 class="text-xs font-mono uppercase tracking-wider text-muted-foreground-custom font-semibold">
|
||||
Project Details
|
||||
</h3>
|
||||
|
||||
<div class="divide-y divide-border-custom/50 text-sm">
|
||||
{project.category && (
|
||||
<div class="flex items-center justify-between py-2.5">
|
||||
<span class="text-muted-foreground-custom text-xs font-mono">Category</span>
|
||||
<span class="text-foreground-custom font-medium text-xs text-right">{project.category}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{project.status && (
|
||||
<div class="flex items-center justify-between py-2.5">
|
||||
<span class="text-muted-foreground-custom text-xs font-mono">Status</span>
|
||||
<span class="text-emerald-500 dark:text-emerald-400 font-mono text-xs font-semibold">{project.status}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{project.publishedAt && (
|
||||
<div class="flex items-center justify-between py-2.5">
|
||||
<span class="text-muted-foreground-custom text-xs font-mono">Year</span>
|
||||
<span class="text-foreground-custom font-mono text-xs">{project.publishedAt}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class="flex items-center justify-between py-2.5">
|
||||
<span class="text-muted-foreground-custom text-xs font-mono">Type</span>
|
||||
<span class="text-foreground-custom font-medium text-xs">Software Application</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Technologies Used Card */}
|
||||
<div class="bento-card p-6 border border-border-custom space-y-4">
|
||||
<h3 class="text-xs font-mono uppercase tracking-wider text-muted-foreground-custom font-semibold">
|
||||
Technologies Used
|
||||
</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{project.technologies.map((t) => (
|
||||
<span
|
||||
key={t}
|
||||
class="px-3 py-1.5 rounded-lg bg-muted-custom/40 border border-border-custom text-xs font-mono font-medium text-foreground-custom hover:border-primary-custom/40 transition-colors"
|
||||
>
|
||||
{t}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons in Sidebar */}
|
||||
<div class="bento-card p-6 border border-border-custom space-y-3">
|
||||
<h3 class="text-xs font-mono uppercase tracking-wider text-muted-foreground-custom font-semibold mb-3">
|
||||
Links
|
||||
</h3>
|
||||
{project.liveUrl && (
|
||||
<a
|
||||
href={project.liveUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="w-full py-2.5 px-4 rounded-xl bg-primary-custom text-white text-xs font-semibold hover:bg-primary-custom/90 transition-all text-center flex items-center justify-center gap-2 shadow-sm"
|
||||
>
|
||||
Demo
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2.5" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
|
||||
</svg>
|
||||
</a>
|
||||
)}
|
||||
{project.repositoryUrl && (
|
||||
<a
|
||||
href={project.repositoryUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="w-full py-2.5 px-4 rounded-xl border border-border-custom hover:border-primary-custom/40 text-xs font-mono font-medium text-foreground-custom hover:text-primary-custom transition-all text-center flex items-center justify-center gap-2"
|
||||
>
|
||||
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
||||
</svg>
|
||||
Code
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</aside>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Bottom Adjacent Navigation Strip */}
|
||||
<div class="border-t border-border-custom mt-16 pt-8 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
{prevProject ? (
|
||||
<a
|
||||
href={`/projects/${prevProject.slug}`}
|
||||
class="inline-flex items-center gap-2 text-xs font-mono text-muted-foreground-custom hover:text-foreground-custom transition-colors group"
|
||||
>
|
||||
<svg class="w-4 h-4 group-hover:-translate-x-1 transition-transform" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" />
|
||||
</svg>
|
||||
<span>Previous Project: <strong class="text-foreground-custom font-semibold">{prevProject.title}</strong></span>
|
||||
</a>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
|
||||
<a
|
||||
href="/projects"
|
||||
class="px-4 py-2 rounded-xl bg-card-custom border border-border-custom hover:border-primary-custom/40 text-xs font-mono font-medium text-muted-foreground-custom hover:text-foreground-custom transition-all"
|
||||
>
|
||||
Back to Projects
|
||||
</a>
|
||||
|
||||
{nextProject ? (
|
||||
<a
|
||||
href={`/projects/${nextProject.slug}`}
|
||||
class="inline-flex items-center gap-2 text-xs font-mono text-muted-foreground-custom hover:text-foreground-custom transition-colors group sm:ml-auto"
|
||||
>
|
||||
<span>Next Project: <strong class="text-foreground-custom font-semibold">{nextProject.title}</strong></span>
|
||||
<svg class="w-4 h-4 group-hover:translate-x-1 transition-transform" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3" />
|
||||
</svg>
|
||||
</a>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
Reference in new issue
Block a user