/** * Safe animation wrapper to prevent crashes if the motion library fails to load or acts up. */ const getMotion = () => { if (typeof animate !== 'undefined') return { animate, spring }; if (window.motion) return window.motion; return null; }; const safeAnimate = (target, values, options) => { const m = getMotion(); if (m && typeof m.animate === 'function') { try { const controls = m.animate(target, values, options); if (controls && !controls.finished) { controls.finished = Promise.resolve(); } return controls; } catch (e) { console.error("Animation execution failed:", e); } } return { finished: Promise.resolve() }; }; const questions = [ { ko: { q: "비행기 표 예매 완료! 그다음 내 행동은?", a: "엑셀로 분 단위 계획", b: "일단 유튜브 브이로그 정주행" }, en: { q: "Flight tickets booked! What's your next move?", a: "Plan every minute in Excel", b: "Watch YouTube travel vlogs" }, ja: { q: "航空券の予約完了!次にとる行動は?", a: "エクセルで分単位の計画", b: "とりあえずYouTubeのVlogを見る" }, scoreA: ['type1'], scoreB: ['type4'] }, { ko: { q: "현지 맛집을 찾을 때, 내가 더 믿는 것은?", a: "인스타그램 릴스 비주얼 폭발 핫플", b: "구글맵 평점 4.0 이상 찐 후기" }, en: { q: "What do you trust more for finding restaurants?", a: "Viral visual spots on Instagram", b: "4.0+ rating, real reviews on Maps" }, ja: { q: "現地のグルメを探す時、より信じるのは?", a: "インスタのリールの映えスポット", b: "Googleマップの星4.0以上の口コミ" }, scoreA: ['type3'], scoreB: ['type1'] }, { ko: { q: "도톤보리 맛집 대기 줄이 2시간이라면?", a: "'여기 오려고 온 건데!' 무조건 기다림", b: "'기다리다 지쳐...' 다른 식당으로 턴" }, en: { q: "2-hour wait at a famous Dotonbori spot?", a: "\"I came here for this!\" Wait.", b: "\"Too tired to wait.\" Go elsewhere." }, ja: { q: "道頓堀の有名店、2時間待ちだとしたら?", a: "「これを食べに来たんだ!」並ぶ", b: "「待つのは無理…」別の店に行く" }, scoreA: ['type3'], scoreB: ['type2'] }, { ko: { q: "길을 잃었다! 휴대폰 배터리도 5% 남았다면?", a: "당황하지 않고 바디랭귀지로 길 묻기", b: "꺼지기 전에 지도 앱부터 캡처한다" }, en: { q: "Lost and phone battery is at 5%?", a: "Ask directions with body language", b: "Screenshot the map immediately" }, ja: { q: "道に迷い、バッテリーも5%なら?", a: "ボディランゲージで道を尋ねる", b: "切れる前に地図をスクショする" }, scoreA: ['type4'], scoreB: ['type1'] }, { ko: { q: "친구가 '저기 예쁘다! 그냥 들어가 볼까?' 할 때?", a: "'오, 괜찮은데? 가보자!'", b: "'잠깐만, 평점 먼저 좀 보고...'" }, en: { q: "Friend: 'That place looks pretty! Go in?'", a: "\"Oh, sure! Let's go!\"", b: "\"Wait, let me check the rating...\"" }, ja: { q: "「あそこ綺麗!入ってみる?」と言われたら?", a: "「いいね!行こう!」", b: "「ちょっと待って、評価を見てから」" }, scoreA: ['type4'], scoreB: ['type1'] }, { ko: { q: "인생샷 성지 발견! 하지만 사람이 줄이 너무 길다면?", a: "'여기까지 왔는데 남겨야지!' 줄을 선다", b: "'기 빨린다... 사람 없는 데로 가자.' 포기" }, en: { q: "Famous photo spot has a massive line?", a: "\"I must take this photo!\" Wait.", b: "\"Too crowded... let's find a quiet spot.\"" }, ja: { q: "映えスポット発見!でも行列だったら?", a: "「ここまで来たからには!」並ぶ", b: "「疲れちゃう…静かなところへ」" }, scoreA: ['type3'], scoreB: ['type2'] }, { ko: { q: "숙소 예약 시 내가 가장 중요하게 보는 것은?", a: "통창 뷰, 인테리어 컨셉 등 '무드'", b: "가성비, 지하철역과의 거리 등 '효율'" }, en: { q: "What's most important when booking stays?", a: "The mood (view, interior design)", b: "Efficiency (price, distance to station)" }, ja: { q: "宿を予約する時に一番重視するのは?", a: "ムード(景色、インテリアなど)", b: "効率(コスパ、駅からの距離など)" }, scoreA: ['type3'], scoreB: ['type1'] }, { ko: { q: "친구가 계속 딴짓을 하거나 늦잠을 잔다면?", a: "'야, 우리 일정 다 꼬여!' 화가 난다", b: "'내 멘탈이 먼저다...' 혼자 산책한다" }, en: { q: "Friend keeps oversleeping or slacking off?", a: "\"Our schedule is ruined!\" Get angry.", b: "\"Peace of mind first.\" Walk alone." }, ja: { q: "友達が遅刻や寝坊を繰り返したら?", a: "「予定が台無しだよ!」怒る", b: "「自分の時間を楽しもう…」散歩する" }, scoreA: ['type1'], scoreB: ['type2'] }, { ko: { q: "우연히 들어간 식당이 불친절하고 맛도 없다면?", a: "'뭐 어때, 이것도 다 추억이지!' 웃어넘긴다", b: "'내 소중한 한 끼를 망치다니...' 기분이 안 좋아짐" }, en: { q: "Unfriendly and bad food at a random place?", a: "\"Whatever, it's a memory!\" Laugh it off.", b: "\"My precious meal is ruined...\" Feel bad." }, ja: { q: "入った店が不親切で味も最悪だったら?", a: "「まあいいや、思い出だよ!」笑い飛ばす", b: "「一食が台無しに…」気分が下がる" }, scoreA: ['type4'], scoreB: ['type2'] }, { ko: { q: "여행 마지막 날 밤, 나의 모습은?", a: "'벌써 끝이야?' 아쉬움에 다음 항공권을 검색", b: "'집이 최고다...' 하얗게 불태우고 쉴 준비" }, en: { q: "The last night of the trip...", a: "\"Already over?\" Search for next flights.", b: "\"Home is best.\" Time to rest." }, ja: { q: "旅行最終日の夜、あなたの姿は?", a: "「もう終わり?」次回の航空券を探す", b: "「家が一番…」燃え尽きて休む準備をする" }, scoreA: ['type3'], scoreB: ['type2'] } ]; const results = { "type1": { ko: { title: "구글맵 맹신론자,
척척박사 인간 내비게이션", emoji: "🧭", desc: "구글맵 평점과 현지인 후기를 교차 검증해야 직성이 풀리는 완벽주의자. 당신과 함께라면 굶을 일은 없어요!", bestMate: "무계획 방랑자 (\"내가 짠 계획대로 불만 없이 쫄래쫄래 잘 따라옴\")", worstMate: "인간 핀터레스트 (\"동선 다 짜놨는데 자꾸 예쁜 카페 보인다고 계획 틀어버림\")", cta: "당신이 떠나고 싶은 여행지 현재 상황이 궁금하다면 실시간 혼잡도 확인하기" }, en: { title: "Google Maps Loyalist,
Human Navigation", emoji: "🧭", desc: "A perfectionist who cross-checks everything with local reviews. You're the navigator everyone relies on!", bestMate: "Spontaneous Nomad (\"Follows my plans without a complaint\")", worstMate: "Human Pinterest (\"Ruins routes with unplanned cafe stops\")", cta: "Curious about the current situation at your destination? Check real-time crowds!" }, ja: { title: "Googleマップ信者、
博識な人間ナビゲーション", emoji: "🧭", desc: "Googleマップの星数と現地の口コミを徹底的にチェックする完璧主義者。あなたと一緒なら美味しい店に間違いなし!", bestMate: "無計画な放浪者 (「自分が立てた計画通りに文句を言わずについてきてくれる」)", worstMate: "人間ピンタレスト (「計画を完璧に立てたのに、可愛いカフェがあるたびに立ち寄って計画を狂わせる」)", cta: "旅行先の今の状況が気になりませんか?リアルタイムの混雑状況をチェック!" } }, "type2": { ko: { title: "인파 속 기 빨리는
유리멘탈 개복치", emoji: "🫧", desc: "사람 많은 곳에 가면 에너지가 급격히 방전됩니다. 시끌벅적한 관광지보다는 아무도 없는 골목길이나 조용한 카페 자리를 더 사랑합니다.", bestMate: "인생샷 사냥꾼 (\"인파 없는 감성 스팟만 쏙쏙 골라가줘서 행복함\")", worstMate: "무계획 방랑자 (\"예약도 안 하고 무작정 갔다가 웨이팅 지옥에 갇힘\")", cta: "사람 많은 곳을 피하고 싶을 때 👉 CheckEastPoint" }, en: { title: "Overwhelmed by Crowds,
The Fragile Sunfish", emoji: "🫧", desc: "Crowds drain your battery instantly. You prefer quiet alleys and window seats over bustling tourist traps.", bestMate: "Human Pinterest (\"Happy to visit hidden gems with no crowds selected for me\")", worstMate: "Spontaneous Nomad (\"Trapped in waiting hell because we just went without a plan\")", cta: "Curious about the current situation at your destination? Check real-time crowds!" }, ja: { title: "人混みを避ける
ガラスのメンタルのマンボウ", emoji: "🫧", desc: "人が多い場所に行くとエネルギーが切れます。騒がしい観光地よりも、静かな路地裏やカフェが大好きです。", bestMate: "人間ピンタレスト (「人混みのないオシャレなスポットを厳選して連れて行ってくれる」)", worstMate: "無計画な放浪者 (「予約なしで突撃して、行列地獄にハマる」)", cta: "旅行先の今の状況が気になりませんか?リアルタイムの混雑状況をチェック!" } }, "type3": { ko: { title: "감성에 살고 감성에 죽는
인간 핀터레스트", emoji: "📸", desc: "남는 건 사진뿐! 음식 맛보다는 플레이팅과 인테리어가, 숙소의 편안함보다는 창밖 뷰가 더 중요합니다. 1,000장 찍어 1장 건지면 대성공!", bestMate: "유리멘탈 개복치 (\"예쁜 곳 데려가면 리액션 최고! 묵묵히 짐도 잘 들어줌\")", worstMate: "구글맵 맹신론자 (\"사진 좀 찍으려는데 다음 일정 늦는다고 빨리 오라고 재촉함\")", cta: "당신이 떠나고 싶은 여행지 현재 상황이 궁금하다면 실시간 혼잡도 확인하기" }, en: { title: "Aesthetics First,
The Human Pinterest", emoji: "📸", desc: "If it's not on camera, it didn't happen! The view and plating are everything to you. 1 perfect shot is a victory!", bestMate: "The Fragile Sunfish (\"Best reactions to pretty places! Quietly helps carry gear\")", worstMate: "Google Maps Loyalist (\"Constantly rushes me to the next spot while I'm taking photos\")", cta: "Looking for the perfect shot? Check real-time crowds!" }, ja: { title: "感性に生き感性に死ぬ
人間ピンタレスト", emoji: "📸", desc: "残るのは写真だけ!味よりも見た目、快適さよりも映えが重要です。1,000枚撮って奇跡の1枚が撮れれば大成功です。", bestMate: "メンタル激弱マンボウ (「素敵な場所に連れて行くとリアクション最高!荷物も持ってくれる」)", worstMate: "Googleマップ信者 (「写真を撮りたいのに、次の予定に遅れるからと早く来るように急かす」)", cta: "映えスポットを狙うなら?リアルタイムの混雑状況をチェック!" } }, "type4": { ko: { title: "발길 닿는 곳이 곧 길,
무계획 방랑자", emoji: "🍃", desc: "여권과 지갑만 있으면 어디든 갈 수 있습니다. 철저한 계획보다는 우연히 발견한 길거리 식당에서의 한 끼를 더 오래 기억하는 진정한 여행자입니다.", bestMate: "구글맵 맹신론자 (\"나는 아무 생각 없는데 알아서 하드캐리 해줘서 몸이 편함\")", worstMate: "유리멘탈 개복치 (\"무계획으로 한참 걷다가 길 잃으면 개복치가 쓰러져서 눈치 보임\")", cta: "당신이 떠나고 싶은 여행지 현재 상황이 궁금하다면 실시간 혼잡도 확인하기" }, en: { title: "Wherever Wind Blows,
The Spontaneous nomad", emoji: "🍃", desc: "As long as you have your passport and wallet, you're good. You cherish random street food over a set itinerary.", bestMate: "Google Maps Loyalist (\"Carries the whole planning so I can just relax\")", worstMate: "The Fragile Sunfish (\"If I get lost, they collapse from exhaustion, making me feel guilty\")", cta: "No plans? No problem! Check real-time crowds!" }, ja: { title: "足の向くまま風の向くまま、
無計画な放浪者", emoji: "🍃", desc: "パスポートと財布さえあればどこへでも行けます。徹底した計画よりも、偶然見つけた屋台での一食を大切にする真の旅行者です。", bestMate: "Googleマップ信者 (「自分は何も考えていないのに、スマートにリードしてくれるので体が楽」)", worstMate: "メンタル激弱マンボウ (「無計画でしばらく歩いて道に迷うと、マンボウが倒れ出すので気を使う」)", cta: "計画なしでも大丈夫!リアルタイムの混雑状況をチェック!" } } }; let currentQ = 0; let scores = { type1: 0, type2: 0, type3: 0, type4: 0 }; let answers = []; // stores 'A' or 'B' for each answered question let currentLang = 'ko'; let finalResultType = null; function getBestLanguage() { const supported = ['ko', 'en', 'ja']; // Check URL param first (from shared links) const urlParams = new URLSearchParams(window.location.search); const urlLang = urlParams.get('lang'); if (urlLang && supported.includes(urlLang)) return urlLang; // Fallback to browser language const browserLang = (navigator.language || navigator.userLanguage).split('-')[0].toLowerCase(); return supported.includes(browserLang) ? browserLang : 'en'; } function switchView(hideId, showId) { const hideEl = document.getElementById(hideId); const showEl = document.getElementById(showId); if (!hideEl || !showEl) return; safeAnimate(hideEl, { opacity: 0, y: -20 }, { duration: 0.25 }).finished.then(() => { hideEl.classList.remove('active'); hideEl.classList.add('hidden'); showEl.classList.remove('hidden'); showEl.classList.add('active'); safeAnimate(showEl, { opacity: [0, 1], y: [30, 0] }, { duration: 0.4, ease: [0.22, 1, 0.36, 1] }); }); } function initTest() { currentLang = getBestLanguage(); const langSelector = document.getElementById('lang-selector'); if (langSelector) { langSelector.value = currentLang; } initStepDots(); showAdByLang(); document.getElementById('btn-start')?.addEventListener('click', () => { currentQ = 0; scores = { type1: 0, type2: 0, type3: 0, type4: 0 }; answers = []; renderQuestion(); switchView('view-landing', 'view-question'); }); document.getElementById('btn-opt-A')?.addEventListener('click', () => handleAnswer('A')); document.getElementById('btn-opt-B')?.addEventListener('click', () => handleAnswer('B')); document.getElementById('btn-back')?.addEventListener('click', handleBack); document.getElementById('btn-restart')?.addEventListener('click', (e) => { e.preventDefault(); // Clear result param from URL const url = new URL(window.location.href); url.searchParams.delete('result'); window.history.replaceState({}, '', url); location.reload(); }); document.getElementById('btn-share-kakao')?.addEventListener('click', shareKakao); document.getElementById('btn-share-insta')?.addEventListener('click', shareInsta); document.getElementById('btn-share-x')?.addEventListener('click', shareX); document.getElementById('btn-share-reddit')?.addEventListener('click', shareReddit); document.getElementById('btn-viral-cta')?.addEventListener('click', shareNative); } function renderQuestion() { const qData = questions[currentQ][currentLang] || questions[currentQ]['en']; const qNumEl = document.getElementById('q-num'); const qTitleEl = document.getElementById('q-title'); const btnA = document.getElementById('btn-opt-A'); const btnB = document.getElementById('btn-opt-B'); if (qNumEl) qNumEl.innerText = `Q${currentQ + 1}.`; if (qTitleEl) qTitleEl.innerText = qData.q; if (btnA) btnA.innerHTML = qData.a; if (btnB) btnB.innerHTML = qData.b; const progress = ((currentQ) / questions.length) * 100; const progressEl = document.getElementById('progress-fill'); if (progressEl) progressEl.style.width = `${progress}%`; updateStepDots(currentQ); safeAnimate(".btn-option", { opacity: [0, 1], y: [20, 0] }, { duration: 0.35 }); } function initStepDots() { const container = document.getElementById('step-dots'); if (!container) return; container.innerHTML = ''; for (let i = 0; i < questions.length; i++) { const dot = document.createElement('div'); dot.className = 'step-dot'; dot.id = `dot-${i}`; container.appendChild(dot); } } function updateStepDots(currentIndex) { for (let i = 0; i < questions.length; i++) { const dot = document.getElementById(`dot-${i}`); if (!dot) continue; dot.className = 'step-dot'; if (i < currentIndex) dot.classList.add('done'); else if (i === currentIndex) dot.classList.add('active'); } } function handleBack() { if (currentQ <= 0) return; // Remove last answer's scores const prevAns = answers.pop(); currentQ--; const q = questions[currentQ]; const scoreKeys = (prevAns === 'A') ? q.scoreA : q.scoreB; scoreKeys.forEach(type => scores[type]--); renderQuestion(); updateBackButton(); } function updateBackButton() { const btn = document.getElementById('btn-back'); if (!btn) return; btn.style.display = currentQ > 0 ? 'flex' : 'none'; } function handleAnswer(opt) { const q = questions[currentQ]; const scoreKeys = (opt === 'A') ? q.scoreA : q.scoreB; scoreKeys.forEach(type => scores[type]++); answers.push(opt); currentQ++; if (currentQ < questions.length) { renderQuestion(); updateBackButton(); } else { showResult(); } } function showAdByLang() { const kakaoLanding = document.getElementById('kakao-ad-landing'); const kakaoResult = document.getElementById('kakao-ad-result'); const klAd = document.getElementById('klook-ad'); const isKo = (currentLang === 'ko'); if (isKo) { // Show Kakao ads, hide Klook if (kakaoLanding) { kakaoLanding.style.display = 'block'; const ins = kakaoLanding.querySelector('ins'); if (ins) ins.style.display = 'block'; } if (kakaoResult) { kakaoResult.style.display = 'block'; const ins = kakaoResult.querySelector('ins'); if (ins) ins.style.display = 'block'; } if (klAd) klAd.style.display = 'none'; } else { // Hide Kakao ads, show Klook if (kakaoLanding) kakaoLanding.style.display = 'none'; if (kakaoResult) kakaoResult.style.display = 'none'; if (klAd) klAd.style.display = 'block'; } } function showResult() { const progressEl = document.getElementById('progress-fill'); if (progressEl) progressEl.style.width = `100%`; switchView('view-question', 'view-loading'); setTimeout(() => { calculateAndRenderResult(); switchView('view-loading', 'view-result'); showAdByLang(); setTimeout(() => { animateSlideUp('#chemistry-section', 0); animateSlideUp('#viral-cta-section', 120); animateSlideUp('#affiliate-ads', 180); animateSlideUp('#action-buttons', 240); }, 500); }, 1500); } function animateSlideUp(selector, delayMs) { const el = document.querySelector(selector); if (!el) return; setTimeout(() => { safeAnimate(el, { opacity: [0, 1], y: [40, 0] }, { duration: 0.6 }); }, delayMs); } function calculateAndRenderResult() { const priority = ['type2', 'type1', 'type3', 'type4']; let winner = priority[0]; const maxVal = Math.max(...Object.values(scores)); for (const type of priority) { if (scores[type] === maxVal) { winner = type; break; } } finalResultType = winner; // Update URL with result so sharing includes the result const url = new URL(window.location.href); url.searchParams.set('result', finalResultType); window.history.replaceState({}, '', url); renderResultData(); } function calculateAndRenderResult_direct() { // Used when loading from shared URL — result type already set renderResultData(); } function renderResultData() { if (!finalResultType) return; const resData = results[finalResultType][currentLang] || results[finalResultType]['en']; const titleEl = document.getElementById('result-title'); const emojiEl = document.getElementById('result-emoji'); const descEl = document.getElementById('result-desc'); const bestMateEl = document.getElementById('result-best-mate'); const worstMateEl = document.getElementById('result-worst-mate'); const ctaEl = document.getElementById('cta-text'); if (titleEl) titleEl.innerHTML = resData.title; if (emojiEl) emojiEl.innerText = resData.emoji; if (descEl) descEl.innerText = resData.desc; if (bestMateEl) bestMateEl.innerText = resData.bestMate; if (worstMateEl) worstMateEl.innerText = resData.worstMate; // Explicitly update CTA from resData to match the personality type context, // or fall back to the generic i18n key we just updated. if (ctaEl) { ctaEl.innerText = resData.cta || (window.translations[currentLang]?.["nav.home"]); } } function getShareUrl() { const url = new URL(window.location.href); if (finalResultType) url.searchParams.set('result', finalResultType); // Include lang param so the recipient sees OG content in the correct language url.searchParams.set('lang', currentLang); return url.toString(); } function getResultTitlePlain() { if (!finalResultType) return ''; const resData = results[finalResultType][currentLang] || results[finalResultType]['en']; return resData.title.replace(//gi, ' ').trim(); } function getShareText() { const title = getResultTitlePlain(); if (!title) { if (currentLang === 'ja') return '旅行生存タイプ診断、やってみて!'; if (currentLang === 'en') return 'Take the Travel Survival Type Test!'; return '여행 생존 유형 테스트 해보세요!'; } const resData = results[finalResultType][currentLang] || results[finalResultType]['en']; const bestHeader = (currentLang === 'ja') ? '💚 最高のパートナー: ' : (currentLang === 'en' ? '💚 Best Mate: ' : '💚 환상의 여행 메이트: '); const worstHeader = (currentLang === 'ja') ? '💔 最悪のパートナー: ' : (currentLang === 'en' ? '💔 Worst Mate: ' : '💔 환장의 여행 메이트: '); if (currentLang === 'ja') { return `私の旅行タイプは「${title}」!\n\n"${resData.desc}"\n\n${bestHeader}${resData.bestMate}\n${worstHeader}${resData.worstMate}\n\n診断はこちら 👇`; } if (currentLang === 'en') { return `My travel persona is "${title}"!\n\n"${resData.desc}"\n\n${bestHeader}${resData.bestMate}\n${worstHeader}${resData.worstMate}\n\nTake the test here 👇`; } return `나의 여행 자아는 "${title}"!\n\n"${resData.desc}"\n\n${bestHeader}${resData.bestMate}\n${worstHeader}${resData.worstMate}\n\n테스트 해보기 👇`; } function shareNative() { const title = (currentLang === 'ja') ? '旅行生存タイプテスト' : (currentLang === 'en' ? 'Travel Survival Test' : '여행 생존 유형 테스트'); const text = getShareText(); const url = getShareUrl(); if (navigator.share) { navigator.share({ title, text, url }).catch(console.error); } else { navigator.clipboard.writeText(`${text}\n${url}`).then(() => { const msg = (currentLang === 'ja') ? 'リンクがコピーされました!' : (currentLang === 'en' ? 'Link copied!' : '링크가 복사되었어요!'); showToast(msg); }); } } function shareX() { const url = getShareUrl(); const text = getShareText(); window.open(`https://twitter.com/intent/tweet?text=${encodeURIComponent(text)}&url=${encodeURIComponent(url)}`, '_blank'); } function shareReddit() { const url = getShareUrl(); const title = (currentLang === 'ja') ? '旅行生存タイプテスト' : (currentLang === 'en' ? 'Travel Survival Test' : '여행 생존 유형 테스트'); window.open(`https://www.reddit.com/submit?url=${encodeURIComponent(url)}&title=${encodeURIComponent(title)}`, '_blank'); } function showToast(message) { const toast = document.getElementById('toast'); if (!toast) return; toast.textContent = message; toast.classList.add('show'); setTimeout(() => toast.classList.remove('show'), 2500); } function shareKakao() { if (window.Kakao && window.Kakao.isInitialized()) { const shareUrl = getShareUrl(); const resultTitle = getResultTitlePlain(); const resData = results[finalResultType][currentLang] || results[finalResultType]['en']; const title = (currentLang === 'ja') ? `私は「${resultTitle}」!` : (currentLang === 'en' ? `I'm "${resultTitle}"!` : `나의 여행 자아는 "${resultTitle}"!`); const bestHeader = (currentLang === 'ja') ? '💚 最高のパートナー: ' : (currentLang === 'en' ? '💚 Best Mate: ' : '💚 환상의 메이트: '); const desc = `${resData.desc}\n\n${bestHeader}${resData.bestMate}`; Kakao.Share.sendDefault({ objectType: 'feed', content: { title: title, description: desc, imageUrl: (currentLang === 'ja') ? 'https://www.checkeastpoint.com/event1/og_thumb_ja.png' : (currentLang === 'en' ? 'https://www.checkeastpoint.com/event1/og_thumb_en.png' : 'https://www.checkeastpoint.com/event1/og_thumb.png'), link: { mobileWebUrl: shareUrl, webUrl: shareUrl }, }, buttons: [{ title: (currentLang === 'ja') ? 'テストしてみる' : (currentLang === 'en' ? 'Take the test' : '테스트 해보기'), link: { mobileWebUrl: shareUrl, webUrl: shareUrl }, }], }); } else { showToast('Kakao sharing not ready.'); } } function shareInsta() { if (typeof html2canvas === 'undefined') return; const captureEl = document.getElementById('capture-area'); if (!captureEl) return; html2canvas(captureEl, { scale: 2 }).then(canvas => { const link = document.createElement('a'); link.download = 'travel-persona.png'; link.href = canvas.toDataURL(); link.click(); const msg = (currentLang === 'ja') ? '画像が保存されました!' : (currentLang === 'en' ? 'Image saved!' : '이미지가 저장되었어요!'); showToast(msg); }); } window.addEventListener('languageChanged', (e) => { currentLang = e.detail?.lang || e.detail || 'ko'; // Sync ads with the new language showAdByLang(); const questionView = document.getElementById('view-question'); const resultView = document.getElementById('view-result'); if (questionView?.classList.contains('active')) { renderQuestion(); } else if (resultView?.classList.contains('active')) { renderResultData(); } }); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initTest); } else { initTest(); }