Skip to content

Commit f44bec4

Browse files
fix: restore visual and route transition behavior
1 parent f78413c commit f44bec4

9 files changed

Lines changed: 54 additions & 162 deletions

File tree

src/App.css

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,6 @@
55
background: var(--color-black);
66
}
77

8-
.route-loading {
9-
min-height: 60vh;
10-
display: grid;
11-
place-items: center;
12-
color: rgba(255, 255, 255, 0.72);
13-
font-size: 0.8rem;
14-
letter-spacing: 0.14em;
15-
text-transform: uppercase;
16-
}
17-
188
main {
199
position: relative;
2010
width: 100%;
@@ -417,4 +407,3 @@ main {
417407
}
418408

419409
/* ===== Flying Posters section ===== */
420-

src/App.jsx

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ function waitForImagesReady(container, timeout = IMAGE_LOAD_TIMEOUT) {
4848
return new Promise((resolve) => {
4949
const settle = () => requestAnimationFrame(() => requestAnimationFrame(resolve))
5050
if (!container) return settle()
51-
const imgs = Array.from(container.querySelectorAll('img[data-transition-critical="true"]'))
51+
const imgs = Array.from(container.querySelectorAll('img'))
5252
const pending = imgs.filter((img) => {
5353
if (img.loading === 'lazy') return false
5454
return !img.complete || img.naturalWidth === 0
@@ -158,10 +158,6 @@ function PageMeta() {
158158
return null
159159
}
160160

161-
function RouteFallback() {
162-
return <div className="route-loading" role="status" aria-live="polite">Loading page…</div>
163-
}
164-
165161
// StaggeredMenu renders menu items as <a href={link}> (official React Bits
166162
// implementation — left untouched). To enable client-side routing without
167163
// modifying the official component source, we intercept clicks on
@@ -314,37 +310,37 @@ function Layout() {
314310
<Routes>
315311
<Route path="/" element={<Home />} />
316312
<Route path="/about" element={
317-
<Suspense fallback={<RouteFallback />}>
313+
<Suspense fallback={null}>
318314
<About />
319315
</Suspense>
320316
} />
321317
<Route path="/projects" element={
322-
<Suspense fallback={<RouteFallback />}>
318+
<Suspense fallback={null}>
323319
<Projects />
324320
</Suspense>
325321
} />
326322
<Route path="/gallery" element={
327-
<Suspense fallback={<RouteFallback />}>
323+
<Suspense fallback={null}>
328324
<Gallery />
329325
</Suspense>
330326
} />
331327
<Route path="/gallery/:category" element={
332-
<Suspense fallback={<RouteFallback />}>
328+
<Suspense fallback={null}>
333329
<GalleryCategory />
334330
</Suspense>
335331
} />
336332
<Route path="/blog" element={
337-
<Suspense fallback={<RouteFallback />}>
333+
<Suspense fallback={null}>
338334
<Blog />
339335
</Suspense>
340336
} />
341337
<Route path="/blog/:slug" element={
342-
<Suspense fallback={<RouteFallback />}>
338+
<Suspense fallback={null}>
343339
<BlogPost />
344340
</Suspense>
345341
} />
346342
<Route path="/awards" element={
347-
<Suspense fallback={<RouteFallback />}>
343+
<Suspense fallback={null}>
348344
<Awards />
349345
</Suspense>
350346
} />

src/components/Ballpit/Ballpit.jsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,6 @@ const Ballpit = ({ className = '', followCursor = true, ...props }) => {
240240
const canvasRef = useRef(null);
241241
const spheresInstanceRef = useRef(null);
242242
const configRef = useRef({ followCursor, ...props });
243-
244243
useEffect(() => {
245244
const canvas = canvasRef.current;
246245
if (!canvas) return;

src/components/Hero/Hero.css

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,6 @@
1515
z-index: 0;
1616
}
1717

18-
.hero-ballpit-static {
19-
width: 100%;
20-
height: 100%;
21-
background:
22-
radial-gradient(circle at 22% 34%, rgba(0, 240, 255, 0.18), transparent 22%),
23-
radial-gradient(circle at 68% 62%, rgba(255, 255, 255, 0.12), transparent 24%),
24-
#000;
25-
}
26-
2718
.hero-ballpit canvas {
2819
width: 100% !important;
2920
height: 100% !important;

src/components/Hero/Hero.jsx

Lines changed: 26 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,6 @@ export default function Hero() {
2424
};
2525

2626
const [showScroll, setShowScroll] = useState(false);
27-
const [enableWebGL, setEnableWebGL] = useState(() => {
28-
if (typeof window === 'undefined' || !window.matchMedia) return true
29-
return !window.matchMedia('(prefers-reduced-motion: reduce), (max-width: 640px)').matches
30-
})
31-
32-
useEffect(() => {
33-
if (!window.matchMedia) return
34-
const query = window.matchMedia('(prefers-reduced-motion: reduce), (max-width: 640px)')
35-
const update = () => setEnableWebGL(!query.matches)
36-
query.addEventListener?.('change', update)
37-
return () => query.removeEventListener?.('change', update)
38-
}, [])
3927
useEffect(() => {
4028
const timer = setTimeout(() => setShowScroll(true), 2500);
4129
return () => clearTimeout(timer);
@@ -45,23 +33,21 @@ export default function Hero() {
4533
<section className="hero">
4634
{/* Ballpit background */}
4735
<div className="hero-ballpit">
48-
{enableWebGL ? (
49-
<ErrorBoundary>
50-
<Ballpit
51-
count={150}
52-
gravity={0.5}
53-
friction={0.9975}
54-
wallBounce={0.9}
55-
followCursor={true}
56-
colors={[0x00f0ff, 0xffffff, 0x9c9c9c]}
57-
ambientIntensity={0.4}
58-
lightIntensity={80}
59-
minSize={0.5}
60-
maxSize={1.5}
61-
maxVelocity={0.12}
62-
/>
63-
</ErrorBoundary>
64-
) : <div className="hero-ballpit-static" aria-hidden="true" />}
36+
<ErrorBoundary>
37+
<Ballpit
38+
count={150}
39+
gravity={0.5}
40+
friction={0.9975}
41+
wallBounce={0.9}
42+
followCursor={true}
43+
colors={[0x00f0ff, 0xffffff, 0x9c9c9c]}
44+
ambientIntensity={0.4}
45+
lightIntensity={80}
46+
minSize={0.5}
47+
maxSize={1.5}
48+
maxVelocity={0.12}
49+
/>
50+
</ErrorBoundary>
6551
</div>
6652

6753
{/* Content overlay */}
@@ -131,19 +117,17 @@ export default function Hero() {
131117
{/* Right side - Lanyard with EvanGongIcon on card */}
132118
<div className="hero-lanyard-section">
133119
<div className="hero-lanyard-wrapper">
134-
{enableWebGL && (
135-
<ErrorBoundary>
136-
<Lanyard
137-
position={[0, 0, 25]}
138-
gravity={[0, -40, 0]}
139-
fov={20}
140-
frontImage={evanGongIcon}
141-
backImage={evanGongIcon}
142-
imageFit="cover"
143-
lanyardWidth={1.2}
144-
/>
145-
</ErrorBoundary>
146-
)}
120+
<ErrorBoundary>
121+
<Lanyard
122+
position={[0, 0, 25]}
123+
gravity={[0, -40, 0]}
124+
fov={20}
125+
frontImage={evanGongIcon}
126+
backImage={evanGongIcon}
127+
imageFit="cover"
128+
lanyardWidth={1.2}
129+
/>
130+
</ErrorBoundary>
147131
</div>
148132
</div>
149133
</div>

src/components/Home/Home.jsx

Lines changed: 14 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
1-
import { lazy, Suspense, useState, useEffect, useRef } from 'react'
1+
import Hero from '../Hero/Hero.jsx'
22
import ScrollVelocity from '../ScrollVelocity/ScrollVelocity.jsx'
33
import ScrollReveal from '../ScrollReveal/ScrollReveal.jsx'
44
import ScrollStack, { ScrollStackItem } from '../ScrollStack/ScrollStack.jsx'
55
import LogoLoop from '../LogoLoop/LogoLoop.jsx'
66
import TrueFocus from '../TrueFocus/TrueFocus.jsx'
7+
import FlyingPostersSection from '../FlyingPosters/FlyingPostersSection.jsx'
8+
import FlowingMenu from '../FlowingMenu/FlowingMenu.jsx'
9+
import LensesShowcase from '../LensesShowcase/LensesShowcase.jsx'
10+
import ContactShowcase from '../ContactShowcase/ContactShowcase.jsx'
711
import ErrorBoundary from '../ErrorBoundary.jsx'
812

9-
const Hero = lazy(() => import('../Hero/Hero.jsx'))
10-
const FlyingPostersSection = lazy(() => import('../FlyingPosters/FlyingPostersSection.jsx'))
11-
const FlowingMenu = lazy(() => import('../FlowingMenu/FlowingMenu.jsx'))
12-
const LensesShowcase = lazy(() => import('../LensesShowcase/LensesShowcase.jsx'))
13-
const ContactShowcase = lazy(() => import('../ContactShowcase/ContactShowcase.jsx'))
14-
1513
// FlowingMenu images (specific photography picks from subfolders)
1614
import parisImg from '/Photography/Paris/IMG_1598.jpg'
1715
import chaoshanImg from '/Photography/Chaoshan/A395CF89-F602-44F6-97F0-747AD556F2C4_1_105_c.jpeg'
@@ -95,39 +93,10 @@ const posterImages = Object.values(
9593
import.meta.glob('/Photography/*.{jpeg,jpg,png}', { eager: true, query: '?url', import: 'default' })
9694
)
9795

98-
// Defers mounting of heavy off-screen sections (Hyperspeed, ASCIIText,
99-
// TextPressure, FluidGlass) until they approach the viewport. Without this,
100-
// the Home page initializes ~6 WebGL contexts on load — most far below the
101-
// fold — causing a large initial GPU/CPU spike. The wrapper stays in the
102-
// document flow (0-height until mounted) so IntersectionObserver can fire; it
103-
// grows once children mount, naturally re-triggering observers of sections
104-
// below it. rootMargin '400px' mounts + renders content before the user sees
105-
// it, avoiding visible layout shift.
106-
function DeferredMount({ children, rootMargin = '400px' }) {
107-
const [shouldMount, setShouldMount] = useState(false)
108-
const ref = useRef(null)
109-
useEffect(() => {
110-
const el = ref.current
111-
if (!el) return
112-
if (typeof IntersectionObserver === 'undefined') { setShouldMount(true); return }
113-
const io = new IntersectionObserver((entries) => {
114-
if (entries[0].isIntersecting) {
115-
setShouldMount(true)
116-
io.disconnect()
117-
}
118-
}, { rootMargin })
119-
io.observe(el)
120-
return () => io.disconnect()
121-
}, [rootMargin])
122-
return <div ref={ref}>{shouldMount ? children : null}</div>
123-
}
124-
12596
export default function Home() {
12697
return (
12798
<>
128-
<Suspense fallback={<div className="hero hero-loading" aria-hidden="true" />}>
129-
<Hero />
130-
</Suspense>
99+
<Hero />
131100
<section className="scroll-velocity-section" aria-label="Interests marquee">
132101
<ScrollVelocity
133102
texts={[
@@ -288,33 +257,17 @@ export default function Home() {
288257
/>
289258
</section>
290259

291-
<DeferredMount rootMargin="600px">
292-
<Suspense fallback={null}>
293-
<ErrorBoundary>
294-
<FlyingPostersSection items={posterImages} />
295-
</ErrorBoundary>
296-
</Suspense>
297-
</DeferredMount>
260+
<ErrorBoundary>
261+
<FlyingPostersSection items={posterImages} />
262+
</ErrorBoundary>
298263

299-
<DeferredMount rootMargin="600px">
300-
<Suspense fallback={<div style={{ height: '600px' }} aria-hidden="true" />}>
301-
<div style={{ height: '600px', position: 'relative' }}>
302-
<FlowingMenu items={flowingMenuItems} />
303-
</div>
304-
</Suspense>
305-
</DeferredMount>
264+
<div style={{ height: '600px', position: 'relative' }}>
265+
<FlowingMenu items={flowingMenuItems} />
266+
</div>
306267

307-
<DeferredMount>
308-
<Suspense fallback={null}>
309-
<LensesShowcase />
310-
</Suspense>
311-
</DeferredMount>
268+
<LensesShowcase />
312269

313-
<DeferredMount>
314-
<Suspense fallback={null}>
315-
<ContactShowcase />
316-
</Suspense>
317-
</DeferredMount>
270+
<ContactShowcase />
318271
</>
319272
)
320273
}

src/components/Hyperspeed/Hyperspeed.jsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ const Hyperspeed = ({ effectOptions = DEFAULT_EFFECT_OPTIONS }) => {
382382
fogNear: { value: fog.near },
383383
fogFar: { value: fog.far }
384384
};
385-
this.clock = new THREE.Timer().connect(document);
385+
this.clock = new THREE.Clock();
386386
this.assets = {};
387387
this.disposed = false;
388388
// Paused by the IntersectionObserver/visibilitychange wrapper when the
@@ -560,7 +560,7 @@ const Hyperspeed = ({ effectOptions = DEFAULT_EFFECT_OPTIONS }) => {
560560
this.speedUp += lerp(this.speedUp, this.speedUpTarget, lerpPercentage, 0.00001);
561561
this.timeOffset += this.speedUp * delta;
562562

563-
let time = this.clock.getElapsed() + this.timeOffset;
563+
let time = this.clock.elapsedTime + this.timeOffset;
564564

565565
this.rightCarLights.update(time);
566566
this.leftCarLights.update(time);
@@ -597,8 +597,6 @@ const Hyperspeed = ({ effectOptions = DEFAULT_EFFECT_OPTIONS }) => {
597597

598598
dispose() {
599599
this.disposed = true;
600-
this.clock?.dispose();
601-
602600
if (this.scene) {
603601
this.scene.traverse(object => {
604602
const obj = object;
@@ -677,7 +675,6 @@ const Hyperspeed = ({ effectOptions = DEFAULT_EFFECT_OPTIONS }) => {
677675
}
678676

679677
if (this.hasValidSize) {
680-
this.clock.update();
681678
const delta = this.clock.getDelta();
682679
this.render(delta);
683680
this.update(delta);
@@ -696,7 +693,7 @@ const Hyperspeed = ({ effectOptions = DEFAULT_EFFECT_OPTIONS }) => {
696693
resume() {
697694
if (this.disposed || !this.paused) return;
698695
this.paused = false;
699-
this.clock.reset();
696+
this.clock.getDelta();
700697
this.tick();
701698
}
702699
}

src/components/PixelBlast/PixelBlast.jsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ const PixelBlast = ({
407407
const quadGeom = new THREE.PlaneGeometry(2, 2);
408408
const quad = new THREE.Mesh(quadGeom, material);
409409
scene.add(quad);
410-
const clock = new THREE.Timer().connect(document);
410+
const clock = new THREE.Clock();
411411
const setSize = () => {
412412
const w = container.clientWidth || 1;
413413
const h = container.clientHeight || 1;
@@ -499,13 +499,12 @@ const PixelBlast = ({
499499
passive: true
500500
});
501501
let raf = 0;
502-
const animate = timestamp => {
502+
const animate = () => {
503503
if (autoPauseOffscreen && !visibilityRef.current.visible) {
504504
raf = requestAnimationFrame(animate);
505505
return;
506506
}
507-
clock.update(timestamp);
508-
uniforms.uTime.value = timeOffset + clock.getElapsed() * speedRef.current;
507+
uniforms.uTime.value = timeOffset + clock.getElapsedTime() * speedRef.current;
509508
if (liquidEffect) liquidEffect.uniforms.get('uTime').value = uniforms.uTime.value;
510509
if (composer) {
511510
if (touch) touch.update();
@@ -567,7 +566,6 @@ const PixelBlast = ({
567566
if (!threeRef.current) return;
568567
const t = threeRef.current;
569568
t.resizeObserver?.disconnect();
570-
t.clock?.dispose();
571569
cancelAnimationFrame(t.raf);
572570
t.quad?.geometry.dispose();
573571
t.material.dispose();

0 commit comments

Comments
 (0)