|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> |
|
|
<title>Glow Chatbot</title> |
|
|
<style> |
|
|
:root { |
|
|
--neon-color: #0ff; |
|
|
--bg-color: #0a0a0a; |
|
|
--text-color: rgba(255,255,255,0.9); |
|
|
} |
|
|
|
|
|
* { |
|
|
box-sizing: border-box; |
|
|
} |
|
|
|
|
|
body { |
|
|
margin: 0; |
|
|
padding: 0; |
|
|
background: var(--bg-color); |
|
|
font-family: 'Courier New', monospace; |
|
|
color: var(--text-color); |
|
|
touch-action: manipulation; |
|
|
height: 100vh; |
|
|
overflow: hidden; |
|
|
} |
|
|
|
|
|
.chat-container { |
|
|
width: 100%; |
|
|
height: 100vh; |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
background: radial-gradient(circle at center, #1a1a1a 0%, #0a0a0a 100%); |
|
|
} |
|
|
|
|
|
.chat-header { |
|
|
padding: 1rem; |
|
|
text-align: center; |
|
|
background: rgba(0,0,0,0.3); |
|
|
border-bottom: 2px solid var(--neon-color); |
|
|
box-shadow: 0 0 15px var(--neon-color); |
|
|
position: relative; |
|
|
z-index: 2; |
|
|
} |
|
|
|
|
|
.chat-messages { |
|
|
flex: 1; |
|
|
overflow-y: auto; |
|
|
padding: 1rem; |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
gap: 15px; |
|
|
-webkit-overflow-scrolling: touch; |
|
|
} |
|
|
|
|
|
.message { |
|
|
max-width: 85%; |
|
|
padding: 12px 18px; |
|
|
border-radius: 20px; |
|
|
position: relative; |
|
|
animation: messageAppear 0.3s ease; |
|
|
word-break: break-word; |
|
|
line-height: 1.4; |
|
|
} |
|
|
|
|
|
.user-message { |
|
|
background: linear-gradient(145deg, #006666, #009999); |
|
|
align-self: flex-end; |
|
|
color: white; |
|
|
border: 1px solid var(--neon-color); |
|
|
box-shadow: 0 0 10px var(--neon-color); |
|
|
} |
|
|
|
|
|
.bot-message { |
|
|
background: linear-gradient(145deg, #1a1a1a, #2a2a2a); |
|
|
align-self: flex-start; |
|
|
border: 1px solid #444; |
|
|
box-shadow: 0 0 10px rgba(0,255,255,0.2); |
|
|
} |
|
|
|
|
|
.input-container { |
|
|
display: flex; |
|
|
padding: 1rem; |
|
|
gap: 10px; |
|
|
background: rgba(0,0,0,0.5); |
|
|
border-top: 1px solid var(--neon-color); |
|
|
position: relative; |
|
|
z-index: 2; |
|
|
} |
|
|
|
|
|
input[type="text"] { |
|
|
flex: 1; |
|
|
padding: 15px; |
|
|
border: none; |
|
|
border-radius: 25px; |
|
|
background: rgba(255,255,255,0.1); |
|
|
color: var(--neon-color); |
|
|
font-size: 16px; |
|
|
text-shadow: 0 0 5px var(--neon-color); |
|
|
caret-color: var(--neon-color); |
|
|
} |
|
|
|
|
|
input::placeholder { |
|
|
color: #4d4d4d; |
|
|
font-style: italic; |
|
|
} |
|
|
|
|
|
button { |
|
|
background: linear-gradient(145deg, #006666, #009999); |
|
|
border: none; |
|
|
padding: 0 25px; |
|
|
border-radius: 25px; |
|
|
color: white; |
|
|
font-weight: bold; |
|
|
cursor: pointer; |
|
|
transition: all 0.3s ease; |
|
|
min-width: 50px; |
|
|
} |
|
|
|
|
|
button:active { |
|
|
transform: scale(0.95); |
|
|
filter: brightness(1.2); |
|
|
} |
|
|
|
|
|
.typing-indicator { |
|
|
display: inline-flex; |
|
|
padding: 10px 15px; |
|
|
background: rgba(0,0,0,0.5); |
|
|
border-radius: 15px; |
|
|
gap: 5px; |
|
|
align-items: center; |
|
|
} |
|
|
|
|
|
.typing-dot { |
|
|
width: 8px; |
|
|
height: 8px; |
|
|
background: var(--neon-color); |
|
|
border-radius: 50%; |
|
|
animation: typing 1.4s infinite ease-in-out; |
|
|
} |
|
|
|
|
|
@keyframes messageAppear { |
|
|
from { |
|
|
opacity: 0; |
|
|
transform: translateY(10px); |
|
|
} |
|
|
to { |
|
|
opacity: 1; |
|
|
transform: translateY(0); |
|
|
} |
|
|
} |
|
|
|
|
|
@keyframes typing { |
|
|
0%, 100% { |
|
|
transform: translateY(0); |
|
|
opacity: 0.5; |
|
|
} |
|
|
50% { |
|
|
transform: translateY(-5px); |
|
|
opacity: 1; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
::-webkit-scrollbar { |
|
|
width: 5px; |
|
|
} |
|
|
|
|
|
::-webkit-scrollbar-track { |
|
|
background: rgba(0,0,0,0.2); |
|
|
} |
|
|
|
|
|
::-webkit-scrollbar-thumb { |
|
|
background: var(--neon-color); |
|
|
border-radius: 5px; |
|
|
} |
|
|
|
|
|
@media (max-width: 480px) { |
|
|
.message { |
|
|
max-width: 90%; |
|
|
padding: 10px 15px; |
|
|
font-size: 14px; |
|
|
} |
|
|
|
|
|
input[type="text"] { |
|
|
padding: 12px; |
|
|
font-size: 14px; |
|
|
} |
|
|
|
|
|
button { |
|
|
padding: 0 20px; |
|
|
font-size: 14px; |
|
|
} |
|
|
|
|
|
.typing-dot { |
|
|
width: 6px; |
|
|
height: 6px; |
|
|
} |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<div class="chat-container"> |
|
|
<div class="chat-header"> |
|
|
<h1 style="color: var(--neon-color); text-shadow: 0 0 10px var(--neon-color); margin: 0;">GLOW CHAT</h1> |
|
|
</div> |
|
|
|
|
|
<div class="chat-messages" id="chatMessages"> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div class="input-container"> |
|
|
<input type="text" id="userInput" placeholder="Ketik pesan..." autocomplete="off"> |
|
|
<button onclick="sendMessage()">➤</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
const chatMessages = document.getElementById('chatMessages'); |
|
|
const userInput = document.getElementById('userInput'); |
|
|
let isBotTyping = false; |
|
|
|
|
|
function createTypingIndicator() { |
|
|
const container = document.createElement('div'); |
|
|
container.className = 'message bot-message typing-indicator'; |
|
|
|
|
|
for(let i = 0; i < 3; i++) { |
|
|
const dot = document.createElement('div'); |
|
|
dot.className = 'typing-dot'; |
|
|
dot.style.animationDelay = `${i * 0.2}s`; |
|
|
container.appendChild(dot); |
|
|
} |
|
|
|
|
|
return container; |
|
|
} |
|
|
|
|
|
async function sendMessage() { |
|
|
const message = userInput.value.trim(); |
|
|
if (!message || isBotTyping) return; |
|
|
|
|
|
|
|
|
const userDiv = document.createElement('div'); |
|
|
userDiv.className = 'message user-message'; |
|
|
userDiv.textContent = message; |
|
|
chatMessages.appendChild(userDiv); |
|
|
|
|
|
userInput.value = ''; |
|
|
scrollToBottom(); |
|
|
|
|
|
|
|
|
isBotTyping = true; |
|
|
const typingIndicator = createTypingIndicator(); |
|
|
chatMessages.appendChild(typingIndicator); |
|
|
scrollToBottom(); |
|
|
|
|
|
try { |
|
|
const response = await fetch('http://localhost:3000/chat', { |
|
|
method: 'POST', |
|
|
headers: { 'Content-Type': 'application/json' }, |
|
|
body: JSON.stringify({ message }) |
|
|
}); |
|
|
|
|
|
const data = await response.json(); |
|
|
|
|
|
|
|
|
typingIndicator.remove(); |
|
|
const botDiv = document.createElement('div'); |
|
|
botDiv.className = 'message bot-message'; |
|
|
botDiv.textContent = data.reply; |
|
|
chatMessages.appendChild(botDiv); |
|
|
scrollToBottom(); |
|
|
|
|
|
} catch (error) { |
|
|
typingIndicator.remove(); |
|
|
const errorDiv = document.createElement('div'); |
|
|
errorDiv.className = 'message bot-message'; |
|
|
errorDiv.textContent = '⚠️ Gagal terhubung ke server'; |
|
|
chatMessages.appendChild(errorDiv); |
|
|
scrollToBottom(); |
|
|
} finally { |
|
|
isBotTyping = false; |
|
|
} |
|
|
} |
|
|
|
|
|
function scrollToBottom() { |
|
|
chatMessages.scrollTop = chatMessages.scrollHeight; |
|
|
} |
|
|
|
|
|
|
|
|
userInput.addEventListener('keypress', (e) => { |
|
|
if (e.key === 'Enter' && !e.shiftKey) { |
|
|
e.preventDefault(); |
|
|
sendMessage(); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
window.addEventListener('load', () => { |
|
|
userInput.focus(); |
|
|
}); |
|
|
</script> |
|
|
</body> |
|
|
</html> |