diff --git a/src/app/globals.css b/src/app/globals.css index e003ed95..f08005bb 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -110,23 +110,13 @@ body { } } -/* Masthead logo layering: - - `.logo-touch-only` is the full 2D SVG with corner triangles — - shown on touch / small screens, hidden on desktop. - - `.logo-desktop-only` is a static "sphere placeholder" — hidden on - touch, shown on desktop so first paint is not a blank slot while - the 3D chunk loads. The real 3D mounts on top once ready and - covers the placeholder cleanly (same circular silhouette). */ -.logo-desktop-only { - display: none; -} +/* Masthead logo: hide the SVG fallback on desktop (real mouse + ≥ 768px) + so it never paints alongside the 3D sphere. Touch / small screens + ignore this and keep the SVG as the only logo. */ @media (pointer: fine) and (min-width: 768px) { .logo-touch-only { display: none; } - .logo-desktop-only { - display: block; - } } /* Tabular nums for everything that displays numbers */ diff --git a/src/components/site-logo-switcher.tsx b/src/components/site-logo-switcher.tsx index 8cd2e63b..c9d486e8 100644 --- a/src/components/site-logo-switcher.tsx +++ b/src/components/site-logo-switcher.tsx @@ -2,74 +2,32 @@ /** * Hybrid masthead logo: 2D SVG on touch / small screens, interactive 3D - * sphere on desktop. The 3D bundle is loaded with `next/dynamic` so it - * only ships when actually mounted. + * sphere on desktop. * - * Three layers stacked in the same slot: - * 1. `` (logo-touch-only) — the full brand SVG with corner - * triangles; visible on touch / small screens only. - * 2. `` (logo-desktop-only) — a tiny static SVG of - * a sphere with the C-mark centred; paints instantly on desktop's - * first frame, so the slot is never blank while the 3D chunk loads. - * 3. `` — mounted once enable3D + chunk are ready; covers - * the placeholder byte-for-byte (same circular silhouette), so the - * swap is visually invisible. + * The 3D component is imported statically (not via next/dynamic) so the + * sphere mounts as soon as the main JS executes — no separate chunk to + * download. Trade-off: the three.js code ends up in the shared client + * bundle (~150 KB gz), which mobile users also fetch even though they + * never mount the component. Worth it because the previous dynamic + * setup left a visible blank slot while the chunk loaded. + * + * Layering: + * - `` (logo-touch-only) — the full brand SVG with corner + * triangles; visible on touch / small screens only. + * - `` — overlaid on top once enable3D flips to true on + * desktop. The SVG is CSS-hidden on desktop (`logo-touch-only`) so + * nothing ever paints alongside the 3D there. * * Detection: `(pointer: fine) and (min-width: 768px)`. Touchscreen * laptops with a mouse still qualify as desktop. */ -import dynamic from "next/dynamic"; import { useEffect, useState } from "react"; import { SiteLogo } from "@/components/site-logo"; +import { SiteLogo3D } from "@/components/site-logo-3d"; const DESKTOP_MQ = "(pointer: fine) and (min-width: 768px)"; -const SiteLogo3D = dynamic( - () => import("@/components/site-logo-3d").then((m) => m.SiteLogo3D), - { ssr: false, loading: () => null }, -); - -/** - * Static SVG that mimics the 3D sphere's silhouette + centred C-mark. - * Identical proportions to so the real 3D canvas can - * cover this byte-for-byte without a visible transition. - */ -function SpherePlaceholder({ size }: { size: number }) { - return ( - - {/* Sphere body — matches --color-surface so it blends with the - header just like the 3D sphere does. */} - - {/* C-mark centred inside the sphere — same geometry as - (cx=45 cy=50 r=45, inner ellipse rx=22 ry=40, right rect), - scaled to ~55% so it sits in the visible front face the same - way it does on the 3D sphere. */} - - - - - - - - - - ); -} - export function SiteLogoSwitcher({ size = 22 }: { size?: number }) { const [enable3D, setEnable3D] = useState(false); @@ -78,11 +36,6 @@ export function SiteLogoSwitcher({ size = 22 }: { size?: number }) { const sync = () => setEnable3D(mq.matches); sync(); mq.addEventListener("change", sync); - - // Eagerly fetch the 3D chunk on desktop so it is in the browser - // cache by the time React tries to mount . - if (mq.matches) import("@/components/site-logo-3d"); - return () => mq.removeEventListener("change", sync); }, []); @@ -91,9 +44,6 @@ export function SiteLogoSwitcher({ size = 22 }: { size?: number }) {
-
- -
{enable3D && (