푸터 조건 단순화 + 심볼 타입 배지 테마 대응

- 푸터를 홈 경로에서만 렌더링 (!fullscreen → isHome)
  이동 시 푸터가 잠깐 보였다 사라지는 플래시 제거
- 심볼 관리 타입 배지(아케인/어센틱/그랜드 어센틱) 테마별 토큰화
  라이트 모드에서 violet/sky/amber-300이 흰 배경에 안 보이던 문제 해결
- 계산기 페이지의 fullscreen 훅을 useLayoutEffect로 변경

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
caadiq 2026-04-19 11:12:19 +09:00
parent e78a18dedb
commit 45d325dfbe
6 changed files with 51 additions and 13 deletions

View file

@ -206,12 +206,15 @@ function HomeLinkButton() {
} }
export default function Layout() { export default function Layout() {
const location = useLocation()
const [fullscreen, setFullscreen] = useState(false) const [fullscreen, setFullscreen] = useState(false)
const [loginOpen, setLoginOpen] = useState(false) const [loginOpen, setLoginOpen] = useState(false)
const isAdmin = !!useMatch('/admin/*') const isAdmin = !!useMatch('/admin/*')
const homeTo = isAdmin ? '/admin' : '/' const homeTo = isAdmin ? '/admin' : '/'
const theme = useThemeStore((s) => s.theme) const theme = useThemeStore((s) => s.theme)
const isHome = location.pathname === '/'
useEffect(() => { useEffect(() => {
const root = document.documentElement const root = document.documentElement
if (theme === 'light') root.setAttribute('data-theme', 'light') if (theme === 'light') root.setAttribute('data-theme', 'light')
@ -256,7 +259,7 @@ export default function Layout() {
}`}> }`}>
<Outlet /> <Outlet />
</main> </main>
{!fullscreen && <Footer />} {isHome && <Footer />}
</div> </div>
</LayoutContext.Provider> </LayoutContext.Provider>
) )

View file

@ -1,4 +1,4 @@
import { useEffect } from 'react' import { useEffect, useLayoutEffect } from 'react'
import { useQuery, useQueries } from '@tanstack/react-query' import { useQuery, useQueries } from '@tanstack/react-query'
import { api } from '../../api/client' import { api } from '../../api/client'
import { useLayout } from '../../components/Layout' import { useLayout } from '../../components/Layout'
@ -21,7 +21,7 @@ export default function BossCrystal() {
// ( + ) // ( + )
const { setFullscreen } = useLayout() const { setFullscreen } = useLayout()
useEffect(() => { useLayoutEffect(() => {
setFullscreen(true) setFullscreen(true)
return () => setFullscreen(false) return () => setFullscreen(false)
}, [setFullscreen]) }, [setFullscreen])

View file

@ -1,4 +1,4 @@
import { useState, useEffect } from 'react' import { useState, useEffect, useLayoutEffect } from 'react'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { api } from '../../api/client' import { api } from '../../api/client'
@ -62,7 +62,7 @@ function calcMonthlyEarn(weekData) {
export default function Liberation() { export default function Liberation() {
const { setFullscreen } = useLayout() const { setFullscreen } = useLayout()
useEffect(() => { useLayoutEffect(() => {
setFullscreen(true) setFullscreen(true)
return () => setFullscreen(false) return () => setFullscreen(false)
}, [setFullscreen]) }, [setFullscreen])

View file

@ -1,4 +1,4 @@
import { useState, useEffect, useMemo } from 'react' import { useState, useEffect, useLayoutEffect, useMemo } from 'react'
import { useQuery, useQueries, useMutation } from '@tanstack/react-query' import { useQuery, useQueries, useMutation } from '@tanstack/react-query'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc' import utc from 'dayjs/plugin/utc'
@ -371,7 +371,7 @@ function SymbolCard({ symbol, equipped, charId }) {
export default function Symbol() { export default function Symbol() {
const { setFullscreen } = useLayout() const { setFullscreen } = useLayout()
useEffect(() => { useLayoutEffect(() => {
setFullscreen(true) setFullscreen(true)
return () => setFullscreen(false) return () => setFullscreen(false)
}, [setFullscreen]) }, [setFullscreen])

View file

@ -12,14 +12,26 @@ import {
import { CSS } from '@dnd-kit/utilities' import { CSS } from '@dnd-kit/utilities'
import { api } from '../../../api/client' import { api } from '../../../api/client'
const TYPE_COLOR = { const TYPE_STYLE = {
'아케인': { text: 'text-violet-300', bg: 'bg-violet-500/15', border: 'border-violet-500/30' }, '아케인': {
'어센틱': { text: 'text-sky-300', bg: 'bg-sky-500/15', border: 'border-sky-500/30' }, color: 'var(--symbol-arcane-text)',
'그랜드 어센틱': { text: 'text-amber-300', bg: 'bg-amber-500/15', border: 'border-amber-500/30' }, background: 'var(--symbol-arcane-bg)',
borderColor: 'var(--symbol-arcane-border)',
},
'어센틱': {
color: 'var(--symbol-authentic-text)',
background: 'var(--symbol-authentic-bg)',
borderColor: 'var(--symbol-authentic-border)',
},
'그랜드 어센틱': {
color: 'var(--symbol-grand-text)',
background: 'var(--symbol-grand-bg)',
borderColor: 'var(--symbol-grand-border)',
},
} }
function SymbolCardContent({ symbol, dragging = false }) { function SymbolCardContent({ symbol, dragging = false }) {
const color = TYPE_COLOR[symbol.type] || TYPE_COLOR['아케인'] const badgeStyle = TYPE_STYLE[symbol.type] || TYPE_STYLE['아케인']
return ( return (
<div <div
className="flex items-stretch rounded-2xl border" className="flex items-stretch rounded-2xl border"
@ -53,7 +65,10 @@ function SymbolCardContent({ symbol, dragging = false }) {
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-baseline gap-2 flex-wrap"> <div className="flex items-baseline gap-2 flex-wrap">
<h3 className="font-medium truncate">{symbol.region}</h3> <h3 className="font-medium truncate">{symbol.region}</h3>
<span className={`text-[10px] font-medium px-1.5 py-0.5 rounded border ${color.text} ${color.bg} ${color.border}`}> <span
className="text-[10px] font-medium px-1.5 py-0.5 rounded border"
style={badgeStyle}
>
{symbol.type} {symbol.type}
</span> </span>
</div> </div>

View file

@ -145,6 +145,16 @@
--liberation-primary-bar: rgba(167, 139, 250, 0.5); --liberation-primary-bar: rgba(167, 139, 250, 0.5);
--liberation-secondary: #fda4af; --liberation-secondary: #fda4af;
--liberation-secondary-bar: rgba(253, 164, 175, 0.5); --liberation-secondary-bar: rgba(253, 164, 175, 0.5);
--symbol-arcane-text: #c4b5fd;
--symbol-arcane-bg: rgba(139, 92, 246, 0.15);
--symbol-arcane-border: rgba(139, 92, 246, 0.3);
--symbol-authentic-text: #7dd3fc;
--symbol-authentic-bg: rgba(14, 165, 233, 0.15);
--symbol-authentic-border: rgba(14, 165, 233, 0.3);
--symbol-grand-text: #fcd34d;
--symbol-grand-bg: rgba(245, 158, 11, 0.15);
--symbol-grand-border: rgba(245, 158, 11, 0.3);
} }
/* 테마 토큰 - light */ /* 테마 토큰 - light */
@ -286,6 +296,16 @@
--liberation-primary-bar: rgba(124, 58, 237, 0.5); --liberation-primary-bar: rgba(124, 58, 237, 0.5);
--liberation-secondary: #e11d48; --liberation-secondary: #e11d48;
--liberation-secondary-bar: rgba(225, 29, 72, 0.5); --liberation-secondary-bar: rgba(225, 29, 72, 0.5);
--symbol-arcane-text: #6d28d9;
--symbol-arcane-bg: rgba(139, 92, 246, 0.12);
--symbol-arcane-border: rgba(109, 40, 217, 0.4);
--symbol-authentic-text: #0369a1;
--symbol-authentic-bg: rgba(14, 165, 233, 0.12);
--symbol-authentic-border: rgba(3, 105, 161, 0.4);
--symbol-grand-text: #b45309;
--symbol-grand-bg: rgba(245, 158, 11, 0.15);
--symbol-grand-border: rgba(180, 83, 9, 0.4);
} }
html, body, #root { html, body, #root {