Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Translator</title> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --primary-color: #F26522; /* Orange */ | |
| --primary-hover: #E05B1F; /* Slightly darker orange for hover */ | |
| --bg-color: #FFFFFF; /* White */ | |
| --text-color: #78797D; /* Grey */ | |
| --border-color: #E5E7EB; /* Light grey for borders */ | |
| --success-color: #F26522; /* Use primary color for success */ | |
| --error-color: #DC2626; /* Red for errors */ | |
| --container-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); | |
| } | |
| [data-theme='dark'] { | |
| --bg-color: #1A1A1A; | |
| --text-color: #E5E5E5; | |
| --border-color: #333333; | |
| --container-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2), 0 2px 4px -1px rgba(0, 0, 0, 0.1); | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| transition: background-color 0.3s ease, color 0.3s ease; | |
| } | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| background-color: var(--bg-color); | |
| color: var(--text-color); | |
| line-height: 1.5; | |
| min-height: 100vh; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| padding: 20px; | |
| } | |
| .container { | |
| background-color: var(--bg-color); | |
| padding: 2.5rem; | |
| border-radius: 1.5rem; | |
| box-shadow: var(--container-shadow); | |
| width: 100%; | |
| max-width: 800px; | |
| transition: all 0.3s ease; | |
| } | |
| .container:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 6px 8px -1px rgba(0, 0, 0, 0.1), 0 4px 6px -1px rgba(0, 0, 0, 0.06); | |
| } | |
| h1 { | |
| font-size: 2.25rem; | |
| font-weight: 600; | |
| color: var(--text-color); | |
| margin-bottom: 2rem; | |
| text-align: center; | |
| letter-spacing: -0.5px; | |
| } | |
| .form-group { | |
| margin-bottom: 2rem; | |
| } | |
| .input-container, .output-container { | |
| position: relative; | |
| margin-bottom: 1.5rem; | |
| } | |
| .label { | |
| display: block; | |
| font-size: 0.95rem; | |
| font-weight: 500; | |
| color: var(--text-color); | |
| margin-bottom: 0.75rem; | |
| letter-spacing: 0.2px; | |
| } | |
| textarea { | |
| width: 100%; | |
| padding: 1rem; | |
| border: 2px solid var(--border-color); | |
| border-radius: 0.75rem; | |
| font-size: 1rem; | |
| font-family: inherit; | |
| min-height: 120px; | |
| resize: vertical; | |
| transition: all 0.2s ease; | |
| background-color: var(--bg-color); | |
| color: var(--text-color); | |
| } | |
| textarea:focus { | |
| outline: none; | |
| border-color: var(--primary-color); | |
| box-shadow: 0 0 0 3px rgba(242, 101, 34, 0.1); | |
| } | |
| .theme-toggle { | |
| position: fixed; | |
| top: 20px; | |
| right: 20px; | |
| padding: 12px 24px; | |
| background-color: var(--primary-color); | |
| color: white; | |
| border: none; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| font-size: 0.95rem; | |
| font-weight: 500; | |
| transition: all 0.3s ease; | |
| z-index: 1000; | |
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | |
| } | |
| .theme-toggle:hover { | |
| background-color: var(--primary-hover); | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| } | |
| .theme-toggle:active { | |
| transform: translateY(0); | |
| } | |
| .button-container { | |
| display: flex; | |
| gap: 1rem; | |
| margin-top: 1rem; | |
| } | |
| .translate-btn, .speak-btn { | |
| padding: 0.75rem 1.5rem; | |
| background-color: var(--primary-color); | |
| color: white; | |
| border: none; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| font-size: 0.95rem; | |
| font-weight: 500; | |
| transition: all 0.3s ease; | |
| flex: 1; | |
| } | |
| .translate-btn:hover, .speak-btn:hover { | |
| background-color: var(--primary-hover); | |
| transform: translateY(-2px); | |
| } | |
| .translate-btn:active, .speak-btn:active { | |
| transform: translateY(0); | |
| } | |
| .char-count { | |
| position: absolute; | |
| bottom: -1.5rem; | |
| right: 0; | |
| font-size: 0.85rem; | |
| color: var(--text-color); | |
| opacity: 0.8; | |
| } | |
| .chat-history { | |
| margin-top: 2rem; | |
| padding: 1.5rem; | |
| border-radius: 1rem; | |
| background-color: var(--bg-color); | |
| border: 2px solid var(--border-color); | |
| } | |
| .chat-history h2 { | |
| font-size: 1.25rem; | |
| margin-bottom: 1rem; | |
| color: var(--text-color); | |
| } | |
| .chat-item { | |
| padding: 1rem; | |
| margin-bottom: 1rem; | |
| border-radius: 0.75rem; | |
| background-color: rgba(242, 101, 34, 0.1); | |
| border: 1px solid var(--border-color); | |
| } | |
| .chat-item:last-child { | |
| margin-bottom: 0; | |
| } | |
| .translation-bubble { | |
| margin-top: 1.5rem; | |
| padding: 1rem; | |
| border-radius: 1rem; | |
| background-color: var(--bg-color); | |
| border: 2px solid var(--border-color); | |
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | |
| max-width: 600px; | |
| margin-left: auto; | |
| margin-right: auto; | |
| } | |
| .translation-bubble h2 { | |
| font-size: 1.25rem; | |
| margin-bottom: 0.5rem; | |
| color: var(--text-color); | |
| } | |
| .translation-bubble p { | |
| font-size: 1rem; | |
| color: var(--text-color); | |
| line-height: 1.5; | |
| } | |
| @media (max-width: 640px) { | |
| .container { | |
| padding: 1.5rem; | |
| } | |
| h1 { | |
| font-size: 1.75rem; | |
| } | |
| .button-container { | |
| flex-direction: column; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body data-theme="light"> | |
| <button class="theme-toggle">Switch to Dark Mode</button> | |
| <div class="container"> | |
| <h1>Translator</h1> | |
| <div class="form-group"> | |
| <div class="input-container"> | |
| <label class="label" for="inputText">Original</label> | |
| <textarea | |
| id="inputText" | |
| placeholder="Enter text to translate..." | |
| maxlength="500" | |
| oninput="updateCharCount(this)" | |
| ></textarea> | |
| <div class="char-count">0/500</div> | |
| </div> | |
| <div class="direction-container"> | |
| <label class="label" for="direction">Translation Direction</label> | |
| <select id="direction"> | |
| <option value="en-fi">English to Finnish</option> | |
| <option value="fi-en">Finnish to English</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="button-container"> | |
| <button class="translate-btn" onclick="translateText()"> | |
| Translate | |
| </button> | |
| <button class="speak-btn" onclick="speakText()"> | |
| Speak Translation | |
| </button> | |
| </div> | |
| <div class="error" id="errorMsg"></div> | |
| <div class="translation-bubble"> | |
| <h2>Translation</h2> | |
| <p id="result"></p> | |
| </div> | |
| <div class="chat-history" id="chatHistory"> | |
| <h2>Chat History</h2> | |
| <ul class="chat-list" id="chatList"> | |
| </ul> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', () => { | |
| const themeToggleButton = document.querySelector('.theme-toggle'); | |
| if (themeToggleButton) { | |
| console.log('Theme toggle button found'); | |
| themeToggleButton.addEventListener('click', () => { | |
| console.log('Toggle theme button clicked'); | |
| const body = document.body; | |
| const currentTheme = body.getAttribute('data-theme'); | |
| console.log('Current theme:', currentTheme); | |
| const newTheme = currentTheme === 'light' ? 'dark' : 'light'; | |
| console.log('Switching to theme:', newTheme); | |
| body.setAttribute('data-theme', newTheme); | |
| themeToggleButton.textContent = `Switch to ${newTheme === 'light' ? 'Dark' : 'Light'} Mode`; | |
| }); | |
| } else { | |
| console.error('Theme toggle button not found'); | |
| } | |
| }); | |
| function updateCharCount(textarea) { | |
| const maxLength = textarea.getAttribute('maxlength'); | |
| const currentLength = textarea.value.length; | |
| const charCountElement = textarea.parentElement.querySelector('.char-count'); | |
| charCountElement.textContent = `${currentLength}/${maxLength}`; | |
| } | |
| async function translateText() { | |
| const inputText = document.getElementById('inputText').value; | |
| const direction = document.getElementById('direction').value; | |
| const translateBtn = document.querySelector('.translate-btn'); | |
| const loading = document.querySelector('.loading'); | |
| const result = document.getElementById('result'); | |
| const resultContainer = document.querySelector('.translation-bubble'); | |
| const errorMsg = document.getElementById('errorMsg'); | |
| if (!inputText.trim()) { | |
| errorMsg.textContent = 'Please enter some text to translate'; | |
| errorMsg.style.display = 'block'; | |
| return; | |
| } | |
| // Reset states | |
| errorMsg.style.display = 'none'; | |
| translateBtn.disabled = true; | |
| result.textContent = ''; | |
| resultContainer.classList.remove('show'); | |
| try { | |
| const response = await fetch('/translate', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ text: inputText, direction: direction }) | |
| }); | |
| const data = await response.json(); | |
| if (response.ok) { | |
| result.textContent = data.translation; | |
| resultContainer.classList.add('show'); | |
| // Update chat history | |
| updateChatHistory(inputText, data.translation); | |
| } else { | |
| errorMsg.textContent = data.error || 'Failed to translate'; | |
| errorMsg.style.display = 'block'; | |
| } | |
| } catch (error) { | |
| console.error('Translation error:', error); | |
| errorMsg.textContent = 'Failed to connect to the server'; | |
| errorMsg.style.display = 'block'; | |
| } finally { | |
| translateBtn.disabled = false; | |
| } | |
| } | |
| function updateChatHistory(input, output) { | |
| const chatList = document.getElementById('chatList'); | |
| const messageLi = document.createElement('li'); | |
| messageLi.classList.add('chat-item'); | |
| const originalDiv = document.createElement('div'); | |
| originalDiv.classList.add('chat-original'); | |
| originalDiv.textContent = `Original: ${input}`; | |
| const translationDiv = document.createElement('div'); | |
| translationDiv.classList.add('chat-translation'); | |
| translationDiv.textContent = `Translation: ${output}`; | |
| messageLi.appendChild(originalDiv); | |
| messageLi.appendChild(translationDiv); | |
| chatList.appendChild(messageLi); | |
| chatList.scrollTop = chatList.scrollHeight; | |
| } | |
| function speakText() { | |
| const text = document.getElementById('result').textContent; | |
| if (!text) return; | |
| const utterance = new SpeechSynthesisUtterance(text); | |
| window.speechSynthesis.speak(utterance); | |
| } | |
| </script> | |
| </body> | |
| </html> | |