테마 전환 타이밍 튜닝
- 라이트 모드 새로고침 시 FOUC 방지 (index.html 블로킹 스크립트) - 헤더 배경 제거 + backdrop-blur만 유지 → 배경과 동시 전환 - 전환 시간 300ms → 500ms로 일관되게 느리게 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7020794add
commit
749e77774a
5 changed files with 31 additions and 44 deletions
|
|
@ -9,6 +9,16 @@
|
|||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700;900&display=swap" rel="stylesheet" />
|
||||
<link href="https://cdn.jsdelivr.net/gh/fonts-archive/Maplestory/Maplestory.css" rel="stylesheet" />
|
||||
<script type="text/javascript" src="https://openapi.nexon.com/js/analytics.js?app_id=274844" async></script>
|
||||
<script>
|
||||
(function () {
|
||||
try {
|
||||
var raw = localStorage.getItem('maple-theme');
|
||||
if (!raw) return;
|
||||
var theme = JSON.parse(raw).state && JSON.parse(raw).state.theme;
|
||||
if (theme === 'light') document.documentElement.setAttribute('data-theme', 'light');
|
||||
} catch (e) {}
|
||||
})();
|
||||
</script>
|
||||
<title>메이플스토리 유틸리티</title>
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
export default function Footer() {
|
||||
return (
|
||||
<footer
|
||||
className="border-t mt-16 transition-colors duration-300"
|
||||
className="border-t mt-16 transition-colors duration-500"
|
||||
style={{ borderColor: 'var(--header-border)' }}
|
||||
>
|
||||
<div className="mx-auto max-w-5xl px-6 py-8 space-y-4">
|
||||
|
|
@ -11,7 +11,7 @@ export default function Footer() {
|
|||
</div>
|
||||
|
||||
<div
|
||||
className="grid gap-2 sm:grid-cols-2 text-xs transition-colors duration-300"
|
||||
className="grid gap-2 sm:grid-cols-2 text-xs transition-colors duration-500"
|
||||
style={{ color: 'var(--text-dim)' }}
|
||||
>
|
||||
<div className="space-y-1">
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ function CurrentMenuTitle() {
|
|||
|
||||
return (
|
||||
<div
|
||||
className="flex items-center gap-3 transition-colors duration-300"
|
||||
className="flex items-center gap-3 transition-colors duration-500"
|
||||
style={{ color: 'var(--text-muted)' }}
|
||||
>
|
||||
<span style={{ color: 'var(--text-slash)' }}>/</span>
|
||||
|
|
@ -51,7 +51,7 @@ function CurrentMenuTitle() {
|
|||
<img src={menu.image.url} alt="" className="w-5 h-5 object-contain" />
|
||||
)}
|
||||
<span
|
||||
className="text-sm font-medium transition-colors duration-300"
|
||||
className="text-sm font-medium transition-colors duration-500"
|
||||
style={{ color: 'var(--text-emphasis)' }}
|
||||
>
|
||||
{menu.title}
|
||||
|
|
@ -66,13 +66,7 @@ function ThemeToggle() {
|
|||
const toggleTheme = useThemeStore((s) => s.toggleTheme)
|
||||
const isLight = theme === 'light'
|
||||
|
||||
const handleToggle = () => {
|
||||
if (typeof document !== 'undefined' && document.startViewTransition) {
|
||||
document.startViewTransition(() => toggleTheme())
|
||||
} else {
|
||||
toggleTheme()
|
||||
}
|
||||
}
|
||||
const handleToggle = () => toggleTheme()
|
||||
|
||||
return (
|
||||
<button
|
||||
|
|
@ -80,7 +74,7 @@ function ThemeToggle() {
|
|||
onClick={handleToggle}
|
||||
aria-label={isLight ? '다크 모드로 전환' : '라이트 모드로 전환'}
|
||||
title={isLight ? '다크 모드' : '라이트 모드'}
|
||||
className="relative inline-flex h-8 w-14 items-center rounded-full border transition-colors duration-300 hover:border-emerald-500/40"
|
||||
className="relative inline-flex h-8 w-14 items-center rounded-full border transition-colors duration-500 hover:border-emerald-500/40"
|
||||
style={{
|
||||
background: 'var(--toggle-bg)',
|
||||
borderColor: 'var(--toggle-border)',
|
||||
|
|
@ -123,15 +117,14 @@ export default function Layout() {
|
|||
return (
|
||||
<LayoutContext.Provider value={{ fullscreen, setFullscreen }}>
|
||||
<div
|
||||
className={`min-w-[1280px] flex flex-col transition-colors duration-300 ${
|
||||
className={`min-w-[1280px] flex flex-col transition-colors duration-500 ${
|
||||
fullscreen ? 'h-dvh' : 'min-h-screen'
|
||||
}`}
|
||||
style={{ color: 'var(--text-strong)' }}
|
||||
>
|
||||
<header
|
||||
className="sticky top-0 z-20 border-b backdrop-blur-md shrink-0 transition-colors duration-300"
|
||||
className="sticky top-0 z-20 border-b backdrop-blur-md shrink-0 transition-colors duration-500"
|
||||
style={{
|
||||
background: 'var(--header-bg)',
|
||||
borderColor: 'var(--header-border)',
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -101,7 +101,9 @@ html, body, #root {
|
|||
background-color: var(--bg-from);
|
||||
background-image: linear-gradient(to bottom right, var(--bg-from), var(--bg-via), var(--bg-to));
|
||||
background-attachment: fixed;
|
||||
transition: background-color 400ms ease, background-image 400ms ease;
|
||||
transition:
|
||||
background-color 500ms cubic-bezier(0.4, 0, 0.2, 1),
|
||||
background-image 500ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
html {
|
||||
overscroll-behavior-y: contain;
|
||||
|
|
@ -160,24 +162,6 @@ input[type="number"] {
|
|||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* 테마 전환 뷰 트랜지션 - 부드러운 크로스페이드 */
|
||||
::view-transition-old(root),
|
||||
::view-transition-new(root) {
|
||||
animation-duration: 400ms;
|
||||
animation-timing-function: ease;
|
||||
}
|
||||
::view-transition-old(root) {
|
||||
animation-name: themeFadeOut;
|
||||
}
|
||||
::view-transition-new(root) {
|
||||
animation-name: themeFadeIn;
|
||||
}
|
||||
@keyframes themeFadeOut {
|
||||
to { opacity: 0; }
|
||||
}
|
||||
@keyframes themeFadeIn {
|
||||
from { opacity: 0; }
|
||||
}
|
||||
|
||||
|
||||
/* 커스텀 스크롤바 (테마별) */
|
||||
|
|
|
|||
|
|
@ -14,17 +14,17 @@ export default function Home() {
|
|||
{/* 구분선 */}
|
||||
<div className="flex items-center gap-4">
|
||||
<div
|
||||
className="h-px flex-1 transition-colors duration-300"
|
||||
className="h-px flex-1 transition-colors duration-500"
|
||||
style={{ backgroundImage: 'linear-gradient(to right, transparent, var(--divider-line), transparent)' }}
|
||||
/>
|
||||
<span
|
||||
className="text-xs uppercase tracking-widest transition-colors duration-300"
|
||||
className="text-xs uppercase tracking-widest transition-colors duration-500"
|
||||
style={{ color: 'var(--text-dim)' }}
|
||||
>
|
||||
Utilities
|
||||
</span>
|
||||
<div
|
||||
className="h-px flex-1 transition-colors duration-300"
|
||||
className="h-px flex-1 transition-colors duration-500"
|
||||
style={{ backgroundImage: 'linear-gradient(to right, transparent, var(--divider-line), transparent)' }}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -36,19 +36,19 @@ export default function Home() {
|
|||
{Array.from({ length: 3 }).map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="h-32 rounded-2xl animate-pulse transition-colors duration-300"
|
||||
className="h-32 rounded-2xl animate-pulse transition-colors duration-500"
|
||||
style={{ background: 'var(--skeleton-bg)' }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : menus.length === 0 ? (
|
||||
<div
|
||||
className="rounded-2xl border p-16 text-center transition-colors duration-300"
|
||||
className="rounded-2xl border p-16 text-center transition-colors duration-500"
|
||||
style={{ background: 'var(--empty-bg)', borderColor: 'var(--empty-border)' }}
|
||||
>
|
||||
<div className="text-5xl mb-4 opacity-50">🍁</div>
|
||||
<p
|
||||
className="transition-colors duration-300"
|
||||
className="transition-colors duration-500"
|
||||
style={{ color: 'var(--text-muted)' }}
|
||||
>
|
||||
아직 등록된 기능이 없습니다
|
||||
|
|
@ -76,7 +76,7 @@ export default function Home() {
|
|||
<div>
|
||||
<h2 className="font-medium">{menu.title}</h2>
|
||||
<p
|
||||
className="text-sm mt-1 leading-relaxed transition-colors duration-300"
|
||||
className="text-sm mt-1 leading-relaxed transition-colors duration-500"
|
||||
style={{ color: 'var(--text-muted)' }}
|
||||
>
|
||||
{menu.description}
|
||||
|
|
@ -92,17 +92,17 @@ export default function Home() {
|
|||
{/* 구분선 */}
|
||||
<div className="flex items-center gap-4">
|
||||
<div
|
||||
className="h-px flex-1 transition-colors duration-300"
|
||||
className="h-px flex-1 transition-colors duration-500"
|
||||
style={{ backgroundImage: 'linear-gradient(to right, transparent, var(--divider-line), transparent)' }}
|
||||
/>
|
||||
<span
|
||||
className="text-xs uppercase tracking-widest transition-colors duration-300"
|
||||
className="text-xs uppercase tracking-widest transition-colors duration-500"
|
||||
style={{ color: 'var(--text-dim)' }}
|
||||
>
|
||||
Notices
|
||||
</span>
|
||||
<div
|
||||
className="h-px flex-1 transition-colors duration-300"
|
||||
className="h-px flex-1 transition-colors duration-500"
|
||||
style={{ backgroundImage: 'linear-gradient(to right, transparent, var(--divider-line), transparent)' }}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue