From c09094b968044af340b43888753987812d7f3928 Mon Sep 17 00:00:00 2001 From: "Hunter W." Date: Sat, 20 Jun 2026 18:07:14 -0400 Subject: [PATCH] yet more mobile improvements. --- app/globals.css | 7 ++- components/ProjectCard.tsx | 15 ++--- components/SimpleGallery.tsx | 103 ++++++++++++++--------------------- data/content.ts | 4 +- styles/gallery.css | 27 +++++---- styles/project-card.css | 24 ++++++++ 6 files changed, 93 insertions(+), 87 deletions(-) create mode 100644 styles/project-card.css diff --git a/app/globals.css b/app/globals.css index d6ad20e..b6f0e94 100644 --- a/app/globals.css +++ b/app/globals.css @@ -188,9 +188,10 @@ nav { nav { font-size: 16px; } -.project-grid { - scrollbar-width: 10px; -} + + .project-grid { + scrollbar-width: 10px; + } } diff --git a/components/ProjectCard.tsx b/components/ProjectCard.tsx index 2d5ff03..ede06f9 100644 --- a/components/ProjectCard.tsx +++ b/components/ProjectCard.tsx @@ -5,7 +5,7 @@ import type { Project } from "@/data/content"; import SimpleGallery from "./SimpleGallery"; import Link from "next/link"; import { relative } from "path"; - +import "../styles/project-card.css"; type Props = { project: Project; @@ -114,7 +114,7 @@ export default function ProjectCard({ project, visible, selected = false, setSel style={{ minWidth: "10em", maxWidth: "90vw", - overflow: "hidden", + textDecoration: "none", padding: 28, marginTop: 3, @@ -130,9 +130,7 @@ export default function ProjectCard({ project, visible, selected = false, setSel pointerEvents: "auto", cursor: "pointer" }}> -
+
{project.title} @@ -142,7 +140,8 @@ export default function ProjectCard({ project, visible, selected = false, setSel
{projectTagsComponent(project)} -

+

{project.description}

@@ -158,15 +157,11 @@ export default function ProjectCard({ project, visible, selected = false, setSel border: `1px solid ${hovered ? "var(--accent)" : "var(--border)"}`, backgroundColor: "var(--bg)", height: "3em", - display: "block", alignContent: "center", alignItems: "center", textAlign: "center", width: "10em", borderRadius: "var(--radius-md)", - position: "absolute", - right: "1em", - bottom: "1em" }} href={"/project/"+project.slug} >See More
); diff --git a/components/SimpleGallery.tsx b/components/SimpleGallery.tsx index ab2f199..e1e7f10 100644 --- a/components/SimpleGallery.tsx +++ b/components/SimpleGallery.tsx @@ -1,6 +1,6 @@ import Image from "next/image"; -import { useState, useEffect, useRef, useCallback } from "react"; +import { useState, useEffect, useRef } from "react"; import { createPortal } from "react-dom"; import '@/styles/gallery.css'; @@ -10,9 +10,6 @@ type Props = { title: string; }; -// 0 = inline default, 1 = inline enlarged, 2 = fullscreen overlay -type EnlargeLevel = 0 | 1 | 2; - export default function SimpleGallery({ images, videos = [], @@ -20,9 +17,10 @@ export default function SimpleGallery({ }: Props) { const [active, setActive] = useState(0); - const [enlargeLevel, setEnlargeLevel] = useState(0); + const [isEnlarged, setIsEnlarged] = useState(false); const timerRef = useRef | null>(null); const videoRef = useRef(null); + const fsPortalRef = useRef(null); const totalMedia = images.length + videos.length; const isVideo = active >= images.length; @@ -31,24 +29,8 @@ export default function SimpleGallery({ if (totalMedia === 0) return null; - function handleEnlarge() { - if (enlargeLevel === 0) { - setEnlargeLevel(1); - } else if (enlargeLevel === 1) { - setEnlargeLevel(2); - } - } - - function handleShrink() { - if (enlargeLevel === 2) { - setEnlargeLevel(1); - } else if (enlargeLevel === 1) { - setEnlargeLevel(0); - } - } - function handleFullscreenClose() { - setEnlargeLevel(1); + setIsEnlarged(false); } function handleThumbnailClick(index: number) { @@ -57,14 +39,22 @@ export default function SimpleGallery({ function expandButton() { return ( - + Expand + ); } @@ -84,7 +76,7 @@ export default function SimpleGallery({ bottom: 8, right: 8 }} - onClick={handleShrink} + onClick={()=>setIsEnlarged(false)} title="Shrink image" > @@ -96,17 +88,8 @@ export default function SimpleGallery({ } function inlineMedia() { - const enlarged = enlargeLevel === 1; - const imageStyle: React.CSSProperties = enlarged ? { - maxWidth: "100%", - maxHeight: "90vh", - height: "auto", - borderRadius: 6, - objectFit: "contain", - boxShadow: "0 8px 40px rgba(0,0,0,0.6)", - transition: "all 0.3s ease", - } : { + const imageStyle: React.CSSProperties = { width: "100%", borderRadius: 4, objectFit: "cover", @@ -116,14 +99,7 @@ export default function SimpleGallery({ zIndex: 1, }; - const videoStyle: React.CSSProperties = enlarged ? { - maxWidth: "100%", - maxHeight: "90vh", - objectFit: "contain" as const, - borderRadius: 6, - boxShadow: "0 8px 40px rgba(0,0,0,0.6)", - transition: "all 0.3s ease", - } : { + const videoStyle: React.CSSProperties = { width: "100%", borderRadius: 4, maxHeight: 250, @@ -155,22 +131,24 @@ export default function SimpleGallery({ /> )} - {enlargeLevel === 0 && expandButton()} - {enlargeLevel === 1 && ( - <> - {shrinkButton()} - {expandButton()} - - )} + {!isEnlarged && expandButton()} + {isEnlarged && shrinkButton()} ); } function fullscreenOverlay() { - if (enlargeLevel !== 2) return null; + if (!isEnlarged) return null; + setTimeout(()=>{ + if(!fsPortalRef.current) return + fsPortalRef.current.style.opacity = "1" + }, 50); + return createPortal(
@@ -258,7 +236,11 @@ export default function SimpleGallery({ if (totalMedia <= 1) return null; return ( -
+
{images.map((src, i) => ( { - if (enlargeLevel === 0) return; + if (!isEnlarged) return; function onKeyDown(e: KeyboardEvent) { - if (e.key === "Escape" && enlargeLevel === 2) handleFullscreenClose(); - if (e.key === "Escape" && enlargeLevel === 1) handleShrink(); + if (e.key === "Escape" && isEnlarged) handleFullscreenClose(); } window.addEventListener("keydown", onKeyDown); return () => window.removeEventListener("keydown", onKeyDown); - }, [enlargeLevel]); + }, [isEnlarged]); return ( <> diff --git a/data/content.ts b/data/content.ts index 8cc4ddd..6438bb3 100644 --- a/data/content.ts +++ b/data/content.ts @@ -105,9 +105,7 @@ The Flow-based Agent Managemment (FAM) platform is designed to streamline high-v ## Private Repository -This project was tailored to the needs of a large organization. As such, and naturally so — the source for this project is private. - -I am happy to discuss the architecture and design decisions made for this project in detail during an interview. +The complete implementation of this project was tailored to the needs of a large organization. As such, the source for this project is private.
A short video of the platform's operation.