diff --git a/app/globals.css b/app/globals.css index ecb8987..7e191e6 100644 --- a/app/globals.css +++ b/app/globals.css @@ -169,6 +169,20 @@ body { transform: translateY(-14px) scale(1.02); } +/* Mobile: the pinned scene is replaced by a stacked list, so the tile sizes to + the screen instead of being scaled down. Height follows content — the icon is + smaller here, so a fixed 560px would leave a big empty gap. */ +@media (max-width: 767px) { + .app-card { + width: min(320px, 86vw); + height: auto; + min-height: 420px; + padding: 40px 24px 32px; + /* the Reveal wrapper is full-width, so centre the tile inside it */ + margin-inline: auto; + } +} + @media (prefers-reduced-motion: reduce) { .app-card { transition: none; @@ -210,6 +224,15 @@ body { background: color-mix(in oklab, var(--acc) 18%, rgba(255, 255, 255, 0.02)); } +/* Mobile shows chips at 1:1 (no scene scaling), so nudge them up to a + comfortable reading size. */ +@media (max-width: 767px) { + .cap-chip { + font-size: 12.5px; + padding: 7px 13px; + } +} + @media (prefers-reduced-motion: reduce) { .cap-caret { animation: none; diff --git a/components/sections/AppCards.tsx b/components/sections/AppCards.tsx index 7526b77..c80a79e 100644 --- a/components/sections/AppCards.tsx +++ b/components/sections/AppCards.tsx @@ -2,6 +2,8 @@ import { useEffect, useRef } from "react"; import { useTranslations } from "next-intl"; +import { useIsMobile } from "@/lib/useIsMobile"; +import Reveal from "@/components/Reveal"; /** Per-card layout + scroll-entrance constants (from the source scene). */ const CFG = [ @@ -97,11 +99,68 @@ function AppIcon({ return ))} diff --git a/components/sections/CapabilitiesScene.tsx b/components/sections/CapabilitiesScene.tsx index cd89cf6..5125da3 100644 --- a/components/sections/CapabilitiesScene.tsx +++ b/components/sections/CapabilitiesScene.tsx @@ -2,6 +2,8 @@ import { useEffect, useRef } from "react"; import { useTranslations } from "next-intl"; +import { useIsMobile } from "@/lib/useIsMobile"; +import Reveal from "@/components/Reveal"; type Group = { no: string; title: string; items: string[]; tools: string[] }; @@ -35,6 +37,7 @@ export default function CapabilitiesScene() { const label = t("label"); const title = t("title"); const lead = t("lead"); + const isMobile = useIsMobile(); const rootRef = useRef(null); const spotRef = useRef(null); @@ -52,7 +55,7 @@ export default function CapabilitiesScene() { useEffect(() => { const root = rootRef.current; - if (!root) return; + if (!root || isMobile) return; const mouse = { x: -1, y: -1 }; const spotPos = { x: 0, y: 0 }; @@ -329,7 +332,62 @@ export default function CapabilitiesScene() { document.removeEventListener("visibilitychange", onVis); window.removeEventListener("mousemove", onMouse); }; - }, [groups.length]); + }, [groups.length, isMobile]); + + // ── Mobile: the pinned scene scales the 1280px grid down to ~58%, dropping + // list text to 9px and chips to 7px. Fall back to a single column at full size. + if (isMobile) { + return ( +
+ +

+ // {label} +

+

+ {title} +

+

{lead}

+
+ +
+ {groups.slice(0, 4).map((group, c) => ( + +
+
+ + {group.no} + +

{group.title}

+
+
    + {group.items.map((item) => ( +
  • + + {item} +
  • + ))} +
+
    + {group.tools.map((tool) => ( +
  • + {tool} +
  • + ))} +
+
+
+ ))} +
+
+ ); + } return (
{ + const mq = window.matchMedia(MOBILE_QUERY); + const update = () => setIsMobile(mq.matches); + update(); + mq.addEventListener("change", update); + return () => mq.removeEventListener("change", update); + }, []); + + return isMobile; +}