diff --git a/src/assets/elements/animated/earth/frame-00.png b/src/assets/elements/animated/earth/frame-00.png new file mode 100644 index 0000000..24afb15 Binary files /dev/null and b/src/assets/elements/animated/earth/frame-00.png differ diff --git a/src/assets/elements/animated/earth/frame-01.png b/src/assets/elements/animated/earth/frame-01.png new file mode 100644 index 0000000..abce28a Binary files /dev/null and b/src/assets/elements/animated/earth/frame-01.png differ diff --git a/src/assets/elements/animated/earth/frame-02.png b/src/assets/elements/animated/earth/frame-02.png new file mode 100644 index 0000000..a74f9ce Binary files /dev/null and b/src/assets/elements/animated/earth/frame-02.png differ diff --git a/src/assets/elements/animated/earth/frame-03.png b/src/assets/elements/animated/earth/frame-03.png new file mode 100644 index 0000000..7f9ed42 Binary files /dev/null and b/src/assets/elements/animated/earth/frame-03.png differ diff --git a/src/assets/elements/animated/earth/frame-04.png b/src/assets/elements/animated/earth/frame-04.png new file mode 100644 index 0000000..1f8d034 Binary files /dev/null and b/src/assets/elements/animated/earth/frame-04.png differ diff --git a/src/assets/elements/animated/earth/frame-05.png b/src/assets/elements/animated/earth/frame-05.png new file mode 100644 index 0000000..816ee4a Binary files /dev/null and b/src/assets/elements/animated/earth/frame-05.png differ diff --git a/src/assets/elements/animated/earth/frame-06.png b/src/assets/elements/animated/earth/frame-06.png new file mode 100644 index 0000000..73b8814 Binary files /dev/null and b/src/assets/elements/animated/earth/frame-06.png differ diff --git a/src/assets/elements/animated/earth/frame-07.png b/src/assets/elements/animated/earth/frame-07.png new file mode 100644 index 0000000..e8dd03d Binary files /dev/null and b/src/assets/elements/animated/earth/frame-07.png differ diff --git a/src/assets/elements/animated/earth/sheet.png b/src/assets/elements/animated/earth/sheet.png new file mode 100644 index 0000000..0586edf Binary files /dev/null and b/src/assets/elements/animated/earth/sheet.png differ diff --git a/src/assets/elements/animated/fire/frame-00.png b/src/assets/elements/animated/fire/frame-00.png new file mode 100644 index 0000000..f619424 Binary files /dev/null and b/src/assets/elements/animated/fire/frame-00.png differ diff --git a/src/assets/elements/animated/fire/frame-01.png b/src/assets/elements/animated/fire/frame-01.png new file mode 100644 index 0000000..44b3e44 Binary files /dev/null and b/src/assets/elements/animated/fire/frame-01.png differ diff --git a/src/assets/elements/animated/fire/frame-02.png b/src/assets/elements/animated/fire/frame-02.png new file mode 100644 index 0000000..217878d Binary files /dev/null and b/src/assets/elements/animated/fire/frame-02.png differ diff --git a/src/assets/elements/animated/fire/frame-03.png b/src/assets/elements/animated/fire/frame-03.png new file mode 100644 index 0000000..16d6263 Binary files /dev/null and b/src/assets/elements/animated/fire/frame-03.png differ diff --git a/src/assets/elements/animated/fire/frame-04.png b/src/assets/elements/animated/fire/frame-04.png new file mode 100644 index 0000000..f860152 Binary files /dev/null and b/src/assets/elements/animated/fire/frame-04.png differ diff --git a/src/assets/elements/animated/fire/frame-05.png b/src/assets/elements/animated/fire/frame-05.png new file mode 100644 index 0000000..d996dfc Binary files /dev/null and b/src/assets/elements/animated/fire/frame-05.png differ diff --git a/src/assets/elements/animated/fire/frame-06.png b/src/assets/elements/animated/fire/frame-06.png new file mode 100644 index 0000000..c58a5bc Binary files /dev/null and b/src/assets/elements/animated/fire/frame-06.png differ diff --git a/src/assets/elements/animated/fire/frame-07.png b/src/assets/elements/animated/fire/frame-07.png new file mode 100644 index 0000000..36fa804 Binary files /dev/null and b/src/assets/elements/animated/fire/frame-07.png differ diff --git a/src/assets/elements/animated/fire/sheet.png b/src/assets/elements/animated/fire/sheet.png new file mode 100644 index 0000000..1579af9 Binary files /dev/null and b/src/assets/elements/animated/fire/sheet.png differ diff --git a/src/assets/elements/animated/manifest.json b/src/assets/elements/animated/manifest.json new file mode 100644 index 0000000..7015af2 --- /dev/null +++ b/src/assets/elements/animated/manifest.json @@ -0,0 +1,63 @@ +{ + "frameSize": [ + 512, + 512 + ], + "frameCount": 8, + "layout": "horizontal", + "fps": 12, + "elements": { + "fire": { + "sheet": "src/assets/elements/animated/fire/sheet.png", + "frames": [ + "src/assets/elements/animated/fire/frame-00.png", + "src/assets/elements/animated/fire/frame-01.png", + "src/assets/elements/animated/fire/frame-02.png", + "src/assets/elements/animated/fire/frame-03.png", + "src/assets/elements/animated/fire/frame-04.png", + "src/assets/elements/animated/fire/frame-05.png", + "src/assets/elements/animated/fire/frame-06.png", + "src/assets/elements/animated/fire/frame-07.png" + ] + }, + "water": { + "sheet": "src/assets/elements/animated/water/sheet.png", + "frames": [ + "src/assets/elements/animated/water/frame-00.png", + "src/assets/elements/animated/water/frame-01.png", + "src/assets/elements/animated/water/frame-02.png", + "src/assets/elements/animated/water/frame-03.png", + "src/assets/elements/animated/water/frame-04.png", + "src/assets/elements/animated/water/frame-05.png", + "src/assets/elements/animated/water/frame-06.png", + "src/assets/elements/animated/water/frame-07.png" + ] + }, + "wind": { + "sheet": "src/assets/elements/animated/wind/sheet.png", + "frames": [ + "src/assets/elements/animated/wind/frame-00.png", + "src/assets/elements/animated/wind/frame-01.png", + "src/assets/elements/animated/wind/frame-02.png", + "src/assets/elements/animated/wind/frame-03.png", + "src/assets/elements/animated/wind/frame-04.png", + "src/assets/elements/animated/wind/frame-05.png", + "src/assets/elements/animated/wind/frame-06.png", + "src/assets/elements/animated/wind/frame-07.png" + ] + }, + "earth": { + "sheet": "src/assets/elements/animated/earth/sheet.png", + "frames": [ + "src/assets/elements/animated/earth/frame-00.png", + "src/assets/elements/animated/earth/frame-01.png", + "src/assets/elements/animated/earth/frame-02.png", + "src/assets/elements/animated/earth/frame-03.png", + "src/assets/elements/animated/earth/frame-04.png", + "src/assets/elements/animated/earth/frame-05.png", + "src/assets/elements/animated/earth/frame-06.png", + "src/assets/elements/animated/earth/frame-07.png" + ] + } + } +} diff --git a/src/assets/elements/animated/preview-contact-sheet.png b/src/assets/elements/animated/preview-contact-sheet.png new file mode 100644 index 0000000..8d57ccc Binary files /dev/null and b/src/assets/elements/animated/preview-contact-sheet.png differ diff --git a/src/assets/elements/animated/water/frame-00.png b/src/assets/elements/animated/water/frame-00.png new file mode 100644 index 0000000..d1a701b Binary files /dev/null and b/src/assets/elements/animated/water/frame-00.png differ diff --git a/src/assets/elements/animated/water/frame-01.png b/src/assets/elements/animated/water/frame-01.png new file mode 100644 index 0000000..f99ebeb Binary files /dev/null and b/src/assets/elements/animated/water/frame-01.png differ diff --git a/src/assets/elements/animated/water/frame-02.png b/src/assets/elements/animated/water/frame-02.png new file mode 100644 index 0000000..2f45020 Binary files /dev/null and b/src/assets/elements/animated/water/frame-02.png differ diff --git a/src/assets/elements/animated/water/frame-03.png b/src/assets/elements/animated/water/frame-03.png new file mode 100644 index 0000000..daa17e9 Binary files /dev/null and b/src/assets/elements/animated/water/frame-03.png differ diff --git a/src/assets/elements/animated/water/frame-04.png b/src/assets/elements/animated/water/frame-04.png new file mode 100644 index 0000000..bee1532 Binary files /dev/null and b/src/assets/elements/animated/water/frame-04.png differ diff --git a/src/assets/elements/animated/water/frame-05.png b/src/assets/elements/animated/water/frame-05.png new file mode 100644 index 0000000..07cd3f1 Binary files /dev/null and b/src/assets/elements/animated/water/frame-05.png differ diff --git a/src/assets/elements/animated/water/frame-06.png b/src/assets/elements/animated/water/frame-06.png new file mode 100644 index 0000000..2d2dcee Binary files /dev/null and b/src/assets/elements/animated/water/frame-06.png differ diff --git a/src/assets/elements/animated/water/frame-07.png b/src/assets/elements/animated/water/frame-07.png new file mode 100644 index 0000000..2c8b0f6 Binary files /dev/null and b/src/assets/elements/animated/water/frame-07.png differ diff --git a/src/assets/elements/animated/water/sheet.png b/src/assets/elements/animated/water/sheet.png new file mode 100644 index 0000000..01d11b4 Binary files /dev/null and b/src/assets/elements/animated/water/sheet.png differ diff --git a/src/assets/elements/animated/wind/frame-00.png b/src/assets/elements/animated/wind/frame-00.png new file mode 100644 index 0000000..3752464 Binary files /dev/null and b/src/assets/elements/animated/wind/frame-00.png differ diff --git a/src/assets/elements/animated/wind/frame-01.png b/src/assets/elements/animated/wind/frame-01.png new file mode 100644 index 0000000..187e30a Binary files /dev/null and b/src/assets/elements/animated/wind/frame-01.png differ diff --git a/src/assets/elements/animated/wind/frame-02.png b/src/assets/elements/animated/wind/frame-02.png new file mode 100644 index 0000000..00c5d35 Binary files /dev/null and b/src/assets/elements/animated/wind/frame-02.png differ diff --git a/src/assets/elements/animated/wind/frame-03.png b/src/assets/elements/animated/wind/frame-03.png new file mode 100644 index 0000000..f21aa83 Binary files /dev/null and b/src/assets/elements/animated/wind/frame-03.png differ diff --git a/src/assets/elements/animated/wind/frame-04.png b/src/assets/elements/animated/wind/frame-04.png new file mode 100644 index 0000000..c0bcfc5 Binary files /dev/null and b/src/assets/elements/animated/wind/frame-04.png differ diff --git a/src/assets/elements/animated/wind/frame-05.png b/src/assets/elements/animated/wind/frame-05.png new file mode 100644 index 0000000..3858e85 Binary files /dev/null and b/src/assets/elements/animated/wind/frame-05.png differ diff --git a/src/assets/elements/animated/wind/frame-06.png b/src/assets/elements/animated/wind/frame-06.png new file mode 100644 index 0000000..3a0945d Binary files /dev/null and b/src/assets/elements/animated/wind/frame-06.png differ diff --git a/src/assets/elements/animated/wind/frame-07.png b/src/assets/elements/animated/wind/frame-07.png new file mode 100644 index 0000000..26a50d3 Binary files /dev/null and b/src/assets/elements/animated/wind/frame-07.png differ diff --git a/src/assets/elements/animated/wind/sheet.png b/src/assets/elements/animated/wind/sheet.png new file mode 100644 index 0000000..d746bfa Binary files /dev/null and b/src/assets/elements/animated/wind/sheet.png differ diff --git a/src/assets/elements/base-elements-sheet.png b/src/assets/elements/base-elements-sheet.png new file mode 100644 index 0000000..2e10c25 Binary files /dev/null and b/src/assets/elements/base-elements-sheet.png differ diff --git a/src/assets/elements/earth.png b/src/assets/elements/earth.png new file mode 100644 index 0000000..ef8e65c Binary files /dev/null and b/src/assets/elements/earth.png differ diff --git a/src/assets/elements/fire.png b/src/assets/elements/fire.png new file mode 100644 index 0000000..36b7909 Binary files /dev/null and b/src/assets/elements/fire.png differ diff --git a/src/assets/elements/water.png b/src/assets/elements/water.png new file mode 100644 index 0000000..75778a7 Binary files /dev/null and b/src/assets/elements/water.png differ diff --git a/src/assets/elements/wind.png b/src/assets/elements/wind.png new file mode 100644 index 0000000..f6870de Binary files /dev/null and b/src/assets/elements/wind.png differ diff --git a/src/components/AdBanner.tsx b/src/components/AdBanner.tsx new file mode 100644 index 0000000..e7b2661 --- /dev/null +++ b/src/components/AdBanner.tsx @@ -0,0 +1,24 @@ +import { css } from '@emotion/react'; + +const bannerStyle = css` + position: fixed; + left: 0; + right: 0; + bottom: calc(58px + env(safe-area-inset-bottom, 0px)); + height: 30px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(255, 255, 255, 0.92); + border-top: 1px solid #eef0f3; + color: #98a2b3; + font-size: 10px; + font-weight: 700; + z-index: 90; + pointer-events: none; +`; + +export function AdBanner() { + return
Rewarded Ads Ready
; +} + diff --git a/src/components/DiscoveryHero.tsx b/src/components/DiscoveryHero.tsx new file mode 100644 index 0000000..2e8db61 --- /dev/null +++ b/src/components/DiscoveryHero.tsx @@ -0,0 +1,137 @@ +import { css, keyframes } from '@emotion/react'; +import { CharacterSprite } from './CharacterSprite'; + +interface DiscoveryHeroProps { + elementId: string; + name: string; + color: string; + tier: number; + rarity: string; + discoveredCount: number; + isLucky?: boolean; + onClose: () => void; +} + +const enter = keyframes` + from { opacity: 0; transform: scale(1.08); } + to { opacity: 1; transform: scale(1); } +`; + +const pop = keyframes` + 0% { transform: translateY(18px) scale(0.82); opacity: 0; } + 60% { transform: translateY(-4px) scale(1.08); opacity: 1; } + 100% { transform: translateY(0) scale(1); opacity: 1; } +`; + +const overlayStyle = (color: string) => css` + position: fixed; + inset: 0; + z-index: 1400; + display: flex; + align-items: center; + justify-content: center; + padding: 24px; + background: + radial-gradient(circle at 50% 36%, ${color}66, transparent 28%), + linear-gradient(160deg, #090b14, #151b2e 52%, #090b14); + animation: ${enter} 0.18s ease-out; +`; + +const cardStyle = css` + width: min(360px, 100%); + text-align: center; + color: #ffffff; + animation: ${pop} 0.42s ease-out; +`; + +const labelStyle = css` + font-size: 12px; + font-weight: 900; + color: rgba(255, 255, 255, 0.68); + letter-spacing: 0; + margin-bottom: 12px; +`; + +const spriteStyle = css` + width: 168px; + height: 168px; + margin: 0 auto 14px; + filter: drop-shadow(0 18px 34px rgba(0, 0, 0, 0.35)); +`; + +const nameStyle = css` + font-size: 30px; + font-weight: 1000; + line-height: 1.08; + margin-bottom: 8px; +`; + +const metaStyle = css` + display: inline-flex; + gap: 8px; + align-items: center; + justify-content: center; + border-radius: 999px; + background: rgba(255, 255, 255, 0.12); + border: 1px solid rgba(255, 255, 255, 0.16); + padding: 7px 12px; + font-size: 12px; + font-weight: 800; + margin-bottom: 16px; +`; + +const copyStyle = css` + color: rgba(255, 255, 255, 0.76); + font-size: 14px; + line-height: 1.55; + margin-bottom: 22px; +`; + +const buttonStyle = css` + width: 100%; + border: none; + border-radius: 14px; + padding: 15px; + color: #111827; + background: #ffffff; + font-size: 15px; + font-weight: 900; + cursor: pointer; +`; + +export function DiscoveryHero({ + elementId, + name, + color, + tier, + rarity, + discoveredCount, + isLucky = false, + onClose, +}: DiscoveryHeroProps) { + return ( +
+
e.stopPropagation()}> +
{isLucky ? 'LUCKY DISCOVERY' : 'NEW DISCOVERY'}
+
+ +
+
{name}
+
+ Tier {tier} + {rarity} + {discoveredCount}번째 발견 +
+
+ 창조자여, 새로운 빛을 발견했습니다. +
+ 이 원소는 이제 당신의 세계에 기록됩니다. +
+ +
+
+ ); +} + diff --git a/src/components/IntroSplash.tsx b/src/components/IntroSplash.tsx new file mode 100644 index 0000000..f05bff9 --- /dev/null +++ b/src/components/IntroSplash.tsx @@ -0,0 +1,88 @@ +import { css, keyframes } from '@emotion/react'; +import { useEffect } from 'react'; + +interface IntroSplashProps { + onDone: () => void; +} + +const fade = keyframes` + 0% { opacity: 0; } + 14%, 82% { opacity: 1; } + 100% { opacity: 0; } +`; + +const rise = keyframes` + from { transform: translateY(16px) scale(0.9); opacity: 0; } + to { transform: translateY(0) scale(1); opacity: 1; } +`; + +const overlayStyle = css` + position: fixed; + inset: 0; + z-index: 1600; + display: flex; + align-items: center; + justify-content: center; + background: radial-gradient(circle at 50% 38%, #27364f, #05070d 62%); + color: white; + animation: ${fade} 1.55s ease-in-out forwards; + cursor: pointer; +`; + +const contentStyle = css` + text-align: center; +`; + +const iconsStyle = css` + display: flex; + justify-content: center; + gap: 14px; + font-size: 34px; + margin-bottom: 18px; + + span { + opacity: 0; + animation: ${rise} 0.36s ease-out forwards; + } + + span:nth-of-type(2) { animation-delay: 0.16s; } + span:nth-of-type(3) { animation-delay: 0.32s; } + span:nth-of-type(4) { animation-delay: 0.48s; } +`; + +const lineStyle = css` + font-size: 16px; + line-height: 1.5; + font-weight: 800; + opacity: 0; + animation: ${rise} 0.36s 0.68s ease-out forwards; +`; + +export function IntroSplash({ onDone }: IntroSplashProps) { + useEffect(() => { + const timer = window.setTimeout(onDone, 1550); + return () => window.clearTimeout(timer); + }, [onDone]); + + return ( +
{ + if (event.key === 'Enter' || event.key === ' ') onDone(); + }} + > +
+
+ 🔥 + 💧 + 🌪️ + 🌱 +
+
당신이 세상의 첫 불꽃입니다
+
+
+ ); +} diff --git a/src/lib/sfx.ts b/src/lib/sfx.ts new file mode 100644 index 0000000..b547685 --- /dev/null +++ b/src/lib/sfx.ts @@ -0,0 +1,60 @@ +type Rarity = 'common' | 'uncommon' | 'rare' | 'epic' | 'legendary'; + +const FREQS: Record = { + common: [420], + uncommon: [520, 680], + rare: [620, 820, 1040], + epic: [440, 660, 880, 1320], + legendary: [330, 660, 990, 1320, 1760], +}; + +function getAudioContext(): AudioContext | null { + const AudioCtx = + window.AudioContext || + (window as Window & { webkitAudioContext?: typeof AudioContext }).webkitAudioContext; + if (!AudioCtx) return null; + return new AudioCtx(); +} + +export function playRaritySfx(rarity: string, enabled: boolean): void { + if (!enabled) return; + + const ctx = getAudioContext(); + if (!ctx) return; + + const normalized = (['common', 'uncommon', 'rare', 'epic', 'legendary'].includes(rarity) + ? rarity + : 'common') as Rarity; + const freqs = FREQS[normalized]; + const now = ctx.currentTime; + const master = ctx.createGain(); + master.gain.setValueAtTime(normalized === 'legendary' ? 0.12 : 0.08, now); + master.connect(ctx.destination); + + freqs.forEach((freq, index) => { + const osc = ctx.createOscillator(); + const gain = ctx.createGain(); + const start = now + index * 0.055; + const duration = normalized === 'legendary' ? 0.34 : normalized === 'epic' ? 0.24 : 0.16; + + osc.type = normalized === 'common' ? 'sine' : normalized === 'legendary' ? 'triangle' : 'square'; + osc.frequency.setValueAtTime(freq, start); + gain.gain.setValueAtTime(0.0001, start); + gain.gain.exponentialRampToValueAtTime(0.65, start + 0.018); + gain.gain.exponentialRampToValueAtTime(0.0001, start + duration); + + osc.connect(gain); + gain.connect(master); + osc.start(start); + osc.stop(start + duration + 0.03); + }); + + window.setTimeout(() => { + void ctx.close().catch(() => undefined); + }, 900); +} + +export function vibrate(pattern: number | number[], enabled: boolean): void { + if (!enabled || !navigator.vibrate) return; + navigator.vibrate(pattern); +} diff --git a/src/platform/ads.ts b/src/platform/ads.ts new file mode 100644 index 0000000..1ab6116 --- /dev/null +++ b/src/platform/ads.ts @@ -0,0 +1,9 @@ +export interface RewardedAdResult { + rewarded: boolean; +} + +export async function showRewardedAd(): Promise { + await new Promise((resolve) => window.setTimeout(resolve, 450)); + return { rewarded: true }; +} +