Skip to content

Commit 7d351b2

Browse files
committed
Mobile UX improvements and touch support
- Swipe left/right to flip cards on mobile - Larger NEXT/PREV buttons for touch targets - Adjusted ring and orb sizes - Increased lock duration for mobile touch events - Swipe hint text on mobile
1 parent 8a6c73b commit 7d351b2

File tree

6 files changed

+84
-12
lines changed

6 files changed

+84
-12
lines changed

src/App.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@ export default function App() {
3939
const fov = 65 - vs.vw * 15
4040

4141
const toggleSection = useCallback((key) => {
42-
closeLock.current = Date.now() + 800
42+
closeLock.current = Date.now() + 1200
4343
setOpenSection((prev) => (prev === key ? null : key))
4444
}, [])
4545

4646
const lockClose = useCallback(() => {
47-
closeLock.current = Date.now() + 800
47+
closeLock.current = Date.now() + 1200
4848
}, [])
4949

5050
const closeSection = useCallback(() => {}, [])

src/components/canvas/Scene.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export default function Scene({ mouseParallax, hoveredSection, openSection, togg
5050
{t > 0.5 && <CircuitLines position={[-3, 0, -6]} color="#06b6d4" />}
5151

5252
{/* Energy rings — positions interpolated */}
53-
<EnergyRing position={[lerp(0.8, 4, t), lerp(3, 1.5, t), lerp(5, 2, t)]} radius={lerp(0.6, 1.2, t)} color="#818cf8" speed={0.3} />
53+
<EnergyRing position={[lerp(0.8, 4, t), lerp(3, 1.5, t), lerp(5, 2, t)]} radius={lerp(0.75, 1.2, t)} color="#818cf8" speed={0.3} />
5454
<EnergyRing position={[lerp(-0.8, -5, t), lerp(3, 1.5, t), lerp(5, 3, t)]} radius={lerp(0.6, 1.0, t)} color="#06b6d4" speed={0.25} />
5555
{openSection !== 'contact' && (
5656
<EnergyRing position={[0, lerp(1.2, 1.5, t), lerp(8, 7, t)]} radius={lerp(0.5, 0.9, t)} color="#a78bfa" speed={0.2} />

src/components/sections/ContactSection.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ export default function ContactSection({ hoveredSection, openSection, toggleSect
120120
onPointerLeave={() => { isHovered.current = false; hoveredSection.current = 'none'; document.body.style.cursor = 'auto' }}
121121
onClick={(e) => { e.stopPropagation(); toggleSection('contact') }}
122122
>
123-
<icosahedronGeometry args={[0.3, 2]} />
123+
<icosahedronGeometry args={[0.25, 2]} />
124124
<meshStandardMaterial color="#a78bfa" emissive="#a78bfa" emissiveIntensity={1.5} wireframe transparent opacity={0.7} toneMapped={false} />
125125
</mesh>
126126
<mesh visible={false}

src/components/sections/EducationSection.jsx

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ function PanelContent({ data, index, total, onNext, onPrev }) {
5151
</div>
5252
<div className="holo-footer">
5353
<button className={`holo-btn ${index <= 0 ? 'disabled' : ''}`} onClick={onPrev} disabled={index <= 0}>◀ PREV</button>
54-
<span className="holo-scroll-label"><span className="scroll-icon"></span>scroll to flip</span>
54+
<span className="holo-scroll-label"><span className="scroll-icon"></span><span className="desktop-hint">scroll to flip</span><span className="mobile-hint">swipe to flip</span></span>
5555
<button className={`holo-btn ${index >= total - 1 ? 'disabled' : ''}`} onClick={onNext} disabled={index >= total - 1}>NEXT ▶</button>
5656
</div>
5757
</div>
@@ -152,11 +152,42 @@ export default function EducationSection({ hoveredSection, openSection, toggleSe
152152
else if (e.deltaY < -15) goPrev()
153153
}, [isOpen, goNext, goPrev, peelData])
154154

155+
const touchStart = useRef({ x: 0, y: 0 })
156+
const onTouchStart = useCallback((e) => {
157+
if (!isOpen) return
158+
touchStart.current = { x: e.touches[0].clientX, y: e.touches[0].clientY }
159+
}, [isOpen])
160+
161+
const onTouchEnd = useCallback((e) => {
162+
if (!isOpen || scrollCooldown.current || peelData) return
163+
const dx = e.changedTouches[0].clientX - touchStart.current.x
164+
// Swipe right = next (opposite of experience)
165+
if (dx > 40) {
166+
scrollCooldown.current = true
167+
setTimeout(() => { scrollCooldown.current = false }, 400)
168+
lockClose()
169+
goNext()
170+
}
171+
// Swipe left = prev
172+
else if (dx < -40) {
173+
scrollCooldown.current = true
174+
setTimeout(() => { scrollCooldown.current = false }, 400)
175+
lockClose()
176+
goPrev()
177+
}
178+
}, [isOpen, goNext, goPrev, peelData, lockClose])
179+
155180
useEffect(() => {
156181
if (!isOpen) return
157182
window.addEventListener('wheel', onWheel)
158-
return () => window.removeEventListener('wheel', onWheel)
159-
}, [isOpen, onWheel])
183+
window.addEventListener('touchstart', onTouchStart, { passive: true })
184+
window.addEventListener('touchend', onTouchEnd)
185+
return () => {
186+
window.removeEventListener('wheel', onWheel)
187+
window.removeEventListener('touchstart', onTouchStart)
188+
window.removeEventListener('touchend', onTouchEnd)
189+
}
190+
}, [isOpen, onWheel, onTouchStart, onTouchEnd])
160191

161192
const currentData = education[currentIndex]
162193
const cardsAhead = education.length - 1 - currentIndex

src/components/sections/ExperienceSection.jsx

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ function PanelContent({ data, index, total, onNext, onPrev }) {
5353
</div>
5454
<div className="holo-footer">
5555
<button className={`holo-btn ${index <= 0 ? 'disabled' : ''}`} onClick={onPrev} disabled={index <= 0}>◀ PREV</button>
56-
<span className="holo-scroll-label"><span className="scroll-icon"></span>scroll to flip</span>
56+
<span className="holo-scroll-label"><span className="scroll-icon"></span><span className="desktop-hint">scroll to flip</span><span className="mobile-hint">swipe to flip</span></span>
5757
<button className={`holo-btn ${index >= total - 1 ? 'disabled' : ''}`} onClick={onNext} disabled={index >= total - 1}>NEXT ▶</button>
5858
</div>
5959
</div>
@@ -158,11 +158,43 @@ export default function ExperienceSection({ hoveredSection, openSection, toggleS
158158
else if (e.deltaY < -15) goPrev()
159159
}, [isOpen, goNext, goPrev, peelData])
160160

161+
// Touch swipe support
162+
const touchStart = useRef({ x: 0, y: 0 })
163+
const onTouchStart = useCallback((e) => {
164+
if (!isOpen) return
165+
touchStart.current = { x: e.touches[0].clientX, y: e.touches[0].clientY }
166+
}, [isOpen])
167+
168+
const onTouchEnd = useCallback((e) => {
169+
if (!isOpen || scrollCooldown.current || peelData) return
170+
const dx = e.changedTouches[0].clientX - touchStart.current.x
171+
// Swipe left = next
172+
if (dx < -40) {
173+
scrollCooldown.current = true
174+
setTimeout(() => { scrollCooldown.current = false }, 400)
175+
lockClose()
176+
goNext()
177+
}
178+
// Swipe right = prev
179+
else if (dx > 40) {
180+
scrollCooldown.current = true
181+
setTimeout(() => { scrollCooldown.current = false }, 400)
182+
lockClose()
183+
goPrev()
184+
}
185+
}, [isOpen, goNext, goPrev, peelData, lockClose])
186+
161187
useEffect(() => {
162188
if (!isOpen) return
163189
window.addEventListener('wheel', onWheel)
164-
return () => window.removeEventListener('wheel', onWheel)
165-
}, [isOpen, onWheel])
190+
window.addEventListener('touchstart', onTouchStart, { passive: true })
191+
window.addEventListener('touchend', onTouchEnd)
192+
return () => {
193+
window.removeEventListener('wheel', onWheel)
194+
window.removeEventListener('touchstart', onTouchStart)
195+
window.removeEventListener('touchend', onTouchEnd)
196+
}
197+
}, [isOpen, onWheel, onTouchStart, onTouchEnd])
166198

167199
const currentData = experience[currentIndex]
168200
const cardsAhead = experience.length - 1 - currentIndex

src/styles/global.css

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,14 @@ html, body, #root {
334334
animation: scrollBounce 1.5s ease-in-out infinite;
335335
}
336336

337+
.mobile-hint { display: none; }
338+
.desktop-hint { display: inline; }
339+
340+
@media (max-width: 768px) {
341+
.mobile-hint { display: inline; }
342+
.desktop-hint { display: none; }
343+
}
344+
337345
@keyframes scrollBounce {
338346
0%, 100% { transform: translateY(0); }
339347
50% { transform: translateY(-2px); }
@@ -437,8 +445,9 @@ html, body, #root {
437445
.holo-company, .holo-school { font-size: 11px; }
438446
.holo-details li { font-size: 10px; line-height: 1.5; }
439447
.holo-meta, .holo-project { font-size: 9px; }
440-
.holo-btn { font-size: 7px; padding: 3px 7px; }
441-
.holo-scroll-label { font-size: 7px; }
448+
.holo-btn { font-size: 10px; padding: 8px 14px; min-height: 32px; min-width: 60px; }
449+
.holo-scroll-label { font-size: 8px; }
450+
.holo-footer { margin-top: 14px; padding-top: 10px; }
442451

443452
.hotspot-label { font-size: 9px; letter-spacing: 0.15em; }
444453

0 commit comments

Comments
 (0)