fix(ux): address critic review on mobile landscape, dual hud fade and clipboard feedback

- Optimize short/landscape screen (< 520px height) viewports with compact padding, shrinkable containers and zero-overflow layout
- Synchronize dual-HUD immersive fading for both Header and Controls with 3s stillness and global wake-up interactions
- Enhance PasteModal with user-friendly error alerts and auto-focusing textarea on clipboard denial

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
2026-10-20 09:24:00 +08:00
parent 1f7a6a2054
commit 47cae39718
6 changed files with 175 additions and 60 deletions

View File

@@ -215,7 +215,7 @@ export default function App() {
}
};
// Mouse activity detection for HUD fading
// Mouse/touch/key activity detection for HUD fading
const resetInactivity = useCallback(() => {
setIsUserActive(true);
if (inactivityTimerRef.current) {
@@ -224,7 +224,7 @@ export default function App() {
if (isPlaying && settings.autoHideControls) {
inactivityTimerRef.current = window.setTimeout(() => {
setIsUserActive(false);
}, 2200);
}, 3000);
}
}, [isPlaying, settings.autoHideControls]);
@@ -235,11 +235,28 @@ export default function App() {
}
return;
}
const handleInteraction = () => {
resetInactivity();
};
window.addEventListener('mousemove', handleInteraction);
window.addEventListener('keydown', handleInteraction);
window.addEventListener('touchstart', handleInteraction);
window.addEventListener('pointerdown', handleInteraction);
const timer = window.setTimeout(() => {
setIsUserActive(false);
}, 2200);
return () => window.clearTimeout(timer);
}, [isPlaying, settings.autoHideControls]);
}, 3000);
return () => {
window.clearTimeout(timer);
window.removeEventListener('mousemove', handleInteraction);
window.removeEventListener('keydown', handleInteraction);
window.removeEventListener('touchstart', handleInteraction);
window.removeEventListener('pointerdown', handleInteraction);
};
}, [isPlaying, settings.autoHideControls, resetInactivity]);
// Keyboard shortcuts
const anyModalOpen =
@@ -278,6 +295,9 @@ export default function App() {
const themeClass = `theme-${settings.theme}`;
const isHudDimmed = isPlaying && !isUserActive && settings.autoHideControls;
const hudClasses = isHudDimmed
? 'opacity-0 pointer-events-none transition-opacity duration-500'
: 'opacity-100 pointer-events-auto transition-opacity duration-300';
return (
<div
@@ -286,11 +306,7 @@ export default function App() {
className={`relative w-full h-full flex flex-col justify-between overflow-hidden transition-colors duration-300 font-sans ${themeClass}`}
>
{/* Top Header HUD */}
<div
className={`w-full z-20 transition-opacity duration-500 ${
isHudDimmed ? 'opacity-0 hover:opacity-100' : 'opacity-100'
}`}
>
<div className={`w-full z-20 shrink-0 ${hudClasses}`}>
<Header
book={activeBook}
currentChapter={currentChapter}
@@ -307,7 +323,7 @@ export default function App() {
</div>
{/* Main RSVP Focus Arena */}
<main className="flex-1 flex flex-col items-center justify-center px-4 z-10 w-full my-auto">
<main className="flex-1 min-h-0 flex flex-col items-center justify-center px-2 sm:px-4 z-10 w-full overflow-hidden my-auto">
{isLoading ? (
<div className="flex flex-col items-center space-y-3 text-neutral-400">
<div className="w-8 h-8 rounded-full border-2 border-rose-500 border-t-transparent animate-spin" />
@@ -330,11 +346,7 @@ export default function App() {
</main>
{/* Bottom Controls HUD */}
<div
className={`w-full pb-6 z-20 transition-opacity duration-500 ${
isHudDimmed ? 'opacity-10 hover:opacity-100' : 'opacity-100'
}`}
>
<div className={`w-full pb-2 sm:pb-6 short:pb-1 z-20 shrink-0 ${hudClasses}`}>
<Controls
currentIndex={currentIndex}
totalTokens={totalTokens}