From ded4cc1607809ed19a91f597a5f434a77e421510 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier Date: Sun, 31 May 2026 18:11:04 +0200 Subject: [PATCH] logo: static import 3D, drop dynamic + placeholder --- src/app/globals.css | 5 ++-- src/components/site-logo-switcher.tsx | 36 +++++++++++---------------- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index fcfe4028..f08005bb 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -111,9 +111,8 @@ body { } /* Masthead logo: hide the SVG fallback on desktop (real mouse + ≥ 768px) - so it never paints alongside the 3D sphere — desktop sees a brief - empty slot while the 3D chunk loads, then the 3D appears. Touch / - small screens ignore this and keep the SVG as the only logo. */ + 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; diff --git a/src/components/site-logo-switcher.tsx b/src/components/site-logo-switcher.tsx index 735a4d4c..c9d486e8 100644 --- a/src/components/site-logo-switcher.tsx +++ b/src/components/site-logo-switcher.tsx @@ -2,31 +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. * - * Desktop never shows the SVG. A pure-CSS media-query gate in globals.css - * (`.logo-touch-only { display: none }` under `pointer: fine` + ≥ 768px) - * keeps the SSR-painted SVG off the screen on real-mouse viewports, so - * the only thing the user ever sees there is the 3D sphere (with a tiny - * empty slot while the chunk loads — eagerly fetched on mount to keep - * that gap under one frame in the common case). + * 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 }, -); - export function SiteLogoSwitcher({ size = 22 }: { size?: number }) { const [enable3D, setEnable3D] = useState(false); @@ -35,18 +36,11 @@ 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); }, []); return (
- {/* SVG fallback — hidden on desktop via the `.logo-touch-only` - rule in globals.css so it never paints alongside the 3D. */}