/** * @typedef {Object} WordleData * @property {number} id * @property {string} solution * @property {string} print_date * @property {number} [days_since_launch] * @property {string} [editor] */ const moonIconPath = 'M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z'; const sunIconPath = 'M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z'; const themeToggle = document.getElementById('themeToggle'); const themeIcon = document.getElementById('themeIcon'); const themeText = document.getElementById('themeText'); const html = document.documentElement; /** * @param {string} theme * @returns {void} */ function setTheme(theme) { html.setAttribute('data-theme', theme); localStorage.setItem('theme', theme); if (theme === 'dark') { themeIcon.innerHTML = ``; themeText.textContent = 'Light Mode'; } else { themeIcon.innerHTML = ``; themeText.textContent = 'Dark Mode'; } } /** * @returns {string} */ function getInitialTheme() { const savedTheme = localStorage.getItem('theme'); if (savedTheme) return savedTheme; if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { return 'dark'; } return 'light'; } setTheme(getInitialTheme()); if (window.matchMedia) { window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { if (!localStorage.getItem('theme')) { setTheme(e.matches ? 'dark' : 'light'); } }); } themeToggle.addEventListener('click', () => { const currentTheme = html.getAttribute('data-theme'); setTheme(currentTheme === 'dark' ? 'light' : 'dark'); }); /** * @param {Date} date * @returns {string} */ function formatDateForAPI(date) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } /** * @param {Date} date * @returns {string} */ function formatDateForDisplay(date) { return date.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); } /** * @param {string} dateStr * @returns {Date|null} */ function parseDateParam(dateStr) { const parts = dateStr.split('-'); if (parts.length !== 3) return null; const [day, month, year] = parts.map(Number); if (isNaN(day) || isNaN(month) || isNaN(year)) return null; return new Date(year, month - 1, day); } /** * @returns {Date} */ function getMaxAllowedDate() { const today = new Date(); today.setHours(0, 0, 0, 0); const maxDate = new Date(today); maxDate.setDate(maxDate.getDate() + 21); return maxDate; } /** * @returns {Date} */ function getWordleLaunchDate() { const launchDate = new Date(2021, 5, 19); launchDate.setHours(0, 0, 0, 0); return launchDate; } const urlParams = new URLSearchParams(window.location.search); const dateParam = urlParams.get('date'); let selectedDate = dateParam ? parseDateParam(dateParam) : new Date(); if (!selectedDate || isNaN(selectedDate.getTime())) { selectedDate = new Date(); } selectedDate.setHours(0, 0, 0, 0); let currentCalendarMonth = new Date(selectedDate); currentCalendarMonth.setDate(1); /** @type {WordleData|null} */ let currentAnswer = null; const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; const monthSelect = document.getElementById('monthSelect'); const monthDisplay = document.getElementById('monthDisplay'); const monthDropdown = document.getElementById('monthDropdown'); const yearSelect = document.getElementById('yearSelect'); const yearDisplay = document.getElementById('yearDisplay'); const yearDropdown = document.getElementById('yearDropdown'); months.forEach((month, idx) => { const option = document.createElement('div'); option.className = 'select-option'; option.textContent = month; option.dataset.value = idx.toString(); option.addEventListener('click', () => { currentCalendarMonth.setMonth(idx); monthDisplay.textContent = month; closeAllDropdowns(); renderCalendar(); }); monthDropdown.appendChild(option); }); const launchYear = getWordleLaunchDate().getFullYear(); const maxAllowedYear = getMaxAllowedDate().getFullYear(); for (let year = launchYear; year <= maxAllowedYear; year++) { const option = document.createElement('div'); option.className = 'select-option'; option.textContent = year.toString(); option.dataset.value = year.toString(); option.addEventListener('click', () => { currentCalendarMonth.setFullYear(year); yearDisplay.textContent = year.toString(); closeAllDropdowns(); renderCalendar(); }); yearDropdown.appendChild(option); } monthSelect.querySelector('.select-display').addEventListener('click', (e) => { e.stopPropagation(); const isOpen = monthDropdown.classList.contains('open'); closeAllDropdowns(); if (!isOpen) { monthDropdown.classList.add('open'); monthSelect.querySelector('.select-display').classList.add('open'); } }); yearSelect.querySelector('.select-display').addEventListener('click', (e) => { e.stopPropagation(); const isOpen = yearDropdown.classList.contains('open'); closeAllDropdowns(); if (!isOpen) { yearDropdown.classList.add('open'); yearSelect.querySelector('.select-display').classList.add('open'); } }); /** * @returns {void} */ function closeAllDropdowns() { monthDropdown.classList.remove('open'); yearDropdown.classList.remove('open'); monthSelect.querySelector('.select-display').classList.remove('open'); yearSelect.querySelector('.select-display').classList.remove('open'); } document.addEventListener('click', closeAllDropdowns); /** * @param {Date} date * @returns {Promise} */ async function fetchWordleAnswer(date) { const apiDate = formatDateForAPI(date); const proxyBase = "https://wordle-proxy.bobbyskhs.workers.dev"; try { const response = await fetch(`${proxyBase}/?date=${apiDate}`, { headers: { "Accept": "application/json", }, cache: "no-cache", }); if (!response.ok) { throw new Error(`Answer not available for ${apiDate}`); } const data = await response.json(); if (!data || !data.solution) { throw new Error("Invalid data received from proxy"); } return data; } catch (error) { console.error("Wordle fetch error:", error); throw new Error("Failed to fetch Wordle data"); } } /** * @param {WordleData} data * @param {Date} date * @returns {void} */ function displayAnswer(data, date) { const dateLabel = document.getElementById('dateLabel'); const answerDisplay = document.getElementById('answerDisplay'); const metaInfo = document.getElementById('metaInfo'); const errorContainer = document.getElementById('errorContainer'); const answerPreview = document.getElementById('answerPreview'); errorContainer.innerHTML = ''; dateLabel.textContent = formatDateForDisplay(date); currentAnswer = data; const letters = data.solution.toUpperCase().split(''); answerDisplay.innerHTML = letters.map(letter => `
${letter}
` ).join(''); answerPreview.textContent = data.solution.toUpperCase(); const daysSinceLaunch = data.days_since_launch !== undefined ? data.days_since_launch : 0; const editor = data.editor || '---'; metaInfo.innerHTML = `
Puzzle #
${data.id}
Days Since Launch
${daysSinceLaunch}
Editor
${editor}
`; } /** * @param {string} message * @returns {void} */ function showError(message) { const errorContainer = document.getElementById('errorContainer'); errorContainer.innerHTML = `
⚠️ ${message}
`; } /** * @param {string} message * @returns {void} */ function showToast(message) { const toast = document.createElement('div'); toast.className = 'success-toast'; toast.innerHTML = ` ${message}`; document.body.appendChild(toast); setTimeout(() => toast.remove(), 3000); } /** * @param {WordleData} data * @param {Date} date * @returns {Promise} */ async function updateMetaTags(data, date) { const isToday = date.toDateString() === new Date().toDateString(); const dateStr = formatDateForDisplay(date); const answer = data.solution.toUpperCase(); let title, description; if (isToday) { title = `Today's Wordle Answer is ${answer} - Unwordled`; description = `Today's Wordle #${data.id} answer is ${answer}! Find daily Wordle solutions on Unwordled.`; } else { title = `Wordle Answer for ${dateStr} is ${answer} - Unwordled`; description = `Wordle #${data.id} answer for ${dateStr} is ${answer}. Discover past and future Wordle answers on Unwordled.`; } document.title = title; document.querySelector('meta[name="description"]').setAttribute('content', description); } /** * @param {Date} date * @returns {Promise} */ async function loadAnswer(date) { try { const data = await fetchWordleAnswer(date); displayAnswer(data, date); await updateMetaTags(data, date); } catch (error) { showError(error.message); document.getElementById('answerDisplay').innerHTML = '
Unable to load answer
'; } } /** * @returns {void} */ function updateURL() { const day = String(selectedDate.getDate()).padStart(2, '0'); const month = String(selectedDate.getMonth() + 1).padStart(2, '0'); const year = selectedDate.getFullYear(); const dateStr = `${day}-${month}-${year}`; const newUrl = `${window.location.pathname}?date=${dateStr}`; window.history.pushState({}, '', newUrl); } /** * @param {string} text * @returns {void} */ function copyToClipboard(text) { if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(text).then(() => showToast('Copied!')).catch(() => fallbackCopy(text)); } else { fallbackCopy(text); } } /** * @param {string} text * @returns {void} */ function fallbackCopy(text) { const temp = document.createElement('textarea'); temp.value = text; document.body.appendChild(temp); temp.select(); document.execCommand('copy'); document.body.removeChild(temp); showToast('Copied!'); } document.getElementById('shareBtn').addEventListener('click', async () => { if (!currentAnswer) return; const day = String(selectedDate.getDate()).padStart(2, '0'); const month = String(selectedDate.getMonth() + 1).padStart(2, '0'); const year = selectedDate.getFullYear(); const dateStr = `${day}-${month}-${year}`; const shareUrl = `${window.location.origin}${window.location.pathname}?date=${dateStr}`; const shareText = `Wordle #${currentAnswer.id} answer: ${currentAnswer.solution.toUpperCase()} - Found on Unwordled`; if (navigator.share) { try { await navigator.share({ title: 'Unwordled', text: shareText, url: shareUrl }); } catch (err) { if (err.name !== 'AbortError') copyToClipboard(shareUrl); } } else { copyToClipboard(shareUrl); } }); document.getElementById('copyAnswerBtn').addEventListener('click', () => { if (currentAnswer) copyToClipboard(currentAnswer.solution.toUpperCase()); }); /** * @returns {void} */ function renderCalendar() { const calendarDays = document.getElementById('calendarDays'); const year = currentCalendarMonth.getFullYear(); const month = currentCalendarMonth.getMonth(); monthDisplay.textContent = months[month]; yearDisplay.textContent = year.toString(); const firstDay = new Date(year, month, 1).getDay(); const daysInMonth = new Date(year, month + 1, 0).getDate(); const today = new Date(); today.setHours(0, 0, 0, 0); const maxDate = getMaxAllowedDate(); const launchDate = getWordleLaunchDate(); calendarDays.innerHTML = ''; for (let i = 0; i < firstDay; i++) { calendarDays.innerHTML += '
'; } for (let day = 1; day <= daysInMonth; day++) { const cellDate = new Date(year, month, day); cellDate.setHours(0, 0, 0, 0); const isSelected = cellDate.getTime() === selectedDate.getTime(); const isToday = cellDate.getTime() === today.getTime(); const isDisabled = cellDate > maxDate || cellDate < launchDate; const classes = ['day-cell']; if (isSelected) classes.push('selected'); if (isToday) classes.push('today'); if (isDisabled) classes.push('disabled'); const dayCell = document.createElement('div'); dayCell.className = classes.join(' '); dayCell.textContent = day.toString(); if (!isDisabled) { dayCell.addEventListener('click', () => { selectedDate = new Date(year, month, day); selectedDate.setHours(0, 0, 0, 0); loadAnswer(selectedDate); renderCalendar(); updateURL(); }); } calendarDays.appendChild(dayCell); } } document.getElementById('prevMonth').addEventListener('click', () => { currentCalendarMonth.setMonth(currentCalendarMonth.getMonth() - 1); renderCalendar(); }); document.getElementById('nextMonth').addEventListener('click', () => { currentCalendarMonth.setMonth(currentCalendarMonth.getMonth() + 1); renderCalendar(); }); document.getElementById('todayBtn').addEventListener('click', () => { const today = new Date(); today.setHours(0, 0, 0, 0); selectedDate = today; currentCalendarMonth = new Date(today); currentCalendarMonth.setDate(1); loadAnswer(selectedDate); renderCalendar(); updateURL(); }); loadAnswer(selectedDate); renderCalendar();