// work.jsx — full portfolio
const { useState: useStateW, useMemo: useMemoW } = React;
function Work({ setRoute }) {
const [filter, setFilter] = useStateW("All");
const [active, setActive] = useStateW(null);
const [view, setView] = useStateW("mosaic"); // mosaic | grid
const filters = ["All", "Minimal", "Kinetic", "3D"];
const items = useMemoW(() => {
if (filter === "All") return VM_PORTFOLIO;
return VM_PORTFOLIO.filter((it) => it.style === filter);
}, [filter]);
return (
{/* header */}
— Index of work
Recent{" "}
work
logos
in motion.
Every project below was animated from scratch in After Effects.
Hover to preview · click to open full-quality.
{/* controls */}
{filters.map((f) => {
const active = filter === f;
return (
);
})}
VIEW —
{/* grid */}
{/* CTA strip */}
— Next slot opens June 3
Want yours in the next batch?
{active && setActive(null)} />}
);
}
function iconBtnStyle(active) {
return {
width: 32, height: 32, borderRadius: 8,
display: "flex", alignItems: "center", justifyContent: "center",
background: active ? "var(--glass-hi)" : "transparent",
color: active ? "var(--ink)" : "var(--ink-3)",
border: "1px solid",
borderColor: active ? "var(--line-2)" : "transparent",
cursor: "pointer"
};
}
function MosaicGrid({ items, onPlay }) {
return (
{items.map((it, i) => {
const span = mosaicSpan(i);
return (
onPlay(it)} big={span.c >= 4} />
);
})}
);
}
function mosaicSpan(i) {
// pattern: big-big-small repeats
const pattern = [
{ c: 4, r: 2 }, { c: 2, r: 1 }, { c: 2, r: 1 },
{ c: 3, r: 2 }, { c: 3, r: 2 },
{ c: 2, r: 1 }, { c: 2, r: 1 }, { c: 2, r: 1 }];
return pattern[i % pattern.length];
}
function mosaicAspect(i) {
const span = mosaicSpan(i);
// Big tiles wider/short
if (span.c >= 4) return "16/9";
if (span.c === 3) return "16/10";
return "4/3";
}
function PlainGrid({ items, onPlay }) {
return (
{items.map((it, i) =>
onPlay(it)} />
)}
);
}
Object.assign(window, { Work });