Place brand mark in header, footer, and hero; ship PNG favicons, apple-touch icon, PWA manifest, and 1200×630 Open Graph card.
114 lines
3.6 KiB
TypeScript
114 lines
3.6 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { Link } from "react-router-dom";
|
|
import { api, type Project, type SiteSettings, type Category } from "../lib/api";
|
|
import { ProjectCard } from "../components/ProjectCard";
|
|
import "./HomePage.scss";
|
|
|
|
export function HomePage() {
|
|
const [site, setSite] = useState<SiteSettings | null>(null);
|
|
const [featured, setFeatured] = useState<Project[]>([]);
|
|
const [recent, setRecent] = useState<Project[]>([]);
|
|
const [categories, setCategories] = useState<Category[]>([]);
|
|
|
|
useEffect(() => {
|
|
Promise.all([
|
|
api.site(),
|
|
api.featured().catch(() => [] as Project[]),
|
|
api.projects({ sort: "date", perPage: 6 }),
|
|
api.categories(),
|
|
]).then(([s, f, r, c]) => {
|
|
setSite(s);
|
|
setFeatured(f);
|
|
setRecent(r.data);
|
|
setCategories(c);
|
|
});
|
|
}, []);
|
|
|
|
const heroTitle = site?.heroTitle || site?.name || "jmartgraphix";
|
|
const heroSub = site?.heroSubtitle || "3D · Design · Motion · Craft";
|
|
|
|
return (
|
|
<div className="home page-enter">
|
|
<section className="home-hero">
|
|
<div className="container">
|
|
<img
|
|
src="/logo.png"
|
|
alt="jmartgraphix"
|
|
className="home-hero__logo"
|
|
width={96}
|
|
height={96}
|
|
decoding="async"
|
|
/>
|
|
<p className="home-hero__eyebrow">Portfolio</p>
|
|
<h1>
|
|
<span className="home-hero__serif">{heroTitle}</span>
|
|
</h1>
|
|
<p className="home-hero__sub">{heroSub}</p>
|
|
<p className="home-hero__about">
|
|
{site?.about ||
|
|
"A curated collection of 3D modeling, CAD, animation, and visual design."}
|
|
</p>
|
|
<div className="home-hero__actions">
|
|
<Link to="/portfolio" className="btn btn--primary">
|
|
View portfolio
|
|
</Link>
|
|
<Link to="/resume" className="btn btn--ghost">
|
|
Resume
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="home-cats container">
|
|
<div className="home-cats__list">
|
|
{categories.map((c) => (
|
|
<Link key={c.id} to={`/portfolio/${c.slug}`} className="home-cats__chip">
|
|
{c.name}
|
|
{typeof c.projectCount === "number" && (
|
|
<span>{c.projectCount}</span>
|
|
)}
|
|
</Link>
|
|
))}
|
|
<Link to="/portfolio/game-dev" className="home-cats__chip home-cats__chip--view">
|
|
Game Dev view
|
|
</Link>
|
|
<Link to="/portfolio/engineering" className="home-cats__chip home-cats__chip--view">
|
|
Engineering view
|
|
</Link>
|
|
</div>
|
|
</section>
|
|
|
|
{featured.length > 0 && (
|
|
<section className="container home-section">
|
|
<div className="home-section__head">
|
|
<h2>Featured</h2>
|
|
<Link to="/portfolio">All work →</Link>
|
|
</div>
|
|
<div className="project-grid">
|
|
{featured.map((p, i) => (
|
|
<ProjectCard key={p.id} project={p} index={i} />
|
|
))}
|
|
</div>
|
|
</section>
|
|
)}
|
|
|
|
<section className="container home-section">
|
|
<div className="home-section__head">
|
|
<h2>Recent</h2>
|
|
</div>
|
|
{recent.length === 0 ? (
|
|
<p className="home-empty">
|
|
Projects will appear here once published.
|
|
</p>
|
|
) : (
|
|
<div className="project-grid">
|
|
{recent.map((p, i) => (
|
|
<ProjectCard key={p.id} project={p} index={i} />
|
|
))}
|
|
</div>
|
|
)}
|
|
</section>
|
|
</div>
|
|
);
|
|
}
|