feat: 가창 보너스 덮어쓰기 추가
This commit is contained in:
@@ -46,6 +46,7 @@
|
|||||||
fixedCooldownReductions?: (CooldownReduction | null)[];
|
fixedCooldownReductions?: (CooldownReduction | null)[];
|
||||||
allBlueNodes?: boolean[];
|
allBlueNodes?: boolean[];
|
||||||
generationBonusOverrides?: (number | null)[];
|
generationBonusOverrides?: (number | null)[];
|
||||||
|
songScoreBonusOverride?: number | null;
|
||||||
leaderCharacterId: string;
|
leaderCharacterId: string;
|
||||||
leaderOutfitCardId: string;
|
leaderOutfitCardId: string;
|
||||||
};
|
};
|
||||||
@@ -96,6 +97,7 @@
|
|||||||
let fixedCooldownReductions: (CooldownReduction | null)[] = selectedCardIds.map(() => null);
|
let fixedCooldownReductions: (CooldownReduction | null)[] = selectedCardIds.map(() => null);
|
||||||
let allBlueNodes = selectedCardIds.map(() => false);
|
let allBlueNodes = selectedCardIds.map(() => false);
|
||||||
let generationBonusOverrides: (number | null)[] = selectedCardIds.map(() => null);
|
let generationBonusOverrides: (number | null)[] = selectedCardIds.map(() => null);
|
||||||
|
let songScoreBonusOverride: number | null = null;
|
||||||
let leaderCharacterId = leaderMembers[0]?.characterId ?? '';
|
let leaderCharacterId = leaderMembers[0]?.characterId ?? '';
|
||||||
let leaderOutfitCardId = leaderMembers[0]?.id ?? '';
|
let leaderOutfitCardId = leaderMembers[0]?.id ?? '';
|
||||||
let worker: Worker | null = null;
|
let worker: Worker | null = null;
|
||||||
@@ -141,7 +143,10 @@
|
|||||||
$: boardEffects = calculateBoardEffects(boardSummary, effectiveMemberBoards, selectedSourceCards, leaderCharacterId, selectedChart, {
|
$: boardEffects = calculateBoardEffects(boardSummary, effectiveMemberBoards, selectedSourceCards, leaderCharacterId, selectedChart, {
|
||||||
placements: boardCards,
|
placements: boardCards,
|
||||||
cards: connectionCards
|
cards: connectionCards
|
||||||
}, { generationBonusByCardId });
|
}, {
|
||||||
|
generationBonusByCardId,
|
||||||
|
...(songScoreBonusOverride === null ? {} : { directSongScoreBonusPercent: songScoreBonusOverride })
|
||||||
|
});
|
||||||
$: memoryPermil = memoryParameterPermil(memoryCount, memorySummary.effects);
|
$: memoryPermil = memoryParameterPermil(memoryCount, memorySummary.effects);
|
||||||
$: unitPower = (() => {
|
$: unitPower = (() => {
|
||||||
try {
|
try {
|
||||||
@@ -180,6 +185,7 @@
|
|||||||
fixedCooldownReductions,
|
fixedCooldownReductions,
|
||||||
allBlueNodes,
|
allBlueNodes,
|
||||||
generationBonusOverrides,
|
generationBonusOverrides,
|
||||||
|
songScoreBonusOverride,
|
||||||
leaderCharacterId,
|
leaderCharacterId,
|
||||||
leaderOutfitCardId
|
leaderOutfitCardId
|
||||||
} satisfies SavedAnalysisState;
|
} satisfies SavedAnalysisState;
|
||||||
@@ -192,6 +198,7 @@
|
|||||||
fixedCooldownReductions,
|
fixedCooldownReductions,
|
||||||
allBlueNodes,
|
allBlueNodes,
|
||||||
generationBonusOverrides,
|
generationBonusOverrides,
|
||||||
|
songScoreBonusOverride,
|
||||||
leaderCharacterId,
|
leaderCharacterId,
|
||||||
leaderOutfitCardId
|
leaderOutfitCardId
|
||||||
});
|
});
|
||||||
@@ -301,6 +308,7 @@
|
|||||||
if (!legacy && state.schemaVersion !== 2) return;
|
if (!legacy && state.schemaVersion !== 2) return;
|
||||||
if (typeof state.songId === 'string' && songs.some((song) => song.id === state.songId)) selectedSongId = state.songId;
|
if (typeof state.songId === 'string' && songs.some((song) => song.id === state.songId)) selectedSongId = state.songId;
|
||||||
if (state.difficulty === 'hard' || state.difficulty === 'expert') difficulty = state.difficulty;
|
if (state.difficulty === 'hard' || state.difficulty === 'expert') difficulty = state.difficulty;
|
||||||
|
songScoreBonusOverride = normalizeSongScoreBonusOverride(state.songScoreBonusOverride);
|
||||||
|
|
||||||
if (Array.isArray(state.cardIds) && state.cardIds.length === 5) {
|
if (Array.isArray(state.cardIds) && state.cardIds.length === 5) {
|
||||||
const restoredCards = state.cardIds.map((id) => cardById.get(id)).filter((card): card is CardRow => Boolean(card));
|
const restoredCards = state.cardIds.map((id) => cardById.get(id)).filter((card): card is CardRow => Boolean(card));
|
||||||
@@ -380,6 +388,19 @@
|
|||||||
result = null;
|
result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeSongScoreBonusOverride(value: unknown): number | null {
|
||||||
|
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) return null;
|
||||||
|
return Math.min(10, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSongScoreBonusOverride(input: HTMLInputElement): void {
|
||||||
|
const parsed = Number(input.value);
|
||||||
|
if (input.value.trim() === '' || !Number.isFinite(parsed) || parsed < 0) input.value = '';
|
||||||
|
if (parsed > 10) input.value = '10';
|
||||||
|
songScoreBonusOverride = input.value === '' ? null : Math.min(10, parsed);
|
||||||
|
result = null;
|
||||||
|
}
|
||||||
|
|
||||||
function boardsForSelectedCards(
|
function boardsForSelectedCards(
|
||||||
storedBoards: MemberBoards,
|
storedBoards: MemberBoards,
|
||||||
cardIds: readonly string[],
|
cardIds: readonly string[],
|
||||||
@@ -439,7 +460,10 @@
|
|||||||
const localBoardEffects = calculateBoardEffects(boardSummary, effectiveMemberBoards, selectedSourceCards, leaderCharacterId, chartSummary, {
|
const localBoardEffects = calculateBoardEffects(boardSummary, effectiveMemberBoards, selectedSourceCards, leaderCharacterId, chartSummary, {
|
||||||
placements,
|
placements,
|
||||||
cards: connectionCards
|
cards: connectionCards
|
||||||
}, { generationBonusByCardId });
|
}, {
|
||||||
|
generationBonusByCardId,
|
||||||
|
...(songScoreBonusOverride === null ? {} : { directSongScoreBonusPercent: songScoreBonusOverride })
|
||||||
|
});
|
||||||
const localUnitPower = calculateUnitPower(selectedCardIds.map((id, index) => ({
|
const localUnitPower = calculateUnitPower(selectedCardIds.map((id, index) => ({
|
||||||
card: cardById.get(id)!, awakening: selectedAwakenings[index]
|
card: cardById.get(id)!, awakening: selectedAwakenings[index]
|
||||||
})), leaderOutfit, localBoardEffects, memoryPermil, memberPowerUpPercent);
|
})), leaderOutfit, localBoardEffects, memoryPermil, memberPowerUpPercent);
|
||||||
@@ -776,8 +800,11 @@
|
|||||||
<option value="hard">Hard</option>
|
<option value="hard">Hard</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
<label>가창 보너스 덮어쓰기 (0~10%)
|
||||||
|
<input class="number-input" type="number" min="0" max="10" step="any" value={songScoreBonusOverride ?? ''} placeholder="실제 보드값" on:input={(event) => updateSongScoreBonusOverride(event.currentTarget)}>
|
||||||
|
</label>
|
||||||
<div class="power-summary"><span>자동 계산 종합력</span><strong>{overallPower.toLocaleString('ko-KR')}</strong></div>
|
<div class="power-summary"><span>자동 계산 종합력</span><strong>{overallPower.toLocaleString('ko-KR')}</strong></div>
|
||||||
<div class="chart-meta"><span>길이 {selectedChart?.durationSeconds ?? 0}초</span><span>노트 {selectedChart?.totalNotes?.toLocaleString('ko-KR') ?? 0}개</span><span>1노트 {Math.ceil(perfectNoteScore).toLocaleString('ko-KR')}점 · {scoreCalibrationLabel}</span>{#if boardEffects.directSongScoreBonusPercent > 0}<span>가창 보너스 {boardEffects.directSongScoreBonusPercent}%</span>{/if}</div>
|
<div class="chart-meta"><span>길이 {selectedChart?.durationSeconds ?? 0}초</span><span>노트 {selectedChart?.totalNotes?.toLocaleString('ko-KR') ?? 0}개</span><span>1노트 {Math.ceil(perfectNoteScore).toLocaleString('ko-KR')}점 · {scoreCalibrationLabel}</span>{#if boardEffects.directSongScoreBonusPercent > 0 || songScoreBonusOverride !== null}<span>가창 보너스 {boardEffects.directSongScoreBonusPercent}%{songScoreBonusOverride === null ? '' : ' · 덮어쓰기'}</span>{/if}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-fields">
|
<div class="card-fields">
|
||||||
@@ -835,7 +862,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
<p class="board-guide">‘파란 노드 전체’를 사용하지 않으면 사용자 데이터에 저장된 해당 멤버의 보드를 사용합니다. 기수 보너스는 빈칸이면 실제 보드 수치를 사용하고, 숫자를 입력하면 해당 값으로 대체합니다.</p>
|
<p class="board-guide">‘파란 노드 전체’를 사용하지 않으면 사용자 데이터에 저장된 해당 멤버의 보드를 사용합니다. 기수 보너스와 가창 보너스 덮어쓰기는 빈칸이면 실제 보드 수치를 사용하고, 숫자를 입력하면 해당 값으로 대체합니다.</p>
|
||||||
<div class="leader-fields">
|
<div class="leader-fields">
|
||||||
<label>리더
|
<label>리더
|
||||||
<select value={leaderCharacterId} on:change={(event) => updateLeader(event.currentTarget.value)}>
|
<select value={leaderCharacterId} on:change={(event) => updateLeader(event.currentTarget.value)}>
|
||||||
@@ -1010,9 +1037,9 @@
|
|||||||
h2 { margin: 0; font-size: 18px; }
|
h2 { margin: 0; font-size: 18px; }
|
||||||
.section-title > strong { color: #aeb6c4; font-size: 14px; }
|
.section-title > strong { color: #aeb6c4; font-size: 14px; }
|
||||||
label { display: grid; gap: 7px; color: #8f98a8; font-size: 14px; font-weight: 750; }
|
label { display: grid; gap: 7px; color: #8f98a8; font-size: 14px; font-weight: 750; }
|
||||||
select { width: 100%; min-width: 0; height: 42px; padding: 0 12px; border: 1px solid #343c49; border-radius: 9px; color: #e8ebf1; background: #11151b; font: inherit; }
|
select, .number-input { width: 100%; min-width: 0; height: 42px; padding: 0 12px; border: 1px solid #343c49; border-radius: 9px; color: #e8ebf1; background: #11151b; font: inherit; }
|
||||||
select:focus, .generation-input:focus { border-color: #d86f9c; outline: 3px solid rgb(216 111 156 / 14%); }
|
select:focus, .number-input:focus, .generation-input:focus { border-color: #d86f9c; outline: 3px solid rgb(216 111 156 / 14%); }
|
||||||
.song-fields { display: grid; grid-template-columns: minmax(0, 4fr) clamp(110px, 11vw, 170px) clamp(140px, 14vw, 220px); align-items: start; gap: 12px; margin-top: 22px; }
|
.song-fields { display: grid; grid-template-columns: minmax(0, 4fr) clamp(110px, 11vw, 170px) clamp(190px, 17vw, 250px) clamp(140px, 14vw, 220px); align-items: start; gap: 12px; margin-top: 22px; }
|
||||||
.song-picker-field { min-width: 0; }
|
.song-picker-field { min-width: 0; }
|
||||||
.power-summary { display: grid; gap: 4px; min-height: 42px; margin-top: 25px; padding: 7px 11px; border-radius: 9px; background: #22202a; }
|
.power-summary { display: grid; gap: 4px; min-height: 42px; margin-top: 25px; padding: 7px 11px; border-radius: 9px; background: #22202a; }
|
||||||
.power-summary span { color: #929cab; font-size: 12px; font-weight: 750; }
|
.power-summary span { color: #929cab; font-size: 12px; font-weight: 750; }
|
||||||
|
|||||||
@@ -63,6 +63,7 @@
|
|||||||
fixedAllBlueNodes?: boolean[];
|
fixedAllBlueNodes?: boolean[];
|
||||||
fixedMaxGenerationBonuses?: boolean[];
|
fixedMaxGenerationBonuses?: boolean[];
|
||||||
fixedGenerationBonusOverrides?: (number | null)[];
|
fixedGenerationBonusOverrides?: (number | null)[];
|
||||||
|
songScoreBonusOverride?: number | null;
|
||||||
ownedCardsOnly?: boolean;
|
ownedCardsOnly?: boolean;
|
||||||
maximizeCompute?: boolean;
|
maximizeCompute?: boolean;
|
||||||
leaderCharacterId: string;
|
leaderCharacterId: string;
|
||||||
@@ -77,6 +78,7 @@
|
|||||||
fixedCooldownReductions: (CooldownReduction | null)[];
|
fixedCooldownReductions: (CooldownReduction | null)[];
|
||||||
allBlueNodes: boolean[];
|
allBlueNodes: boolean[];
|
||||||
generationBonusOverrides: (number | null)[];
|
generationBonusOverrides: (number | null)[];
|
||||||
|
songScoreBonusOverride?: number | null;
|
||||||
leaderCharacterId: string;
|
leaderCharacterId: string;
|
||||||
leaderOutfitCardId: string;
|
leaderOutfitCardId: string;
|
||||||
};
|
};
|
||||||
@@ -115,6 +117,7 @@
|
|||||||
let fixedCooldownReductions: (CooldownReduction | null)[] = [null, null, null, null];
|
let fixedCooldownReductions: (CooldownReduction | null)[] = [null, null, null, null];
|
||||||
let fixedAllBlueNodes = [false, false, false, false];
|
let fixedAllBlueNodes = [false, false, false, false];
|
||||||
let fixedGenerationBonusOverrides: (number | null)[] = [null, null, null, null];
|
let fixedGenerationBonusOverrides: (number | null)[] = [null, null, null, null];
|
||||||
|
let songScoreBonusOverride: number | null = 10;
|
||||||
let ownedCardsOnly = false;
|
let ownedCardsOnly = false;
|
||||||
let maximizeCompute = false;
|
let maximizeCompute = false;
|
||||||
let leaderCharacterId = leaderMembers[0]?.characterId ?? '';
|
let leaderCharacterId = leaderMembers[0]?.characterId ?? '';
|
||||||
@@ -195,6 +198,7 @@
|
|||||||
fixedCooldownReductions,
|
fixedCooldownReductions,
|
||||||
fixedAllBlueNodes,
|
fixedAllBlueNodes,
|
||||||
fixedGenerationBonusOverrides,
|
fixedGenerationBonusOverrides,
|
||||||
|
songScoreBonusOverride,
|
||||||
ownedCardsOnly,
|
ownedCardsOnly,
|
||||||
maximizeCompute,
|
maximizeCompute,
|
||||||
leaderCharacterId,
|
leaderCharacterId,
|
||||||
@@ -211,6 +215,7 @@
|
|||||||
fixedCooldownReductions,
|
fixedCooldownReductions,
|
||||||
fixedAllBlueNodes,
|
fixedAllBlueNodes,
|
||||||
fixedGenerationBonusOverrides,
|
fixedGenerationBonusOverrides,
|
||||||
|
songScoreBonusOverride,
|
||||||
ownedCardsOnly,
|
ownedCardsOnly,
|
||||||
maximizeCompute,
|
maximizeCompute,
|
||||||
leaderCharacterId,
|
leaderCharacterId,
|
||||||
@@ -306,6 +311,11 @@
|
|||||||
return Math.max(0, Math.min(900, Number.isFinite(value) ? Math.round(Number(value)) : 0));
|
return Math.max(0, Math.min(900, Number.isFinite(value) ? Math.round(Number(value)) : 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeSongScoreBonusOverride(value: unknown): number | null {
|
||||||
|
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) return null;
|
||||||
|
return Math.min(10, value);
|
||||||
|
}
|
||||||
|
|
||||||
function sanitizeGenerationBonusInput(input: HTMLInputElement, previousValue: number | null): string {
|
function sanitizeGenerationBonusInput(input: HTMLInputElement, previousValue: number | null): string {
|
||||||
if (input.value === '') {
|
if (input.value === '') {
|
||||||
input.value = '';
|
input.value = '';
|
||||||
@@ -348,6 +358,9 @@
|
|||||||
if (typeof savedValue === 'number' && Number.isFinite(savedValue)) return normalizeGenerationBonus(savedValue);
|
if (typeof savedValue === 'number' && Number.isFinite(savedValue)) return normalizeGenerationBonus(savedValue);
|
||||||
return state.fixedMaxGenerationBonuses?.[index] === true ? 900 : null;
|
return state.fixedMaxGenerationBonuses?.[index] === true ? 900 : null;
|
||||||
});
|
});
|
||||||
|
songScoreBonusOverride = state.songScoreBonusOverride === null
|
||||||
|
? null
|
||||||
|
: normalizeSongScoreBonusOverride(state.songScoreBonusOverride) ?? 10;
|
||||||
ownedCardsOnly = state.ownedCardsOnly === true;
|
ownedCardsOnly = state.ownedCardsOnly === true;
|
||||||
if (ownedCardsOnly) toggleOwnedCardsOnly(true);
|
if (ownedCardsOnly) toggleOwnedCardsOnly(true);
|
||||||
maximizeCompute = state.maximizeCompute === true;
|
maximizeCompute = state.maximizeCompute === true;
|
||||||
@@ -389,6 +402,7 @@
|
|||||||
const value = state.generationBonusOverrides?.[index];
|
const value = state.generationBonusOverrides?.[index];
|
||||||
return typeof value === 'number' && Number.isFinite(value) ? normalizeGenerationBonus(value) : null;
|
return typeof value === 'number' && Number.isFinite(value) ? normalizeGenerationBonus(value) : null;
|
||||||
}),
|
}),
|
||||||
|
songScoreBonusOverride: normalizeSongScoreBonusOverride(state.songScoreBonusOverride),
|
||||||
leaderCharacterId: typeof state.leaderCharacterId === 'string' ? state.leaderCharacterId : '',
|
leaderCharacterId: typeof state.leaderCharacterId === 'string' ? state.leaderCharacterId : '',
|
||||||
leaderOutfitCardId: typeof state.leaderOutfitCardId === 'string' ? state.leaderOutfitCardId : ''
|
leaderOutfitCardId: typeof state.leaderOutfitCardId === 'string' ? state.leaderOutfitCardId : ''
|
||||||
};
|
};
|
||||||
@@ -444,6 +458,14 @@
|
|||||||
result = null;
|
result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateSongScoreBonusOverride(input: HTMLInputElement): void {
|
||||||
|
const parsed = Number(input.value);
|
||||||
|
if (input.value.trim() === '' || !Number.isFinite(parsed) || parsed < 0) input.value = '';
|
||||||
|
if (parsed > 10) input.value = '10';
|
||||||
|
songScoreBonusOverride = input.value === '' ? null : Math.min(10, parsed);
|
||||||
|
result = null;
|
||||||
|
}
|
||||||
|
|
||||||
function toggleOwnedCardsOnly(enabled: boolean): void {
|
function toggleOwnedCardsOnly(enabled: boolean): void {
|
||||||
ownedCardsOnly = enabled;
|
ownedCardsOnly = enabled;
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
@@ -669,7 +691,7 @@
|
|||||||
cards: connectionCards
|
cards: connectionCards
|
||||||
}, {
|
}, {
|
||||||
generationBonusByCardId,
|
generationBonusByCardId,
|
||||||
directSongScoreBonusPercent: 10
|
...(songScoreBonusOverride === null ? {} : { directSongScoreBonusPercent: songScoreBonusOverride })
|
||||||
});
|
});
|
||||||
const power = calculateUnitPower(
|
const power = calculateUnitPower(
|
||||||
cards.map((card) => ({ card, awakening: awakeningForCard(card) })),
|
cards.map((card) => ({ card, awakening: awakeningForCard(card) })),
|
||||||
@@ -732,7 +754,12 @@
|
|||||||
const boardEffects = calculateBoardEffects(boardSummary, baselineBoards, cards, state.leaderCharacterId, chartMetadata, {
|
const boardEffects = calculateBoardEffects(boardSummary, baselineBoards, cards, state.leaderCharacterId, chartMetadata, {
|
||||||
placements: boardCards,
|
placements: boardCards,
|
||||||
cards: connectionCards
|
cards: connectionCards
|
||||||
}, { generationBonusByCardId });
|
}, {
|
||||||
|
generationBonusByCardId,
|
||||||
|
...(state.songScoreBonusOverride === null || state.songScoreBonusOverride === undefined
|
||||||
|
? {}
|
||||||
|
: { directSongScoreBonusPercent: state.songScoreBonusOverride })
|
||||||
|
});
|
||||||
const power = calculateUnitPower(
|
const power = calculateUnitPower(
|
||||||
cards.map((card, index) => ({ card, awakening: state.awakenings[index] })),
|
cards.map((card, index) => ({ card, awakening: state.awakenings[index] })),
|
||||||
outfit,
|
outfit,
|
||||||
@@ -1055,6 +1082,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<label>자동 후보 기수 보너스 덮어쓰기 (0~900)<input class="number-input" type="text" inputmode="numeric" pattern="[0-9]*" maxlength="3" value={candidateGenerationBonus ?? ''} placeholder="미사용" on:input={(event) => updateCandidateGenerationBonus(sanitizeGenerationBonusInput(event.currentTarget, candidateGenerationBonus))}></label>
|
<label>자동 후보 기수 보너스 덮어쓰기 (0~900)<input class="number-input" type="text" inputmode="numeric" pattern="[0-9]*" maxlength="3" value={candidateGenerationBonus ?? ''} placeholder="미사용" on:input={(event) => updateCandidateGenerationBonus(sanitizeGenerationBonusInput(event.currentTarget, candidateGenerationBonus))}></label>
|
||||||
|
<label>가창 보너스 덮어쓰기 (0~10%)<input class="number-input" type="number" min="0" max="10" step="any" value={songScoreBonusOverride ?? ''} placeholder="실제 보드값" on:input={(event) => updateSongScoreBonusOverride(event.currentTarget)}></label>
|
||||||
</div>
|
</div>
|
||||||
<label class="owned-cards-option">
|
<label class="owned-cards-option">
|
||||||
<input type="checkbox" checked={ownedCardsOnly} disabled={running} on:change={(event) => toggleOwnedCardsOnly(event.currentTarget.checked)}>
|
<input type="checkbox" checked={ownedCardsOnly} disabled={running} on:change={(event) => toggleOwnedCardsOnly(event.currentTarget.checked)}>
|
||||||
@@ -1112,7 +1140,7 @@
|
|||||||
<span>리더 의상 효과</span>
|
<span>리더 의상 효과</span>
|
||||||
<p>{leaderOutfitDescription || '적용할 수 있는 5성 의상이 없습니다.'}</p>
|
<p>{leaderOutfitDescription || '적용할 수 있는 5성 의상이 없습니다.'}</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="hint">기수 보너스 덮어쓰기는 빈칸이면 저장된 실제 보드를 사용하고, 숫자를 입력하면 해당 값으로 대체합니다. 자동 후보는 파란 노드 전체 활성화·기수 보너스 {candidateGenerationBonus === null ? '저장값' : candidateGenerationBonus}·악곡 보너스 10% 조건으로 비교합니다.</p>
|
<p class="hint">기수 보너스와 가창 보너스 덮어쓰기는 빈칸이면 저장된 실제 보드를 사용하고, 숫자를 입력하면 해당 값으로 대체합니다. 자동 후보는 파란 노드 전체 활성화·기수 보너스 {candidateGenerationBonus === null ? '저장값' : candidateGenerationBonus}·가창 보너스 {songScoreBonusOverride === null ? '실제 보드값' : `${songScoreBonusOverride}%`} 조건으로 비교합니다.</p>
|
||||||
<PresetManager storageKey={PRESET_STORAGE_KEY} snapshot={presetSnapshot} disabled={running} on:apply={applyPreset} />
|
<PresetManager storageKey={PRESET_STORAGE_KEY} snapshot={presetSnapshot} disabled={running} on:apply={applyPreset} />
|
||||||
{#if errorMessage}<p class="error" role="alert">{errorMessage}</p>{/if}
|
{#if errorMessage}<p class="error" role="alert">{errorMessage}</p>{/if}
|
||||||
<button class="search" type="button" disabled={running} on:click={search}>{running ? searchPhase === 'screening' ? `후보 선별 중 ${Math.round(screeningProgress * 100)}%` : searchPhase === 'probability' ? `달성 확률 계산 중 ${Math.round(progress.evaluatedCandidates / Math.max(1, progress.totalCandidates) * 100)}%` : `정밀 분석 중 ${Math.round(progress.evaluatedCandidates / Math.max(1, progress.totalCandidates) * 100)}%` : '최적 조합 탐색'}</button>
|
<button class="search" type="button" disabled={running} on:click={search}>{running ? searchPhase === 'screening' ? `후보 선별 중 ${Math.round(screeningProgress * 100)}%` : searchPhase === 'probability' ? `달성 확률 계산 중 ${Math.round(progress.evaluatedCandidates / Math.max(1, progress.totalCandidates) * 100)}%` : `정밀 분석 중 ${Math.round(progress.evaluatedCandidates / Math.max(1, progress.totalCandidates) * 100)}%` : '최적 조합 탐색'}</button>
|
||||||
@@ -1211,7 +1239,7 @@
|
|||||||
.song-picker-field { min-width: 0; }
|
.song-picker-field { min-width: 0; }
|
||||||
.chart-meta { display: flex; grid-column: 1 / -1; flex-wrap: wrap; align-items: center; gap: 8px; min-height: 42px; }
|
.chart-meta { display: flex; grid-column: 1 / -1; flex-wrap: wrap; align-items: center; gap: 8px; min-height: 42px; }
|
||||||
.chart-meta span { padding: 7px 9px; border-radius: 7px; color: #adb5c2; background: #202631; font-size: 13px; white-space: nowrap; }
|
.chart-meta span { padding: 7px 9px; border-radius: 7px; color: #adb5c2; background: #202631; font-size: 13px; white-space: nowrap; }
|
||||||
.candidate-fields { display: grid; grid-template-columns: clamp(130px, 12vw, 180px) clamp(260px, 24vw, 380px); gap: 9px; margin-top: 18px; }
|
.candidate-fields { display: grid; grid-template-columns: clamp(130px, 12vw, 180px) repeat(2, clamp(230px, 21vw, 340px)); gap: 9px; margin-top: 18px; }
|
||||||
.candidate-awakening { display: grid; gap: 7px; color: #8f98a8; font-size: 14px; font-weight: 750; }
|
.candidate-awakening { display: grid; gap: 7px; color: #8f98a8; font-size: 14px; font-weight: 750; }
|
||||||
.candidate-guide { margin: 7px 0 0; color: #737d8d; font-size: 12px; line-height: 1.5; }
|
.candidate-guide { margin: 7px 0 0; color: #737d8d; font-size: 12px; line-height: 1.5; }
|
||||||
.fixed-fields { display: grid; gap: 9px; margin-top: 16px; }
|
.fixed-fields { display: grid; gap: 9px; margin-top: 16px; }
|
||||||
|
|||||||
Reference in New Issue
Block a user