feat: recommend card investments for board connections
Publish GitHub Pages output / publish (push) Successful in 35s

This commit is contained in:
dal4segno committed 2026-09-30 19:02:43 +09:00
1 parent 115dc33937
commit de165bb53d
8 files changed
+428 -1

No files matched your search

@@ -0,0 +1,82 @@
<script lang="ts">
import { onMount } from 'svelte';
import ToolsNav from '$lib/components/ToolsNav.svelte';
import BoardAwakening from '$lib/components/BoardAwakening.svelte';
import { listAnalyses, readGrowth } from '$lib/storage/analysis-history.ts';
import { loadBrowserData } from '$lib/storage/browser-storage.ts';
import type { AnalysisRecord, GrowthSnapshot } from '$lib/models/analysis-history.ts';
import cards from '$lib/data/cards-summary.json';
import boards from '$lib/data/boards-summary.json';
import catalog from '$lib/data/exact-chart-catalog.json';
let records: AnalysisRecord[] = [];
let selectedId = '';
let selected: AnalysisRecord | null = null;
let growth: GrowthSnapshot | null = null;
let source: 'current' | 'saved' = 'current';
let error = '';
let loading = true;
let revision = 0;
const byId = new Map(cards.cards.map((card) => [card.id, card]));
const cardName = (id: string) => { const card = byId.get(id); return card ? `★${card.rarity} ${card.name}${card.versionName ? ` · ${card.versionName}` : ''}` : id; };
$: chart = selected ? catalog.charts.find((item) => item.songId === selected?.settings.songId && item.difficulty === selected?.settings.difficulty) : undefined;
onMount(() => { void refresh(); });
async function refresh() {
try { records = await listAnalyses(); selectedId = records.some((item) => item.id === selectedId) ? selectedId : records[0]?.id ?? ''; await select(); }
catch { error = '저장한 분석 기록을 불러오지 못했습니다.'; }
finally { loading = false; }
}
async function select() {
const version = ++revision;
selected = null; growth = null; error = ''; loading = true;
try {
const record = records.find((item) => item.id === selectedId);
if (!record) return;
const value = source === 'saved' ? await readGrowth(record.snapshotId) : loadBrowserData({ types: [], stats: [], activeConditions: [], specialConditions: [], activeProbabilities: [], cardVariants: [], generations: [], sortKey: null, sortDirection: 'desc', skillLevel: 1 });
if (version !== revision) return;
if (!value) throw new Error('성장 기록을 찾을 수 없습니다.');
const snapshot: GrowthSnapshot = structuredClone({ collection: value.collection, memberBoards: value.memberBoards, boardCards: value.boardCards, memoryCount: value.memoryCount, memberPowerUpPercent: value.memberPowerUpPercent });
record.settings.cardIds.forEach((id, index) => {
if (!record.settings.allBlueNodes?.[index]) return;
const member = boards.members.find((item) => item.id === byId.get(id)?.characterId);
if (!member) return;
const positions = (boards.models as Record<string, { groupId: string }[]>)[member.positionGroupId] ?? [];
snapshot.memberBoards[member.id] = [...(snapshot.memberBoards[member.id] ?? []).filter((id) => !id.startsWith('B')), ...positions.filter((item) => item.groupId.startsWith('B')).map((item) => item.groupId)];
});
selected = record; growth = snapshot;
} catch (e) { if (version === revision) error = e instanceof Error ? e.message : '조건을 불러오지 못했습니다.'; }
finally { if (version === revision) loading = false; }
}
</script>
<svelte:head><title>연결노드 개화 추천 · 도구 · 홀로도리 계산기</title></svelte:head>
<main>
<h1>도구</h1>
<ToolsNav active="board-awakening" />
<h2>보드 연결노드용 개화 추천</h2>
<p>저장한 분석의 편성·개화·리더 의상·곡·스페셜 순서·쿨타임을 고정하고 연결 카드 투자 효과를 비교합니다.</p>
<p>보유 카드와 활성 보드는 기본적으로 현재 데이터를 사용합니다. 분석의 보드 보정 설정도 반영하며, 액티브는 모두 발동 가능한 상태로 계산합니다.</p>
<div class="controls">
<label>기준 분석 <select bind:value={selectedId} on:change={select} disabled={loading}>{#each records as record}<option value={record.id}>{record.name} · {record.title} · {record.settings.difficulty}</option>{/each}</select></label>
<label>성장 데이터 <select bind:value={source} on:change={select} disabled={loading}><option value="current">현재 보유 카드·보드</option><option value="saved">분석 저장 당시</option></select></label>
<button on:click={refresh} disabled={loading}>다시 불러오기</button>
</div>
{#if error}<p role="alert">{error}</p>{/if}
{#if loading}<p role="status">조건을 불러오는 중…</p>
{:else if !records.length}<p><a href="/analysis">배치 분석</a>에서 편성을 분석하고 결과를 저장하면 여기서 선택할 수 있습니다.</p>{/if}
{#if selected && growth}
<p>고정 편성: {selected.settings.cardIds.map((id, index) => `${cardName(id)} (${selected!.settings.awakenings[index]}개화)`).join(' / ')}</p>
{#if !chart}<p role="alert">저장된 곡의 채보를 찾을 수 없습니다. 다른 분석을 선택해 주세요.</p>{/if}
{#key selected.id + source + revision}
<BoardAwakening settings={selected.settings} {growth} order={selected.result.order} cooldownReductions={selected.result.cooldownReductions} chartMetadata={chart} {cardName}
socketName={(member, socket) => `${boards.members.find((item) => item.id === member)?.name ?? member} · ${socket}`} />
{/key}
{/if}
</main>
<style>
main { width: min(1200px, calc(100% - var(--page-gutter))); margin: 0 auto; padding: 42px 0 80px; }
p { color: #a4adba; line-height: 1.7; } a { color: #f3a5c5; }
.controls { display: flex; gap: 12px; flex-wrap: wrap; align-items: end; }
label { display: grid; gap: 8px; max-width: 100%; }
select, button { max-width: 100%; padding: 10px; border: 1px solid #414b5b; border-radius: 8px; background: #171c24; color: #e8ebf1; }
button { cursor: pointer; } button:disabled { opacity: .5; }
</style>