diff --git a/package-lock.json b/package-lock.json index 899270a..d314d1a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,8 @@ "prism-react-renderer": "^2.1.0", "react": "^18.0.0", "react-dom": "^18.0.0", - "react-scroll-parallax": "^3.4.5" + "react-scroll-parallax": "^3.4.5", + "react-virtualized-auto-sizer": "^1.0.24" }, "devDependencies": { "@docusaurus/module-type-aliases": "^3.1.1", @@ -12737,6 +12738,15 @@ "react-dom": ">=16.6.0" } }, + "node_modules/react-virtualized-auto-sizer": { + "version": "1.0.24", + "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.24.tgz", + "integrity": "sha512-3kCn7N9NEb3FlvJrSHWGQ4iVl+ydQObq2fHMn12i5wbtm74zHOPhz/i64OL3c1S1vi9i2GXtZqNqUJTQ+BnNfg==", + "peerDependencies": { + "react": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0", + "react-dom": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", diff --git a/package.json b/package.json index a887b03..62f6e08 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,8 @@ "prism-react-renderer": "^2.1.0", "react": "^18.0.0", "react-dom": "^18.0.0", - "react-scroll-parallax": "^3.4.5" + "react-scroll-parallax": "^3.4.5", + "react-virtualized-auto-sizer": "^1.0.24" }, "devDependencies": { "@docusaurus/module-type-aliases": "^3.1.1", diff --git a/src/components/AppBar.tsx b/src/components/AppBar.tsx index 9104f74..0be0b5d 100644 --- a/src/components/AppBar.tsx +++ b/src/components/AppBar.tsx @@ -8,7 +8,6 @@ import { import { Box, Button, - Fade, IconButton, Stack, Typography, @@ -17,9 +16,9 @@ import { import PopupState, { bindTrigger } from "material-ui-popup-state"; import { createPortal } from "react-dom"; import l10n from "../pages/en-au.json"; -import { space } from "./space"; import { Logo } from "./Logo"; import { useMode } from "./ModeContext"; +import { space } from "./space"; import { usePaper } from "./theme"; import { useSm } from "./useSm"; @@ -73,84 +72,79 @@ export function AppBar() { zIndex: (t) => t.zIndex.appBar, }} > - - - - - - {sm ? ( - <> - - {openPosthoc} - - {(state) => ( - <> - - - - {createPortal( - - t.transitions.create([ - "opacity", - "backdrop-filter", - ]), - position: "fixed", - zIndex: (t) => t.zIndex.modal, - top: 0, - left: 0, - width: "100dvw", - height: "100vh", - borderRadius: 0, - }} - > - - - - - {darkToggle} - {menu} - {openPosthoc} - - , - document.body - )} - - )} - - - ) : ( - <> - {menu} - {space()} - {openPosthoc} - {darkToggle} - - )} - - + + + + + {sm ? ( + <> + + {openPosthoc} + + {(state) => ( + <> + + + + {createPortal( + + t.transitions.create(["opacity", "backdrop-filter"]), + position: "fixed", + zIndex: (t) => t.zIndex.modal, + top: 0, + left: 0, + width: "100dvw", + height: "100vh", + borderRadius: 0, + }} + > + + + + + {darkToggle} + {menu} + {openPosthoc} + + , + document.body + )} + + )} + + + ) : ( + <> + {menu} + {space()} + {openPosthoc} + {darkToggle} + + )} + ); } diff --git a/src/components/Gallery.tsx b/src/components/Gallery.tsx index f2ad57e..f5b4ef9 100644 --- a/src/components/Gallery.tsx +++ b/src/components/Gallery.tsx @@ -1,43 +1,283 @@ -import { Box, Stack, Tab, Tabs } from "@mui/material"; -import { map } from "lodash"; -import { useState } from "react"; -import { usePaper } from "./theme"; +import { WorkspacesOutlined } from "@mui/icons-material"; +import { + Avatar, + Box, + Button, + ButtonBase, + Stack, + Typography, +} from "@mui/material"; +import { useEffect, useState } from "react"; +import ReactVirtualizedAutoSizer from "react-virtualized-auto-sizer"; import l10n from "../pages/en-au.json"; +import { useMode } from "./ModeContext"; +import { useSm } from "./useSm"; +import { clamp } from "lodash"; +import { usePaper } from "./theme"; + +const isVisible = (d: HTMLDivElement) => { + var rect = d.getBoundingClientRect(); + var viewHeight = Math.max( + document.documentElement.clientHeight, + window.innerHeight + ); + return !(rect.bottom < 0 || rect.top - viewHeight >= 0); +}; + +const center = (d: HTMLDivElement) => { + const box = d.getBoundingClientRect(); + return box.left + box.width / 2; +}; + +const SCROLL_FAC_NEAR = 1 / 500; export function Gallery() { + const [mode] = useMode(); const paper = usePaper(); - const [selected, setSelected] = useState(0); + const sm = useSm(); + const [ref, setRef] = useState(null); + useEffect(() => { + if (ref) { + const nodes = new Set(); + let cancelled = false; + const f = () => { + const basis = center(ref); + nodes.forEach((c) => { + const a = center(c); + c.style.setProperty( + "--factor-near", + `${clamp(-(((a - basis) * SCROLL_FAC_NEAR) ** 2) + 1, 0, 1)}` + ); + c.style.setProperty("--factor", `${a - basis}`); + }); + if (!cancelled) { + requestAnimationFrame(f); + } + }; + const observer = new IntersectionObserver( + (entries) => { + for (const entry of entries) { + if (entry.isIntersecting) { + nodes.add(entry.target as HTMLDivElement); + } else { + nodes.delete(entry.target as HTMLDivElement); + } + } + }, + { root: ref } + ); + ref.childNodes.forEach((c) => { + observer.observe(c as HTMLDivElement); + }); + + requestAnimationFrame(f); + return () => { + observer.disconnect(); + cancelled = true; + }; + } + }, [ref]); return ( - - - - - setSelected(i)} - sx={{ - ...paper(0), - borderRadius: 32, - mt: 4, - mx: 2, - " button.Mui-selected": { color: "text.primary" }, - }} - > - {map(l10n.gallery, ({ label, url }, i) => ( - - ))} - - + + {({ width }) => ( + + + + {l10n.gallery.map( + ({ label, url, description, workspace, author }) => ( + { + (e.target as HTMLDivElement).scrollIntoView({ + behavior: "smooth", + block: "nearest", + }); + }} + sx={{ + p: sm ? 1 : 4, + minWidth: width + (sm ? 1 : 4) * 8 * 2, + scrollSnapAlign: "center", + }} + > + + {sm ? ( + + ) : ( + + )} + + + {label} + + + + + {author ?? "Anonymous"} + + + + {description} + + + + + + ) + )} + { + (e.target as HTMLDivElement).scrollIntoView({ + behavior: "smooth", + block: "nearest", + }); + }} + sx={{ + p: sm ? 1 : 4, + minWidth: width + (sm ? 1 : 4) * 8 * 2, + scrollSnapAlign: "center", + }} + > + + + See more examples + + + + + + + + )} + ); } diff --git a/src/components/theme.ts b/src/components/theme.ts index 7e60b28..a486d0a 100644 --- a/src/components/theme.ts +++ b/src/components/theme.ts @@ -78,7 +78,7 @@ export const makeTheme = (mode: "light" | "dark", theme?: AccentColor) => }, h2: { fontSize: "max(26px, min(32px, 4vw))", - fontWeight: mode === "dark" ? 400 : 500, + fontWeight: mode === "dark" ? 400 : 420, fontFamily: headingFamily, }, h3: { diff --git a/src/css/custom.css b/src/css/custom.css index edaca98..d63c978 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -370,7 +370,7 @@ b { @media (pointer: fine) { *::-webkit-scrollbar { width: 6px; - height: 14px; + height: 6px; background-color: transparent; } *::-webkit-scrollbar-track { @@ -379,9 +379,9 @@ b { *::-webkit-scrollbar-thumb { transition: background-color 300ms ease; background-color: color-mix(in srgb, currentColor 45%, transparent); - min-height: 64px; padding: 2px; background-clip: padding-box; + border-radius: 6px; } *::-webkit-scrollbar-thumb:hover { background-color: color-mix(in srgb, currentColor 60%, transparent); diff --git a/src/pages/en-au.json b/src/pages/en-au.json index a608e48..edd247d 100644 --- a/src/pages/en-au.json +++ b/src/pages/en-au.json @@ -24,6 +24,7 @@ "gallery": [ { "url": "/img/gallery/complex-view.png", + "workspace": "content/dps.workspace", "label": "Post-game analysis, StarCraft" }, { @@ -32,7 +33,10 @@ }, { "url": "/img/gallery/image-7.png", - "label": "Compression algorithm analysis, RLE" + "label": "Compression algorithm analysis", + "workspace": null, + "author": "Mark Carlson", + "description": "Mark used Posthoc to showcase an encoding scheme for black-and-white video based on run-length encoding." }, { "url": "/img/gallery/image-3.png", diff --git a/static/content/dps.workspace b/static/content/dps.workspace new file mode 100644 index 0000000..af022d7 Binary files /dev/null and b/static/content/dps.workspace differ