From 73ac54fe9589a5bc6834f85bfc41055e25a851f1 Mon Sep 17 00:00:00 2001 From: cocos02 Date: Thu, 11 Jun 2026 21:37:59 +0800 Subject: [PATCH] feat: redesign StoryGallery with full-screen split layout, gold accents, and progress rings --- src/components/StoryGallery.vue | 607 +++++++++++++++++++++++--------- 1 file changed, 438 insertions(+), 169 deletions(-) diff --git a/src/components/StoryGallery.vue b/src/components/StoryGallery.vue index a98da48..0f6d8d3 100644 --- a/src/components/StoryGallery.vue +++ b/src/components/StoryGallery.vue @@ -19,7 +19,16 @@ const emit = defineEmits<{ close: [] }>() -const expandedChapterId = ref(null) +const selectedChapterId = ref(null) + +const selectedChapter = computed(() => + props.chapters.find(c => c.id === selectedChapterId.value) ?? null, +) + +function selectChapter(chapterId: string) { + if (!props.unlockedChapterIds.has(chapterId)) return + selectedChapterId.value = selectedChapterId.value === chapterId ? null : chapterId +} function collectReachable(startId: string): Set { const visited = new Set() @@ -79,6 +88,11 @@ function chapterProgress(chapterId: string) { return { count, total: reachable.size, pct: Math.round((count / reachable.size) * 100) } } +function ringDash(pct: number): string { + const circum = 2 * Math.PI * 22 + return `${(pct / 100) * circum} ${circum}` +} + function lockHint(sceneId: string): string { for (const [, src] of Object.entries(props.scenes)) { if (src.choices) { @@ -102,15 +116,12 @@ function lockHint(sceneId: string): string { function buildPlayerTree(sceneId: string, depth: number, pathSet: Set): PlayerTreeNode | null { if (depth > 10) return null if (pathSet.has(sceneId)) return null - const scene = props.scenes[sceneId] const label = scene?.id ?? sceneId const visited = props.visitedIds.has(sceneId) const hint = visited ? '' : lockHint(sceneId) const locked = !visited && hint !== '' - pathSet.add(sceneId) - const children: PlayerTreeNode[] = [] if (scene) { if (scene.choices) { @@ -144,7 +155,6 @@ function buildPlayerTree(sceneId: string, depth: number, pathSet: Set): } } } - pathSet.delete(sceneId) return { sceneId, label, visited, locked, lockHint: hint, children } } @@ -155,64 +165,136 @@ function buildTreeForChapter(chapterId: string): PlayerTreeNode | null { return buildPlayerTree(ch.startScene, 0, new Set()) } -function toggleExpand(chapterId: string) { - expandedChapterId.value = expandedChapterId.value === chapterId ? null : chapterId -} +const totalChaptersComplete = computed(() => { + let count = 0 + for (const ch of props.chapters) { + if (chapterProgress(ch.id).pct > 0) count++ + } + return count +}) @@ -221,183 +303,370 @@ function toggleExpand(chapterId: string) { .story-overlay { position: fixed; inset: 0; - background: rgba(0, 0, 0, 0.88); + background: radial-gradient(ellipse at center, rgba(20,16,10,0.92) 0%, rgba(8,6,4,0.97) 100%); display: flex; - align-items: center; + align-items: stretch; justify-content: center; z-index: 200; + padding: 24px; } -.story-panel { - background: #1a1a2e; - border: 1px solid rgba(255, 255, 255, 0.12); - border-radius: 10px; - padding: 36px 40px; - max-width: 800px; - max-height: 85vh; +.story-shell { + width: 100%; + max-width: 1100px; display: flex; flex-direction: column; + background: rgba(16,14,20,0.85); + border: 1px solid rgba(255,255,255,0.06); + border-radius: 12px; + overflow: hidden; +} + +.story-header { + display: flex; + align-items: center; + padding: 20px 24px; + border-bottom: 1px solid rgba(255,255,255,0.05); +} + +.back-btn { + padding: 6px 16px; + font-size: 13px; + color: #888; + background: rgba(255,255,255,0.04); + border: 1px solid rgba(255,255,255,0.08); + border-radius: 4px; + cursor: pointer; + transition: all 0.15s; + width: 90px; + text-align: center; +} + +.back-btn:hover { + color: #ccc; + background: rgba(255,255,255,0.08); } .story-title { + flex: 1; text-align: center; - font-size: 22px; - font-weight: 400; - color: #ddd; - letter-spacing: 3px; - margin-bottom: 24px; + font-size: 20px; + font-weight: 500; + color: #c9a84c; + letter-spacing: 6px; } -.story-grid { +.header-spacer { width: 90px; } + +.story-body { + flex: 1; display: flex; - gap: 16px; - justify-content: center; - overflow-y: auto; - padding-right: 8px; + min-height: 0; } -.story-card { +.chapter-sidebar { + width: 320px; + flex-shrink: 0; + border-right: 1px solid rgba(255,255,255,0.05); + padding: 12px; display: flex; flex-direction: column; - background: rgba(255, 255, 255, 0.04); - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 8px; - width: 240px; - overflow: hidden; - transition: border-color 0.2s; -} - -.story-card.locked { - opacity: 0.4; -} - -.story-card.expanded { - border-color: rgba(255, 255, 255, 0.2); -} - -.card-main { - display: flex; - flex-direction: column; - align-items: center; gap: 8px; - padding: 16px 14px; - cursor: pointer; + overflow-y: auto; } -.story-card.locked .card-main { +.chapter-item { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 12px; + background: rgba(255,255,255,0.02); + border: 1px solid rgba(255,255,255,0.04); + border-radius: 8px; + cursor: pointer; + transition: all 0.2s; +} + +.chapter-item:hover:not(.locked) { + background: rgba(255,255,255,0.05); + border-color: rgba(255,255,255,0.1); +} + +.chapter-item.selected { + background: rgba(201,168,76,0.08); + border-color: rgba(201,168,76,0.25); +} + +.chapter-item.locked { + opacity: 0.35; cursor: default; } -.card-thumb { - width: 140px; - height: 78px; - background: rgba(0, 0, 0, 0.4); - border-radius: 6px; +.ch-thumb { + width: 90px; + height: 50px; + flex-shrink: 0; + background: rgba(0,0,0,0.4); + border-radius: 4px; overflow: hidden; - display: flex; - align-items: center; - justify-content: center; + position: relative; } -.thumb-img { +.ch-thumb-img { width: 100%; height: 100%; object-fit: cover; } -.thumb-placeholder { - font-size: 28px; - color: #555; +.ch-thumb-placeholder { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + font-size: 22px; + color: #444; } -.card-label { - font-size: 14px; +.ch-lock { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + background: rgba(0,0,0,0.5); + font-size: 18px; +} + +.ch-info { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 4px; +} + +.ch-label { + font-size: 13px; color: #ddd; - text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } -.card-progress { - width: 100%; +.ch-progress { display: flex; align-items: center; gap: 6px; } -.mini-progress-bar { +.ch-ring { + width: 28px; + height: 28px; + flex-shrink: 0; +} + +.ring-fill { + transition: stroke-dasharray 0.6s ease; +} + +.ch-pct { + font-size: 12px; + color: #c9a84c; + font-weight: 600; +} + +.ch-locked-text { + font-size: 11px; + color: #555; +} + +.ch-start { + width: 30px; + height: 30px; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: 13px; + color: #c9a84c; + background: rgba(201,168,76,0.1); + border: 1px solid rgba(201,168,76,0.2); + border-radius: 50%; + cursor: pointer; + transition: all 0.15s; +} + +.ch-start:hover { + background: rgba(201,168,76,0.25); + color: #e0c060; +} + +.detail-area { flex: 1; - height: 4px; - background: rgba(255, 255, 255, 0.1); + padding: 20px 24px; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 16px; +} + +.detail-empty { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 12px; + color: #444; +} + +.empty-icon { + font-size: 42px; + color: #333; +} + +.empty-text { + font-size: 14px; +} + +.detail-hero { + display: flex; + align-items: center; + gap: 16px; +} + +.hero-img { + width: 180px; + height: 100px; + object-fit: cover; + border-radius: 6px; + border: 1px solid rgba(255,255,255,0.08); +} + +.hero-title { + font-size: 22px; + font-weight: 500; + color: #e0d0a0; + letter-spacing: 2px; +} + +.detail-stats { + display: flex; + gap: 24px; +} + +.stat-box { + display: flex; + align-items: baseline; + gap: 4px; + padding: 10px 16px; + background: rgba(255,255,255,0.03); + border: 1px solid rgba(255,255,255,0.05); + border-radius: 6px; +} + +.stat-value { + font-size: 24px; + font-weight: 600; + color: #c9a84c; +} + +.stat-unit { + font-size: 14px; + color: #666; +} + +.stat-label { + font-size: 11px; + color: #555; + margin-left: 8px; +} + +.section-label { + font-size: 11px; + color: #555; + text-transform: uppercase; + letter-spacing: 2px; + margin-bottom: 8px; +} + +.detail-endings { + padding: 12px 0; + border-top: 1px solid rgba(255,255,255,0.04); +} + +.ending-chips { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.ending-chip { + padding: 5px 14px; + font-size: 12px; + color: #555; + background: rgba(255,255,255,0.02); + border: 1px solid rgba(255,255,255,0.06); + border-radius: 20px; +} + +.ending-chip.unlocked { + color: #c9a84c; + border-color: rgba(201,168,76,0.25); + background: rgba(201,168,76,0.06); +} + +.detail-tree { + flex: 1; + padding: 12px 0; + border-top: 1px solid rgba(255,255,255,0.04); +} + +.tree-container { + background: rgba(0,0,0,0.2); + border: 1px solid rgba(255,255,255,0.04); + border-radius: 6px; + padding: 12px 16px; + max-height: 240px; + overflow: auto; +} + +.tree-empty { + font-size: 12px; + color: #444; + text-align: center; + padding: 16px; +} + +.story-footer { + padding: 12px 24px; + border-top: 1px solid rgba(255,255,255,0.05); +} + +.footer-progress { + display: flex; + align-items: center; + gap: 12px; +} + +.footer-bar { + flex: 1; + height: 3px; + background: rgba(255,255,255,0.06); border-radius: 2px; overflow: hidden; } -.mini-progress-fill { +.footer-bar-fill { height: 100%; - background: #4caf50; + background: linear-gradient(90deg, #8b6914, #c9a84c); border-radius: 2px; - transition: width 0.3s ease; + transition: width 0.4s ease; } -.mini-progress-text { - font-size: 11px; - color: #888; - white-space: nowrap; -} - -.card-endings { - display: flex; - flex-direction: column; - gap: 3px; - width: 100%; -} - -.ending-tag { +.footer-text { font-size: 11px; color: #666; -} - -.ending-tag.unlocked { - color: #ccc; -} - -.card-start-btn { - padding: 6px 20px; - margin-top: 4px; - font-size: 12px; - color: #8cf; - background: rgba(100, 200, 255, 0.08); - border: 1px solid rgba(100, 200, 255, 0.2); - border-radius: 3px; - cursor: pointer; - transition: background 0.15s; -} - -.card-start-btn:hover { - background: rgba(100, 200, 255, 0.15); -} - -.card-recap { - border-top: 1px solid rgba(255, 255, 255, 0.06); - padding: 12px 14px; - max-height: 220px; - overflow-y: auto; -} - -.story-close { - margin-top: 20px; - padding: 10px 36px; - font-size: 14px; - color: #888; - background: rgba(255, 255, 255, 0.05); - border: 1px solid rgba(255, 255, 255, 0.12); - border-radius: 4px; - cursor: pointer; - transition: background 0.15s, color 0.15s; - align-self: center; -} - -.story-close:hover { - background: rgba(255, 255, 255, 0.1); - color: #ccc; + white-space: nowrap; }