Spaces:
Sleeping
Sleeping
| <html lang="id"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Character AI Chat</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
| background: url('background.png'), linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| background-size: cover; | |
| background-attachment: fixed; | |
| height: 100vh; | |
| overflow: hidden; | |
| } | |
| .chat-container { | |
| height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| max-width: 100%; | |
| margin: 0 auto; | |
| background: rgba(255, 255, 255, 0.95); | |
| backdrop-filter: blur(10px); | |
| } | |
| /* Header */ | |
| .chat-header { | |
| background: #075e54; | |
| color: white; | |
| padding: 10px 16px; | |
| display: flex; | |
| align-items: center; | |
| position: sticky; | |
| top: 0; | |
| z-index: 100; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.1); | |
| } | |
| .avatar { | |
| width: 40px; | |
| height: 40px; | |
| border-radius: 50%; | |
| margin-right: 12px; | |
| background: url('avatar.png'), linear-gradient(45deg, #25d366, #128c7e); | |
| background-size: cover; | |
| background-position: center; | |
| } | |
| .header-info { | |
| flex: 1; | |
| } | |
| .char-name { | |
| font-size: 16px; | |
| font-weight: 600; | |
| margin-bottom: 2px; | |
| } | |
| .status { | |
| font-size: 13px; | |
| opacity: 0.8; | |
| color: #dcf8c6; | |
| } | |
| .header-actions { | |
| display: flex; | |
| align-items: center; | |
| gap: 20px; | |
| } | |
| .three-dots { | |
| cursor: pointer; | |
| padding: 8px; | |
| border-radius: 50%; | |
| transition: background 0.2s; | |
| } | |
| .three-dots:hover { | |
| background: rgba(255,255,255,0.1); | |
| } | |
| /* Settings Popup */ | |
| .settings-popup { | |
| display: none; | |
| position: absolute; | |
| top: 60px; | |
| right: 16px; | |
| background: white; | |
| border-radius: 12px; | |
| box-shadow: 0 8px 32px rgba(0,0,0,0.2); | |
| min-width: 280px; | |
| z-index: 1000; | |
| padding: 8px 0; | |
| } | |
| .settings-popup.show { | |
| display: block; | |
| animation: popupIn 0.2s ease-out; | |
| } | |
| @keyframes popupIn { | |
| from { opacity: 0; transform: translateY(-10px) scale(0.95); } | |
| to { opacity: 1; transform: translateY(0) scale(1); } | |
| } | |
| .settings-section { | |
| padding: 12px 20px; | |
| border-bottom: 1px solid #eee; | |
| } | |
| .settings-section:last-child { | |
| border-bottom: none; | |
| } | |
| .settings-label { | |
| font-size: 14px; | |
| font-weight: 600; | |
| color: #333; | |
| margin-bottom: 8px; | |
| } | |
| .model-select, .input-field { | |
| width: 100%; | |
| padding: 8px 12px; | |
| border: 1px solid #ddd; | |
| border-radius: 8px; | |
| font-size: 14px; | |
| background: #f8f9fa; | |
| } | |
| .input-field { | |
| margin-top: 4px; | |
| } | |
| /* Chat Body */ | |
| .chat-body { | |
| flex: 1; | |
| overflow-y: auto; | |
| padding: 20px 16px; | |
| background: url('background.png'), | |
| radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.3), transparent), | |
| radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.3), transparent), | |
| linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| background-size: cover, 400px 400px, 400px 400px, cover; | |
| background-attachment: fixed; | |
| } | |
| /* Message Bubbles */ | |
| .message { | |
| display: flex; | |
| margin-bottom: 12px; | |
| animation: messageSlide 0.3s ease-out; | |
| } | |
| @keyframes messageSlide { | |
| from { opacity: 0; transform: translateY(20px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .message.user { | |
| justify-content: flex-end; | |
| } | |
| .message.char { | |
| justify-content: flex-start; | |
| } | |
| .message-avatar { | |
| width: 32px; | |
| height: 32px; | |
| border-radius: 50%; | |
| margin: 0 8px; | |
| background: url('avatar.png'), linear-gradient(45deg, #25d366, #128c7e); | |
| background-size: cover; | |
| background-position: center; | |
| align-self: flex-end; | |
| } | |
| .message.user .message-avatar { | |
| background: linear-gradient(45deg, #0084ff, #00a0ff); | |
| } | |
| .bubble { | |
| max-width: 70%; | |
| padding: 12px 16px; | |
| border-radius: 18px; | |
| position: relative; | |
| word-wrap: break-word; | |
| backdrop-filter: blur(10px); | |
| } | |
| .message.user .bubble { | |
| background: linear-gradient(135deg, #0084ff, #00a0ff); | |
| color: white; | |
| border-bottom-right-radius: 6px; | |
| } | |
| .message.char .bubble { | |
| background: rgba(255, 255, 255, 0.95); | |
| color: #333; | |
| border-bottom-left-radius: 6px; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.1); | |
| } | |
| .message-text { | |
| font-size: 16px; | |
| line-height: 1.4; | |
| margin-bottom: 6px; | |
| } | |
| .message-meta { | |
| display: flex; | |
| align-items: center; | |
| justify-content: flex-end; | |
| gap: 4px; | |
| font-size: 12px; | |
| opacity: 0.7; | |
| } | |
| .timestamp { | |
| color: inherit; | |
| } | |
| .checkmarks { | |
| color: #4fc3f7; | |
| font-weight: bold; | |
| } | |
| .message.char .checkmarks { | |
| display: none; | |
| } | |
| /* Typing Indicator */ | |
| .typing-indicator { | |
| display: none; | |
| align-items: center; | |
| margin-bottom: 12px; | |
| } | |
| .typing-indicator.show { | |
| display: flex; | |
| } | |
| .typing-dots { | |
| background: rgba(255, 255, 255, 0.95); | |
| border-radius: 18px; | |
| padding: 12px 16px; | |
| margin-left: 48px; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.1); | |
| } | |
| .typing-dots span { | |
| display: inline-block; | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 50%; | |
| background: #999; | |
| margin-right: 4px; | |
| animation: typing 2s infinite; | |
| } | |
| .typing-dots span:nth-child(2) { animation-delay: 0.2s; } | |
| .typing-dots span:nth-child(3) { animation-delay: 0.4s; } | |
| @keyframes typing { | |
| 0%, 60%, 100% { transform: translateY(0); opacity: 0.4; } | |
| 30% { transform: translateY(-10px); opacity: 1; } | |
| } | |
| /* Footer */ | |
| .chat-footer { | |
| background: rgba(255, 255, 255, 0.95); | |
| backdrop-filter: blur(10px); | |
| padding: 12px 16px; | |
| border-top: 1px solid rgba(0,0,0,0.1); | |
| position: sticky; | |
| bottom: 0; | |
| } | |
| .input-container { | |
| display: flex; | |
| align-items: flex-end; | |
| gap: 8px; | |
| } | |
| .emoji-btn { | |
| background: none; | |
| border: none; | |
| font-size: 24px; | |
| cursor: pointer; | |
| padding: 8px; | |
| border-radius: 50%; | |
| transition: background 0.2s; | |
| } | |
| .emoji-btn:hover { | |
| background: rgba(0,0,0,0.05); | |
| } | |
| .message-input { | |
| flex: 1; | |
| min-height: 40px; | |
| max-height: 120px; | |
| padding: 10px 16px; | |
| border: 1px solid #ddd; | |
| border-radius: 20px; | |
| font-size: 16px; | |
| font-family: inherit; | |
| resize: none; | |
| outline: none; | |
| background: white; | |
| transition: border-color 0.2s; | |
| } | |
| .message-input:focus { | |
| border-color: #075e54; | |
| } | |
| .send-btn { | |
| background: #075e54; | |
| color: white; | |
| border: none; | |
| width: 40px; | |
| height: 40px; | |
| border-radius: 50%; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: all 0.2s; | |
| font-size: 18px; | |
| } | |
| .send-btn:hover { | |
| background: #128c7e; | |
| transform: scale(1.05); | |
| } | |
| .send-btn:disabled { | |
| background: #ccc; | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| /* Emoji Picker */ | |
| .emoji-picker { | |
| display: none; | |
| position: absolute; | |
| bottom: 70px; | |
| left: 16px; | |
| background: white; | |
| border-radius: 12px; | |
| box-shadow: 0 8px 32px rgba(0,0,0,0.2); | |
| padding: 16px; | |
| max-width: 300px; | |
| z-index: 1000; | |
| } | |
| .emoji-picker.show { | |
| display: block; | |
| animation: popupIn 0.2s ease-out; | |
| } | |
| .emoji-grid { | |
| display: grid; | |
| grid-template-columns: repeat(8, 1fr); | |
| gap: 8px; | |
| max-height: 200px; | |
| overflow-y: auto; | |
| } | |
| .emoji-item { | |
| background: none; | |
| border: none; | |
| font-size: 24px; | |
| cursor: pointer; | |
| padding: 8px; | |
| border-radius: 8px; | |
| transition: background 0.2s; | |
| } | |
| .emoji-item:hover { | |
| background: #f0f0f0; | |
| } | |
| /* Responsive */ | |
| @media (max-width: 768px) { | |
| .chat-container { | |
| height: 100vh; | |
| } | |
| .bubble { | |
| max-width: 85%; | |
| } | |
| .settings-popup { | |
| right: 8px; | |
| min-width: 260px; | |
| } | |
| } | |
| /* Scrollbar Styling */ | |
| .chat-body::-webkit-scrollbar { | |
| width: 6px; | |
| } | |
| .chat-body::-webkit-scrollbar-track { | |
| background: transparent; | |
| } | |
| .chat-body::-webkit-scrollbar-thumb { | |
| background: rgba(0,0,0,0.2); | |
| border-radius: 3px; | |
| } | |
| .chat-body::-webkit-scrollbar-thumb:hover { | |
| background: rgba(0,0,0,0.3); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="chat-container"> | |
| <!-- Header --> | |
| <div class="chat-header"> | |
| <div class="avatar"></div> | |
| <div class="header-info"> | |
| <div class="char-name" id="charName">Sayang</div> | |
| <div class="status" id="status">online</div> | |
| </div> | |
| <div class="header-actions"> | |
| <div class="three-dots" onclick="toggleSettings()"> | |
| <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"> | |
| <circle cx="12" cy="5" r="2"/> | |
| <circle cx="12" cy="12" r="2"/> | |
| <circle cx="12" cy="19" r="2"/> | |
| </svg> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Settings Popup --> | |
| <div class="settings-popup" id="settingsPopup"> | |
| <div class="settings-section"> | |
| <div class="settings-label">Model AI</div> | |
| <select class="model-select" id="modelSelect"> | |
| <option value="distil-gpt-2">DistilGPT-2 ⚡</option> | |
| <option value="gpt-2-tinny">GPT-2 Tinny ⚡</option> | |
| <option value="bert-tinny">BERT Tinny 🎭</option> | |
| <option value="distilbert-base-uncased">DistilBERT 🎭</option> | |
| <option value="albert-base-v2">ALBERT Base 🎭</option> | |
| <option value="electra-small">ELECTRA Small 🎭</option> | |
| <option value="t5-small">T5 Small 🔄</option> | |
| <option value="gpt-2">GPT-2 Standard</option> | |
| <option value="tinny-llama">Tinny Llama</option> | |
| <option value="pythia">Pythia</option> | |
| <option value="gpt-neo">GPT-Neo</option> | |
| </select> | |
| </div> | |
| <div class="settings-section"> | |
| <div class="settings-label">Karakter</div> | |
| <input type="text" class="input-field" id="charNameInput" placeholder="Nama karakter" value="Sayang"> | |
| <input type="text" class="input-field" id="userNameInput" placeholder="Nama kamu" value="Kamu"> | |
| </div> | |
| <div class="settings-section"> | |
| <div class="settings-label">Situasi & Lokasi</div> | |
| <input type="text" class="input-field" id="situationInput" placeholder="Situasi" value="Santai"> | |
| <input type="text" class="input-field" id="locationInput" placeholder="Lokasi" value="Ruang tamu"> | |
| </div> | |
| <div class="settings-section"> | |
| <div class="settings-label">Panjang Pesan</div> | |
| <input type="range" class="input-field" id="maxLengthRange" min="50" max="300" value="150"> | |
| <div style="font-size: 12px; color: #666; margin-top: 4px;"> | |
| <span id="maxLengthValue">150</span> karakter maksimal | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Chat Body --> | |
| <div class="chat-body" id="chatBody"> | |
| <!-- Welcome Message --> | |
| <div class="message char"> | |
| <div class="message-avatar"></div> | |
| <div class="bubble"> | |
| <div class="message-text">Hai! Aku siap ngobrol sama kamu nih. Mau bahas apa hari ini? 😊</div> | |
| <div class="message-meta"> | |
| <span class="timestamp" id="welcome-time"></span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Typing Indicator --> | |
| <div class="typing-indicator" id="typingIndicator"> | |
| <div class="message-avatar"></div> | |
| <div class="typing-dots"> | |
| <span></span> | |
| <span></span> | |
| <span></span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Footer --> | |
| <div class="chat-footer"> | |
| <!-- Emoji Picker --> | |
| <div class="emoji-picker" id="emojiPicker"> | |
| <div class="emoji-grid" id="emojiGrid"></div> | |
| </div> | |
| <div class="input-container"> | |
| <button class="emoji-btn" onclick="toggleEmojiPicker()">😊</button> | |
| <textarea class="message-input" id="messageInput" placeholder="Ketik pesan..." rows="1"></textarea> | |
| <button class="send-btn" id="sendBtn" onclick="sendMessage()"> | |
| <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"> | |
| <path d="M2,21L23,12L2,3V10L17,12L2,14V21Z"/> | |
| </svg> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Configuration | |
| const API_BASE = window.location.origin; | |
| let isTyping = false; | |
| let currentSettings = { | |
| model: 'distil-gpt-2', | |
| charName: 'Sayang', | |
| userName: 'Kamu', | |
| situation: 'Santai', | |
| location: 'Ruang tamu', | |
| maxLength: 150 | |
| }; | |
| // Emoji list | |
| const emojis = [ | |
| '😊', '😃', '😄', '😁', '😆', '😅', '😂', '🤣', | |
| '😉', '😊', '😇', '🥰', '😍', '🤩', '😘', '😗', | |
| '😚', '😙', '😋', '😛', '😜', '🤪', '😝', '🤑', | |
| '🤗', '🤭', '🤫', '🤔', '🤐', '🤨', '😐', '😑', | |
| '😶', '😏', '😒', '🙄', '😬', '🤥', '😔', '😪', | |
| '🤤', '😴', '😷', '🤒', '🤕', '🤢', '🤮', '🤧', | |
| '🥵', '🥶', '🥴', '😵', '🤯', '🤠', '🥳', '😎', | |
| '🤓', '🧐', '😕', '😟', '🙁', '☹️', '😮', '😯', | |
| '😲', '😳', '🥺', '😦', '😧', '😨', '😰', '😥', | |
| '😢', '😭', '😱', '😖', '😣', '😞', '😓', '😩', | |
| '😫', '🥱', '😤', '😡', '😠', '🤬', '😈', '👿', | |
| '💀', '☠️', '💩', '🤡', '👹', '👺', '👻', '👽' | |
| ]; | |
| // Initialize | |
| document.addEventListener('DOMContentLoaded', function() { | |
| initializeApp(); | |
| loadEmojiPicker(); | |
| setupEventListeners(); | |
| setWelcomeTime(); | |
| }); | |
| function initializeApp() { | |
| // Load settings from elements | |
| document.getElementById('charName').textContent = currentSettings.charName; | |
| document.getElementById('charNameInput').value = currentSettings.charName; | |
| document.getElementById('userNameInput').value = currentSettings.userName; | |
| document.getElementById('situationInput').value = currentSettings.situation; | |
| document.getElementById('locationInput').value = currentSettings.location; | |
| document.getElementById('maxLengthRange').value = currentSettings.maxLength; | |
| document.getElementById('maxLengthValue').textContent = currentSettings.maxLength; | |
| document.getElementById('modelSelect').value = currentSettings.model; | |
| } | |
| function setupEventListeners() { | |
| // Message input | |
| const messageInput = document.getElementById('messageInput'); | |
| messageInput.addEventListener('keydown', function(e) { | |
| if (e.key === 'Enter' && !e.shiftKey) { | |
| e.preventDefault(); | |
| sendMessage(); | |
| } | |
| }); | |
| messageInput.addEventListener('input', function() { | |
| this.style.height = 'auto'; | |
| this.style.height = Math.min(this.scrollHeight, 120) + 'px'; | |
| }); | |
| // Settings inputs | |
| document.getElementById('charNameInput').addEventListener('input', updateCharName); | |
| document.getElementById('maxLengthRange').addEventListener('input', updateMaxLength); | |
| // Model select | |
| document.getElementById('modelSelect').addEventListener('change', function() { | |
| currentSettings.model = this.value; | |
| }); | |
| // Click outside to close popups | |
| document.addEventListener('click', function(e) { | |
| if (!e.target.closest('.settings-popup') && !e.target.closest('.three-dots')) { | |
| document.getElementById('settingsPopup').classList.remove('show'); | |
| } | |
| if (!e.target.closest('.emoji-picker') && !e.target.closest('.emoji-btn')) { | |
| document.getElementById('emojiPicker').classList.remove('show'); | |
| } | |
| }); | |
| } | |
| function setWelcomeTime() { | |
| const now = new Date(); | |
| const timeString = now.toLocaleTimeString('id-ID', { | |
| hour: '2-digit', | |
| minute: '2-digit' | |
| }); | |
| document.getElementById('welcome-time').textContent = timeString; | |
| } | |
| function updateCharName() { | |
| const newName = document.getElementById('charNameInput').value || 'Sayang'; | |
| currentSettings.charName = newName; | |
| document.getElementById('charName').textContent = newName; | |
| } | |
| function updateMaxLength() { | |
| const value = document.getElementById('maxLengthRange').value; | |
| currentSettings.maxLength = parseInt(value); | |
| document.getElementById('maxLengthValue').textContent = value; | |
| } | |
| function toggleSettings() { | |
| const popup = document.getElementById('settingsPopup'); | |
| popup.classList.toggle('show'); | |
| } | |
| function toggleEmojiPicker() { | |
| const picker = document.getElementById('emojiPicker'); | |
| picker.classList.toggle('show'); | |
| } | |
| function loadEmojiPicker() { | |
| const grid = document.getElementById('emojiGrid'); | |
| emojis.forEach(emoji => { | |
| const button = document.createElement('button'); | |
| button.className = 'emoji-item'; | |
| button.textContent = emoji; | |
| button.onclick = () => insertEmoji(emoji); | |
| grid.appendChild(button); | |
| }); | |
| } | |
| function insertEmoji(emoji) { | |
| const input = document.getElementById('messageInput'); | |
| const start = input.selectionStart; | |
| const end = input.selectionEnd; | |
| const text = input.value; | |
| input.value = text.substring(0, start) + emoji + text.substring(end); | |
| input.selectionStart = input.selectionEnd = start + emoji.length; | |
| input.focus(); | |
| document.getElementById('emojiPicker').classList.remove('show'); | |
| } | |
| function getCurrentTime() { | |
| const now = new Date(); | |
| return now.toLocaleTimeString('id-ID', { | |
| hour: '2-digit', | |
| minute: '2-digit' | |
| }); | |
| } | |
| function addMessage(text, isUser = false, showTime = true) { | |
| const chatBody = document.getElementById('chatBody'); | |
| const messageDiv = document.createElement('div'); | |
| messageDiv.className = `message ${isUser ? 'user' : 'char'}`; | |
| const time = showTime ? getCurrentTime() : ''; | |
| const checkmarks = isUser ? '<span class="checkmarks">✓✓</span>' : ''; | |
| messageDiv.innerHTML = ` | |
| <div class="message-avatar"></div> | |
| <div class="bubble"> | |
| <div class="message-text">${text}</div> | |
| <div class="message-meta"> | |
| <span class="timestamp">${time}</span> | |
| ${checkmarks} | |
| </div> | |
| </div> | |
| `; | |
| chatBody.appendChild(messageDiv); | |
| chatBody.scrollTop = chatBody.scrollHeight; | |
| } | |
| function showTyping() { | |
| if (isTyping) return; | |
| isTyping = true; | |
| document.getElementById('status').textContent = 'mengetik...'; | |
| document.getElementById('typingIndicator').classList.add('show'); | |
| const chatBody = document.getElementById('chatBody'); | |
| chatBody.scrollTop = chatBody.scrollHeight; | |
| } | |
| function hideTyping() { | |
| if (!isTyping) return; | |
| isTyping = false; | |
| document.getElementById('status').textContent = 'online'; | |
| document.getElementById('typingIndicator').classList.remove('show'); | |
| } | |
| async function sendMessage() { | |
| const input = document.getElementById('messageInput'); | |
| const message = input.value.trim(); | |
| if (!message) return; | |
| // Update settings from inputs | |
| currentSettings.charName = document.getElementById('charNameInput').value || 'Sayang'; | |
| currentSettings.userName = document.getElementById('userNameInput').value || 'Kamu'; | |
| currentSettings.situation = document.getElementById('situationInput').value || 'Santai'; | |
| currentSettings.location = document.getElementById('locationInput').value || 'Ruang tamu'; | |
| // Add user message | |
| addMessage(message, true); | |
| input.value = ''; | |
| input.style.height = 'auto'; | |
| // Show typing | |
| showTyping(); | |
| // Disable send button | |
| const sendBtn = document.getElementById('sendBtn'); | |
| sendBtn.disabled = true; | |
| try { | |
| const response = await fetch(`${API_BASE}/chat`, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ | |
| message: message, | |
| model: currentSettings.model, | |
| situation: currentSettings.situation, | |
| location: currentSettings.location, | |
| char_name: currentSettings.charName, | |
| user_name: currentSettings.userName, | |
| max_length: currentSettings.maxLength | |
| }) | |
| }); | |
| const data = await response.json(); | |
| // Simulate typing delay | |
| await new Promise(resolve => setTimeout(resolve, 1000 + Math.random() * 2000)); | |
| hideTyping(); | |
| if (data.status === 'success') { | |
| addMessage(data.response); | |
| } else { | |
| addMessage('Maaf, ada masalah dengan sistem. Coba lagi ya! 😅'); | |
| } | |
| } catch (error) { | |
| hideTyping(); | |
| console.error('Error:', error); | |
| addMessage('Ups, koneksi bermasalah. Coba lagi nanti ya! 🔄'); | |
| } | |
| // Re-enable send button | |
| sendBtn.disabled = false; | |
| } | |
| // Handle online/offline status | |
| window.addEventListener('online', function() { | |
| document.getElementById('status').textContent = 'online'; | |
| }); | |
| window.addEventListener('offline', function() { | |
| if (!isTyping) { | |
| document.getElementById('status').textContent = 'offline'; | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> |