Skip to content

Commit 40db76f

Browse files
committed
Release v1.1.3 bat collection expansion
1 parent 8e357fe commit 40db76f

18 files changed

Lines changed: 207 additions & 77 deletions

‎app/page.tsx‎

Lines changed: 69 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ type StartResponse = {
8989
};
9090

9191
const TOTAL_PITCHES = 10;
92-
const APP_VERSION = 'v1.1.2';
92+
const APP_VERSION = 'v1.1.3';
9393
const BATTER_FRAMES = [
9494
'ready',
9595
'load',
@@ -103,9 +103,32 @@ const BATTER_FRAMES = [
103103
const DAILY_BAT_STORAGE_KEY = 'utang-baseball-daily-bat-v1';
104104
const BAT_SPRITES: Record<BatType, string> = {
105105
basic: '/utang-batter-v8-strip.png',
106+
aluminum: '/utang-batter-v8-aluminum-strip.png',
106107
gold: '/utang-batter-v8-gold-strip.png',
108+
ruby: '/utang-batter-v8-ruby-strip.png',
107109
diamond: '/utang-batter-v8-diamond-strip.png',
108110
};
111+
const BAT_LABELS: Record<BatType, string> = {
112+
basic: '기본',
113+
aluminum: '알루미늄',
114+
gold: '황금',
115+
ruby: '루비',
116+
diamond: '다이아몬드',
117+
};
118+
const BAT_ICONS: Record<BatType, string> = {
119+
basic: '/utang-bat-gold-v093.png',
120+
aluminum: '/utang-bat-aluminum-v113.png',
121+
gold: '/utang-bat-gold-v093.png',
122+
ruby: '/utang-bat-ruby-v113.png',
123+
diamond: '/utang-bat-diamond-v093.png',
124+
};
125+
const BAT_COLLECTION: Array<{ type: BatType; games: number }> = [
126+
{ type: 'basic', games: 0 },
127+
{ type: 'aluminum', games: 1 },
128+
{ type: 'gold', games: 2 },
129+
{ type: 'ruby', games: 3 },
130+
{ type: 'diamond', games: 4 },
131+
];
109132
const RANKING_PAGE_SIZE = 5;
110133
const WINDUP_MS = 760;
111134
const CONTACT_PROGRESS = 0.86;
@@ -262,18 +285,10 @@ function saveDailyBatState(state: DailyBatState) {
262285
}
263286
}
264287
function missPoseForBat(bat: BatType) {
265-
return bat === 'gold'
266-
? '/utang-pose-miss-v093-gold.png'
267-
: bat === 'diamond'
268-
? '/utang-pose-miss-v093-diamond.png'
269-
: RESULT_META.WHIFF.pose;
288+
return bat === 'basic' ? RESULT_META.WHIFF.pose : `/utang-pose-miss-v093-${bat}.png`;
270289
}
271290
function followPoseForBat(bat: BatType) {
272-
return bat === 'gold'
273-
? '/utang-batter-v8-gold-follow.png'
274-
: bat === 'diamond'
275-
? '/utang-batter-v8-diamond-follow.png'
276-
: RESULT_META.HOME_RUN.pose;
291+
return bat === 'basic' ? RESULT_META.HOME_RUN.pose : `/utang-batter-v8-${bat}-follow.png`;
277292
}
278293
function triggerHitHaptic(outcome: Outcome) {
279294
if (
@@ -534,11 +549,17 @@ export default function Home() {
534549
useEffect(() => {
535550
const characterAssets = [
536551
'/utang-batter-v8-strip.png',
552+
'/utang-batter-v8-aluminum-strip.png',
537553
'/utang-batter-v8-gold-strip.png',
554+
'/utang-batter-v8-ruby-strip.png',
538555
'/utang-batter-v8-diamond-strip.png',
556+
'/utang-bat-aluminum-v113.png',
539557
'/utang-bat-gold-v093.png',
558+
'/utang-bat-ruby-v113.png',
540559
'/utang-bat-diamond-v093.png',
560+
'/utang-pose-miss-v093-aluminum.png',
541561
'/utang-pose-miss-v093-gold.png',
562+
'/utang-pose-miss-v093-ruby.png',
542563
'/utang-pose-miss-v093-diamond.png',
543564
'/utang-pitcher-v090-strip.png',
544565
'/utang-umpire-v091-strip.png',
@@ -1273,37 +1294,22 @@ export default function Home() {
12731294
<section className="bat-collection" aria-label="오늘 모은 배트">
12741295
<div className="bat-collection-head">
12751296
<strong>오늘 모은 배트</strong>
1276-
<span>{Math.min(3, dailyBatState.completedGames + 1)} / 3</span>
1297+
<span>{Math.min(5, dailyBatState.completedGames + 1)} / 5</span>
12771298
</div>
12781299
<div className="bat-collection-list">
1279-
<div className="bat-slot is-owned">
1280-
<img
1281-
className="bat-thumb bat-thumb-basic"
1282-
src="/utang-bat-gold-v093.png"
1283-
alt=""
1284-
/>
1285-
<span>기본</span>
1286-
</div>
1287-
<div
1288-
className={`bat-slot ${dailyBatState.completedGames >= 1 ? 'is-owned' : 'is-locked'}`}
1289-
>
1290-
<img
1291-
className="bat-thumb"
1292-
src="/utang-bat-gold-v093.png"
1293-
alt=""
1294-
/>
1295-
<span>황금</span>
1296-
</div>
1297-
<div
1298-
className={`bat-slot ${dailyBatState.completedGames >= 2 ? 'is-owned' : 'is-locked'}`}
1299-
>
1300-
<img
1301-
className="bat-thumb"
1302-
src="/utang-bat-diamond-v093.png"
1303-
alt=""
1304-
/>
1305-
<span>다이아</span>
1306-
</div>
1300+
{BAT_COLLECTION.map(({ type, games }) => (
1301+
<div
1302+
className={`bat-slot ${dailyBatState.completedGames >= games ? 'is-owned' : 'is-locked'}`}
1303+
key={type}
1304+
>
1305+
<img
1306+
className={`bat-thumb ${type === 'basic' ? 'bat-thumb-basic' : ''}`}
1307+
src={BAT_ICONS[type]}
1308+
alt=""
1309+
/>
1310+
<span>{BAT_LABELS[type]}</span>
1311+
</div>
1312+
))}
13071313
</div>
13081314
</section>
13091315
<div className="intro-ranking">
@@ -1543,6 +1549,24 @@ export default function Home() {
15431549
/>
15441550
</div>
15451551
{umpire}
1552+
{contact && contact.outcome !== 'WHIFF' && (
1553+
<span
1554+
className={`comic-contact-effect comic-${RESULT_META[contact.outcome].tier}`}
1555+
aria-hidden="true"
1556+
>
1557+
<b>
1558+
{contact.outcome === 'HOME_RUN'
1559+
? '쾅!'
1560+
: contact.outcome === 'FOUL'
1561+
? '탁!'
1562+
: '딱!'}
1563+
</b>
1564+
<i />
1565+
<i />
1566+
<i />
1567+
<i />
1568+
</span>
1569+
)}
15461570
{contact?.outcome === 'HOME_RUN' && (
15471571
<span className="batter-impact-bubble" aria-hidden="true">
15481572
!!
@@ -1763,7 +1787,7 @@ export default function Home() {
17631787
<Trophy size={16} /> 오늘 잘 친 우땅이 TOP 3
17641788
</div>
17651789
{records.slice(0, 3).map((item, index) => (
1766-
<div className="ranking-row" key={`${item.playedAt}-${index}`}>
1790+
<div className={`ranking-row rank-${index + 1}`} key={`${item.playedAt}-${index}`}>
17671791
<b>{index + 1}</b>
17681792
<span>{item.nickname}</span>
17691793
<strong>{item.score.toLocaleString()}점</strong>
@@ -1805,7 +1829,7 @@ export default function Home() {
18051829
<dialog
18061830
open
18071831
className="bat-reward-overlay"
1808-
aria-label={`${batReward === 'gold' ? '황금' : '다이아몬드'}배트 획득`}
1832+
aria-label={`${BAT_LABELS[batReward]}배트 획득`}
18091833
>
18101834
<div className={`bat-reward-card reward-${batReward}`}>
18111835
<span
@@ -1821,18 +1845,10 @@ export default function Home() {
18211845
✦
18221846
</span>
18231847
<small>오늘의 플레이 보상</small>
1824-
<h2>
1825-
{batReward === 'gold'
1826-
? '황금배트를 얻어따!'
1827-
: '다이아몬드배트를 얻어따!'}
1828-
</h2>
1848+
<h2>{BAT_LABELS[batReward]}배트를 얻어따!</h2>
18291849
<img
1830-
src={
1831-
batReward === 'gold'
1832-
? '/utang-bat-gold-v093.png'
1833-
: '/utang-bat-diamond-v093.png'
1834-
}
1835-
alt={batReward === 'gold' ? '황금배트' : '다이아몬드배트'}
1850+
src={BAT_ICONS[batReward]}
1851+
alt={`${BAT_LABELS[batReward]}배트`}
18361852
/>
18371853
<Button type="button" onClick={() => setBatReward(null)}>
18381854
확인

‎app/v100.css‎

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,56 @@
1111
.bat-collection-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; font-size: 11px; font-weight: 950; }
1212
.bat-collection-head strong::before { content: '⚾'; margin-right: 6px; font-size: 12px; }
1313
.bat-collection-head span { color: #9a6848; font-size: 10px; }
14-
.bat-collection-list { display: grid; grid-template-columns: repeat(3, 1fr); gap: 7px; }
14+
.bat-collection-list { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 5px; }
1515
.bat-slot { position: relative; min-width: 0; height: 43px; display: flex; align-items: center; justify-content: center; gap: 3px; overflow: hidden; border: 1px solid rgb(102 65 42 / 12%); border-radius: 12px 10px 13px 11px; background: #f8eddb; }
16-
.bat-slot span { font-size: 9px; font-weight: 900; white-space: nowrap; }
16+
.bat-slot span { font-size: 8px; font-weight: 900; white-space: nowrap; }
1717
.bat-slot.is-locked { color: #a99b8c; filter: saturate(.2); opacity: .58; }
1818
.bat-slot.is-locked::after { content: '?'; position: absolute; right: 5px; top: 4px; width: 13px; height: 13px; display: grid; place-items: center; color: white; border-radius: 50%; background: #9c8d7c; font-size: 8px; font-weight: 950; }
19-
.bat-thumb { width: 55px; height: 31px; display: block; flex: 0 0 auto; object-fit: contain; transform: rotate(-4deg); filter: drop-shadow(0 2px 2px rgb(70 39 21 / 13%)); }
19+
.bat-thumb { width: 38px; height: 27px; display: block; flex: 0 1 auto; object-fit: contain; transform: rotate(-4deg); filter: drop-shadow(0 2px 2px rgb(70 39 21 / 13%)); }
2020
.bat-thumb-basic { filter: saturate(.3) sepia(.35) hue-rotate(-16deg) brightness(1.06) drop-shadow(0 2px 2px rgb(70 39 21 / 13%)); }
2121
.network-notice { position: fixed; z-index: 350; left: 50%; bottom: max(18px, env(safe-area-inset-bottom)); width: max-content; max-width: calc(100vw - 32px); padding: 9px 13px; color: #fff9e9; border: 2px solid #fff1c7; border-radius: 999px; background: #68432e; box-shadow: 0 6px 16px rgb(44 24 14 / 28%); transform: translateX(-50%); font-size: 10px; font-weight: 900; }
2222
@media (max-height: 740px) {
2323
.bat-collection { margin-top: 8px; padding-block: 8px; }
2424
.bat-collection-head { margin-bottom: 6px; }
2525
.bat-slot { height: 33px; }
2626
}
27+
28+
/* v1.1.3: podium highlights and a small comic contact burst. */
29+
.ranking-row.rank-1,
30+
.ranking-row.rank-2,
31+
.ranking-row.rank-3 { position: relative; margin-inline: -4px; padding-inline: 4px; border-radius: 9px; }
32+
.ranking-row.rank-1 { background: linear-gradient(90deg, rgb(255 220 91 / 24%), transparent 72%); }
33+
.ranking-row.rank-2 { background: linear-gradient(90deg, rgb(201 216 228 / 25%), transparent 72%); }
34+
.ranking-row.rank-3 { background: linear-gradient(90deg, rgb(222 157 103 / 19%), transparent 72%); }
35+
.ranking-row.rank-1 b,
36+
.ranking-row.rank-2 b,
37+
.ranking-row.rank-3 b { position: relative; box-shadow: inset 0 1px rgb(255 255 255 / 72%), 0 2px 5px rgb(77 48 26 / 12%); }
38+
.ranking-row.rank-1 b::after,
39+
.ranking-row.rank-2 b::after,
40+
.ranking-row.rank-3 b::after { position: absolute; right: -7px; top: -10px; font-size: 12px; line-height: 1; filter: drop-shadow(0 1px 0 white); }
41+
.ranking-row.rank-1 b::after { content: '♛'; color: #d99b00; transform: rotate(8deg); }
42+
.ranking-row.rank-2 b::after { content: '✦'; color: #8aa0b4; }
43+
.ranking-row.rank-3 b::after { content: '★'; color: #bf7641; font-size: 9px; }
44+
45+
.comic-contact-effect { position: absolute; z-index: 30; left: 21%; bottom: 24%; width: 86px; height: 86px; display: grid; place-items: center; pointer-events: none; animation: comic-contact-pop 650ms ease-out both; }
46+
.comic-contact-effect::before { content: ''; position: absolute; inset: 3px; background: repeating-conic-gradient(from 6deg, #ffe56c 0deg 8deg, transparent 8deg 24deg); clip-path: polygon(50% 0,59% 31%,79% 8%,73% 38%,100% 34%,77% 52%,96% 69%,68% 65%,70% 96%,52% 72%,36% 100%,37% 68%,7% 82%,29% 57%,0 47%,32% 43%,15% 16%,43% 34%); opacity: .92; }
47+
.comic-contact-effect b { position: relative; z-index: 2; color: #8c321f; font: 950 24px/1 'Arial Rounded MT Bold', 'Malgun Gothic', sans-serif; letter-spacing: -2px; text-shadow: 2px 2px 0 #fff8d7; transform: rotate(-10deg); }
48+
.comic-contact-effect i { position: absolute; width: 19px; height: 3px; border-radius: 99px; background: #6a3828; transform-origin: 45px 1px; }
49+
.comic-contact-effect i:nth-of-type(1) { transform: translateX(43px) rotate(6deg); }
50+
.comic-contact-effect i:nth-of-type(2) { transform: translateX(43px) rotate(96deg); }
51+
.comic-contact-effect i:nth-of-type(3) { transform: translateX(43px) rotate(186deg); }
52+
.comic-contact-effect i:nth-of-type(4) { transform: translateX(43px) rotate(276deg); }
53+
.comic-foul::before { background: repeating-conic-gradient(from 6deg, #cceaff 0deg 8deg, transparent 8deg 24deg); }
54+
.comic-homer { width: 105px; height: 105px; left: 19%; bottom: 22%; }
55+
.comic-homer b { color: #b21f2d; font-size: 29px; }
56+
@keyframes comic-contact-pop { 0% { opacity: 0; transform: scale(.35) rotate(-8deg); } 36% { opacity: 1; transform: scale(1.16) rotate(3deg); } 72% { opacity: 1; transform: scale(1); } 100% { opacity: 0; transform: scale(1.08) translateY(-7px); } }
57+
@media (prefers-reduced-motion: reduce) { .comic-contact-effect { animation: none; opacity: 1; } }
58+
59+
.bat-reward-card.reward-aluminum { background: linear-gradient(155deg, #fbfdff 0%, #e4ebf0 55%, #bcc9d3 100%); }
60+
.bat-reward-card.reward-aluminum h2 { color: #526779; text-shadow: 0 2px #fff; }
61+
.bat-reward-card.reward-aluminum button { background: #718797; border-color: #475a68; box-shadow: 0 5px 0 #475a68; }
62+
.bat-reward-card.reward-ruby { background: linear-gradient(155deg, #fff8f8 0%, #ffd7dc 55%, #eaa2ad 100%); }
63+
.bat-reward-card.reward-ruby h2 { color: #a51935; text-shadow: 0 2px #fff; }
64+
.bat-reward-card.reward-ruby button { background: #c92e4c; border-color: #8e1830; box-shadow: 0 5px 0 #8e1830; }
65+
.reward-aluminum .reward-spark { color: #8ca2b4; }
66+
.reward-ruby .reward-spark { color: #d5264a; }

‎lib/daily-bat.ts‎

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export type BatType = 'basic' | 'gold' | 'diamond';
1+
export type BatType = 'basic' | 'aluminum' | 'gold' | 'ruby' | 'diamond';
22
export type BatReward = Exclude<BatType, 'basic'>;
33

44
export type DailyBatState = {
@@ -9,7 +9,9 @@ export type DailyBatState = {
99

1010
const BAT_SCORE_MULTIPLIERS: Record<BatType, number> = {
1111
basic: 1,
12+
aluminum: 1.05,
1213
gold: 1.1,
14+
ruby: 1.15,
1315
diamond: 1.2,
1416
};
1517

@@ -18,8 +20,10 @@ export function koreanDayStart(now = Date.now()) {
1820
}
1921

2022
export function batForCompletedGames(completedGames: number): BatType {
21-
if (completedGames >= 2) return 'diamond';
22-
if (completedGames >= 1) return 'gold';
23+
if (completedGames >= 4) return 'diamond';
24+
if (completedGames >= 3) return 'ruby';
25+
if (completedGames >= 2) return 'gold';
26+
if (completedGames >= 1) return 'aluminum';
2327
return 'basic';
2428
}
2529

@@ -38,12 +42,17 @@ export function normalizeDailyBatState(value: unknown, now = Date.now()): DailyB
3842
export function completeDailyGame(value: unknown, now = Date.now()): { state: DailyBatState; reward: BatReward | null } {
3943
const current = normalizeDailyBatState(value, now);
4044
const completedGames = current.completedGames + 1;
41-
const reward: BatReward | null = completedGames === 1 ? 'gold' : completedGames === 2 ? 'diamond' : null;
45+
const reward: BatReward | null =
46+
completedGames === 1 ? 'aluminum'
47+
: completedGames === 2 ? 'gold'
48+
: completedGames === 3 ? 'ruby'
49+
: completedGames === 4 ? 'diamond'
50+
: null;
4251
return { state: { dayStart: current.dayStart, completedGames, equippedBat: batForCompletedGames(completedGames) }, reward };
4352
}
4453

4554
export function isBatType(value: unknown): value is BatType {
46-
return value === 'basic' || value === 'gold' || value === 'diamond';
55+
return value === 'basic' || value === 'aluminum' || value === 'gold' || value === 'ruby' || value === 'diamond';
4756
}
4857

4958
export function calculateEarnedScore(points: number, combo: number, bat: BatType) {

‎lib/player-session.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ export function batProgress(completedGames: number, now = Date.now()): ServerBat
3838
dayStart: koreanDayStart(now),
3939
completedGames: count,
4040
equippedBat: batForCompletedGames(count),
41-
reward: count === 1 ? 'gold' : count === 2 ? 'diamond' : null,
41+
reward:
42+
count === 1 ? 'aluminum'
43+
: count === 2 ? 'gold'
44+
: count === 3 ? 'ruby'
45+
: count === 4 ? 'diamond'
46+
: null,
4247
};
4348
}

‎package-lock.json‎

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "utang-baseball",
3-
"version": "1.1.2",
3+
"version": "1.1.3",
44
"private": true,
55
"engines": {
66
"node": ">=22.13.0"

‎public/utang-bat-aluminum-v113.png‎

84.5 KB
Loading

‎public/utang-bat-ruby-v113.png‎

84.2 KB
Loading
192 KB
Loading
510 KB
Loading

0 commit comments

Comments
 (0)