[요청 사항] 매우 강조한다! 오른쪽 상단 언어 선택 버튼과, 언어 선택 관련한 모달추가만 할 것 게임의 디자인, 게임의 요소 등은 절대 수정하지 말고 삭제하지 말것! 매우 강조한다! 오른쪽 상단 언어 선택 버튼과 언어 선택 모달 등의 Z-INDEX 는 99999 수준으로 최고 수준으로 높여서 절대 최고 위의 레이어가 되도록 해야 한다! 아래 첨부된 '킹수학' 웹 게임 소스 코드에 다국어(다국적 언어) 지원 기능을 완벽하게 추가해 주세요. 언어 선택 기능 외 게임 로직이나 인터페이스는 절대 손대지 마세요. 최근에 논의된 '검색창이 포함된 모달 스타일의 언어 선택 UI(우측 상단 위치, 다크톤)'를 게임 화면 우측 상단에 적용해야 합니다. UI 디자인과 동작 로직은 하단의 [참고 UI 코드]를 바탕으로 현재 게임 소스에 융합해 주세요. 다국어 기능 구현 시 아래의 5가지 핵심 요구사항을 반드시 모두 충족하는 완성된 코드를 작성해 주세요. 1. 지원 언어 및 번역 가이드 (총 16개국) - 기존 한국어 버전의 텍스트와 UI는 원형을 유지하세요. - 기본 7개국: 한국어(ko), 영어(en), 중국어 간체(zh), 일본어(ja), 스페인어(es), 프랑스어(fr), 러시아어(ru) - 추가 9개국: 인도네시아어(id), 베트남어(vi), 태국어(th), 포르투갈어-브라질(pt-br), 힌디어(hi), 벵골어(bn), 터키어(tr), 독일어(de), 이탈리아어(it) - 번역 어조: 각 나라의 '초등학교 수학 교육과정'에서 사용하는 직관적이고 쉬운 어휘를 사용하세요. (예: Sum → Plus, Calculate → Solve) - 언어 번역을 범용처리 하지 말고 모든 언어 각 나라언어별로 각각 16개국 언어로 번역을 하세요. - 레이아웃 대응: 번역된 텍스트가 버튼이나 영역을 벗어나지 않도록, 글자 수에 따라 동적으로 폰트 사이즈가 줄어드는 CSS/JS 로직을 추가하세요. - 게임 타이틀 현지화: 각 언어별로 'King(왕)'이라는 의미가 들어가면서 게임의 성격을 잘 나타내는 재미있는 이름으로 번역/작명해 주세요. (예: Math King, Rey de las Matemáticas 등) - SEO 강화: 각 언어에 맞춰 `<title>`, `<meta name="description">`, `<meta name="keywords">` 등 강력한 다국어 SEO 태그가 동적으로 변경되도록 구현하세요. 2. 언어 감지 및 기본 설정 로직 (우선순위 철저히 지킬 것) - 1순위 (URL 파라미터): 접속 주소 뒤에 `?lang=fr` 처럼 파라미터가 있다면 해당 언어를 최우선으로 적용. - 2순위 (저장된 설정): 이전에 방문해서 언어를 선택한 기록(`localStorage`)이 있다면 그 언어를 적용. - 3순위 (기기 설정): 사용자의 브라우저/기기 언어(`navigator.language`)를 감지하여 지원하는 16개 언어 중 하나라면 적용. - 기본값: 위 3가지 조건에 모두 해당하지 않는 경우 기본적으로 **영어(en)**가 출력되도록 설정. 3. 상태 유지 및 공유 기능 - 상태 유지: 사용자가 언어를 변경하면 `localStorage`에 즉시 저장되어, 새로고침하거나 재접속해도 선택한 언어가 유지되어야 합니다. (향후 출시될 KingsMath 시리즈에 공통 적용할 수 있도록 key 값을 'kingsmath_lang' 등으로 설정하세요) - 링크 공유 및 URL 업데이트: 언어를 변경할 때마다 브라우저의 URL 주소창도 해당 언어 파라미터로 즉시 업데이트(`history.pushState` 활용)되어, 사용자가 현재 언어 상태 그대로 링크를 복사하여 공유할 수 있도록 구현하세요. 4. 도메인 및 링크 수정 규칙 - 한국어 버전에서는 '킹수학.com'이라는 텍스트를 사용하고, 그 외의 15개 외국어 버전에서는 'KingsMath.com'으로 번역하여 표시하세요. - 게임 내에서 홈으로 가거나 메인으로 이동하는 등 킹수학과 관련된 모든 하이퍼링크(a 태그)는 새 창이 아닌 **현재 창(self)**에서 `https://11ukul.com/kingsmathgames/` 주소로 연결되도록 하드코딩 해주세요. 🏠 홈 이모지는 전체 화면일 때 지체 없이 첫 화면으로 가게 해줘 아래 소스를 참고해 [홈 이모지 전체 화면 지체 없이 첫화면 가는 소스 시작] <div id="btn-home" class="floating-btn" style="cursor:pointer;">🏠</div> document.getElementById('btn-home').addEventListener('click', () => { // 1. 만약 전체 화면 모드라면 if (document.fullscreenElement) { // 새로고침 대신 화면 초기화 로직 실행 (전체 화면 유지) state.isPlaying = false; clearInterval(state.timerInterval); cancelAnimationFrame(state.animationId); document.getElementById('langSelectorContainer').style.display = 'block'; document.getElementById('game-board').style.display = 'none'; document.getElementById('header-info').style.display = 'none'; document.getElementById('game-over-controls').style.display = 'none'; document.getElementById('setup-screen').style.display = 'flex'; if(window.playSound) window.playSound('click'); } else { // 2. 전체 화면이 아니라면 그냥 새로고침 location.reload(); } }); [홈 이모지 전체 화면 지체 없이 첫화면 가는 소스 끝] 5. UI 통합 및 화면 전환 로직 - 하단의 [참고 UI 코드]에 있는 '검색창이 포함된 모달 기반의 언어 선택기'의 디자인과 로직을 [게임 소스 코드]에 완벽하게 녹여내세요. 우측 상단에 지구본 버튼이 예쁘게 배치되어야 합니다. - 화면 전환 로직: 언어 선택기(헤더 영역)는 **'게임 시작' 버튼이 있는 첫 메인 화면에서만 노출**되도록 하고, 게임이 본격적으로 시작되는 실행 화면에서는 몰입을 위해 언어 선택 UI가 보이지 않도록 숨김(CSS hidden 등) 처리해 주세요. - 오른쪽 상단의 버튼과 모달은 z-index 9999 수준으로 만들어서 어떤 상황에서도 가장 최상단에 나오도록 해주세요. 6. 폰트 어썸 아이콘 깨짐 방지용 예외 처리 코드를 넣어서 이모지 폰트가 안깨지도록 해줘 7. 한글 폰트는 이미 세팅되어 있는 폰트로 하고 그 외 폰트들은 가장 일반적이고 무난한 보통 모양의 폰트를 이용하세요. 8. 제미나이가 자주하는 실수인데 모달에서 목록을 그려주는 함수 호출을 해주는 거 체크해 'renderLanguages();' 넣는거야 9. 제미나이가 자주 하는 실수인데 언어를 선택했을 때 모달이 닫히지 않는 오류가 있다 이것도 잘 체크해. 10. 모달창 내부 박스 오류가 가끔 있더라 언어 모달창 내부 박스 사이징 강제 보정을 해 #langModal, #langModal * {box-sizing: border-box !important;} 11. 모달의 CSS와 기존 게임의 CSS가 출돌하지 않도록 게임을 수정하도록 해. 또한 모달과 게임에 공통 적용되는 CSS가 없도록 해. 모달과 게임을 각각 독립적인 CSS로 운영되도록 세팅하도록 해 게임의 폰트가 커지거나 작아지는 문제가 있어. 이렇게 되지 않도록 폰트 부분 확실하게 구분 지어. 12. 내가 첨부한 소스 코드의 캔버스 문법상 무시되거나 충돌을 일으키는 오류가 없도록 유의해. inherit(부모 속성 상속)이라는 값을 무조건적으로 넣어서 캔버스가 문법 오류로 인식해서 명령 자체를 무시해버리는 경우가 없도록 해 13. 모바일버젼으로 만들어진 앱의 경우(3분할, 랭킹 시스템 적용 됨) 왼쪽 상단의 킹수학.com 혹은 KingsMath.com 텍스트로 구성된 링크가 있다면 오른쪽 상단의 모달의 버튼 배경, 폰트, 테두리, 이모지 색은 왼쪽 링크 버튼의 디자인과 동일하게 해서 균형감을 맞춰줘. 버튼의 높이와 왼쪽 오른쪽 간격 적당히 어울리게 맞춰줘 킹수학.com kingsmath.com 링크는 https://11ukul.com/kingsmathgames/ 여기로 하는 것으로 위의 명령과 동일함. [참고 UI 코드 시작] <!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>글로벌 언어 선택기 예시</title> <script src="https://cdn.tailwindcss.com"></script> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet"> <style> @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&display=swap'); body { font-family: 'Noto Sans KR', sans-serif; } /* 커스텀 스크롤바 (모달 내부 스크롤용) */ .custom-scrollbar::-webkit-scrollbar { width: 8px; } .custom-scrollbar::-webkit-scrollbar-track { background: rgba(31, 41, 55, 0.5); /* gray-800 */ border-radius: 4px; } .custom-scrollbar::-webkit-scrollbar-thumb { background: #4b5563; /* gray-600 */ border-radius: 4px; } .custom-scrollbar::-webkit-scrollbar-thumb:hover { background: #6b7280; /* gray-500 */ } /* 모달 등장 애니메이션 */ .modal-overlay { opacity: 0; visibility: hidden; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } .modal-overlay.active { opacity: 1; visibility: visible; } .modal-content { transform: scale(0.95) translateY(-10px); opacity: 0; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } .modal-overlay.active .modal-content { transform: scale(1) translateY(0); opacity: 1; } /* 토스트 알림 애니메이션 */ .toast { transform: translateY(100%); opacity: 0; transition: all 0.3s ease; } .toast.show { transform: translateY(0); opacity: 1; } </style> </head> <body class="bg-gray-900 text-gray-100 min-h-screen flex flex-col relative overflow-x-hidden"> <div class="absolute top-4 right-4 sm:top-6 sm:right-6 z-40"> <button id="langTriggerBtn" class="flex items-center gap-2 bg-gray-700/50 hover:bg-gray-700 border border-gray-600 rounded-full px-4 py-2 transition-all duration-200 text-sm font-medium shadow-lg"> <i class="fa-solid fa-globe text-gray-400"></i> <img id="currentFlag" src="https://flagcdn.com/w40/kr.png" alt="KR" class="w-5 h-auto rounded-[2px] shadow-sm"> <span id="currentLangName" class="hidden sm:inline-block">한국어</span> <i class="fa-solid fa-chevron-down text-gray-400 ml-1 text-xs"></i> </button> </div> <div id="langModal" class="modal-overlay fixed inset-0 z-[9999] flex items-start sm:items-center justify-center bg-black/60 backdrop-blur-sm pt-16 sm:pt-0 px-4"> <div class="modal-content bg-gray-900 border border-gray-700 rounded-2xl shadow-2xl w-full max-w-4xl max-h-[85vh] flex flex-col overflow-hidden"> <div class="flex items-center justify-between p-5 border-b border-gray-800 bg-gray-900/95 sticky top-0 z-10"> <div> <h2 class="text-xl font-bold text-white flex items-center gap-2"> <i class="fa-solid fa-language text-yellow-500"></i> <span>Language / 언어</span> </h2> </div> <button id="closeModalBtn" class="w-10 h-10 rounded-full bg-gray-800 hover:bg-gray-700 flex items-center justify-center text-gray-400 hover:text-white transition-colors"> <i class="fa-solid fa-xmark text-lg"></i> </button> </div> <div class="p-5 pb-2 bg-gray-900"> <div class="relative"> <i class="fa-solid fa-magnifying-glass absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400"></i> <input type="text" id="searchInput" placeholder="Search / 검색 ..." class="w-full bg-gray-800 border border-gray-700 text-white text-sm rounded-xl focus:ring-2 focus:ring-yellow-500 focus:border-yellow-500 block pl-11 p-3.5 outline-none transition-all placeholder-gray-500"> </div> </div> <div class="p-5 overflow-y-auto custom-scrollbar flex-1"> <div id="languageGrid" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3"> </div> <div id="noResult" class="hidden flex-col items-center justify-center py-12 text-gray-500"> <i class="fa-regular fa-face-frown-open text-4xl mb-3"></i> <p>No results / 검색 결과 없음</p> </div> </div> </div> </div> <div id="toastNotification" class="toast fixed bottom-6 right-6 bg-gray-800 border border-gray-700 shadow-lg rounded-xl p-4 flex items-center gap-3 z-[9999]"> <div class="w-8 h-8 rounded-full bg-green-500/20 flex items-center justify-center text-green-400"> <i class="fa-solid fa-check"></i> </div> <div> <p id="ui-toast" class="text-sm font-medium text-white">언어가 변경되었습니다.</p> <p id="toastLangName" class="text-xs text-gray-400">Korean</p> </div> </div> <script> // 토스트 알림 전용 다국어 번역 데이터 (언어 변경 성공 시 안내 메시지) const toastTranslations = { 'ko': '언어가 변경되었습니다.', 'en': 'Language changed.', 'zh': '语言已更改。', 'ja': '言語が変更されました。', 'es': 'Idioma cambiado.', 'fr': 'Langue modifiée.', 'ru': 'Язык изменен.', 'vi': 'Ngôn ngữ đã được thay đổi.', 'id': 'Bahasa telah diubah.', 'th': 'เปลี่ยนภาษาเรียบร้อยแล้ว', 'pt-br': 'Idioma alterado.', 'hi': 'भाषा बदल दी गई है।' }; // 16개국 언어 데이터베이스 const languages = [ { code: 'ko', country: 'kr', name: '한국어', enName: 'Korean' }, { code: 'en', country: 'us', name: 'English', enName: 'English' }, { code: 'zh', country: 'cn', name: '中文 (简体)', enName: 'Chinese (Simplified)' }, { code: 'ja', country: 'jp', name: '日本語', enName: 'Japanese' }, { code: 'es', country: 'es', name: 'Español', enName: 'Spanish' }, { code: 'fr', country: 'fr', name: 'Français', enName: 'French' }, { code: 'de', country: 'de', name: 'Deutsch', enName: 'German' }, { code: 'it', country: 'it', name: 'Italiano', enName: 'Italian' }, { code: 'pt-br', country: 'br', name: 'Português', enName: 'Portuguese (Brazil)' }, { code: 'ru', country: 'ru', name: 'Русский', enName: 'Russian' }, { code: 'hi', country: 'in', name: 'हिन्दी', enName: 'Hindi' }, { code: 'bn', country: 'bd', name: 'বাংলা', enName: 'Bengali' }, { code: 'id', country: 'id', name: 'Bahasa Indonesia', enName: 'Indonesian' }, { code: 'vi', country: 'vn', name: 'Tiếng Việt', enName: 'Vietnamese' }, { code: 'th', country: 'th', name: 'ไทย', enName: 'Thai' }, { code: 'tr', country: 'tr', name: 'Türkçe', enName: 'Turkish' } ]; let currentLang = 'ko'; const triggerBtn = document.getElementById('langTriggerBtn'); const modal = document.getElementById('langModal'); const closeBtn = document.getElementById('closeModalBtn'); const searchInput = document.getElementById('searchInput'); const grid = document.getElementById('languageGrid'); const noResult = document.getElementById('noResult'); const currentFlagImg = document.getElementById('currentFlag'); const currentLangSpan = document.getElementById('currentLangName'); const toast = document.getElementById('toastNotification'); const toastLangName = document.getElementById('toastLangName'); let toastTimeout; // 언어 목록 렌더링 함수 function renderLanguages(filterText = '') { grid.innerHTML = ''; const lowerFilter = filterText.toLowerCase(); const filteredLangs = languages.filter(lang => lang.name.toLowerCase().includes(lowerFilter) || lang.enName.toLowerCase().includes(lowerFilter) || lang.code.toLowerCase().includes(lowerFilter) ); if (filteredLangs.length === 0) { grid.classList.add('hidden'); noResult.classList.remove('hidden'); noResult.classList.add('flex'); } else { grid.classList.remove('hidden'); noResult.classList.add('hidden'); noResult.classList.remove('flex'); filteredLangs.forEach(lang => { const isSelected = lang.code === currentLang; const baseClasses = "flex items-center gap-3 p-3 rounded-xl border cursor-pointer transition-all duration-200 group relative overflow-hidden"; const stateClasses = isSelected ? "bg-gray-800 border-yellow-500 ring-1 ring-yellow-500/50" : "bg-gray-800/40 border-gray-700 hover:bg-gray-700 hover:border-gray-500"; const flagUrl = `https://flagcdn.com/w40/${lang.country}.png`; const btn = document.createElement('div'); btn.className = `${baseClasses} ${stateClasses}`; const dirAttr = lang.dir === 'rtl' ? 'dir="rtl"' : ''; btn.innerHTML = ` <div class="shrink-0 w-8 h-6 rounded overflow-hidden shadow-sm border border-gray-700/50"> <img src="${flagUrl}" alt="${lang.country}" class="w-full h-full object-cover group-hover:scale-110 transition-transform duration-300"> </div> <div class="flex-1 min-w-0 flex flex-col items-start" ${dirAttr}> <span class="text-sm font-medium text-white truncate w-full ${isSelected ? 'text-yellow-400' : ''}">${lang.name}</span> <span class="text-xs text-gray-400 truncate w-full">${lang.enName}</span> </div> ${isSelected ? '<i class="fa-solid fa-circle-check text-yellow-500 text-lg ml-2"></i>' : ''} `; btn.addEventListener('click', () => selectLanguage(lang)); grid.appendChild(btn); }); } } // 언어 선택 처리 function selectLanguage(lang) { currentLang = lang.code; // 상단 버튼 업데이트 currentFlagImg.src = `https://flagcdn.com/w40/${lang.country}.png`; currentLangSpan.textContent = lang.name; // 모달 닫기 closeModal(); // 변경 성공 알림 (Toast) - 선택한 언어에 맞는 완료 메시지로 표시 showToast(lang.name, lang.code); // 다시 열었을 때를 위해 렌더링 갱신 renderLanguages(searchInput.value); } function openModal() { modal.classList.add('active'); document.body.style.overflow = 'hidden'; setTimeout(() => searchInput.focus(), 100); } function closeModal() { modal.classList.remove('active'); document.body.style.overflow = ''; } // 토스트 알림 제어 function showToast(langName, langCode) { // 토스트 메시지만 동적으로 변경 (사용자가 방금 선택한 언어이므로 이해할 수 있음) const toastMsg = toastTranslations[langCode] || toastTranslations['en']; document.getElementById('ui-toast').textContent = toastMsg; toastLangName.textContent = langName; toast.classList.add('show'); clearTimeout(toastTimeout); toastTimeout = setTimeout(() => { toast.classList.remove('show'); }, 3000); } // 이벤트 리스너 등록 triggerBtn.addEventListener('click', openModal); closeBtn.addEventListener('click', closeModal); modal.addEventListener('click', (e) => { if (e.target === modal) closeModal(); }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && modal.classList.contains('active')) { closeModal(); } }); searchInput.addEventListener('input', (e) => { renderLanguages(e.target.value); }); // 초기 렌더링 실행 renderLanguages(); </script> </body> </html> [참고 UI 코드 끝] [게임 소스 코드 시작] [게임 소스 코드 끝]
언어 등록
킹수학 게임 고르기
11ukul.com
호스팅, FTP 사용법(패들렛)
padlet.com
글자 효과
wsgga.dothome.co.kr
edrfsdf
sdfasdf
키패드 디자인
wsgga.dothome.co.kr
광주실천교사 바이브코딩
wsgga.dothome.co.kr