메인 콘텐츠로 건너뛰기
종료
⚡ 솔로 챌린지

산책

5.18 14:25~5.19 13:30 · 1명
SOLO CHALLENGE

이번 여정은 여기까지

도큐먼트정
0 km
5.18 ~ 5.19

기록은 남지 않았지만 도전한 마음은 남았어요.
다음 챌린지에서 다시 만나요.

🏅 상장
RO-2603-MC-001
RunnerOn
🏆

우 승 상

CERTIFICATE OF ACHIEVEMENT
위 상은 다음 러너에게 수여합니다
닉네임
홍*동
0.0 km 총 거리
0 러닝
- 베스트 페이스
기간
🏅
RUNNERON OFFICIAL
발급
러너온 운영위원회
); }
이니
개발자 이니 온라인
브레이크아웃·따옴표 인젝션 방지 // 토스트 function showShareToast(msg) { var t = document.createElement('div'); t.className = 'cr-share-toast'; t.textContent = msg; document.body.appendChild(t); requestAnimationFrame(function() { t.classList.add('cr-share-toast--show'); }); setTimeout(function() { t.classList.remove('cr-share-toast--show'); setTimeout(function() { t.remove(); }, 400); }, 2000); } // 클립보드 복사 (clipboard API 미지원 시 textarea 폴백) function copyToClipboard(text, successMsg) { if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(text).then(function() { showShareToast(successMsg); }).catch(function() { fallbackCopy(text, successMsg); }); } else { fallbackCopy(text, successMsg); } } function fallbackCopy(text, successMsg) { var ta = document.createElement('textarea'); ta.value = text; ta.style.cssText = 'position:fixed;left:-9999px;top:-9999px;opacity:0;'; document.body.appendChild(ta); ta.focus(); ta.select(); try { document.execCommand('copy'); showShareToast(successMsg); } catch(e) { showShareToast('텍스트를 길게 눌러 복사해주세요.'); } document.body.removeChild(ta); } // ===================== 공유 메시지 생성 ===================== var totalP = 1; var top1Nick = '도큐먼트정'; var top1Dist = 0; function pick(arr) { return arr[Math.floor(Math.random() * arr.length)]; } function getShareText() { return pick([ '🔥 [' + challengeName + ']\n\n' + totalP + '명이 지금 달리고 있다.\n너는 뭐 하고 있어?\n\n👉 ', '🏆 [' + challengeName + ']\n\n현재 1위: ' + top1Nick + ' (' + top1Dist + 'km)\n이 기록 깰 수 있는 사람?\n\n👉 ', '💨 [' + challengeName + '] 순위 공개!\n\n' + totalP + '명 중 1위는 ' + top1Nick + '!\n도전장 내밀 용기 있어?\n\n👉 ', '👟 [' + challengeName + ']\n\n' + totalP + '명이 뛰는데 넌 아직 소파?\n지금이라도 늦지 않았다!\n\n👉 ', '🏃 [' + challengeName + ']\n\n1위 ' + top1Nick + '(' + top1Dist + 'km)이 질주 중!\n추월할 사람 여기 모여!\n\n👉 ' ]); } function getMyShareText(rank, dist) { if (rank === 1) { return pick([ '👑 [' + challengeName + ']\n\n🥇 1위 달성! ' + dist + 'km\n잡을 수 있으면 잡아봐 😏\n\n👉 ', '🔥 [' + challengeName + ']\n\n1위 ' + dist + 'km 달리는 중!\n이 기록 넘을 자신 있어?\n따라올 테면 따라와 💨\n\n👉 ', '🏆 [' + challengeName + ']\n\n' + dist + 'km로 정상 등극!\n왕좌를 뺏고 싶다면 직접 뛰어봐 🏃\n\n👉 ' ]); } var gap = top1Dist - dist; return pick([ '💪 [' + challengeName + ']\n\n' + rank + '위 · ' + dist + 'km 달성!\n1위까지 ' + gap.toFixed(1) + 'km... 곧 뒤집는다 🔥\n\n같이 뛸 사람? 👉 ', '🏃 [' + challengeName + ']\n\n현재 ' + rank + '위 (' + dist + 'km)\n아직 끝 아니다. 역전극 시작!\n\n도전할 사람 여기로 👉 ', '😤 [' + challengeName + ']\n\n' + rank + '위인데 1위까지 ' + gap.toFixed(1) + 'km 밖에 안 남음\n포기? 그런 단어 내 사전에 없다!\n\n👉 ', '🔥 [' + challengeName + ']\n\n' + rank + '위 ' + dist + 'km!\n오늘 뛰면 내일 순위가 바뀐다 💨\n같이 달릴 사람 구함!\n\n👉 ' ]); } // 공유 실행 (navigator.share → 클립보드 폴백) function doShare(text) { var fullText = text + shareUrl; // 1순위: Web Share API (HTTPS + 네이티브 브라우저에서만 동작) if (navigator.share && window.isSecureContext) { navigator.share({ title: challengeName, text: fullText }) .then(function() { /* 공유 성공 */ }) .catch(function(err) { // 사용자가 취소한 경우(AbortError)는 무시, 그 외는 클립보드 폴백 if (err.name !== 'AbortError') { copyToClipboard(fullText, '복사 완료! 붙여넣기로 공유하세요 📋'); } }); return; } // 2순위: 클립보드 복사 폴백 copyToClipboard(fullText, '복사 완료! 붙여넣기로 공유하세요 📋'); } // 1. 페이지 공유 var shareBtn = document.getElementById('cr-share-btn'); if (shareBtn) { shareBtn.addEventListener('click', function() { doShare(getShareText()); }); } // 2. 내 순위 공유 var shareMyBtn = document.getElementById('cr-share-my'); if (shareMyBtn) { shareMyBtn.addEventListener('click', function() { }); } // 3. 상장 이미지 공유 (body로 이동된 후 재바인딩 필요) function bindCertShare() { var btn = document.getElementById('cr-cert-share'); if (!btn || btn.dataset.bound) return; btn.dataset.bound = '1'; btn.addEventListener('click', function() { var card = document.getElementById('cr-cert-card'); if (!card) { showShareToast('상장을 먼저 열어주세요.'); return; } if (typeof html2canvas === 'undefined') { showShareToast('이미지 저장을 불러오지 못했어요. 스크린샷으로 저장해 주세요.'); return; } btn.disabled = true; btn.textContent = '준비 중...'; html2canvas(card, { scale: 2, useCORS: true, backgroundColor: null, allowTaint: true }).then(function(canvas) { canvas.toBlob(function(blob) { if (!blob) { fallbackShare(); return; } // 파일 공유 시도 if (navigator.share) { try { var file = new File([blob], 'RunnerOn_상장.png', { type: 'image/png' }); var shareData = { files: [file] }; if (navigator.canShare && navigator.canShare(shareData)) { navigator.share(shareData).then(resetBtn).catch(resetBtn); return; } } catch(e) {} // 파일 공유 실패 → 텍스트 공유 navigator.share({ title: challengeName + ' 상장', text: '🏅 [' + challengeName + '] 상장 획득!\n달린 만큼 인정받는다 🏃\n\n' + shareUrl }).then(resetBtn).catch(resetBtn); } else { // 다운로드 폴백 var link = document.createElement('a'); link.href = canvas.toDataURL('image/png'); link.download = 'RunnerOn_상장.png'; link.click(); showShareToast('이미지가 저장되었습니다.'); resetBtn(); } }, 'image/png'); }).catch(function(err) { console.error('[Share] html2canvas 실패:', err); fallbackShare(); }); function fallbackShare() { if (navigator.share) { navigator.share({ title: challengeName, text: '🏅 [' + challengeName + '] 상장!\n달린 만큼 빛나는 훈장 🏃\n\n' + shareUrl }).catch(function(){}); } else { showShareToast('공유할 수 없습니다.'); } resetBtn(); } function resetBtn() { btn.disabled = false; btn.textContent = '공유'; } }); } // 초기 바인딩 + 모달 열릴 때 재바인딩 bindCertShare(); var certObserver = new MutationObserver(function() { bindCertShare(); }); certObserver.observe(document.body, { childList: true }); // ===================== CREW MATCH TAB SWITCHING ===================== var crTabs = document.querySelectorAll('.cr-tab'); if (crTabs.length > 0) { crTabs.forEach(function(tab) { tab.addEventListener('click', function() { crTabs.forEach(function(t) { t.classList.remove('cr-tab--active'); }); this.classList.add('cr-tab--active'); var target = this.dataset.tab; var isCrew = target === 'crew'; // 리스트 패널 전환 var crewPanel = document.getElementById('cr-crew-panel'); var indPanel = document.getElementById('cr-individual-panel'); if (crewPanel) crewPanel.style.display = isCrew ? '' : 'none'; if (indPanel) indPanel.style.display = isCrew ? 'none' : ''; // 시상대(포디움) 전환 var crewStage = document.getElementById('cr-crew-stage'); var indStage = document.getElementById('cr-stage'); if (crewStage) crewStage.style.display = isCrew ? '' : 'none'; if (indStage) indStage.style.display = isCrew ? 'none' : ''; // 전환 시 count-up 재실행 (숫자 업데이트) var targetStage = isCrew ? crewStage : indStage; if (targetStage) { targetStage.querySelectorAll('.cr-pod-dist-num').forEach(function(el) { var target = parseFloat(el.getAttribute('data-target')); if (!isNaN(target)) el.textContent = target.toFixed(1); }); } }); }); }