diff --git a/website/src/components/ExamplesSidebar.astro b/website/src/components/ExamplesSidebar.astro index 0053cc26..afb1d2c1 100644 --- a/website/src/components/ExamplesSidebar.astro +++ b/website/src/components/ExamplesSidebar.astro @@ -85,8 +85,11 @@ const items = [ .ex-sb-desc { font-size: 10px; color: rgba(255, 232, 184, 0.4); margin-top: 1px; } /* Mobile: stack the canvas below a horizontal tab bar instead of a 220px - side rail that would squash the render into a sliver. */ - @media (max-width: 50rem) { + side rail that would squash the render into a sliver. Also triggers on + short viewports (max-height) so a landscape phone — wider than 50rem but + only ~400px tall — gets the compact layout instead of the desktop rail + + header eating the whole screen. */ + @media (max-width: 50rem), (max-height: 34rem) { :global(.ex-layout) { flex-direction: column; } :global(.ex-main) { min-height: 0; } /* Reclaim vertical space for the canvas — the title/description header is diff --git a/website/src/pages/examples/parthenon.astro b/website/src/pages/examples/parthenon.astro index 7978eb3f..8aabc3d0 100644 --- a/website/src/pages/examples/parthenon.astro +++ b/website/src/pages/examples/parthenon.astro @@ -18,6 +18,15 @@ const description = "The Parthenon — octastyle Doric peripteral temple built f .map-head { padding: 24px 32px 12px; border-bottom: 1px solid rgba(0, 255, 102, 0.14); } .map-head h1 { margin: 0; font-size: 22px; color: rgba(214, 255, 228, 0.94); font-weight: 600; letter-spacing: 0.04em; } .map-head p { margin: 6px 0 0; font-size: 13px; color: rgba(160, 232, 184, 0.55); } + /* Landscape phone (very short viewport) → fullscreen game view: hide the + site nav (.header), the examples sidebar, and the page header so the + matrix fills the whole screen. Rotate back to portrait for chrome + nav. */ + @media (max-height: 34rem) { + :global(body > .header) { display: none; } + :global(.ex-sidebar) { display: none; } + .ex-layout { top: 0; } + .map-head { display: none; } + } .map-stage { flex: 1; min-height: 0; position: relative; } /* touch-action:none on the HOST — the .glyph-output
 is created by the
        runtime and lacks the Astro scope class, so a rule on it never matches.
@@ -53,7 +62,7 @@ const description = "The Parthenon — octastyle Doric peripteral temple built f
     }
     .iso-controls h2 { margin: 0 0 10px; font-size: 11px; letter-spacing: 0.08em; color: rgba(56,189,248,0.9); font-weight: 600; }
     .iso-ctl-toggle { display: none; }
-    @media (max-width: 50rem) {
+    @media (max-width: 50rem), (max-height: 34rem) {
       .iso-ctl-toggle {
         display: block;
         position: absolute; left: 0; right: 0; bottom: 0; z-index: 31;
@@ -983,6 +992,18 @@ const description = "The Parthenon — octastyle Doric peripteral temple built f
         document.body.classList.toggle("temple-locked", locked && mode === "fpv");
       });
 
+      // FPV field-of-view scales with viewport WIDTH. The CSS-perspective focal
+      // length sets horizontal FOV ≈ atan((width/2) / P), so a fixed P feels far
+      // too "zoomed in" (telephoto) on a narrow portrait phone — ~22° there vs
+      // ~56° on desktop. Scaling P with the stage width (anchored so a desktop-
+      // width stage keeps FPV_PERSPECTIVE) holds a roughly constant horizontal
+      // FOV across phone / tablet / desktop.
+      const FPV_REF_WIDTH = 1060, FPV_MIN_PERSPECTIVE = 320;
+      function fpvPerspective(): number {
+        const w = host.clientWidth || window.innerWidth || FPV_REF_WIDTH;
+        return Math.max(FPV_MIN_PERSPECTIVE, Math.min(FPV_PERSPECTIVE, (FPV_PERSPECTIVE * w) / FPV_REF_WIDTH));
+      }
+
       function setMode(next: CameraMode): void {
         if (next === mode) return;
         mode = next;
@@ -992,7 +1013,7 @@ const description = "The Parthenon — octastyle Doric peripteral temple built f
           orbitControls.resume();
         } else {
           orbitControls.pause();
-          camera.perspective = FPV_PERSPECTIVE;
+          camera.perspective = fpvPerspective();
           camera.distance = 0;
           camera.zoom = FPV_ZOOM;
           camera.rotX = FPV_ROT.rotX;
@@ -1082,6 +1103,15 @@ const description = "The Parthenon — octastyle Doric peripteral temple built f
       window.addEventListener("blur", () => endLook());
       document.addEventListener("visibilitychange", () => { if (document.hidden) endLook(); });
 
+      // Re-adapt the FPV FOV when the viewport changes (rotation / resize). The
+      // scene auto-refits its grid already; this just keeps the focal length
+      // matched to the new width and re-derives the look-ahead target.
+      window.addEventListener("resize", () => {
+        if (mode !== "fpv") return;
+        camera.perspective = fpvPerspective();
+        applyLook(camera.rotX, camera.rotY);
+      });
+
       // Matrix rain over the temple surfaces: green monochrome code flowing
       // down each face (space: "surface" derives per-face downhill directions,
       // so strands run down the columns, steps, and pediment alike).