feat: add collapsible member board effect summaries
Publish GitHub Pages output / publish (push) Successful in 46s

This commit is contained in:
2026-09-02 10:30:51 +09:00
parent a920d65a21
commit 7308428945
6 changed files with 326 additions and 3 deletions
@@ -0,0 +1,106 @@
<script lang="ts">
import type { MemberBoardEffectSummary } from '$lib/engine/board-effects';
export let memberName: string;
export let summary: MemberBoardEffectSummary;
const stats = [
{ key: 'performance', label: '퍼포먼스' },
{ key: 'technique', label: '테크닉' },
{ key: 'sense', label: '센스' }
] as const;
const areas: Record<string, { label: string; condition: string }> = {
B: { label: '파랑 · 멤버', condition: '이 멤버를 라이브에 편성할 때' },
G: { label: '초록 · 공통', condition: '전원 또는 표시된 기수에 적용' },
R: { label: '빨강 · 리더', condition: '이 멤버를 리더로 설정할 때' },
Y: { label: '노랑 · 콘텐츠', condition: '각 효과에 표시된 조건에서 적용' }
};
const format = (value: number) => value.toLocaleString('ko-KR', { maximumFractionDigits: 3 });
</script>
<details class="effect-summary" open aria-label={`${memberName} 보드 효과 요약`}>
<summary class="effect-summary-heading"><h3>{memberName} · 보드 효과 요약</h3></summary>
<p class="note">현재 활성 노드와 연결 카드 기준입니다. 멤버를 바꾸면 해당 멤버의 요약을 확인할 수 있습니다.</p>
{#if summary.received}
<div class="received-heading">이 멤버가 받는 기본 효과</div>
<dl class="received-stats">
{#each stats as stat}
<div>
<dt>{stat.label}</dt>
<dd class="stat-breakdown">
<span class="rate"><small>기본 능력치에</small><strong>+{format(summary.received.rate[stat.key])}%</strong></span>
<span class="flat blue"><small>파랑 고정 증가</small><strong>+{format(summary.received.flatByArea.B[stat.key])}</strong></span>
<span class="flat green"><small>초록 고정 증가</small><strong>+{format(summary.received.flatByArea.G[stat.key])}</strong></span>
</dd>
</div>
{/each}
<div>
<dt>액티브 발동률 증가</dt>
<dd>+{format(summary.received.activationRateUp)}<small>%</small></dd>
</div>
</dl>
<p class="calculation-note">계산 순서: 기본 능력치 × (1 + 증가율) → 파랑 고정 증가분 더하기 → 초록 고정 증가분 더하기</p>
<p class="note">파랑·초록 고정 증가분에는 위의 % 증가율을 곱하지 않습니다. 기본 능력치는 개화 보너스 적용 후 기준이며, %로 생기는 증가분은 올림합니다.</p>
<p class="note">초록 고정 증가는 모든 멤버 보드의 공통·기수 효과이며, 기수 보너스는 최대 +900을 반영합니다. 리더·가창 조건 효과, 의상·메모리 등은 포함하지 않습니다.</p>
<p class="note">발동률 증가는 기본 발동 확률에 곱하는 비율입니다. 예: 기본 50%에 +30% 적용 시 65%.</p>
{/if}
<details class="effect-details">
<summary>이 멤버 보드의 효과 상세 <span>색상별 합계 · 적용 조건</span></summary>
<p class="note">아래는 이 멤버 보드가 제공하는 효과만 합산한 값입니다. 연결 증폭을 포함하며, 기수·가창 보너스의 전체 보드 합산 상한 적용 전 수치입니다.</p>
<div class="effect-groups">
{#each summary.groups as group}
<section class={`effect-group area-${group.area}`} aria-label={areas[group.area].label}>
<h4>{areas[group.area].label}</h4>
<p class="condition">{areas[group.area].condition}</p>
{#if group.rows.length}
<ul>
{#each group.rows as row}
<li><span>{row.description}</span><small>{row.nodeCount}개 노드{row.boosted ? ' · 연결 증폭 포함' : ''}</small></li>
{/each}
</ul>
{:else}
<p class="empty">활성화된 효과가 없습니다.</p>
{/if}
</section>
{/each}
</div>
<p class="note">발동 빈도는 저장된 노드 효과입니다. 배치 분석·조합 탐색에서는 쿨타임 감소를 별도로 설정하거나 최적화합니다.</p>
</details>
</details>
<style>
.effect-summary { margin: 16px 0 0; padding: 18px; border: 1px solid #343d4d; border-radius: 12px; background: #151b24; }
h3 { display: inline; margin: 0; color: #e9edf4; font-size: 17px; }
.effect-summary-heading { padding: 2px 0; }
.note { margin: 8px 0 0; color: #9aa6b8; font-size: 12px; line-height: 1.65; }
.received-heading { margin-top: 16px; color: #ced6e2; font-size: 13px; font-weight: 700; }
.received-stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; margin: 8px 0; }
.received-stats > div { min-width: 0; padding: 12px; border-radius: 8px; background: #202937; }
dt { color: #aebacf; font-size: 12px; }
dd { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; margin: 7px 0 0; color: #f3a5c5; font-size: 19px; font-weight: 750; overflow-wrap: anywhere; }
dd small { color: #d1d9e6; font-size: 13px; }
.stat-breakdown { display: grid; gap: 8px; font-size: 16px; }
.stat-breakdown > span { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 3px 8px; }
.stat-breakdown small { font-size: 11px; font-weight: 400; }
.stat-breakdown .flat { padding-top: 8px; border-top: 1px solid #354052; }
.blue strong { color: #80bfff; }
.green strong { color: #90d5a0; }
.calculation-note { margin: 12px 0 0; color: #d1d9e6; font-size: 12px; line-height: 1.7; }
.effect-details { margin-top: 16px; border-top: 1px solid #303a49; padding-top: 12px; }
summary { color: #e2e8f1; font-size: 13px; font-weight: 700; cursor: pointer; }
summary span { display: inline-block; margin-left: 6px; color: #9aa6b8; font-size: 12px; font-weight: 400; }
summary:focus-visible { outline: 2px solid #f3a5c5; outline-offset: 4px; }
.effect-groups { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; margin-top: 12px; }
.effect-group { min-width: 0; padding: 14px; border: 1px solid #343d4d; border-top: 3px solid var(--area-color); border-radius: 9px; }
.area-B { --area-color: #80bfff; } .area-G { --area-color: #90d5a0; } .area-R { --area-color: #f095a8; } .area-Y { --area-color: #e5cf85; }
h4 { margin: 0; color: var(--area-color); font-size: 14px; }
.condition, .empty { margin: 7px 0 0; color: #9aa6b8; font-size: 12px; line-height: 1.6; }
ul { margin: 10px 0 0; padding: 0; list-style: none; }
li { padding: 8px 0; border-top: 1px solid #2c3544; color: #dbe2ed; font-size: 13px; line-height: 1.6; overflow-wrap: anywhere; }
li small { display: block; color: #9aa6b8; font-size: 11px; }
@media (max-width: 600px) {
.effect-summary { padding: 14px; }
.received-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.effect-groups { grid-template-columns: minmax(0, 1fr); }
}
</style>