diff --git a/package-lock.json b/package-lock.json index d314d1a..4f747c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,8 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-scroll-parallax": "^3.4.5", - "react-virtualized-auto-sizer": "^1.0.24" + "react-virtualized-auto-sizer": "^1.0.24", + "resolve-url": "^0.2.1" }, "devDependencies": { "@docusaurus/module-type-aliases": "^3.1.1", @@ -13177,6 +13178,12 @@ "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" }, + "node_modules/resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", + "deprecated": "https://github.com/lydell/resolve-url#deprecated" + }, "node_modules/responselike": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", diff --git a/package.json b/package.json index 62f6e08..a245fe4 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,8 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-scroll-parallax": "^3.4.5", - "react-virtualized-auto-sizer": "^1.0.24" + "react-virtualized-auto-sizer": "^1.0.24", + "resolve-url": "^0.2.1" }, "devDependencies": { "@docusaurus/module-type-aliases": "^3.1.1", diff --git a/src/components/AppBar.tsx b/src/components/AppBar.tsx index 0be0b5d..5065026 100644 --- a/src/components/AppBar.tsx +++ b/src/components/AppBar.tsx @@ -85,6 +85,7 @@ export function AppBar() { maxWidth: "100%", height: 64, borderRadius: 32, + boxShadow: (t) => t.shadows[4], }} > diff --git a/src/components/Gallery.tsx b/src/components/Gallery.tsx index 6b1ddd0..2221cee 100644 --- a/src/components/Gallery.tsx +++ b/src/components/Gallery.tsx @@ -1,4 +1,4 @@ -import { WorkspacesOutlined } from "@mui/icons-material"; +import { LaunchOutlined, WorkspacesOutlined } from "@mui/icons-material"; import { Avatar, Box, @@ -6,277 +6,323 @@ import { ButtonBase, Stack, Typography, + alpha, + useMediaQuery, } from "@mui/material"; -import { useEffect, useState } from "react"; +import { clamp, defer, delay, floor } from "lodash"; +import { useEffect, useRef, 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); -}; +import { useSm } from "./useSm"; +import resolve from "resolve-url"; const center = (d: HTMLDivElement) => { const box = d.getBoundingClientRect(); return box.left + box.width / 2; }; -const SCROLL_FAC_NEAR = 1 / 500; +const SCROLL_FAC_NEAR = 1 / 1000; export function Gallery() { - const [mode] = useMode(); const paper = usePaper(); const sm = useSm(); + const lg = useMediaQuery("(min-width: 1200px)"); 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}`); + let basis: number; + let intersectionObserver: IntersectionObserver; + const nodes = new Set(); + const positions = new Map(); + const init = () => { + nodes.clear(); + const observer = new IntersectionObserver( + (entries) => { + for (const entry of entries) { + if (entry.target instanceof HTMLDivElement) { + if (entry.isIntersecting) { + entry.target.style.visibility = "visible"; + nodes.add(entry.target); + } else { + entry.target.style.visibility = "hidden"; + nodes.delete(entry.target); + } + } + } + }, + { root: ref } + ); + ref.childNodes.forEach((c) => { + if (c instanceof HTMLDivElement) { + observer.observe(c); + positions.set(c, c.offsetLeft + c.clientWidth / 2); + } }); + return observer; + }; + const refresh = () => { + intersectionObserver?.disconnect?.(); + intersectionObserver = init(); + basis = center(ref); + }; + const f = () => { if (!cancelled) { + nodes.forEach((c) => { + const a = basis - positions.get(c) + ref.scrollLeft; + c.style.setProperty( + "--factor-near", + `${clamp(-((a * SCROLL_FAC_NEAR) ** 2) + 1, 0, 1)}` + ); + c.style.setProperty("--factor", `${a}`); + }); 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); - }); - + const mutationObserver = new MutationObserver(refresh); + const resizeObserver = new ResizeObserver(refresh); + window.addEventListener("resize", refresh); + resizeObserver.observe(ref); + mutationObserver.observe(ref, { childList: true }); + refresh(); requestAnimationFrame(f); + return () => { - observer.disconnect(); + intersectionObserver.disconnect(); + mutationObserver.disconnect(); + resizeObserver.disconnect(); + window.removeEventListener("resize", refresh); cancelled = true; }; } }, [ref]); + useEffect(() => { + if (ref) { + ref.scrollLeft = ( + ref.childNodes.item(floor(ref.childNodes.length / 2)) as HTMLDivElement + ).offsetLeft; + } + }, [ref]); + const a = (width: number) => ( + { + (e.target as HTMLDivElement).scrollIntoView({ + behavior: "smooth", + block: "nearest", + }); + }} + sx={{ + p: lg ? 0 : width * 0.001, + minWidth: width + (lg ? 0 : width * 0.001) * 8 * 2, + scrollSnapAlign: "center", + }} + > + + + {l10n.galleryCallToAction} + + + + + ); return ( {({ width }) => ( - - - - {l10n.gallery.map( - ({ label, url, description, workspace, author }) => ( - { - (e.target as HTMLDivElement).scrollIntoView({ - behavior: "smooth", - block: "nearest", - }); - }} + + + {a(width)} + {l10n.gallery.map( + ({ label, url, description, workspace, author, tagline }, i) => ( + { + (e.target as HTMLDivElement).scrollIntoView({ + behavior: "smooth", + block: "nearest", + }); + }} + sx={{ + p: lg ? 0 : width * 0.001, + minWidth: width + (lg ? 0 : width * 0.001) * 8 * 2, + scrollSnapAlign: "center", + }} + > + + `0px 16px 32px ${alpha( + t.palette.background.default, + 0.25 + )}`, + aspectRatio: sm ? 10 / 16 : 16 / 10, + width: "100%", + borderRadius: 4, + position: "relative", + overflow: "hidden", + backgroundImage: `url(${url})`, + backgroundSize: "cover", + backgroundPosition: + "calc(50% + calc(var(--factor) * -0.5px)) 50%", + transform: lg + ? `scale(calc(90% + calc(10% * var(--factor-near))))` + : "none", }} > - - {sm ? ( - - ) : ( - - )} - + ) : ( + + )} + + + {tagline} + + + {label} + + - - {label} - - - - - {author ?? "Anonymous"} - - - - {description} + + + {author ?? "Anonymous"} - - - - ) - )} - { - (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 - - - - - - + + {description} + + + + + + ) + )} + {a(width)} + )} diff --git a/src/components/Hero.tsx b/src/components/Hero.tsx index 37eb563..3b62e04 100644 --- a/src/components/Hero.tsx +++ b/src/components/Hero.tsx @@ -14,7 +14,7 @@ export function Hero() { alignItems="center" sx={{ maxWidth: "100dvw", - height: "80svh", + height: "60svh", minHeight: 520, textAlign: "center", pt: 24, diff --git a/src/components/theme.ts b/src/components/theme.ts index a486d0a..7e60b28 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 : 420, + fontWeight: mode === "dark" ? 400 : 500, fontFamily: headingFamily, }, h3: { diff --git a/src/pages/en-au.json b/src/pages/en-au.json index edd247d..93c9a17 100644 --- a/src/pages/en-au.json +++ b/src/pages/en-au.json @@ -23,36 +23,44 @@ ], "gallery": [ { + "tagline": "Analysis", "url": "/img/gallery/complex-view.png", - "workspace": "content/dps.workspace", "label": "Post-game analysis, StarCraft" }, { - "url": "/img/gallery/astar.png", - "label": "Demonstration, A* search" - }, - { + "tagline": "Showcase", "url": "/img/gallery/image-7.png", - "label": "Compression algorithm analysis", + "label": "Video compression", "workspace": null, "author": "Mark Carlson", - "description": "Mark used Posthoc to showcase an encoding scheme for black-and-white video based on run-length encoding." + "description": "A simple encoding scheme for binary video based on run-length encoding." + }, + { + "tagline": "Demonstration", + "url": "/img/gallery/astar.png", + "label": "A* search", + "author": "ShortestPathLab", + "description": "Get to know Posthoc by taking apart and inspecting the classic A* algorithm on a small grid map." }, { + "tagline": "Validation", "url": "/img/gallery/image-3.png", - "label": "Debugging, duel euclidean path search" + "workspace": "/content/dps.workspace", + "label": "Duel euclidean path search" }, { + "tagline": "Debugging", "url": "/img/gallery/room-detection.png", - "label": "Debugging, room detection algorithm" + "label": "Room detection" } ], + "galleryCallToAction": "See more examples in Posthoc", "heroTitle": "Explore decision-making in search", "heroSubtitle": "Posthoc is a way to create simple and effective visualisations from logs to help you understand search.", "heroCallToAction": "Get started", "heroCallToActionUrl": "./docs/get-started", "endCallToActionTitle": "Ready to try Posthoc?", - "demoSectionTitle": "Watch our ICAPS 24 demo", + "demoSectionTitle": "Showcased @ ICAPS24", "demoSectionSubtitle": "We're excited to present Posthoc to the search and planning community", "featuresSectionTitle": "Features", "featuresSectionSubtitle": "featuresSectionSubtitle", diff --git a/src/pages/index.tsx b/src/pages/index.tsx index a8bdbb3..d04039b 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -38,9 +38,9 @@ function Content() { t.transitions.create("background-color", { @@ -74,11 +74,12 @@ function Content() { width: 1000, mx: "auto", maxWidth: "100%", - aspectRatio: "16 / 10", + aspectRatio: sm ? 10 / 16 : 16 / 10, overflow: "hidden", borderRadius: 4, backgroundImage: `url(${l10n.demoVideoThumbnail})`, backgroundSize: "cover", + backgroundPosition: "center", }} >