Port the prototype massing.js to a client-only React component (MassingScene) behind a dynamic(ssr:false) wrapper (MassingViewer), wired into the future3d + insolation drawers, replacing the СКОРО SoonCards. Generative mass from parcel area (real geometry_suitability.area_ha) × КСИТ-цель 3.5 (labelled regulation-default · НСПД — honest, real max_far not in /analyze), OrbitControls (orbit/zoom/auto-rotate), time-of-day sun with shadows (insolation preview), live GFA/КСИТ-факт metrics (green ≤3.5 / amber over). Full renderer/ scene/RAF dispose on drawer close; graceful WebGL-unavailable fallback. Adds three@0.184.0 + @types/three (package.json + package-lock.json in sync). SSR-isolated (three imported only in MassingScene). TS strict, no any.
35 lines
1.1 KiB
TypeScript
35 lines
1.1 KiB
TypeScript
"use client";
|
||
|
||
/**
|
||
* MassingViewer — the only entry point the drawer imports for the 3D massing
|
||
* sandbox. Loads MassingScene (Three.js) client-side only via
|
||
* `dynamic(ssr:false)` so WebGL/Three.js never run on the server, with a dark
|
||
* skeleton while the chunk loads. The drawer gates its children behind
|
||
* `{open && ...}`, so the scene mounts on open and the MassingScene effect
|
||
* cleanup disposes the renderer/scene on close.
|
||
*/
|
||
|
||
import dynamic from "next/dynamic";
|
||
|
||
import styles from "@/app/site-finder/analysis/[cad]/ptica/ptica.module.css";
|
||
import type { MassingSceneProps } from "@/components/site-finder/ptica/massing/MassingScene";
|
||
|
||
const MassingScene = dynamic(
|
||
() => import("@/components/site-finder/ptica/massing/MassingScene"),
|
||
{
|
||
ssr: false,
|
||
loading: () => (
|
||
<div className={styles.massingViewport}>
|
||
<div className={styles.massingSkeleton} aria-hidden="true">
|
||
Загрузка 3D-сцены…
|
||
</div>
|
||
</div>
|
||
),
|
||
},
|
||
);
|
||
|
||
export function MassingViewer(props: MassingSceneProps): React.JSX.Element {
|
||
return <MassingScene {...props} />;
|
||
}
|
||
|
||
export default MassingViewer;
|