feat(ui): implement dynamic view transitions and ambient gradients for themes

This commit is contained in:
Rafif-Riqullah-Siregar committed 2026-07-01 17:48:38 +07:00
1 parent c7ad4a2da9
commit 31f087b0ba
18 files changed
+1577 -199

No files matched your search

+96 -26
View File
@@ -3,6 +3,7 @@
import React, { useEffect, useState, useRef, useMemo } from 'react';
import dynamic from 'next/dynamic';
import { fmtBytes } from '@/lib/utils';
import { useTheme } from "next-themes";
// Dynamically import Globe to avoid SSR issues
const Globe = dynamic(() => import('react-globe.gl'), { ssr: false });
@@ -40,7 +41,46 @@ interface GlobeMapProps {
export const GlobeMap = ({ data }: GlobeMapProps) => {
const globeRef = useRef<any>(null);
const [dimensions, setDimensions] = useState({ width: 800, height: 500 });
const [countries, setCountries] = useState<any>(null);
const containerRef = useRef<HTMLDivElement>(null);
const { theme } = useTheme();
const isLight = theme === 'light';
// Generate a beautiful, ultra-fresh gradient blue texture for the Light Mode ocean
const lightGlobeTexture = useMemo(() => {
if (typeof window === 'undefined') return '';
const canvas = document.createElement('canvas');
canvas.width = 1024;
canvas.height = 512;
const ctx = canvas.getContext('2d');
if (ctx) {
const gradient = ctx.createLinearGradient(0, 0, 0, 512);
gradient.addColorStop(0, '#bae6fd'); // sky-200 (light bright blue at the top)
gradient.addColorStop(0.5, '#38bdf8'); // sky-400 (vibrant blue equator)
gradient.addColorStop(1, '#0284c7'); // sky-600 (deeper rich blue at the bottom)
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 1024, 512);
}
return canvas.toDataURL('image/png');
}, []);
// Generate a sleek, deep gradient dark texture for the Dark Mode ocean
const darkGlobeTexture = useMemo(() => {
if (typeof window === 'undefined') return '';
const canvas = document.createElement('canvas');
canvas.width = 1024;
canvas.height = 512;
const ctx = canvas.getContext('2d');
if (ctx) {
const gradient = ctx.createLinearGradient(0, 0, 0, 512);
gradient.addColorStop(0, '#0f172a'); // slate-900
gradient.addColorStop(0.5, '#020617'); // slate-950 (deepest dark blue/black)
gradient.addColorStop(1, '#000000'); // black
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 1024, 512);
}
return canvas.toDataURL('image/png');
}, []);
useEffect(() => {
// Handle resizing
@@ -52,21 +92,47 @@ export const GlobeMap = ({ data }: GlobeMapProps) => {
});
}
};
updateDimensions();
window.addEventListener('resize', updateDimensions);
// Fetch high-res vector polygons for both themes' continents
fetch('https://unpkg.com/globe.gl/example/datasets/ne_110m_admin_0_countries.geojson')
.then(res => res.json())
.then(setCountries)
.catch(err => console.error('Failed to load countries', err));
return () => window.removeEventListener('resize', updateDimensions);
}, []);
useEffect(() => {
// Auto-rotate configuration
if (globeRef.current) {
globeRef.current.controls().autoRotate = true;
globeRef.current.controls().autoRotateSpeed = 1.0;
// Focus on Southeast Asia initially
globeRef.current.pointOfView({ lat: ORIGIN_LAT, lng: ORIGIN_LNG, altitude: 1.8 }, 2000);
}
// Auto-rotate and controls configuration
// Since Globe is dynamically imported, we must wait until the ref is actually populated.
let checkInterval = setInterval(() => {
if (globeRef.current && globeRef.current.controls) {
clearInterval(checkInterval);
const controls = globeRef.current.controls();
controls.autoRotate = true;
controls.autoRotateSpeed = 1.2; // Adjusted auto-rotate speed to 1.5
// Enable inertia/damping for smooth deceleration after dragging
controls.enableDamping = true;
controls.dampingFactor = 0.035;
// Adjust the manual rotation sensitivity
controls.rotateSpeed = 0.8;
// Remove boundaries to allow 360 degree rotation in all directions (as much as OrbitControls allows)
controls.minPolarAngle = 0; // The top pole (North)
controls.maxPolarAngle = Math.PI; // The bottom pole (South)
// Focus on Southeast Asia initially
globeRef.current.pointOfView({ lat: ORIGIN_LAT, lng: ORIGIN_LNG, altitude: 1.8 }, 2000);
}
}, 100);
return () => clearInterval(checkInterval);
}, []);
const arcsData = useMemo(() => {
@@ -95,7 +161,7 @@ export const GlobeMap = ({ data }: GlobeMapProps) => {
name: arc.name,
value: arc.value
}));
// Add origin point
points.push({
lat: ORIGIN_LAT,
@@ -105,30 +171,34 @@ export const GlobeMap = ({ data }: GlobeMapProps) => {
name: 'Indonesia (Origin)',
value: 0
});
return points;
}, [arcsData]);
return (
<div
ref={containerRef}
className="relative w-full h-[500px] rounded-xl overflow-visible bg-transparent cursor-move flex items-center justify-center"
<div
ref={containerRef}
className="relative w-full h-[500px] rounded-xl overflow-visible bg-transparent cursor-move flex items-center justify-center transition-all duration-500"
>
<div className="absolute top-4 left-4 z-10 flex flex-col gap-1 pointer-events-none bg-black/60 p-3 rounded-lg border border-white/10 backdrop-blur-md">
<div className="text-sm font-medium text-white flex items-center gap-2">
Global Traffic Flows
</div>
<p className="text-xs text-slate-400 mt-1">Interactive 3D visualization of network traffic</p>
</div>
<Globe
ref={globeRef}
width={dimensions.width}
height={dimensions.height}
globeImageUrl="//unpkg.com/three-globe/example/img/earth-night.jpg"
bumpImageUrl="//unpkg.com/three-globe/example/img/earth-topology.png"
backgroundColor="rgba(0,0,0,0)"
showGraticules={true}
globeImageUrl={isLight ? lightGlobeTexture : darkGlobeTexture}
showAtmosphere={true}
atmosphereColor={isLight ? "#0ea5e9" : "#1e40af"}
atmosphereAltitude={isLight ? 0.15 : 0.25}
// Polygons Configuration for Unified Vector Designs
polygonsData={countries ? countries.features : []}
polygonAltitude={0.015}
polygonCapColor={() => isLight ? '#4ade80' : '#0f172a'} // Fresh green for light, dark slate for dark
polygonSideColor={() => isLight ? '#22c55e' : '#020617'} // Depth colors
polygonStrokeColor={() => isLight ? '#bbf7d0' : '#0284c7'} // Crisp bright borders for light, neon blue for dark
// Arcs configuration
arcsData={arcsData}
arcStartLat={(d: any) => d.startLat}
@@ -141,7 +211,7 @@ export const GlobeMap = ({ data }: GlobeMapProps) => {
arcDashAnimateTime={2000}
arcAltitude={0.3}
arcStroke={0.7}
// Points configuration
pointsData={pointsData}
pointLat="lat"
@@ -150,7 +220,7 @@ export const GlobeMap = ({ data }: GlobeMapProps) => {
pointAltitude={0.01}
pointRadius="size"
pointsMerge={true}
// Rings configuration (Ping effect for points)
ringsData={pointsData}
ringLat="lat"
@@ -159,7 +229,7 @@ export const GlobeMap = ({ data }: GlobeMapProps) => {
ringMaxRadius={(d: any) => d.name === 'Indonesia (Origin)' ? 3 : 1.5}
ringPropagationSpeed={3}
ringRepeatPeriod={1000}
// Labels configuration
labelsData={pointsData}
labelLat="lat"
@@ -167,7 +237,7 @@ export const GlobeMap = ({ data }: GlobeMapProps) => {
labelText="name"
labelSize={1.2}
labelDotRadius={0.5}
labelColor={() => 'rgba(255, 255, 255, 0.8)'}
labelColor={() => isLight ? '#0f172a' : 'rgba(255, 255, 255, 0.8)'}
labelResolution={2}
/>
</div>