Compare commits

...

2 Commits

9 changed files with 496 additions and 282 deletions

View File

@@ -649,6 +649,11 @@ async function listGameItems(gameId) {
return rows.map(mapGameItemRow)
}
async function findGameItemById(itemId) {
const rows = await query('SELECT id, game_id, src, label, created_at FROM game_items WHERE id = ? LIMIT 1', [itemId])
return mapGameItemRow(rows[0])
}
async function getGameDetail(gameId) {
const game = await findGameById(gameId)
if (!game) return null
@@ -1208,32 +1213,60 @@ async function getCustomItemUsageMeta() {
async function listCustomItems({ queryText = '', page = 1, limit = 50, orphanOnly = false } = {}) {
const normalizedLimit = Math.min(Math.max(Number(limit) || 50, 1), 200)
const normalizedPage = Math.max(Number(page) || 1, 1)
const hasQuery = !!(queryText || '').trim()
const search = `%${(queryText || '').trim()}%`
const whereClause = hasQuery ? 'WHERE c.label LIKE ? OR c.src LIKE ? OR u.email LIKE ? OR u.nickname LIKE ?' : ''
const params = hasQuery ? [search, search, search, search] : []
const searchText = (queryText || '').trim()
const hasQuery = !!searchText
const search = `%${searchText}%`
const rows = await query(
`
SELECT
c.id,
c.owner_id,
c.src,
c.label,
c.created_at,
u.nickname,
u.email
FROM custom_items c
INNER JOIN users u ON u.id = c.owner_id
${whereClause}
ORDER BY c.created_at DESC
`,
params
)
const [customRows, gameItemRows, usageMeta] = await Promise.all([
query(
`
SELECT
c.id,
c.owner_id,
c.src,
c.label,
c.created_at,
u.nickname,
u.email
FROM custom_items c
INNER JOIN users u ON u.id = c.owner_id
${hasQuery ? 'WHERE c.label LIKE ? OR c.src LIKE ? OR u.email LIKE ? OR u.nickname LIKE ?' : ''}
ORDER BY c.created_at DESC
`,
hasQuery ? [search, search, search, search] : []
),
query(
`
SELECT
gi.id,
gi.game_id,
gi.src,
gi.label,
gi.created_at,
g.name AS game_name
FROM game_items gi
INNER JOIN games g ON g.id = gi.game_id
${hasQuery ? 'WHERE gi.label LIKE ? OR gi.src LIKE ? OR gi.game_id LIKE ? OR g.name LIKE ?' : ''}
ORDER BY gi.created_at DESC
`,
hasQuery ? [search, search, search, search] : []
),
getCustomItemUsageMeta(),
])
const { usageMap, linkedGamesMap } = await getCustomItemUsageMeta()
const allItems = rows
.map((row) => ({
const templateLinkedBySrc = new Map()
gameItemRows.forEach((row) => {
if (!row?.src) return
if (!templateLinkedBySrc.has(row.src)) templateLinkedBySrc.set(row.src, new Map())
templateLinkedBySrc.get(row.src).set(row.game_id, {
id: row.game_id,
name: row.game_name || row.game_id,
})
})
const customItems = customRows.map((row) => {
const linkedGames = Array.from((templateLinkedBySrc.get(row.src) || new Map()).values())
return {
id: row.id,
ownerId: row.owner_id,
src: row.src,
@@ -1241,10 +1274,37 @@ async function listCustomItems({ queryText = '', page = 1, limit = 50, orphanOnl
createdAt: Number(row.created_at),
ownerName: row.nickname || row.email,
ownerEmail: row.email,
usageCount: usageMap.get(row.id) || 0,
linkedGames: linkedGamesMap.get(row.id) || [],
}))
.filter((item) => (orphanOnly ? item.usageCount === 0 : true))
usageCount: usageMeta.usageMap.get(row.id) || 0,
linkedGames,
sourceType: 'user',
sourceLabel: '사용자 업로드',
canDelete: true,
}
})
const templateItems = gameItemRows.map((row) => ({
id: row.id,
ownerId: '',
src: row.src,
label: row.label,
createdAt: Number(row.created_at),
ownerName: row.game_name || row.game_id,
ownerEmail: '',
usageCount: (templateLinkedBySrc.get(row.src) || new Map()).size,
linkedGames: Array.from((templateLinkedBySrc.get(row.src) || new Map()).values()),
sourceType: 'template',
sourceLabel: '관리자 템플릿',
canDelete: false,
sourceGameId: row.game_id,
sourceGameName: row.game_name || row.game_id,
}))
const allItems = [...customItems, ...templateItems]
.filter((item) => {
if (!orphanOnly) return true
return item.sourceType === 'user' && item.usageCount === 0 && item.linkedGames.length === 0
})
.sort((a, b) => Number(b.createdAt || 0) - Number(a.createdAt || 0))
const total = allItems.length
const offset = (normalizedPage - 1) * normalizedLimit
@@ -1935,6 +1995,7 @@ module.exports = {
listGames,
findGameById,
listGameItems,
findGameItemById,
getGameDetail,
createGame,
updateGameThumbnail,

View File

@@ -75,7 +75,7 @@ async function optimizeAndPersist({ file, width, height, fit, quality }) {
}
}
const filename = String(Date.now()) + '-' + nanoid() + '.webp'
const filename = nanoid() + '.webp'
const absoluteDir = path.join(UPLOAD_ROOT, OPTIMIZED_DIR)
const absolutePath = path.join(absoluteDir, filename)
const src = '/uploads/' + OPTIMIZED_DIR + '/' + filename

View File

@@ -8,6 +8,7 @@ const { nanoid } = require('nanoid')
const {
findUserById,
findGameById,
findGameItemById,
createGame,
listGames,
updateGameThumbnail,
@@ -322,12 +323,12 @@ async function removeCustomItemFiles(items) {
)
}
async function promoteCustomItemToGameItem({ customItem, gameId }) {
async function promoteLibraryItemToGameItem({ item, gameId }) {
return createGameItem({
id: nanoid(),
gameId,
src: customItem.src || '',
label: customItem.label,
src: item.src || '',
label: item.label,
})
}
@@ -428,6 +429,8 @@ router.delete('/custom-items/:itemId', requireAdmin, async (req, res) => {
const result = await listCustomItems({ page: 1, limit: 200, orphanOnly: false })
const target = result.items.find((item) => item.id === req.params.itemId)
if (!target) return res.status(404).json({ error: 'not_found' })
if (!target.canDelete) return res.status(409).json({ error: 'item_locked' })
if (target.linkedGames.length > 0) return res.status(409).json({ error: 'item_linked' })
if (target.usageCount > 0) return res.status(409).json({ error: 'item_in_use' })
const items = await findCustomItemsByIds([target.id])
@@ -447,9 +450,11 @@ router.post('/custom-items/:itemId/promote', requireAdmin, async (req, res) => {
if (!game) return res.status(404).json({ error: 'game_not_found' })
const customItem = await findCustomItemById(req.params.itemId)
if (!customItem) return res.status(404).json({ error: 'not_found' })
const gameItem = customItem ? null : await findGameItemById(req.params.itemId)
const sourceItem = customItem || gameItem
if (!sourceItem) return res.status(404).json({ error: 'not_found' })
const item = await promoteCustomItemToGameItem({ customItem, gameId: game.id })
const item = await promoteLibraryItemToGameItem({ item: sourceItem, gameId: game.id })
res.json({ item })
})

View File

@@ -1,7 +1,7 @@
# 할 일 및 이슈
## 중기 개선
- 라이트모드/다크모드 1전환은 붙였으므로, 관리자 화면과 티어 에디터까지 세부 색상 균형을 더 정교하게 맞추는 후속 테마 보정 작업을 이어간다.
- 라이트모드/다크모드 2보정까지 반영했으므로, 남은 작업은 전체 화면을 실제 사용 흐름으로 돌려 보며 대비·명도·아이콘 가독성을 미세하게 QA하는 최종 테마 점검 단계로 가져간다.
- 관리자용 티어표 승인/숨김 처리, 아이템 정렬 UI를 추가한다.
- 회원 일괄 작업(다중 선택, 일괄 비밀번호 초기화, 활동 저조 계정 정리) 같은 관리 보조 기능을 추가한다.
- 티어 행 프리셋 저장, 색상 관리, 행 복제 같은 고급 편집 기능을 추가한다.
@@ -10,3 +10,5 @@
- production에서 SESSION_SECRET 누락 시 서버가 부팅되지 않도록 강제한다.
- helmet 기반 보안 헤더와 업로드 정적 응답 헤더를 정리한다.
- 책 아이콘 기반 사용법 모달은 제작 흐름뿐 아니라 복사, 템플릿 업데이트 요청, 새 템플릿 요청까지 확장했으므로, 실제 16:9 스크린샷 자산과 단계별 문구를 운영 톤에 맞게 채운다.
- 관리자 아이템 라이브러리에서 동일 이미지(src)를 여러 템플릿이 공유하는 경우, 필요하면 묶어서 보거나 대표 카드로 합쳐 보는 후속 정리 옵션을 검토한다.

View File

@@ -1,5 +1,16 @@
# 업데이트 로그
## 2026-04-01 v1.3.34
- 관리자 아이템 관리 오른쪽 사이드에서는 `가져올 게임` 셀렉트를 제거하고, 사용자 업로드와 관리자 템플릿 이미지를 함께 검수하는 라이브러리 흐름으로 단순화함.
- 아이템 상세 모달은 좌측에 검색/정렬 가능한 게임 리스트를 두고 우측에 이미지·메타·액션을 배치하는 2단 레이아웃으로 재구성해, 많은 게임 속에서도 직접 검수 후 템플릿에 연결하기 쉽게 정리함.
- 아이템 라이브러리에는 이제 관리자 템플릿 이미지도 함께 표시하고, 배지로 `사용자 업로드 / 관리자 템플릿`을 구분하며 새 업로드 WebP 파일명에서는 시간 정보처럼 보이는 접두 숫자를 제거함.
- 템플릿 아이템까지 함께 보이는 구조에 맞춰 삭제 API도 사용자 업로드이면서 템플릿에 연결되지 않은 항목만 지울 수 있도록 안전 장치를 보강함.
## 2026-04-01 v1.3.33
- 라이트모드/다크모드 2차 보정으로 관리자 화면과 티어 에디터의 카드, 패널, 입력창, 모달, 썸네일 프레임을 전역 테마 변수 기준으로 다시 맞춰, 후속 화면에서도 명도 차가 더 자연스럽게 이어지도록 정리함.
- 공통 셸도 함께 손봐서 좌측 사이드 아이콘 필터와 텍스트 대비를 테마 변수 기반으로 전환하고, 가이드 모달·축소 검색 모달·내비 활성 상태까지 라이트모드에서 읽기 쉬운 톤으로 보정함.
- 전역 스타일 변수의 다크 기본값과 아이콘 필터 값을 바로잡아, 카드 배경과 텍스트 변수의 자기참조/오동작 가능성을 줄이고 이후 테마 QA 기준을 더 안정적으로 맞춤.
## 2026-04-01 v1.3.32
- 전역 테마 변수와 로컬 저장 기반 테마 토글을 추가해, Settings 화면 오른쪽 사이드에서 라이트모드/다크모드를 전환하고 재방문 시 같은 테마를 유지할 수 있게 함.
- 앱 셸, 홈, 게임 허브, 내 티어표, 즐겨찾기, 검색, 로그인, 설정 화면의 공통 카드·입력·텍스트 색을 테마 변수 기준으로 바꿔, 주요 사용자 화면은 라이트/다크 전환이 자연스럽게 이어지도록 1차 정리함.

View File

@@ -744,7 +744,7 @@ function submitGlobalSearch() {
border-radius: 8px;
border: 1px solid var(--theme-border);
background: var(--theme-pill-bg);
color: rgba(255, 255, 255, 0.72);
color: var(--theme-text-soft);
cursor: pointer;
display: inline-flex;
align-items: center;
@@ -771,7 +771,7 @@ function submitGlobalSearch() {
width: 24px;
height: 24px;
display: block;
filter: brightness(0) saturate(100%) invert(94%) sepia(6%) saturate(207%) hue-rotate(186deg) brightness(96%) contrast(92%);
filter: var(--theme-icon-filter);
}
.ghostIcon--iconOnly {
@@ -818,7 +818,7 @@ function submitGlobalSearch() {
.appUserCard__avatar--fallback {
display: grid;
place-items: center;
background: rgba(255, 255, 255, 0.12);
background: var(--theme-surface-soft-3);
font-weight: 900;
}
@@ -853,7 +853,7 @@ function submitGlobalSearch() {
border-radius: 14px;
border: 1px solid var(--theme-border);
background: var(--theme-pill-bg);
color: rgba(255, 255, 255, 0.62);
color: var(--theme-text-soft);
margin-bottom: 14px;
box-sizing: border-box;
transition: padding 220ms ease, justify-content 220ms ease;
@@ -906,7 +906,7 @@ function submitGlobalSearch() {
gap: 12px;
padding: 11px 12px;
border-radius: 14px;
color: rgba(255, 255, 255, 0.76);
color: var(--theme-text-muted);
text-decoration: none;
transition: background 180ms ease, color 180ms ease, transform 180ms ease;
}
@@ -921,8 +921,8 @@ function submitGlobalSearch() {
.leftNav__item--active,
.leftNav__item.router-link-active {
background: rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.96);
background: var(--theme-surface-soft-3);
color: var(--theme-text-strong);
}
.leftNav__glyph {
@@ -1273,7 +1273,7 @@ function submitGlobalSearch() {
grid-template-columns: 260px minmax(0, 1fr);
border-radius: 28px;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.1);
border: 1px solid var(--theme-border-strong);
background: linear-gradient(180deg, rgba(34, 34, 34, 0.98), rgba(18, 18, 18, 0.98));
box-shadow: 0 28px 90px rgba(0, 0, 0, 0.42);
}
@@ -1291,7 +1291,7 @@ function submitGlobalSearch() {
font-size: 11px;
letter-spacing: 0.14em;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.4);
color: var(--theme-text-faint);
}
.guideModal__title {
@@ -1314,8 +1314,8 @@ function submitGlobalSearch() {
padding: 12px 14px;
border-radius: 16px;
border: 1px solid var(--theme-border);
background: rgba(255, 255, 255, 0.02);
color: rgba(255, 255, 255, 0.8);
background: var(--theme-pill-bg);
color: var(--theme-text-muted);
cursor: pointer;
text-align: left;
}
@@ -1323,13 +1323,13 @@ function submitGlobalSearch() {
.guideModal__listItem--active {
border-color: rgba(77, 127, 233, 0.5);
background: rgba(77, 127, 233, 0.14);
color: rgba(255, 255, 255, 0.96);
color: var(--theme-text-strong);
}
.guideModal__listIndex {
font-size: 12px;
font-weight: 900;
color: rgba(255, 255, 255, 0.54);
color: var(--theme-text-faint);
}
.guideModal__listLabel {
@@ -1400,7 +1400,7 @@ function submitGlobalSearch() {
.guideModal__mediaHint {
font-size: 13px;
color: rgba(255, 255, 255, 0.48);
color: var(--theme-text-faint);
}
.guideModal__text {
@@ -1424,14 +1424,14 @@ function submitGlobalSearch() {
.guideModal__stepSummary {
font-size: 16px;
font-weight: 700;
color: rgba(255, 255, 255, 0.86);
color: var(--theme-text);
}
.guideModal__stepDescription {
margin: 0;
max-width: 720px;
line-height: 1.7;
color: rgba(255, 255, 255, 0.62);
color: var(--theme-text-soft);
}
.guideModal__footer {
@@ -1452,7 +1452,7 @@ function submitGlobalSearch() {
height: 10px;
border-radius: 999px;
border: 0;
background: rgba(255, 255, 255, 0.18);
background: var(--theme-surface-soft-3);
cursor: pointer;
}
@@ -1496,7 +1496,7 @@ function submitGlobalSearch() {
justify-content: center;
align-items: flex-start;
padding: 88px 20px 20px;
background: rgba(0, 0, 0, 0.44);
background: color-mix(in srgb, var(--theme-body-bg) 72%, transparent);
backdrop-filter: blur(6px);
}
@@ -1507,8 +1507,8 @@ function submitGlobalSearch() {
gap: 14px;
padding: 18px 22px;
border-radius: 24px;
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(26, 26, 26, 0.96);
border: 1px solid var(--theme-border-strong);
background: var(--theme-main-bg);
box-shadow: 0 28px 60px rgba(0, 0, 0, 0.34);
}
@@ -1525,7 +1525,7 @@ function submitGlobalSearch() {
width: 28px;
height: 28px;
display: block;
filter: brightness(0) saturate(100%) invert(94%) sepia(6%) saturate(207%) hue-rotate(186deg) brightness(96%) contrast(92%);
filter: var(--theme-icon-filter);
}
.collapsedSearchBar__input {
@@ -1540,7 +1540,7 @@ function submitGlobalSearch() {
}
.collapsedSearchBar__input::placeholder {
color: rgba(255, 255, 255, 0.46);
color: var(--theme-text-faint);
}
.localRightRailRoot {
@@ -1572,7 +1572,7 @@ function submitGlobalSearch() {
padding: 12px 14px;
border-radius: 16px;
border: 1px solid var(--theme-border-strong);
background: rgba(11, 18, 32, 0.94);
background: color-mix(in srgb, var(--theme-main-bg) 94%, transparent);
backdrop-filter: blur(12px);
box-shadow: 0 14px 30px rgba(0, 0, 0, 0.28);
opacity: 1;
@@ -1611,7 +1611,7 @@ function submitGlobalSearch() {
.toast__close {
border: 0;
background: transparent;
color: rgba(255, 255, 255, 0.68);
color: var(--theme-text-muted);
cursor: pointer;
font-size: 12px;
}

View File

@@ -24,16 +24,17 @@
--theme-border: rgba(255, 255, 255, 0.08);
--theme-border-strong: rgba(255, 255, 255, 0.12);
--theme-text: rgba(255, 255, 255, 0.92);
--theme-text-strong: var(--theme-text-strong);
--theme-text-muted: var(--theme-text-muted);
--theme-text-soft: var(--theme-text-soft);
--theme-text-strong: rgba(255, 255, 255, 0.98);
--theme-text-muted: rgba(255, 255, 255, 0.74);
--theme-text-soft: rgba(255, 255, 255, 0.62);
--theme-text-faint: rgba(255, 255, 255, 0.4);
--theme-thumb-fallback-bg: #555;
--theme-select-arrow: var(--theme-select-arrow);
--theme-select-arrow: rgba(255, 255, 255, 0.68);
--theme-danger-bg: rgba(239, 68, 68, 0.1);
--theme-danger-border: rgba(239, 68, 68, 0.18);
--theme-accent-bg: rgba(76, 133, 245, 0.92);
--theme-accent-text: #fff;
--theme-icon-filter: brightness(0) saturate(100%) invert(94%) sepia(6%) saturate(207%) hue-rotate(186deg) brightness(96%) contrast(92%);
}
:root[data-theme='light'] {
@@ -42,7 +43,7 @@
--theme-rail-bg: rgba(248, 250, 253, 0.96);
--theme-main-bg: rgba(241, 244, 249, 0.98);
--theme-workspace-bg: rgba(250, 252, 255, 0.95);
--theme-card-bg: var(--theme-text-strong);
--theme-card-bg: rgba(255, 255, 255, 0.98);
--theme-card-bg-hover: rgba(245, 248, 255, 0.98);
--theme-card-border: rgba(26, 32, 44, 0.1);
--theme-card-shadow: 0 18px 34px rgba(31, 41, 55, 0.08);
@@ -63,6 +64,7 @@
--theme-danger-border: rgba(239, 68, 68, 0.22);
--theme-accent-bg: rgba(64, 110, 226, 0.94);
--theme-accent-text: #fff;
--theme-icon-filter: brightness(0) saturate(100%) invert(14%) sepia(14%) saturate(652%) hue-rotate(182deg) brightness(95%) contrast(91%);
}
* {

View File

@@ -31,8 +31,9 @@ const customItemPage = ref(1)
const customItemLimit = ref(50)
const customItemTotal = ref(0)
const customItemOrphanOnly = ref(false)
const customItemTargetGameId = ref('')
const customItemModalTargetGameId = ref('')
const customItemModalGameQuery = ref('')
const customItemModalGameSort = ref('recent')
const adminTierLists = ref([])
const adminTierListQuery = ref('')
@@ -137,7 +138,7 @@ const activeTabDescription = computed(() => {
return '게임 생성, 선택, 썸네일, 기본 아이템 관리를 전용 작업 화면으로 분리했습니다.'
}
if (activeTab.value === 'items') {
return '사용자 커스텀 이미지를 검색하고, 미사용 이미지를 정리하거나 템플릿으로 승격할 수 있어요.'
return '사용자 업로드와 관리자 템플릿 이미지를 함께 검수하고, 필요한 게임에 직접 연결할 수 있어요.'
}
if (activeTab.value === 'tierlists') {
return tierlistsMode.value === 'requests'
@@ -170,7 +171,7 @@ const adminOverviewStats = computed(() => {
return [
{ label: '검색 결과', value: `${customItemTotal.value}` },
{ label: '미사용', value: `${orphanItems}` },
{ label: '대상 게임', value: customItemTargetGameId.value ? '선택됨' : '미선택' },
{ label: '템플릿 아이템', value: `${customItems.value.filter((item) => item.sourceType === 'template').length}` },
]
}
if (activeTab.value === 'tierlists') {
@@ -283,6 +284,21 @@ const imageDiagnosticsCards = computed(() => {
const visibleLinkedGames = computed(() =>
(modalTargetCustomItem.value?.linkedGames || []).filter((game) => game?.id && game.id !== 'freeform')
)
const filteredCustomItemModalGames = computed(() => {
const query = customItemModalGameQuery.value.trim().toLowerCase()
const linkedIds = new Set(visibleLinkedGames.value.map((game) => game.id))
const list = games.value.filter((game) => {
if (!query) return true
return `${game.name || ''} ${game.id || ''}`.toLowerCase().includes(query)
})
return list.slice().sort((a, b) => {
const linkedDelta = Number(linkedIds.has(a.id)) - Number(linkedIds.has(b.id))
if (linkedDelta !== 0) return linkedDelta
if (customItemModalGameSort.value === 'oldest') return Number(a.createdAt || 0) - Number(b.createdAt || 0)
return Number(b.createdAt || 0) - Number(a.createdAt || 0)
})
})
const imageStatsPeriodLabel = computed(() => (imageStatsMonth.value ? `${imageStatsMonth.value} 기준` : '전체 기간'))
const imageStatsYearOptions = computed(() => {
@@ -532,7 +548,7 @@ async function refreshCustomItems() {
customItemPage.value = data.page || 1
customItemLimit.value = data.limit || customItemLimit.value
} catch (e) {
error.value = '사용자 커스텀 아이템을 불러오지 못했어요.'
error.value = '아이템 라이브러리를 불러오지 못했어요.'
}
}
@@ -1035,6 +1051,8 @@ function moveCustomItemPage(direction) {
function openCustomItemModal(item) {
modalTargetCustomItem.value = item || null
customItemModalTargetGameId.value = ''
customItemModalGameQuery.value = ''
customItemModalGameSort.value = 'recent'
customItemModalOpen.value = true
}
@@ -1042,12 +1060,14 @@ function closeCustomItemModal() {
customItemModalOpen.value = false
modalTargetCustomItem.value = null
customItemModalTargetGameId.value = ''
customItemModalGameQuery.value = ''
customItemModalGameSort.value = 'recent'
}
function openCustomItemDeleteModal(item) {
if (!item) return
if (item.usageCount > 0) {
error.value = '사용 중인 커스텀 이미지는 먼저 참조를 정리해야 삭제할 수 있어요.'
error.value = '사용 중인 사용자 업로드 이미지는 먼저 참조를 정리해야 삭제할 수 있어요.'
return
}
modalTargetCustomItem.value = item
@@ -1062,7 +1082,7 @@ async function removeCustomItem(item = modalTargetCustomItem.value) {
resetMessages()
if (!item) return
if (item.usageCount > 0) {
error.value = '사용 중인 커스텀 이미지는 먼저 참조를 정리해야 삭제할 수 있어요.'
error.value = '사용 중인 사용자 업로드 이미지는 먼저 참조를 정리해야 삭제할 수 있어요.'
return
}
@@ -1071,7 +1091,7 @@ async function removeCustomItem(item = modalTargetCustomItem.value) {
closeCustomItemDeleteModal()
closeCustomItemModal()
await refreshCustomItems()
success.value = '미사용 커스텀 이미지를 삭제했어요.'
success.value = '미사용 사용자 업로드 이미지를 삭제했어요.'
} catch (e) {
error.value = '커스텀 이미지 삭제에 실패했어요.'
}
@@ -1085,7 +1105,7 @@ async function removeUnusedCustomItems() {
try {
const data = await api.deleteAdminUnusedCustomItems({ q: customItemQuery.value })
await refreshCustomItems()
success.value = `${data.deletedCount || 0}개의 미사용 커스텀 이미지를 삭제했어요.`
success.value = `${data.deletedCount || 0}개의 미사용 사용자 업로드 이미지를 삭제했어요.`
} catch (e) {
error.value = '미사용 커스텀 이미지 일괄 삭제에 실패했어요.'
}
@@ -1094,7 +1114,7 @@ async function removeUnusedCustomItems() {
async function promoteCustomItem(item) {
resetMessages()
if (!customItemModalTargetGameId.value) {
error.value = '가져올 게임을 먼저 선택해주세요.'
error.value = '추가할 게임을 먼저 선택해주세요.'
return
}
@@ -1104,9 +1124,9 @@ async function promoteCustomItem(item) {
const targetGameName = games.value.find((game) => game.id === customItemModalTargetGameId.value)?.name || customItemModalTargetGameId.value
if (selectedGameId.value === customItemModalTargetGameId.value) await loadGame()
closeCustomItemModal()
success.value = `"${item.label}" 아이템을 ${targetGameName} 기본 템플릿으로 추가했어요.`
success.value = `"${item.label}" 이미지를 ${targetGameName} 템플릿으로 추가했어요.`
} catch (e) {
error.value = '커스텀 아이템을 기본 템플릿으로 가져오지 못했어요.'
error.value = '선택한 이미지를 템플릿으로 추가하지 못했어요.'
} finally {
item.isPromoting = false
}
@@ -1535,9 +1555,10 @@ async function saveFeaturedOrder() {
<template v-else-if="activeTab === 'items'">
<div class="panel">
<div v-if="!customItems.length" class="hint">조건에 맞는 커스텀 아이템이 없어요.</div>
<div v-if="!customItems.length" class="hint">조건에 맞는 관리 대상 아이템이 없어요.</div>
<div v-else class="customItemGrid">
<button v-for="item in customItems" :key="item.id" type="button" class="customItemCard" @click="openCustomItemModal(item)">
<span class="customItemCard__badge" :class="{ 'customItemCard__badge--template': item.sourceType === 'template' }">{{ item.sourceLabel }}</span>
<img class="customItemCard__image" :src="toApiUrl(item.src)" :alt="item.label" />
<div class="customItemCard__title" :title="item.label">{{ item.label }}</div>
</button>
@@ -1912,19 +1933,54 @@ async function saveFeaturedOrder() {
<div v-if="customItemModalOpen" class="modalOverlay" @click.self="closeCustomItemModal">
<div class="modalCard modalCard--customItem" role="dialog" aria-modal="true">
<div class="modalCard__titleRow">
<div class="modalCard__title">커스텀 아이템 상세</div>
<div class="modalCard__title">아이템 상세</div>
<button class="btn btn--ghost btn--small" @click="closeCustomItemModal">닫기</button>
</div>
<div v-if="modalTargetCustomItem" class="customItemModal">
<div class="customItemModal__side">
<img class="customItemModal__image" :src="toApiUrl(modalTargetCustomItem.src)" :alt="modalTargetCustomItem.label" />
<div class="customItemModal__selector">
<span class="customItemModal__label">기본 템플릿 추가</span>
<select v-model="customItemModalTargetGameId" class="select">
<option value="">게임 선택</option>
<option v-for="game in games" :key="game.id" :value="game.id">{{ game.name }}</option>
<aside class="customItemModal__pickerPanel">
<div class="customItemModal__pickerHead">
<div class="customItemModal__pickerEyebrow">GAME LIBRARY</div>
<div class="customItemModal__pickerTitle">템플릿으로 추가 게임</div>
</div>
<div class="customItemModal__pickerControls">
<input v-model="customItemModalGameQuery" class="input input--dense" placeholder="게임 이름, ID 검색" />
<select v-model="customItemModalGameSort" class="select">
<option value="recent">최신순</option>
<option value="oldest">오래된순</option>
</select>
</div>
<div class="customItemModal__gameList">
<button
v-for="game in filteredCustomItemModalGames"
:key="game.id"
type="button"
class="customItemModal__gameItem"
:class="{
'customItemModal__gameItem--active': customItemModalTargetGameId === game.id,
'customItemModal__gameItem--linked': visibleLinkedGames.some((entry) => entry.id === game.id),
}"
@click="customItemModalTargetGameId = game.id"
>
<span class="customItemModal__gameName">{{ game.name }}</span>
<span class="customItemModal__gameMeta">{{ game.id }}</span>
<span v-if="visibleLinkedGames.some((entry) => entry.id === game.id)" class="customItemModal__gameState">이미 포함됨</span>
</button>
</div>
</aside>
<div class="customItemModal__body">
<div class="customItemModal__titleRow">
<div>
<div class="customItemModal__title">{{ modalTargetCustomItem.label }}</div>
<div class="customItemModal__source">{{ modalTargetCustomItem.sourceLabel }}</div>
</div>
</div>
<img class="customItemModal__image" :src="toApiUrl(modalTargetCustomItem.src)" :alt="modalTargetCustomItem.label" />
<div class="customItemModal__metaList">
<div class="customItemModal__metaRow"><span>파일</span><strong :title="modalTargetCustomItem.src.split('/').pop()">{{ modalTargetCustomItem.src.split('/').pop() }}</strong></div>
<div class="customItemModal__metaRow"><span>업로더/출처</span><strong :title="modalTargetCustomItem.ownerName">{{ modalTargetCustomItem.ownerName }}</strong></div>
<div class="customItemModal__metaRow"><span>템플릿 연결</span><strong>{{ visibleLinkedGames.length }} 게임</strong></div>
<div class="customItemModal__metaRow"><span>등록일</span><strong>{{ fmt(modalTargetCustomItem.createdAt) }}</strong></div>
</div>
<div class="customItemModal__linked">
<span class="customItemModal__label">템플릿에 사용 중인 게임</span>
<div v-if="visibleLinkedGames.length" class="customItemModal__chips">
@@ -1932,21 +1988,12 @@ async function saveFeaturedOrder() {
</div>
<div v-else class="hint hint--tight">아직 템플릿에 연결된 게임이 없어요.</div>
</div>
</div>
<div class="customItemModal__body">
<div class="customItemModal__title">{{ modalTargetCustomItem.label }}</div>
<div class="customItemModal__metaList">
<div class="customItemModal__metaRow"><span>파일</span><strong :title="modalTargetCustomItem.src.split('/').pop()">{{ modalTargetCustomItem.src.split('/').pop() }}</strong></div>
<div class="customItemModal__metaRow"><span>업로더</span><strong :title="modalTargetCustomItem.ownerName">{{ modalTargetCustomItem.ownerName }}</strong></div>
<div class="customItemModal__metaRow"><span>사용 </span><strong>{{ modalTargetCustomItem.usageCount }} 티어표</strong></div>
<div class="customItemModal__metaRow"><span>등록일</span><strong>{{ fmt(modalTargetCustomItem.createdAt) }}</strong></div>
</div>
<div class="customItemModal__actions">
<a class="btn btn--ghost" :href="toApiUrl(modalTargetCustomItem.src)" :download="modalTargetCustomItem.label">이미지 다운로드</a>
<button class="btn btn--ghost" :disabled="!customItemModalTargetGameId || modalTargetCustomItem.isPromoting" @click="promoteCustomItem(modalTargetCustomItem)">
<a class="btn btn--ghost customItemModal__action" :href="toApiUrl(modalTargetCustomItem.src)" :download="modalTargetCustomItem.label">이미지 다운로드</a>
<button class="btn btn--ghost customItemModal__action" :disabled="!customItemModalTargetGameId || modalTargetCustomItem.isPromoting" @click="promoteCustomItem(modalTargetCustomItem)">
{{ modalTargetCustomItem.isPromoting ? '추가중...' : '기본 템플릿에 추가' }}
</button>
<button class="btn btn--danger" :disabled="modalTargetCustomItem.usageCount > 0" @click="openCustomItemDeleteModal(modalTargetCustomItem)">삭제</button>
<button v-if="modalTargetCustomItem.canDelete" class="btn btn--danger customItemModal__action" :disabled="modalTargetCustomItem.usageCount > 0 || visibleLinkedGames.length > 0" @click="openCustomItemDeleteModal(modalTargetCustomItem)">삭제</button>
</div>
</div>
</div>
@@ -1956,7 +2003,7 @@ async function saveFeaturedOrder() {
<div v-if="customItemDeleteModalOpen" class="modalOverlay" @click.self="closeCustomItemDeleteModal">
<div class="modalCard" role="dialog" aria-modal="true">
<div class="modalCard__title">커스텀 아이템 삭제</div>
<div class="modalCard__desc">{{ modalTargetCustomItem ? '"' + modalTargetCustomItem.label + '" 이미지를 삭제할까요? 사용 상태의 이미지에만 삭제를 허용합니다.' : '' }}</div>
<div class="modalCard__desc">{{ modalTargetCustomItem ? '"' + modalTargetCustomItem.label + '" 이미지를 삭제할까요? 사용자 업로드이면서 어디에도 연결되지 않은 이미지에만 삭제를 허용합니다.' : '' }}</div>
<div class="modalCard__actions">
<button class="btn btn--ghost" @click="closeCustomItemDeleteModal">취소</button>
<button class="btn btn--danger" @click="removeCustomItem()">삭제</button>
@@ -2110,13 +2157,9 @@ async function saveFeaturedOrder() {
<option :value="50">50개씩 보기</option>
<option :value="200">200개씩 보기</option>
</select>
<select v-model="customItemTargetGameId" class="select">
<option value="">가져올 게임 선택</option>
<option v-for="game in games" :key="game.id" :value="game.id">{{ game.name }}</option>
</select>
<label class="checkRow checkRow--compact">
<input v-model="customItemOrphanOnly" type="checkbox" @change="toggleCustomItemOrphanOnly" />
<span>미사용 커스텀 이미지 보기</span>
<span>미사용 사용자 업로드 보기</span>
</label>
</div>
<div class="adminSidebar__actions">
@@ -2267,17 +2310,17 @@ async function saveFeaturedOrder() {
gap: 10px;
padding: 22px 24px;
border-radius: 24px;
border: 1px solid rgba(255, 255, 255, 0.08);
border: 1px solid var(--theme-border);
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0.02)),
rgba(255, 255, 255, 0.02);
linear-gradient(180deg, var(--theme-surface-soft-2), var(--theme-pill-bg)),
var(--theme-pill-bg);
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.22);
}
.adminHero__eyebrow {
font-size: 11px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.42);
color: var(--theme-text-faint);
}
.adminHero__title {
margin: 0;
@@ -2288,7 +2331,7 @@ async function saveFeaturedOrder() {
}
.adminHero__desc {
margin: 0;
color: rgba(255, 255, 255, 0.66);
color: var(--theme-text-muted);
line-height: 1.6;
}
.adminHero__stats {
@@ -2302,14 +2345,14 @@ async function saveFeaturedOrder() {
gap: 6px;
padding: 14px 16px;
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(7, 7, 7, 0.18);
border: 1px solid var(--theme-border);
background: var(--theme-pill-bg);
}
.adminHeroStat__label {
font-size: 11px;
letter-spacing: 0.08em;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.46);
color: var(--theme-text-faint);
}
.adminHeroStat__value {
font-size: 22px;
@@ -2326,10 +2369,10 @@ async function saveFeaturedOrder() {
gap: 12px;
padding: 16px;
border-radius: 22px;
border: 1px solid rgba(255, 255, 255, 0.08);
border: 1px solid var(--theme-border);
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.015)),
rgba(13, 13, 13, 0.94);
linear-gradient(180deg, var(--theme-surface-soft), var(--theme-pill-bg)),
color-mix(in srgb, var(--theme-rail-bg) 98%, transparent);
box-shadow: 0 18px 40px rgba(0, 0, 0, 0.18);
}
@@ -2344,10 +2387,10 @@ async function saveFeaturedOrder() {
}
.imageJobRow {
border: 1px solid var(--line);
border: 1px solid var(--theme-border);
border-radius: 14px;
padding: 10px 12px;
background: rgba(255, 255, 255, 0.02);
background: var(--theme-pill-bg);
display: grid;
gap: 4px;
}
@@ -2361,14 +2404,14 @@ async function saveFeaturedOrder() {
}
.imageJobRow__status {
color: var(--text-muted);
color: var(--theme-text-soft);
text-transform: capitalize;
}
.adminSidebar__label {
font-size: 11px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.42);
color: var(--theme-text-faint);
}
.adminSidebar__tabs,
.adminSidebar__group,
@@ -2404,7 +2447,7 @@ async function saveFeaturedOrder() {
.adminSidebar__groupTitle {
font-size: 13px;
font-weight: 800;
color: rgba(255, 255, 255, 0.84);
color: var(--theme-text);
}
.adminGamePicker {
display: grid;
@@ -2419,9 +2462,9 @@ async function saveFeaturedOrder() {
padding: 11px 12px;
text-align: left;
border-radius: 14px;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.03);
color: rgba(255, 255, 255, 0.9);
border: 1px solid var(--theme-border);
background: var(--theme-pill-bg);
color: var(--theme-text);
cursor: pointer;
}
.adminGamePicker__item--active {
@@ -2434,7 +2477,7 @@ async function saveFeaturedOrder() {
}
.adminGamePicker__meta {
font-size: 11px;
color: rgba(255, 255, 255, 0.56);
color: var(--theme-text-soft);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -2444,12 +2487,12 @@ async function saveFeaturedOrder() {
gap: 4px;
padding: 12px 14px;
border-radius: 14px;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.03);
border: 1px solid var(--theme-border);
background: var(--theme-pill-bg);
}
.sidebarStat__label {
font-size: 12px;
color: rgba(255, 255, 255, 0.56);
color: var(--theme-text-soft);
}
.sidebarStat__value {
font-size: 14px;
@@ -2500,8 +2543,8 @@ async function saveFeaturedOrder() {
padding: 12px 14px;
border-radius: 14px;
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(255, 255, 255, 0.04);
color: rgba(255, 255, 255, 0.92);
background: var(--theme-surface-soft);
color: var(--theme-text);
cursor: pointer;
font-weight: 800;
transition:
@@ -2512,7 +2555,7 @@ async function saveFeaturedOrder() {
.tab:hover,
.modeTab:hover {
border-color: rgba(255, 255, 255, 0.18);
background: rgba(255, 255, 255, 0.07);
background: var(--theme-surface-soft-2);
transform: translateY(-1px);
}
.tab--active,
@@ -2529,8 +2572,8 @@ async function saveFeaturedOrder() {
.panel {
border: 1px solid rgba(255, 255, 255, 0.1);
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.018)),
rgba(34, 34, 34, 0.84);
linear-gradient(180deg, var(--theme-surface-soft), var(--theme-pill-bg)),
color-mix(in srgb, var(--theme-card-bg) 96%, transparent);
border-radius: 24px;
padding: 18px;
box-shadow: 0 18px 44px rgba(0, 0, 0, 0.18);
@@ -2554,7 +2597,7 @@ async function saveFeaturedOrder() {
font-weight: 900;
}
.emptyState__desc {
color: rgba(255, 255, 255, 0.66);
color: var(--theme-text-muted);
line-height: 1.6;
}
.featuredOrderPanel {
@@ -2566,7 +2609,7 @@ async function saveFeaturedOrder() {
.featuredOrderPanel__list,
.featuredOrderPanel__picker {
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(255, 255, 255, 0.025);
background: color-mix(in srgb, var(--theme-pill-bg) 85%, transparent);
border-radius: 18px;
padding: 16px;
}
@@ -2585,8 +2628,8 @@ async function saveFeaturedOrder() {
align-items: center;
padding: 14px;
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(0, 0, 0, 0.22);
border: 1px solid var(--theme-border);
background: var(--theme-pill-bg);
}
.featuredCard__meta {
display: flex;
@@ -2630,9 +2673,9 @@ async function saveFeaturedOrder() {
align-items: center;
padding: 14px;
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(0, 0, 0, 0.16);
color: rgba(255, 255, 255, 0.92);
border: 1px solid var(--theme-border);
background: var(--theme-pill-bg);
color: var(--theme-text);
cursor: pointer;
text-align: left;
}
@@ -2651,7 +2694,7 @@ async function saveFeaturedOrder() {
.section {
margin-top: 18px;
padding-top: 18px;
border-top: 1px solid rgba(255, 255, 255, 0.08);
border-top: 1px solid var(--theme-border);
}
.section--topGrid {
display: grid;
@@ -2674,13 +2717,13 @@ async function saveFeaturedOrder() {
}
.adminCard {
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(255, 255, 255, 0.025);
background: color-mix(in srgb, var(--theme-pill-bg) 85%, transparent);
border-radius: 18px;
padding: 16px;
min-width: 0;
}
.adminCard--muted {
background: rgba(255, 255, 255, 0.02);
background: var(--theme-pill-bg);
}
.sectionHeader {
display: flex;
@@ -2725,8 +2768,8 @@ async function saveFeaturedOrder() {
padding: 11px 13px;
border-radius: 14px;
border: 1px solid rgba(255, 255, 255, 0.12);
background: rgba(0, 0, 0, 0.18);
color: rgba(255, 255, 255, 0.92);
background: var(--theme-pill-bg);
color: var(--theme-text);
outline: none;
/* margin-top: 10px; */
}
@@ -2777,7 +2820,7 @@ async function saveFeaturedOrder() {
border-radius: 14px;
border: 1px solid rgba(255, 255, 255, 0.14);
background: rgba(255, 255, 255, 0.06);
color: rgba(255, 255, 255, 0.92);
color: var(--theme-text);
cursor: pointer;
font-weight: 800;
text-align: center;
@@ -2816,7 +2859,7 @@ async function saveFeaturedOrder() {
border-color: rgba(239, 68, 68, 0.28);
}
.btn--ghost {
background: rgba(255, 255, 255, 0.03);
background: var(--theme-pill-bg);
}
.detailHead {
display: flex;
@@ -2845,12 +2888,12 @@ async function saveFeaturedOrder() {
object-fit: cover;
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.12);
background: rgba(255, 255, 255, 0.04);
background: var(--theme-surface-soft);
}
.selectedThumb--empty {
display: grid;
place-items: center;
color: rgba(255, 255, 255, 0.62);
color: var(--theme-text-soft);
}
.selectedThumb--sidebar {
width: 100%;
@@ -2872,7 +2915,7 @@ async function saveFeaturedOrder() {
overflow: hidden;
border-radius: 18px;
border: 1px solid rgba(255, 255, 255, 0.12);
background: rgba(255, 255, 255, 0.03);
background: var(--theme-pill-bg);
text-align: left;
transition: border-color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease;
}
@@ -2888,7 +2931,7 @@ async function saveFeaturedOrder() {
place-items: center;
min-height: 52px;
padding: 12px 16px;
background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(6, 9, 16, 0.86) 46%, rgba(6, 9, 16, 0.94) 100%);
background: linear-gradient(180deg, transparent 0%, color-mix(in srgb, var(--theme-main-bg) 82%, transparent) 46%, color-mix(in srgb, var(--theme-main-bg) 94%, transparent) 100%);
}
.thumbDropZone__title {
font-weight: 900;
@@ -2910,8 +2953,8 @@ async function saveFeaturedOrder() {
.dropZone {
padding: 18px;
border-radius: 16px;
border: 1px dashed rgba(255, 255, 255, 0.2);
background: rgba(255, 255, 255, 0.03);
border: 1px dashed var(--theme-border-strong);
background: var(--theme-pill-bg);
transition:
border-color 0.16s ease,
background 0.16s ease,
@@ -2941,7 +2984,7 @@ async function saveFeaturedOrder() {
padding: 12px;
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.12);
background: rgba(255, 255, 255, 0.04);
background: var(--theme-surface-soft);
}
.itemPreviewGrid {
display: grid;
@@ -2964,7 +3007,7 @@ async function saveFeaturedOrder() {
overflow: hidden;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.12);
background: rgba(0, 0, 0, 0.18);
background: var(--theme-pill-bg);
}
.itemDraftRow__body {
min-width: 0;
@@ -2976,7 +3019,7 @@ async function saveFeaturedOrder() {
border-radius: 12px;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.12);
background: rgba(0, 0, 0, 0.18);
background: var(--theme-pill-bg);
}
.itemPreviewImage {
width: 100%;
@@ -2987,7 +3030,7 @@ async function saveFeaturedOrder() {
min-height: 192px;
display: grid;
place-items: center;
color: rgba(255, 255, 255, 0.62);
color: var(--theme-text-soft);
font-size: 13px;
text-align: center;
line-height: 1.5;
@@ -3001,7 +3044,7 @@ async function saveFeaturedOrder() {
.thumbCard {
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 16px;
background: rgba(255, 255, 255, 0.04);
background: var(--theme-surface-soft);
padding: 12px;
min-width: 0;
}
@@ -3039,9 +3082,10 @@ async function saveFeaturedOrder() {
}
.customItemCard {
appearance: none;
position: relative;
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 18px;
background: rgba(255, 255, 255, 0.04);
background: var(--theme-surface-soft);
overflow: hidden;
display: grid;
gap: 10px;
@@ -3051,6 +3095,24 @@ async function saveFeaturedOrder() {
cursor: pointer;
transition: border-color 0.18s ease, transform 0.18s ease, background 0.18s ease;
}
.customItemCard__badge {
position: absolute;
top: 10px;
left: 10px;
z-index: 1;
display: inline-flex;
align-items: center;
padding: 5px 8px;
border-radius: 999px;
background: color-mix(in srgb, var(--theme-main-bg) 82%, transparent);
color: var(--theme-text);
font-size: 10px;
font-weight: 800;
letter-spacing: 0.02em;
}
.customItemCard__badge--template {
background: rgba(96, 165, 250, 0.18);
}
.customItemCard:hover {
border-color: rgba(126, 162, 255, 0.42);
background: rgba(255, 255, 255, 0.06);
@@ -3062,7 +3124,7 @@ async function saveFeaturedOrder() {
object-fit: cover;
display: block;
border-radius: 14px;
background: rgba(0, 0, 0, 0.18);
background: var(--theme-pill-bg);
}
.customItemCard__title {
min-width: 0;
@@ -3076,36 +3138,91 @@ async function saveFeaturedOrder() {
}
.customItemModal {
display: grid;
grid-template-columns: minmax(200px, 240px) minmax(0, 1fr);
grid-template-columns: minmax(220px, 260px) minmax(0, 1fr);
gap: 18px;
align-items: start;
}
.customItemModal__side {
.customItemModal__pickerPanel {
display: grid;
gap: 12px;
min-width: 0;
padding: 16px;
border-radius: 20px;
border: 1px solid var(--theme-border);
background: var(--theme-pill-bg);
}
.customItemModal__image {
width: 100%;
aspect-ratio: 1 / 1;
object-fit: cover;
border-radius: 18px;
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.1);
.customItemModal__pickerHead {
display: grid;
gap: 4px;
}
.customItemModal__pickerEyebrow {
font-size: 11px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--theme-text-faint);
}
.customItemModal__pickerTitle {
font-size: 18px;
font-weight: 900;
}
.customItemModal__pickerControls {
display: grid;
gap: 10px;
}
.customItemModal__gameList {
display: grid;
gap: 8px;
max-height: 360px;
overflow: auto;
}
.customItemModal__gameItem {
display: grid;
gap: 4px;
padding: 12px 13px;
border-radius: 16px;
border: 1px solid var(--theme-border);
background: var(--theme-surface-soft);
color: var(--theme-text);
text-align: left;
cursor: pointer;
}
.customItemModal__gameItem--active {
border-color: rgba(96, 165, 250, 0.42);
background: rgba(96, 165, 250, 0.12);
}
.customItemModal__gameItem--linked {
border-style: dashed;
}
.customItemModal__gameName {
font-size: 13px;
font-weight: 800;
}
.customItemModal__gameMeta,
.customItemModal__gameState {
font-size: 11px;
color: var(--theme-text-soft);
}
.customItemModal__body {
display: grid;
gap: 14px;
min-width: 0;
}
.customItemModal__selector,
.customItemModal__titleRow,
.customItemModal__linked {
display: grid;
gap: 8px;
}
.customItemModal__image {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
border-radius: 20px;
background: var(--theme-surface-soft);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.customItemModal__label {
font-size: 11px;
color: rgba(255, 255, 255, 0.52);
color: var(--theme-text-faint);
}
.customItemModal__chips {
display: flex;
@@ -3118,6 +3235,11 @@ async function saveFeaturedOrder() {
line-height: 1.35;
word-break: break-word;
}
.customItemModal__source {
margin-top: 4px;
font-size: 12px;
color: var(--theme-text-soft);
}
.customItemModal__metaList {
display: grid;
gap: 10px;
@@ -3129,7 +3251,7 @@ async function saveFeaturedOrder() {
}
.customItemModal__metaRow span {
font-size: 11px;
color: rgba(255, 255, 255, 0.46);
color: var(--theme-text-faint);
}
.customItemModal__metaRow strong {
min-width: 0;
@@ -3137,15 +3259,23 @@ async function saveFeaturedOrder() {
text-overflow: ellipsis;
white-space: nowrap;
font-size: 13px;
color: rgba(255, 255, 255, 0.84);
color: var(--theme-text);
}
.customItemModal__actions {
display: flex;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px;
flex-wrap: wrap;
}
.customItemModal__action {
width: 100%;
min-width: 0;
padding-inline: 12px;
white-space: normal;
line-height: 1.35;
text-align: center;
}
.modalCard--customItem {
width: min(760px, 100%);
width: min(980px, 100%);
}
.pager {
margin-top: 16px;
@@ -3169,7 +3299,7 @@ async function saveFeaturedOrder() {
position: relative;
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 18px;
background: rgba(255, 255, 255, 0.04);
background: var(--theme-surface-soft);
padding: 24px 16px 16px;
overflow: visible;
}
@@ -3253,7 +3383,7 @@ async function saveFeaturedOrder() {
inset: auto 0 0 0;
padding: 10px 0 6px;
background: linear-gradient(180deg, rgba(7, 10, 18, 0), rgba(7, 10, 18, 0.88));
color: rgba(255, 255, 255, 0.9);
color: var(--theme-text);
font-size: 10px;
font-weight: 800;
opacity: 0;
@@ -3296,7 +3426,7 @@ async function saveFeaturedOrder() {
}
.userInfoLine span {
font-size: 12px;
color: rgba(255, 255, 255, 0.56);
color: var(--theme-text-soft);
}
.userInfoLine strong {
min-width: 0;
@@ -3310,7 +3440,7 @@ async function saveFeaturedOrder() {
}
.field__label {
font-size: 13px;
color: rgba(255, 255, 255, 0.62);
color: var(--theme-text-soft);
}
.field__input {
width: 100%;
@@ -3318,7 +3448,7 @@ async function saveFeaturedOrder() {
border: 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.12);
background: transparent;
color: rgba(255, 255, 255, 0.94);
color: var(--theme-text-strong);
outline: none;
font-size: 18px;
letter-spacing: -0.02em;
@@ -3328,7 +3458,7 @@ async function saveFeaturedOrder() {
}
.field__hint {
font-size: 12px;
color: rgba(255, 255, 255, 0.42);
color: var(--theme-text-faint);
}
.userEditForm {
@@ -3358,7 +3488,7 @@ async function saveFeaturedOrder() {
border-radius: 999px;
border: 1px solid rgba(96, 165, 250, 0.28);
background: rgba(96, 165, 250, 0.1);
color: rgba(191, 219, 254, 0.92);
color: color-mix(in srgb, var(--theme-accent-bg) 80%, white);
font-size: 12px;
font-weight: 700;
}
@@ -3377,11 +3507,11 @@ async function saveFeaturedOrder() {
padding: 0;
border-radius: 14px;
border: 1px solid rgba(255, 255, 255, 0.12);
background: rgba(255, 255, 255, 0.04);
background: var(--theme-surface-soft);
cursor: pointer;
}
.iconActionButton__icon {
color: rgba(255, 255, 255, 0.92);
color: var(--theme-text);
}
.iconActionButton:disabled {
cursor: not-allowed;
@@ -3400,7 +3530,7 @@ async function saveFeaturedOrder() {
padding: 0;
border: 0;
background: transparent;
color: rgba(255, 255, 255, 0.46);
color: var(--theme-text-faint);
font-size: 9px;
line-height: 1.4;
letter-spacing: 0.01em;
@@ -3421,7 +3551,7 @@ async function saveFeaturedOrder() {
padding: 18px;
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.12);
background: rgba(255, 255, 255, 0.03);
background: var(--theme-pill-bg);
}
.templateRequestCard__head {
display: flex;
@@ -3436,7 +3566,7 @@ async function saveFeaturedOrder() {
}
.templateRequestCard__desc {
margin-top: 6px;
color: rgba(255, 255, 255, 0.74);
color: var(--theme-text-muted);
line-height: 1.55;
white-space: pre-line;
}
@@ -3460,7 +3590,7 @@ async function saveFeaturedOrder() {
object-fit: cover;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.12);
background: rgba(255, 255, 255, 0.04);
background: var(--theme-surface-soft);
}
.templateRequestItem__label {
font-size: 12px;
@@ -3480,7 +3610,7 @@ async function saveFeaturedOrder() {
}
.templateRequestField__label {
font-size: 11px;
color: rgba(255, 255, 255, 0.52);
color: var(--theme-text-faint);
}
.templateRequestCard__actions {
display: flex;
@@ -3497,11 +3627,11 @@ async function saveFeaturedOrder() {
max-height: 240px;
object-fit: cover;
border-radius: 18px;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.04);
border: 1px solid var(--theme-border);
background: var(--theme-surface-soft);
}
.requestPreview__desc {
color: rgba(255, 255, 255, 0.74);
color: var(--theme-text-muted);
line-height: 1.6;
white-space: pre-line;
}
@@ -3532,7 +3662,7 @@ async function saveFeaturedOrder() {
overflow: hidden;
border-radius: 14px;
border: 1px solid rgba(255, 255, 255, 0.12);
background: rgba(255, 255, 255, 0.04);
background: var(--theme-surface-soft);
min-height: 72px;
}
.requestPreview__item--muted {
@@ -3569,7 +3699,7 @@ async function saveFeaturedOrder() {
gap: 16px;
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 20px;
background: rgba(255, 255, 255, 0.03);
background: var(--theme-pill-bg);
padding: 16px;
}
.tierAdminCard__preview {
@@ -3581,7 +3711,7 @@ async function saveFeaturedOrder() {
object-fit: cover;
display: block;
border-radius: 14px;
background: rgba(255, 255, 255, 0.04);
background: var(--theme-surface-soft);
}
.tierAdminCard__thumb--empty {
background: linear-gradient(135deg, rgba(255,255,255,0.08), rgba(255,255,255,0.03));
@@ -3603,7 +3733,7 @@ async function saveFeaturedOrder() {
}
.tierAdminCard__desc {
margin-top: 6px;
color: rgba(255, 255, 255, 0.74);
color: var(--theme-text-muted);
line-height: 1.5;
display: -webkit-box;
-webkit-line-clamp: 2;
@@ -3627,7 +3757,7 @@ async function saveFeaturedOrder() {
padding: 7px 10px;
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.12);
background: rgba(255, 255, 255, 0.05);
background: var(--theme-surface-soft);
font-size: 12px;
font-weight: 800;
}
@@ -3641,8 +3771,8 @@ async function saveFeaturedOrder() {
gap: 10px;
padding: 14px;
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(0, 0, 0, 0.14);
border: 1px solid var(--theme-border);
background: var(--theme-pill-bg);
}
.tierAdminSection__title {
font-weight: 800;
@@ -3663,9 +3793,9 @@ async function saveFeaturedOrder() {
justify-items: center;
padding: 12px 10px;
border-radius: 14px;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.03);
color: rgba(255, 255, 255, 0.92);
border: 1px solid var(--theme-border);
background: var(--theme-pill-bg);
color: var(--theme-text);
cursor: pointer;
text-align: center;
min-width: 0;
@@ -3691,7 +3821,7 @@ async function saveFeaturedOrder() {
display: grid;
place-items: center;
padding: 20px;
background: rgba(3, 7, 18, 0.66);
background: color-mix(in srgb, var(--theme-body-bg) 76%, transparent);
backdrop-filter: blur(6px);
}
.modalCard {
@@ -3701,7 +3831,7 @@ async function saveFeaturedOrder() {
padding: 20px;
border-radius: 24px;
border: 1px solid rgba(255, 255, 255, 0.12);
background: rgba(11, 18, 32, 0.96);
background: color-mix(in srgb, var(--theme-main-bg) 96%, transparent);
}
.modalCard--preview {
width: min(1200px, 100%);
@@ -3737,7 +3867,7 @@ async function saveFeaturedOrder() {
min-height: min(80vh, 820px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16px;
background: rgba(255, 255, 255, 0.02);
background: var(--theme-pill-bg);
}
.importModeTabs {
display: flex;
@@ -3764,6 +3894,9 @@ async function saveFeaturedOrder() {
.customItemModal {
grid-template-columns: 1fr;
}
.customItemModal__actions {
grid-template-columns: 1fr;
}
.adminSidebar {
display: none;
}

View File

@@ -1361,7 +1361,7 @@ onUnmounted(() => {
letter-spacing: -0.04em;
}
.editorMain__subtitle {
color: rgba(255, 255, 255, 0.58);
color: var(--theme-text-soft);
font-size: 13px;
line-height: 1.5;
}
@@ -1372,13 +1372,13 @@ onUnmounted(() => {
gap: 8px;
flex-wrap: wrap;
font-size: 12px;
color: rgba(255, 255, 255, 0.62);
color: var(--theme-text-soft);
}
.editorMain__sourceLink {
border: 0;
padding: 0;
background: transparent;
color: rgba(191, 219, 254, 0.94);
color: color-mix(in srgb, var(--theme-accent-bg) 78%, white);
font: inherit;
cursor: pointer;
}
@@ -1388,7 +1388,7 @@ onUnmounted(() => {
box-sizing: border-box;
background:
radial-gradient(circle at top, rgba(96, 165, 250, 0.14), transparent 38%),
rgba(11, 18, 32, 0.98);
var(--theme-shell-bg);
}
.previewOnly__sheet {
display: grid;
@@ -1451,13 +1451,13 @@ onUnmounted(() => {
text-align: center;
font-weight: 900;
border-radius: 14px;
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.12);
background: var(--theme-surface-soft-2);
border: 1px solid var(--theme-border-strong);
}
.previewOnly__drop {
border-radius: 14px;
background: rgba(0, 0, 0, 0.18);
border: 1px solid rgba(255, 255, 255, 0.1);
background: var(--theme-pill-bg);
border: 1px solid var(--theme-border);
min-height: calc(var(--thumb-size, 80px) + 24px);
padding: 10px;
display: flex;
@@ -1498,8 +1498,8 @@ onUnmounted(() => {
gap: 12px;
padding: 10px 12px;
border-radius: 14px;
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(255, 255, 255, 0.03);
border: 1px solid var(--theme-border);
background: var(--theme-pill-bg);
cursor: pointer;
user-select: none;
}
@@ -1513,8 +1513,8 @@ onUnmounted(() => {
width: 42px;
height: 24px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.16);
border: 1px solid rgba(255, 255, 255, 0.12);
background: var(--theme-surface-soft-3);
border: 1px solid var(--theme-border-strong);
transition: background 180ms ease, border-color 180ms ease;
flex: 0 0 auto;
}
@@ -1525,13 +1525,13 @@ onUnmounted(() => {
width: 18px;
height: 18px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.94);
background: var(--theme-text-strong);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.24);
transition: transform 180ms ease;
}
.toggleSwitch__label {
font-weight: 800;
color: rgba(255, 255, 255, 0.9);
color: var(--theme-text);
}
.toggleSwitch input:checked ~ .toggleSwitch__track {
background: rgba(96, 165, 250, 0.34);
@@ -1547,14 +1547,14 @@ onUnmounted(() => {
.btn {
padding: 10px 12px;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.14);
background: rgba(255, 255, 255, 0.06);
color: rgba(255, 255, 255, 0.92);
border: 1px solid var(--theme-border-strong);
background: var(--theme-surface-soft-2);
color: var(--theme-text);
cursor: pointer;
font-weight: 700;
}
.btn:hover {
background: rgba(255, 255, 255, 0.08);
background: var(--theme-surface-soft-3);
}
.btn--primary {
background: rgba(110, 231, 183, 0.18);
@@ -1600,8 +1600,8 @@ onUnmounted(() => {
}
.board {
width: min(100%, 960px);
border: 1px solid rgba(255, 255, 255, 0.1);
background: linear-gradient(180deg, rgba(55, 55, 55, 0.86), rgba(42, 42, 42, 0.82));
border: 1px solid var(--theme-border);
background: linear-gradient(180deg, color-mix(in srgb, var(--theme-card-bg) 94%, transparent), color-mix(in srgb, var(--theme-card-bg-hover) 88%, transparent));
border-radius: 22px;
padding: 20px;
align-self: start;
@@ -1614,15 +1614,15 @@ onUnmounted(() => {
display: grid;
place-items: center;
padding: 20px;
background: rgba(4, 8, 16, 0.68);
background: color-mix(in srgb, var(--theme-body-bg) 76%, transparent);
backdrop-filter: blur(4px);
}
.modalCard {
width: min(100%, 420px);
border-radius: 20px;
padding: 24px;
border: 1px solid rgba(255, 255, 255, 0.14);
background: linear-gradient(180deg, rgba(17, 24, 39, 0.96), rgba(11, 18, 32, 0.96));
border: 1px solid var(--theme-border-strong);
background: linear-gradient(180deg, color-mix(in srgb, var(--theme-main-bg) 98%, transparent), color-mix(in srgb, var(--theme-shell-bg) 98%, transparent));
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.38);
display: grid;
gap: 10px;
@@ -1695,24 +1695,24 @@ onUnmounted(() => {
}
.templateRequestDraft__label {
font-size: 12px;
color: rgba(255, 255, 255, 0.64);
color: var(--theme-text-soft);
}
.templateRequestDraft__hint {
font-size: 12px;
color: rgba(255, 255, 255, 0.46);
color: var(--theme-text-faint);
}
.templateRequestDraft__note {
font-size: 12px;
line-height: 1.6;
color: rgba(255, 255, 255, 0.6);
color: var(--theme-text-soft);
}
.templateRequestDraft__input {
width: 100%;
padding: 14px 0;
border: 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.12);
border-bottom: 1px solid var(--theme-border-strong);
background: transparent;
color: rgba(255, 255, 255, 0.94);
color: var(--theme-text-strong);
outline: none;
font-size: 18px;
line-height: 1.5;
@@ -1723,7 +1723,7 @@ onUnmounted(() => {
border-bottom-color: rgba(96, 165, 250, 0.9);
}
.templateRequestDraft__input::placeholder {
color: rgba(255, 255, 255, 0.34);
color: var(--theme-text-faint);
}
.templateRequestDraft__textarea {
min-height: 92px;
@@ -1738,8 +1738,8 @@ onUnmounted(() => {
flex-wrap: wrap;
padding: 10px 12px;
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.03);
border: 1px solid var(--theme-border);
background: var(--theme-pill-bg);
}
.boardTools__left,
.boardTools__right {
@@ -1758,9 +1758,9 @@ onUnmounted(() => {
align-items: center;
justify-content: center;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.12);
background: rgba(255, 255, 255, 0.04);
color: rgba(255, 255, 255, 0.9);
border: 1px solid var(--theme-border-strong);
background: var(--theme-surface-soft);
color: var(--theme-text);
cursor: pointer;
transition: background 160ms ease, border-color 160ms ease, color 160ms ease;
}
@@ -1811,9 +1811,9 @@ onUnmounted(() => {
min-width: 48px;
padding: 9px 10px;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.14);
background: rgba(255, 255, 255, 0.05);
color: rgba(255, 255, 255, 0.92);
border: 1px solid var(--theme-border-strong);
background: var(--theme-surface-soft);
color: var(--theme-text);
cursor: pointer;
font-weight: 800;
}
@@ -1831,7 +1831,7 @@ onUnmounted(() => {
border-radius: 28px;
background:
radial-gradient(circle at top, rgba(96, 165, 250, 0.14), transparent 38%),
rgba(11, 18, 32, 0.98);
var(--theme-shell-bg);
}
.exportBoard__title {
font-size: 28px;
@@ -1869,8 +1869,8 @@ onUnmounted(() => {
.row__label {
position: relative;
border-radius: 16px;
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.12);
background: var(--theme-surface-soft-3);
border: 1px solid var(--theme-border-strong);
display: flex;
align-items: center;
justify-content: center;
@@ -1898,9 +1898,9 @@ onUnmounted(() => {
.columnName {
width: 100%;
border: 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.12);
border-bottom: 1px solid var(--theme-border-strong);
background: transparent;
color: rgba(255, 255, 255, 0.88);
color: var(--theme-text);
padding: 4px 0;
text-align: center;
font-size: 12px;
@@ -1909,7 +1909,7 @@ onUnmounted(() => {
outline: none;
}
.columnName::placeholder {
color: rgba(255, 255, 255, 0.34);
color: var(--theme-text-faint);
}
.columnRemoveText {
position: absolute;
@@ -1924,15 +1924,15 @@ onUnmounted(() => {
border: 0;
border-radius: 999px;
background: transparent;
color: rgba(255, 255, 255, 0.56);
color: var(--theme-text-soft);
font-size: 16px;
line-height: 1;
font-weight: 800;
cursor: pointer;
}
.columnRemoveText:hover {
color: rgba(255, 255, 255, 0.92);
background: rgba(255, 255, 255, 0.06);
color: var(--theme-text);
background: var(--theme-surface-soft-2);
}
.columnRemoveText:disabled {
opacity: 0.32;
@@ -1949,15 +1949,15 @@ onUnmounted(() => {
display: grid;
place-items: center;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.1);
border: 1px solid var(--theme-border);
background: rgba(0, 0, 0, 0.16);
font-size: 12px;
}
.groupName {
width: 100%;
border: 1px solid rgba(255, 255, 255, 0.14);
background: rgba(0, 0, 0, 0.18);
color: rgba(255, 255, 255, 0.92);
border: 1px solid var(--theme-border-strong);
background: var(--theme-pill-bg);
color: var(--theme-text);
border-radius: 10px;
padding: 8px 10px;
font-weight: 900;
@@ -1977,15 +1977,15 @@ onUnmounted(() => {
border: 0;
border-radius: 999px;
background: transparent;
color: rgba(255, 255, 255, 0.6);
color: var(--theme-text-soft);
cursor: pointer;
font-size: 16px;
line-height: 1;
font-weight: 800;
}
.rowRemoveText:hover {
color: rgba(255, 255, 255, 0.92);
background: rgba(255, 255, 255, 0.06);
color: var(--theme-text);
background: var(--theme-surface-soft-2);
}
.rowRemoveText:disabled {
opacity: 0.32;
@@ -1999,7 +1999,7 @@ onUnmounted(() => {
}
.row__drop {
border-radius: 16px;
background: rgba(0, 0, 0, 0.18);
background: var(--theme-pill-bg);
border: 1px solid rgba(255, 255, 255, 0.10);
min-height: calc(var(--thumb-size, 80px) + 24px);
padding: 10px;
@@ -2055,7 +2055,7 @@ onUnmounted(() => {
border-radius: 999px;
border: 1px solid rgba(239, 68, 68, 0.32);
background: rgba(11, 18, 32, 0.92);
color: rgba(255, 255, 255, 0.92);
color: var(--theme-text);
font-size: 16px;
line-height: 1;
font-weight: 900;
@@ -2070,14 +2070,14 @@ onUnmounted(() => {
width: var(--thumb-size, 80px);
height: var(--thumb-size, 80px);
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.14);
background: rgba(255, 255, 255, 0.06);
border: 1px solid var(--theme-border-strong);
background: var(--theme-surface-soft-2);
object-fit: cover;
}
.sidebar {
min-width: 0;
border: 1px solid rgba(255, 255, 255, 0.1);
background: linear-gradient(180deg, rgba(52, 52, 52, 0.84), rgba(36, 36, 36, 0.8));
border: 1px solid var(--theme-border);
background: linear-gradient(180deg, color-mix(in srgb, var(--theme-card-bg) 94%, transparent), color-mix(in srgb, var(--theme-card-bg-hover) 88%, transparent));
border-radius: 22px;
padding: 14px;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
@@ -2114,7 +2114,7 @@ onUnmounted(() => {
.editorSidebar__label {
font-size: 11px;
font-weight: 800;
color: rgba(255, 255, 255, 0.52);
color: var(--theme-text-faint);
text-transform: uppercase;
letter-spacing: 0.12em;
}
@@ -2122,9 +2122,9 @@ onUnmounted(() => {
.editorSidebar__textarea {
width: 100%;
border-radius: 14px;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.04);
color: rgba(255, 255, 255, 0.92);
border: 1px solid var(--theme-border);
background: var(--theme-surface-soft);
color: var(--theme-text);
padding: 11px 12px;
outline: none;
resize: vertical;
@@ -2135,7 +2135,7 @@ onUnmounted(() => {
.editorSidebar__hint {
font-size: 12px;
line-height: 1.5;
color: rgba(255, 255, 255, 0.56);
color: var(--theme-text-soft);
word-break: keep-all;
}
.editorSidebar__hint--warn {
@@ -2147,8 +2147,8 @@ onUnmounted(() => {
aspect-ratio: 16 / 9;
border-radius: 16px;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.08);
background: #4c4c4c;
border: 1px solid var(--theme-border);
background: var(--theme-thumb-fallback-bg);
}
.editorSidebar__thumbFrame--active {
@@ -2165,7 +2165,7 @@ onUnmounted(() => {
height: 100%;
display: grid;
place-items: center;
color: rgba(255, 255, 255, 0.36);
color: var(--theme-text-faint);
font-size: 13px;
}
@@ -2184,7 +2184,7 @@ onUnmounted(() => {
}
.editorSidebar__fileName {
font-size: 12px;
color: rgba(255, 255, 255, 0.56);
color: var(--theme-text-soft);
word-break: break-word;
}
.editorSidebar__favorite {
@@ -2195,9 +2195,9 @@ onUnmounted(() => {
width: 100%;
padding: 11px 12px;
border-radius: 14px;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.03);
color: rgba(255, 255, 255, 0.9);
border: 1px solid var(--theme-border);
background: var(--theme-pill-bg);
color: var(--theme-text);
font-weight: 800;
cursor: pointer;
}
@@ -2223,7 +2223,7 @@ onUnmounted(() => {
border: 0;
padding: 0;
background: transparent;
color: rgba(255, 255, 255, 0.74);
color: var(--theme-text-muted);
font-size: 14px;
cursor: pointer;
}
@@ -2285,16 +2285,16 @@ onUnmounted(() => {
height: 44px;
border-radius: 10px;
object-fit: cover;
border: 1px solid rgba(255, 255, 255, 0.14);
border: 1px solid var(--theme-border-strong);
}
.customItemEditor__input {
width: 100%;
min-width: 0;
padding: 9px 10px;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.12);
background: rgba(0, 0, 0, 0.18);
color: rgba(255, 255, 255, 0.92);
border: 1px solid var(--theme-border-strong);
background: var(--theme-pill-bg);
color: var(--theme-text);
outline: none;
box-sizing: border-box;
}
@@ -2303,7 +2303,7 @@ onUnmounted(() => {
padding: 14px;
border-radius: 16px;
border: 1px dashed rgba(255, 255, 255, 0.18);
background: rgba(255, 255, 255, 0.04);
background: var(--theme-surface-soft);
}
.dropzone--active {
border-color: rgba(110, 231, 183, 0.6);
@@ -2334,7 +2334,7 @@ onUnmounted(() => {
padding: 10px 8px;
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.10);
background: rgba(0, 0, 0, 0.18);
background: var(--theme-pill-bg);
}
.poolItem--readonly {
opacity: 0.58;
@@ -2363,7 +2363,7 @@ onUnmounted(() => {
font-weight: 800;
letter-spacing: 0.04em;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.58);
color: var(--theme-text-soft);
}
.hidden {
display: none;