From ea2fc6a0903ac010e58627740ba273415cc67c36 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 17 Dec 2025 18:26:16 +0000 Subject: [PATCH] feat: Implement dark mode and mobile optimizations This commit introduces dark mode support by defining CSS variables and applies optimizations for mobile devices by reducing polygon counts in 3D models. Co-authored-by: matissjurevics --- src/App.jsx | 3 ++- src/canvas/Globe.jsx | 22 +++++++++++++++++----- src/canvas/HeroModel.jsx | 19 ++++++++++++++++--- src/components/ProductGrid.jsx | 34 ++++++++++++++++++++-------------- src/styles/variables.css | 24 +++++++++++++++++++----- 5 files changed, 74 insertions(+), 28 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index c589b07..4010960 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -5,6 +5,7 @@ import ProductGrid from './components/ProductGrid'; import InfoTabs from './components/InfoTabs'; import Footer from './components/Footer'; import './index.css'; +import './styles/variables.css'; import gsap from 'gsap'; import { ScrollTrigger } from 'gsap/ScrollTrigger'; import Lenis from '@studio-freight/lenis' @@ -83,7 +84,7 @@ function App() { zIndex: 10, pointerEvents: 'none', textAlign: 'center', - color: '#000' // Solid black text to sit on top of wireframe + color: 'var(--text-main, #000)' // Adapts to dark mode }}>

{ const groupRef = useRef(); const [bordersTexture, setBordersTexture] = useState(null); + // Detect mobile device and reduce polygon count accordingly + const isMobile = useMemo(() => { + return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || + (window.innerWidth <= 768); + }, []); + // Generate Borders Texture using D3 useEffect(() => { const generateTexture = async () => { @@ -69,17 +75,23 @@ const GlobeMesh = () => { } }); + // Reduce segment counts for mobile devices + const baseSphereSegments = isMobile ? 24 : 64; // Reduce from 64x64 to 24x24 on mobile + const wireframeSegments = isMobile ? 16 : 32; // Reduce from 32x32 to 16x16 on mobile + const markerSegments = isMobile ? 8 : 16; // Reduce from 16x16 to 8x8 on mobile + const ringSegments = isMobile ? 16 : 32; // Reduce from 32 to 16 on mobile + return ( {/* 1. Base Dark Sphere (blocks background stars/wireframe from showing through backface) */} - + {/* 2. Light Wireframe Sphere - Outer Cage */} - + { {/* 3. Borders Sphere (Texture) */} {bordersTexture && ( - + { {/* Ireland Marker */} - + - + diff --git a/src/canvas/HeroModel.jsx b/src/canvas/HeroModel.jsx index f0a2f46..2dd9077 100644 --- a/src/canvas/HeroModel.jsx +++ b/src/canvas/HeroModel.jsx @@ -8,8 +8,15 @@ const Terrain = () => { const materialRef = useRef(); const noise3D = useMemo(() => createNoise3D(), []); - // Create geometry with HIGHER segment count for smoother, denser wave like the reference - const geometry = useMemo(() => new THREE.PlaneGeometry(20, 20, 100, 100), []); + // Detect mobile device and reduce polygon count accordingly + const isMobile = useMemo(() => { + return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || + (window.innerWidth <= 768); + }, []); + + // Create geometry with reduced segment count for mobile devices + const segments = isMobile ? 40 : 100; // Reduce from 100x100 to 40x40 on mobile (84% reduction) + const geometry = useMemo(() => new THREE.PlaneGeometry(20, 20, segments, segments), [segments]); useFrame((state) => { if (mesh.current) { @@ -52,6 +59,12 @@ const Terrain = () => { }; const HeroModel = () => { + // Detect dark mode for fog color + const fogColor = useMemo(() => { + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + return prefersDark ? '#0a0a0a' : '#e4e4e4'; + }, []); + return (
{ {/* Fog to fade edges into background color */} - +
diff --git a/src/components/ProductGrid.jsx b/src/components/ProductGrid.jsx index 2fe1783..af4a30b 100644 --- a/src/components/ProductGrid.jsx +++ b/src/components/ProductGrid.jsx @@ -111,43 +111,49 @@ const ProductGrid = () => { }, [products]); const onEnter = ({ currentTarget }) => { - gsap.to(currentTarget, { backgroundColor: '#fff', scale: 0.98, duration: 0.3 }); + const computedStyle = getComputedStyle(document.documentElement); + const hoverBg = computedStyle.getPropertyValue('--product-bg-hover').trim() || '#fff'; + const gridLine = computedStyle.getPropertyValue('--grid-line').trim() || '#ccc'; + gsap.to(currentTarget, { backgroundColor: hoverBg, scale: 0.98, duration: 0.3 }); gsap.to(currentTarget.querySelector('.product-img'), { scale: 1.1, duration: 0.3 }); gsap.to(currentTarget.querySelector('.indicator'), { backgroundColor: '#ff4d00', scale: 1.5, duration: 0.3 }); }; const onLeave = ({ currentTarget }) => { - gsap.to(currentTarget, { backgroundColor: '#f5f5f5', scale: 1, duration: 0.3 }); + const computedStyle = getComputedStyle(document.documentElement); + const productBg = computedStyle.getPropertyValue('--product-bg').trim() || '#f5f5f5'; + const gridLine = computedStyle.getPropertyValue('--grid-line').trim() || '#ccc'; + gsap.to(currentTarget, { backgroundColor: productBg, scale: 1, duration: 0.3 }); gsap.to(currentTarget.querySelector('.product-img'), { scale: 1, duration: 0.3 }); - gsap.to(currentTarget.querySelector('.indicator'), { backgroundColor: '#ccc', scale: 1, duration: 0.3 }); + gsap.to(currentTarget.querySelector('.indicator'), { backgroundColor: gridLine, scale: 1, duration: 0.3 }); }; return ( <>
-

Selected Work

- DESIGN / CODE +

Selected Work

+ DESIGN / CODE
{products.map((p, i) => (
{ ref={el => itemRefs.current[i] = el} className="product-item" style={{ - background: '#f5f5f5', + background: 'var(--product-bg, #f5f5f5)', height: '450px', padding: '30px', display: 'flex', @@ -176,7 +182,7 @@ const ProductGrid = () => {
@@ -188,7 +194,7 @@ const ProductGrid = () => { alignItems: 'center', justifyContent: 'center', fontSize: '3rem', - color: '#e0e0e0', + color: 'var(--text-dim, #e0e0e0)', fontWeight: 800, userSelect: 'none', textAlign: 'center', @@ -204,8 +210,8 @@ const ProductGrid = () => {
-

{p.name}

-
+

{p.name}

+
{p.desc} {p.price}
diff --git a/src/styles/variables.css b/src/styles/variables.css index 4d3d148..a763ae8 100644 --- a/src/styles/variables.css +++ b/src/styles/variables.css @@ -1,10 +1,12 @@ :root { - /* Teenage Engineering Palette */ - --bg-color: #e4e4e4; - --text-main: #000000; - --text-dim: #666666; + /* Teenage Engineering Palette - Dark mode default */ + --bg-color: #0a0a0a; + --text-main: #e4e4e4; + --text-dim: #888888; --accent-orange: #ff4d00; - --grid-line: #cccccc; + --grid-line: #333333; + --product-bg: #1a1a1a; + --product-bg-hover: #2a2a2a; /* Typos */ --font-main: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; @@ -13,3 +15,15 @@ /* Layout */ --header-height: 60px; } + +@media (prefers-color-scheme: light) { + :root { + /* Light mode overrides */ + --bg-color: #e4e4e4; + --text-main: #000000; + --text-dim: #666666; + --grid-line: #cccccc; + --product-bg: #f5f5f5; + --product-bg-hover: #ffffff; + } +}