feat: apply feedback updates and streamline online delivery
Publish GitHub Pages output / publish (push) Successful in 34s

This commit is contained in:
2026-09-01 22:21:45 +09:00
parent 98d5575463
commit c0f4060dec
36 changed files with 447 additions and 471 deletions
+15
View File
@@ -0,0 +1,15 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { cardMatchesSearch } from './card-search.ts';
test('IRyS와 AZKi를 한글 별칭으로 검색한다', () => {
assert.equal(cardMatchesSearch({ name: 'IRyS' }, '아이리스'), true);
assert.equal(cardMatchesSearch({ name: 'AZKi' }, '아즈키'), true);
});
test('기존 영문 이름과 버전 검색을 유지한다', () => {
assert.equal(cardMatchesSearch({ name: 'IRyS', versionName: '수영복' }, 'irys'), true);
assert.equal(cardMatchesSearch({ name: 'IRyS', versionName: '수영복' }, '수영복'), true);
assert.equal(cardMatchesSearch({ name: 'IRyS' }, '이리스'), false);
assert.equal(cardMatchesSearch({ name: 'IRyS' }, '아즈키'), false);
});
+24
View File
@@ -0,0 +1,24 @@
export type SearchableCard = {
name: string;
versionName?: string | null;
};
const memberAliases: Readonly<Record<string, readonly string[]>> = {
irys: ['아이리스'],
azki: ['아즈키']
};
function normalizeSearchText(value: string): string {
return value.trim().toLocaleLowerCase('ko-KR').replace(/\s+/g, ' ');
}
export function cardMatchesSearch(card: SearchableCard, query: string): boolean {
const normalizedQuery = normalizeSearchText(query);
if (!normalizedQuery) return true;
const aliases = memberAliases[normalizeSearchText(card.name)] ?? [];
const nativeText = normalizeSearchText([card.name, card.versionName ?? ''].join(' '));
return (
nativeText.includes(normalizedQuery) ||
aliases.some((alias) => normalizeSearchText(alias).startsWith(normalizedQuery))
);
}
+10 -1
View File
@@ -1,7 +1,7 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { events } from '../data/events.ts';
import { activeEventAt, eventBonusBreakdown, eventChallengeSongScoreBonusPercent, eventPointIndex } from './event-optimization.ts';
import { activeEventAt, eventBonusBreakdown, eventChallengeSongScoreBonusPercent, eventOptimizationCardEligible, eventPointIndex } from './event-optimization.ts';
const currentEvent = events[0];
@@ -11,6 +11,15 @@ test('이벤트 기간 안에서만 진행 중 이벤트를 반환한다', () =>
assert.equal(activeEventAt(events, Date.parse('2026-09-06T11:00:00.000Z')), null);
});
test('이벤트 최적화는 모든 4·5성과 보너스 멤버의 저등급 카드를 후보로 사용한다', () => {
const bonusCharacters = new Set(['bonus']);
assert.equal(eventOptimizationCardEligible({ id: 'five', characterId: 'other', rarity: 5 }, bonusCharacters), true);
assert.equal(eventOptimizationCardEligible({ id: 'four', characterId: 'other', rarity: 4 }, bonusCharacters), true);
assert.equal(eventOptimizationCardEligible({ id: 'bonus-three', characterId: 'bonus', rarity: 3 }, bonusCharacters), true);
assert.equal(eventOptimizationCardEligible({ id: 'regular-three', characterId: 'other', rarity: 3 }, bonusCharacters), false);
});
test('편성·신규 카드·개화 보너스를 카드별로 합산한다', () => {
const cards = [
{ id: currentEvent.featuredCardIds[0], characterId: currentEvent.bonusCharacterIds[0], rarity: 5 },
+4
View File
@@ -25,6 +25,10 @@ export type EventBonusBreakdown = {
totalPercent: number;
};
export function eventOptimizationCardEligible(card: EventCard, bonusCharacterIds: ReadonlySet<string>): boolean {
return card.rarity === 5 || card.rarity === 4 || bonusCharacterIds.has(card.characterId);
}
export function activeEventAt(events: readonly EventDefinition[], now: number): EventDefinition | null {
return events.find((event) => Date.parse(event.startsAt) <= now && now <= Date.parse(event.endsAt)) ?? null;
}
+1
View File
@@ -1,5 +1,6 @@
export * from './active-probability.ts';
export * from './card-adapter.ts';
export * from './card-search.ts';
export * from './combination-search.ts';
export * from './awakening-requirements.ts';
export * from './board-effects.ts';