Skip to content

Commit c2c45d9

Browse files
committed
Redesign wedding invitation for Amoria
1 parent aef5276 commit c2c45d9

4 files changed

Lines changed: 544 additions & 62 deletions

File tree

664 KB
Loading

wedding-invitation/game.js

Lines changed: 61 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,28 @@
11
// 초대장 정보는 이 객체만 수정하면 전체 화면에 반영됩니다.
22
const WEDDING = {
3-
groom: "신랑",
4-
bride: "신부",
5-
heroName: "오늘의 주인공",
6-
introDate: "2026. 10. 10 · SAT · 1:00 PM",
7-
dateText: "2026년 10월 10일 토요일 오후 1시",
8-
place: "웨딩빌리지 대성당 · 1층 그랜드홀",
3+
groom: "남궁찬",
4+
bride: "차은서",
5+
heroName: "남궁찬",
6+
introDate: "2027. 05. 08 · SATURDAY",
7+
dateText: "2027년 5월 8일 토요일",
8+
place: "인천 웨스턴팰리스",
99
message:
1010
"서로의 가장 좋은 친구로,\n오래오래 같은 방향을 바라보며 걷겠습니다.\n귀한 걸음으로 첫 페이지를 축복해 주세요.",
11-
calendarStart: "20261010T040000Z",
12-
calendarEnd: "20261010T060000Z",
11+
calendarStart: "20270508",
12+
calendarEnd: "20270509",
13+
calendarAllDay: true,
1314
};
1415

16+
const AMORIA_BGM_ID = "hCdooZxzISo";
17+
1518
const QUESTS = [
1619
{
1720
x: 430,
1821
icon: "💗",
1922
npc: "헤라 & 사랑의 증표 수집가",
2023
label: "사랑의 증표",
2124
title: "네 개의 마음을 모아 주세요",
22-
story: "안내자 헤라를 따라 웨딩빌리지에 도착했어요. 약혼반지를 만들려면 네 마을에서 받은 사랑의 증표가 필요합니다. 함께 웃고, 믿고, 기다리고, 아껴 온 네 가지 마음을 꺼내 보세요.",
25+
story: "안내자 헤라를 따라 아모리아(웨딩빌리지)에 도착했어요. 약혼반지를 만들려면 네 마을에서 받은 사랑의 증표가 필요합니다. 함께 웃고, 믿고, 기다리고, 아껴 온 네 가지 마음을 꺼내 보세요.",
2326
task: "헤라의 안내로 입장해 추억 · 믿음 · 기다림 · 다정함의 증표 4개 모으기",
2427
action: "웨딩빌리지 입장 & 증표 건네기",
2528
toast: "사랑의 증표 4개를 모았습니다!",
@@ -135,6 +138,7 @@ const dom = {
135138
calendarButton: document.querySelector("#calendar-button"),
136139
finalShareButton: document.querySelector("#final-share-button"),
137140
confetti: document.querySelector("#confetti"),
141+
bgmPlayer: document.querySelector("#bgm-player"),
138142
};
139143

140144
const state = {
@@ -152,6 +156,7 @@ const state = {
152156
keys: { left: false, right: false },
153157
soundOn: false,
154158
audio: null,
159+
bgmFrame: null,
155160
toastTimer: null,
156161
};
157162

@@ -443,15 +448,21 @@ async function shareInvitation() {
443448

444449
function saveCalendar() {
445450
const safeTitle = `${WEDDING.groom}${WEDDING.bride} 결혼식`;
451+
const startLine = WEDDING.calendarAllDay
452+
? `DTSTART;VALUE=DATE:${WEDDING.calendarStart}`
453+
: `DTSTART:${WEDDING.calendarStart}`;
454+
const endLine = WEDDING.calendarAllDay
455+
? `DTEND;VALUE=DATE:${WEDDING.calendarEnd}`
456+
: `DTEND:${WEDDING.calendarEnd}`;
446457
const body = [
447458
"BEGIN:VCALENDAR",
448459
"VERSION:2.0",
449460
"PRODID:-//Wedding Quest//KR",
450461
"BEGIN:VEVENT",
451462
`UID:wedding-quest-${WEDDING.calendarStart}@invitation`,
452463
`DTSTAMP:${new Date().toISOString().replace(/[-:]/g, "").replace(/\.\d{3}/, "")}`,
453-
`DTSTART:${WEDDING.calendarStart}`,
454-
`DTEND:${WEDDING.calendarEnd}`,
464+
startLine,
465+
endLine,
455466
`SUMMARY:${safeTitle}`,
456467
`LOCATION:${WEDDING.place}`,
457468
`DESCRIPTION:${WEDDING.message.replaceAll("\n", "\\n")}`,
@@ -481,42 +492,53 @@ function resetGame() {
481492
showToast("첫 번째 퀘스트부터 다시 시작합니다!");
482493
}
483494

484-
function createMusic() {
495+
function createAudioEngine() {
485496
const AudioContext = window.AudioContext || window.webkitAudioContext;
486497
if (!AudioContext) return null;
487498
const context = new AudioContext();
488499
const gain = context.createGain();
489500
gain.gain.value = 0.035;
490501
gain.connect(context.destination);
502+
return { context, gain };
503+
}
504+
505+
function sendBgmCommand(command) {
506+
state.bgmFrame?.contentWindow?.postMessage(
507+
JSON.stringify({ event: "command", func: command, args: [] }),
508+
"*",
509+
);
510+
}
511+
512+
function startAmoriaBgm() {
513+
if (state.bgmFrame) {
514+
sendBgmCommand("playVideo");
515+
return;
516+
}
491517

492-
const notes = [261.63, 329.63, 392, 523.25, 440, 392, 329.63, 293.66];
493-
let step = 0;
494-
const timer = setInterval(() => {
495-
if (!state.soundOn || context.state !== "running") return;
496-
const oscillator = context.createOscillator();
497-
const noteGain = context.createGain();
498-
oscillator.type = step % 4 === 3 ? "triangle" : "sine";
499-
oscillator.frequency.value = notes[step % notes.length];
500-
noteGain.gain.setValueAtTime(0, context.currentTime);
501-
noteGain.gain.linearRampToValueAtTime(0.7, context.currentTime + 0.02);
502-
noteGain.gain.exponentialRampToValueAtTime(0.001, context.currentTime + 0.42);
503-
oscillator.connect(noteGain);
504-
noteGain.connect(gain);
505-
oscillator.start();
506-
oscillator.stop(context.currentTime + 0.44);
507-
step += 1;
508-
}, 380);
509-
510-
return { context, gain, timer };
518+
const frame = document.createElement("iframe");
519+
const origin = encodeURIComponent(window.location.origin);
520+
frame.title = "MapleStory Amoria BGM";
521+
frame.allow = "autoplay; encrypted-media";
522+
frame.referrerPolicy = "strict-origin-when-cross-origin";
523+
frame.src = `https://www.youtube-nocookie.com/embed/${AMORIA_BGM_ID}?autoplay=1&loop=1&playlist=${AMORIA_BGM_ID}&controls=0&disablekb=1&fs=0&playsinline=1&enablejsapi=1&origin=${origin}`;
524+
frame.addEventListener("load", () => {
525+
if (state.soundOn) sendBgmCommand("playVideo");
526+
});
527+
dom.bgmPlayer.appendChild(frame);
528+
state.bgmFrame = frame;
511529
}
512530

513531
function toggleSound(forceOn) {
514532
const next = typeof forceOn === "boolean" ? forceOn : !state.soundOn;
515-
if (!state.audio) state.audio = createMusic();
516-
if (!state.audio) return;
533+
if (!state.audio) state.audio = createAudioEngine();
517534
state.soundOn = next;
518-
if (next) state.audio.context.resume();
519-
else state.audio.context.suspend();
535+
if (next) {
536+
state.audio?.context.resume();
537+
startAmoriaBgm();
538+
} else {
539+
state.audio?.context.suspend();
540+
sendBgmCommand("pauseVideo");
541+
}
520542
dom.soundButton.classList.toggle("is-on", state.soundOn);
521543
dom.soundButton.textContent = state.soundOn ? "♫" : "♪";
522544
dom.soundButton.setAttribute("aria-label", state.soundOn ? "배경음 끄기" : "배경음 켜기");
@@ -545,10 +567,11 @@ function playSuccessSound() {
545567

546568
function bindControls() {
547569
window.addEventListener("keydown", (event) => {
548-
if (["ArrowLeft", "ArrowRight", "ArrowUp", "Alt"].includes(event.key)) event.preventDefault();
570+
const isJumpKey = ["Space", "KeyZ", "AltLeft", "AltRight"].includes(event.code);
571+
if (["ArrowLeft", "ArrowRight", "ArrowUp"].includes(event.key) || isJumpKey) event.preventDefault();
549572
if (event.key === "ArrowLeft") state.keys.left = true;
550573
if (event.key === "ArrowRight") state.keys.right = true;
551-
if (event.key === "Alt" && !event.repeat) jump();
574+
if (isJumpKey && !event.repeat) jump();
552575
if (event.key === "ArrowUp" && !event.repeat) interact();
553576
});
554577

@@ -619,7 +642,7 @@ function init() {
619642
new Promise((resolve) => {
620643
const image = new Image();
621644
image.onload = image.onerror = resolve;
622-
image.src = "assets/wedding-village-panorama.jpg";
645+
image.src = "assets/amoria-background-v2.jpg";
623646
}),
624647
new Promise((resolve) => {
625648
const image = new Image();

wedding-invitation/index.html

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,52 +3,52 @@
33
<head>
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
6-
<meta name="theme-color" content="#72d9ef" />
6+
<meta name="theme-color" content="#078ce5" />
77
<meta
88
name="description"
9-
content="웨딩빌리지를 모험하며 만나는 모바일 게임형 결혼식 초대장"
9+
content="아모리아를 모험하며 만나는 남궁찬과 차은서의 모바일 게임형 결혼식 초대장"
1010
/>
11-
<meta property="og:title" content="우리의 웨딩 퀘스트에 초대합니다" />
12-
<meta property="og:description" content="방향키로 걷고, 점프하고, 포탈을 타고 결혼식에 놀러오세요!" />
13-
<meta property="og:image" content="assets/wedding-village-panorama.jpg" />
14-
<title>Wedding Quest · 모바일 청첩장</title>
15-
<link rel="preload" href="assets/wedding-village-panorama.jpg" as="image" />
11+
<meta property="og:title" content="남궁찬 ♥ 차은서의 웨딩 퀘스트" />
12+
<meta property="og:description" content="2027년 5월 8일, 인천 웨스턴팰리스에서 만나요!" />
13+
<meta property="og:image" content="assets/amoria-background-v2.jpg" />
14+
<title>남궁찬 ♥ 차은서 · Wedding Quest</title>
15+
<link rel="preload" href="assets/amoria-background-v2.jpg" as="image" />
1616
<link rel="preload" href="assets/groom-sprite.png" as="image" />
1717
<link rel="stylesheet" href="styles.css" />
1818
</head>
1919
<body>
2020
<div id="preloader" class="preloader" aria-live="polite">
2121
<div class="preloader__leaf">🍁</div>
22-
<p>웨딩빌리지로 이동 중...</p>
22+
<p>아모리아로 이동 중...</p>
2323
<div class="preloader__bar"><span></span></div>
2424
</div>
2525

2626
<section id="intro" class="intro" aria-labelledby="intro-title">
2727
<div class="intro__sparkles" aria-hidden="true"></div>
2828
<div class="intro__content">
29-
<div class="intro__badge"><span>NEW QUEST</span> WEDDING VILLAGE</div>
29+
<div class="intro__badge"><span>CH. 1</span> AMORIA · WEDDING QUEST</div>
3030
<p class="intro__eyebrow">우리의 가장 설레는 모험</p>
31-
<h1 id="intro-title"><span id="intro-groom">신랑</span><i></i><span id="intro-bride">신부</span></h1>
31+
<h1 id="intro-title"><span id="intro-groom">남궁찬</span><i></i><span id="intro-bride">차은서</span></h1>
3232
<p class="intro__copy">두 사람이 평생 파티를 맺습니다.<br />소중한 당신을 웨딩 퀘스트에 초대해요.</p>
33-
<div class="intro__date" id="intro-date">2026. 10. 10 · SAT · 1:00 PM</div>
33+
<div class="intro__date" id="intro-date">2027. 05. 08 · SATURDAY</div>
3434
<button id="start-button" class="maple-button maple-button--primary" type="button">
3535
<span>🍁</span> 게임 시작
3636
</button>
3737
<div class="intro__controls">
3838
<span><kbd></kbd><kbd></kbd> 이동</span>
39-
<span><kbd>Alt</kbd> 점프</span>
39+
<span><kbd>SPACE</kbd> 점프</span>
4040
<span><kbd></kbd> 포탈</span>
4141
</div>
4242
<p class="intro__mobile-note">모바일에서는 화면 아래 버튼을 이용해 주세요</p>
4343
</div>
4444
<div class="intro__character" aria-hidden="true">
4545
<div class="sprite sprite--celebrate"></div>
4646
</div>
47-
<p class="intro__credit">Wedding Quest Invitation</p>
47+
<p class="intro__credit">MAPLE WEDDING · AMORIA</p>
4848
</section>
4949

5050
<main id="game-shell" class="game-shell is-hidden">
51-
<section id="game-viewport" class="game-viewport" aria-label="웨딩빌리지 횡스크롤 게임">
51+
<section id="game-viewport" class="game-viewport" aria-label="아모리아 횡스크롤 게임">
5252
<div class="sky-glow" aria-hidden="true"></div>
5353
<div id="world" class="world" aria-hidden="true">
5454
<div class="world__mist world__mist--one"></div>
@@ -57,14 +57,15 @@ <h1 id="intro-title"><span id="intro-groom">신랑</span><i>♥</i><span id="int
5757
<div id="player-shadow" class="player-shadow"></div>
5858
<div id="player" class="player">
5959
<div id="hero-sprite" class="sprite sprite--idle"></div>
60-
<div class="player__name" id="player-name">오늘의 주인공</div>
60+
<div class="player__name" id="player-name">남궁찬</div>
6161
</div>
6262
</div>
6363

6464
<header class="hud">
6565
<div class="hud__quest">
66+
<div class="hud__channel"><span>AMORIA</span><b>CH.1</b></div>
6667
<div class="hud__topline">
67-
<span id="location-label">웨딩빌리지 입구</span>
68+
<span id="location-label">아모리아 입구</span>
6869
<strong><span id="progress-count">0</span>/7</strong>
6970
</div>
7071
<p id="quest-label">첫 번째 포탈을 찾아가세요</p>
@@ -82,6 +83,17 @@ <h1 id="intro-title"><span id="intro-groom">신랑</span><i>♥</i><span id="int
8283
<kbd></kbd><span>포탈 타기</span>
8384
</div>
8485

86+
<div class="maple-statusbar" aria-hidden="true">
87+
<div class="maple-statusbar__portrait"><div class="sprite sprite--idle"></div></div>
88+
<div class="maple-statusbar__info">
89+
<div class="maple-statusbar__name"><b>Lv. 10</b><strong>남궁찬</strong></div>
90+
<div class="maple-meter maple-meter--hp"><span></span><em>HP 999 / 999</em></div>
91+
<div class="maple-meter maple-meter--mp"><span></span><em>MP 999 / 999</em></div>
92+
<div class="maple-meter maple-meter--exp"><span></span><em>EXP 05.08%</em></div>
93+
</div>
94+
<div class="maple-statusbar__menu"><span>SHOP</span><span>ITEM</span><span>MENU</span></div>
95+
</div>
96+
8597
<div class="touch-controls" aria-label="모바일 게임 조작 버튼">
8698
<div class="touch-controls__move">
8799
<button class="control-button control-button--left" data-control="left" type="button" aria-label="왼쪽으로 이동"></button>
@@ -92,7 +104,7 @@ <h1 id="intro-title"><span id="intro-groom">신랑</span><i>♥</i><span id="int
92104
<span></span><small>PORTAL</small>
93105
</button>
94106
<button class="control-button control-button--jump" data-control="jump" type="button">
95-
<span>ALT</span><small>JUMP</small>
107+
<span>SPACE</span><small>JUMP</small>
96108
</button>
97109
</div>
98110
</div>
@@ -123,7 +135,7 @@ <h2 id="quest-title">웨딩빌리지에 어서 오세요!</h2>
123135
<h2>웨딩 퀘스트 안내</h2>
124136
<div class="guide-grid">
125137
<div><b>← →</b><span>좌우로 이동</span></div>
126-
<div><b>ALT</b><span>가볍게 점프</span></div>
138+
<div><b>SPACE / Z</b><span>가볍게 점프</span></div>
127139
<div><b></b><span>빛나는 포탈 이용</span></div>
128140
</div>
129141
<p class="guide-card__tip">빛나는 포탈 가까이에서 위쪽 방향키를 누르면 이야기가 이어져요. 모바일에서는 아래 조작 버튼을 눌러주세요.</p>
@@ -137,21 +149,22 @@ <h2>웨딩 퀘스트 안내</h2>
137149
<div class="invitation-card__ribbon">QUEST COMPLETE</div>
138150
<div class="invitation-card__rings">💍</div>
139151
<p class="sheet-kicker">TOGETHER FOREVER</p>
140-
<h2><span id="invite-groom">신랑</span><i></i><span id="invite-bride">신부</span></h2>
152+
<h2><span id="invite-groom">남궁찬</span><i></i><span id="invite-bride">차은서</span></h2>
141153
<p id="invite-message" class="invitation-card__message"></p>
142154
<div class="invitation-card__details">
143-
<div><small>DATE</small><strong id="invite-date">2026년 10월 10일 토요일 오후 1시</strong></div>
144-
<div><small>PLACE</small><strong id="invite-place">웨딩빌리지 대성당 · 1층 그랜드홀</strong></div>
155+
<div><small>DATE</small><strong id="invite-date">2027년 5월 8일 토요일</strong></div>
156+
<div><small>PLACE</small><strong id="invite-place">인천 웨스턴팰리스</strong></div>
145157
</div>
146158
<div class="invitation-card__actions">
147159
<button id="calendar-button" class="maple-button maple-button--subtle" type="button">일정 저장</button>
148160
<button id="final-share-button" class="maple-button maple-button--primary" type="button">초대장 공유</button>
149161
</div>
150-
<button id="replay-button" class="text-button" type="button">웨딩빌리지 다시 걷기 ↺</button>
162+
<button id="replay-button" class="text-button" type="button">아모리아 다시 걷기 ↺</button>
151163
</article>
152164
</dialog>
153165

154166
<div id="confetti" class="confetti" aria-hidden="true"></div>
167+
<div id="bgm-player" class="bgm-player" aria-hidden="true"></div>
155168
<script src="game.js"></script>
156169
</body>
157170
</html>

0 commit comments

Comments
 (0)