yet more mobile improvements.

This commit is contained in:
Hunter W.
2026-06-20 18:07:14 -04:00
parent 7169f76ddc
commit c09094b968
6 changed files with 93 additions and 87 deletions

View File

@@ -188,6 +188,7 @@ nav {
nav {
font-size: 16px;
}
.project-grid {
scrollbar-width: 10px;
}

View File

@@ -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"
}}>
<div
style={{display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 12
}}>
<div className="project-card-title-year-box">
<div
style={{fontFamily: "var(--mono)", fontSize: 18, fontWeight: 700, color: hovered ? "var(--accent)" : "var(--fg)", transition: "color 0.2s"}}>
{project.title}
@@ -142,7 +140,8 @@ export default function ProjectCard({ project, visible, selected = false, setSel
</div>
</div>
{projectTagsComponent(project)}
<p style={{fontFamily: "var(--sans)", fontSize: 14, lineHeight: 1.6, color: "var(--fg-secondary)", margin: "0 0 16px 0"}}>
<p
style={{fontFamily: "var(--sans)", fontSize: 14, lineHeight: 1.6, color: "var(--fg-secondary)", margin: "0 0 16px 0"}}>
{project.description}
</p>
@@ -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</Link>
</div>
);

View File

@@ -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<EnlargeLevel>(0);
const [isEnlarged, setIsEnlarged] = useState<Boolean>(false);
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const videoRef = useRef<HTMLVideoElement | null>(null);
const fsPortalRef = useRef<HTMLDivElement | null>(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 (
<button
<div
className="gallery-action-button"
style={{
top: 8,
right: 8
right: 8,
fontFamily: 'var(--mono)',
fontSize: 'var(--text-xs)',
color: 'var(--muted)',
textWrap: 'nowrap',
cursor: 'pointer',
verticalAlign: 'middle'
}}
onClick={handleEnlarge}
title="Enlarge image"
>
<button
onClick={()=>setIsEnlarged(true)}
title="Expand media"
>
<svg className="action-button-svg" stroke="current" width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 5V1H5" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
@@ -73,6 +63,8 @@ export default function SimpleGallery({
<path d="M13 13L8.5 8.5" strokeWidth="1.5" strokeLinecap="round" />
</svg>
</button>
Expand
</div>
);
}
@@ -84,7 +76,7 @@ export default function SimpleGallery({
bottom: 8,
right: 8
}}
onClick={handleShrink}
onClick={()=>setIsEnlarged(false)}
title="Shrink image"
>
<svg viewBox="0 0 60 60" xmlns="http://www.w3.org/2000/svg" width="14" height="14">
@@ -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()}
</div>
);
}
function fullscreenOverlay() {
if (enlargeLevel !== 2) return null;
if (!isEnlarged) return null;
setTimeout(()=>{
if(!fsPortalRef.current) return
fsPortalRef.current.style.opacity = "1"
}, 50);
return createPortal(
<div
ref={fsPortalRef}
className="gallery-full-screen-portal"
onClick={handleFullscreenClose}
style={{
position: "fixed",
@@ -180,7 +158,7 @@ export default function SimpleGallery({
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: 24,
padding: 24
}}
>
@@ -258,7 +236,11 @@ export default function SimpleGallery({
if (totalMedia <= 1) return null;
return (
<div style={{ display: "flex", gap: 6, marginTop: 8, flexWrap: "wrap" }}>
<div style={{
display: "flex",
gap: 6,
marginTop: 8,
flexWrap: "wrap" }}>
{images.map((src, i) => (
<Image
@@ -323,14 +305,13 @@ export default function SimpleGallery({
// close fullscreen on Escape
useEffect(() => {
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 (
<>

View File

@@ -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.
<blockquote>A short video of the platform's operation.
<video width="640" height="360" controls>

View File

@@ -1,33 +1,40 @@
.gallery-full-screen-portal {
transition: all 0.5s cubic-bezier(0.075, 0.82, 0.165, 1);
opacity: 0;
}
.gallery-action-button {
position: absolute;
cursor: pointer;
display: flex;
padding: 0;
}
.gallery-action-button button {
width: 28;
height: 28;
border-radius: var(--radius-sm);
border: 1px solid var(--accent);
background: var(--surface);
color: var(--fg);
cursor: pointer;
display: flex;
background-color: var(--surface);
align-items: center;
justify-content: center;
padding: 0;
color: var(--fg);
z-index: 1001;
cursor: pointer;
transition: background 0.15s ease, border-color 0.15s ease;
margin-right: 0.3em;
}
.gallery-action-button:hover {
background: var(---surface-hover);
.gallery-action-button button:hover {
filter:brightness(120%)
}
.action-button-svg {
stroke: var(--fg-secondary);
}
@media (max-width: 768px) {
.project-card {
height: 100%;
max-height: 80vh;
}
}

24
styles/project-card.css Normal file
View File

@@ -0,0 +1,24 @@
.project-card{
p {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
line-clamp: 3;
text-overflow: ellipsis;
overflow:hidden;
}
}
.project-card-title-year-box {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12;
}
@media (max-width: 768px) {
.project-card-title-year-box {
flex-direction: column;
}
}