diff --git a/src/App.css b/src/App.css index 9057d28..1fd3720 100644 --- a/src/App.css +++ b/src/App.css @@ -564,6 +564,17 @@ kbd { animation: toastFadeIn 0.35s cubic-bezier(0.16, 1, 0.3, 1) forwards; } +.chapter-toast.warning-toast { + border-color: rgba(245, 158, 11, 0.4); + background: rgba(30, 25, 15, 0.92); +} + +.toast-warning-text { + color: #fbbf24; + font-size: 14px; + font-weight: 600; +} + .toast-badge { background: linear-gradient(135deg, #7c3aed, #4f46e5); color: #fff; diff --git a/src/components/MangaViewer.tsx b/src/components/MangaViewer.tsx index 62e8245..f58d656 100644 --- a/src/components/MangaViewer.tsx +++ b/src/components/MangaViewer.tsx @@ -83,19 +83,31 @@ export function MangaViewer({ const [dismissNavError, setDismissNavError] = useState(false); const [boundaryWarning, setBoundaryWarning] = useState<'first' | 'last' | null>(null); const [dimMap, setDimMap] = useState>({}); - const [toast, setToast] = useState<{ title: string; episode: string | null } | null>(null); + const [toast, setToast] = useState<{ title?: string; episode?: string | null; text?: string; isWarning?: boolean } | null>(null); const toolbarTimer = useRef | null>(null); + const toastTimer = useRef | null>(null); const containerRef = useRef(null); + const showNoticeToast = useCallback((text: string, isWarning = true) => { + setToast({ text, isWarning }); + if (toastTimer.current) clearTimeout(toastTimer.current); + toastTimer.current = setTimeout(() => { + setToast(null); + }, 2800); + }, []); + // Trigger Chapter Notification Toast on chapter load/change useEffect(() => { if (!chapter.title) return; const parsed = parseTitleAndEpisode(chapter.title); - setToast(parsed); - const timer = setTimeout(() => { + setToast({ title: parsed.title, episode: parsed.episode, isWarning: false }); + if (toastTimer.current) clearTimeout(toastTimer.current); + toastTimer.current = setTimeout(() => { setToast(null); }, 2800); - return () => clearTimeout(timer); + return () => { + if (toastTimer.current) clearTimeout(toastTimer.current); + }; }, [currentUrl, chapter.title]); useEffect(() => { @@ -191,8 +203,10 @@ export function MangaViewer({ } else { setBoundaryWarning('last'); } + } else { + showNoticeToast('마지막 화입니다.', true); } - }, [slotIndex, totalSlots, chapter.next_chapter, boundaryWarning, onNavigate]); + }, [slotIndex, totalSlots, chapter.next_chapter, boundaryWarning, onNavigate, showNoticeToast]); const goPrev = useCallback(() => { if (slotIndex > 0) { @@ -204,8 +218,10 @@ export function MangaViewer({ } else { setBoundaryWarning('first'); } + } else { + showNoticeToast('첫 번째 화입니다.', true); } - }, [slotIndex, chapter.prev_chapter, boundaryWarning, onNavigate]); + }, [slotIndex, chapter.prev_chapter, boundaryWarning, onNavigate, showNoticeToast]); const toggleFullscreen = useCallback(async () => { try { @@ -340,11 +356,17 @@ export function MangaViewer({ )} - {/* Chapter Transition Toast */} + {/* Chapter Transition / Warning Toast */} {toast && ( -
- {toast.episode && {toast.episode}} - {toast.title} +
+ {toast.isWarning ? ( + ⚠️ {toast.text} + ) : ( + <> + {toast.episode && {toast.episode}} + {toast.title && {toast.title}} + + )}
)}