diff --git a/src/main/resources/static/app.js b/src/main/resources/static/app.js
new file mode 100644
index 0000000..ec75622
--- /dev/null
+++ b/src/main/resources/static/app.js
@@ -0,0 +1,118 @@
+const chatState = {
+ sessionId: crypto.randomUUID(),
+ isWaiting: false
+};
+
+const dom = {
+ personality: document.getElementById('personality-select'),
+ input: document.getElementById('user-input'),
+ button: document.getElementById('send-button'),
+ window: document.getElementById('chat-window'),
+ typing: document.getElementById('typing-indicator'),
+ chips: document.querySelectorAll('.god-chip')
+};
+
+// ── God chip selection ──────────────────────────────────────────
+
+dom.chips.forEach(chip => {
+ chip.addEventListener('click', () => {
+ dom.chips.forEach(c => c.classList.remove('active'));
+ chip.classList.add('active');
+ dom.personality.value = chip.dataset.god;
+ });
+});
+
+// Set Heimdall as default active chip on load
+const defaultChip = document.querySelector('.god-chip[data-god="HEIMDALL"]');
+if (defaultChip) defaultChip.classList.add('active');
+dom.personality.value = 'HEIMDALL';
+
+// ── Chat ────────────────────────────────────────────────────────
+
+async function sendMessage() {
+ const message = dom.input.value.trim();
+ if (!message || chatState.isWaiting) return;
+
+ const selectedPersonality = dom.personality.value;
+ const godName = getGodName(selectedPersonality);
+
+ appendMessage('user', null, message);
+ dom.input.value = '';
+ setLoading(true);
+
+ try {
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), 15000);
+ let response;
+
+ try {
+ response = await fetch('/api/v1/chat', {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify({
+ personality: selectedPersonality,
+ message: message,
+ sessionId: chatState.sessionId
+ }),
+ signal: controller.signal
+ });
+ } finally {
+ clearTimeout(timeoutId);
+ }
+
+ if (!response.ok) throw new Error("Divine connection lost.");
+
+ const aiText = await response.text();
+ appendMessage('assistant', godName, aiText);
+ } catch (error) {
+ appendMessage('assistant', 'System', "Error: " + error.message);
+ } finally {
+ setLoading(false);
+ }
+}
+
+function appendMessage(role, name, text) {
+ const msgDiv = document.createElement('div');
+ msgDiv.className = `message ${role}`;
+
+ if (role === 'assistant') {
+ const label = document.createElement('div');
+ label.className = 'god-label';
+ label.textContent = name;
+ msgDiv.appendChild(label);
+ msgDiv.appendChild(document.createTextNode(text));
+ } else {
+ msgDiv.textContent = text;
+ }
+
+ dom.window.appendChild(msgDiv);
+ dom.window.scrollTop = dom.window.scrollHeight;
+}
+
+function setLoading(active) {
+ chatState.isWaiting = active;
+ dom.typing.classList.toggle('hidden', !active);
+ dom.button.disabled = active;
+}
+
+function getGodName(value) {
+ const names = {
+ ODIN: 'Odin',
+ LOKI: 'Loki',
+ THOR: 'Thor',
+ FREYJA: 'Freyja',
+ HEIMDALL: 'Heimdall'
+ };
+ return names[value] || value;
+}
+
+// ── Event listeners ─────────────────────────────────────────────
+
+dom.button.addEventListener('click', sendMessage);
+dom.input.addEventListener('keydown', (e) => {
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault();
+ sendMessage().then(r => {
+ }).catch(err => console.error(err));
+ }
+});
\ No newline at end of file
diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html
new file mode 100644
index 0000000..ee4282a
--- /dev/null
+++ b/src/main/resources/static/index.html
@@ -0,0 +1,67 @@
+
+
+
+
+
+ Bifrost AI | Mythological Gateway
+
+
+
+
+
+
+
+
+
+ Asgard AI
+ SPEAK TO THE GODS
+
+
+
+
+
+
+
+
+
+
+
Heimdall
+ I see all who approach the bridge. Who do you wish to speak with, mortal?
+
+
+ The Gods are contemplating...
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/static/style.css b/src/main/resources/static/style.css
new file mode 100644
index 0000000..04b88ad
--- /dev/null
+++ b/src/main/resources/static/style.css
@@ -0,0 +1,293 @@
+:root {
+ --void: #080a0f;
+ --abyss: #0d1117;
+ --runic-gold: #c8973a;
+ --runic-gold-light: #e8b84b;
+ --silver: #cbd5e1;
+ --silver-dim: #64748b;
+ --user-bubble: #1e3a5f;
+ --god-bubble: #161d2e;
+}
+
+*, *::before, *::after {
+ box-sizing: border-box;
+}
+
+body {
+ background-color: var(--void);
+ color: var(--silver);
+ font-family: Georgia, "Times New Roman", serif;
+ margin: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ min-height: 100vh;
+}
+
+.rune-border {
+ width: 100%;
+ height: 5px;
+ flex-shrink: 0;
+ background: linear-gradient(90deg, #8b6914, #c8973a, #e8b84b, #c8973a, #8b6914);
+}
+
+.app-container {
+ width: 100%;
+ max-width: 900px;
+ display: flex;
+ flex-direction: column;
+ padding: 2rem;
+ flex: 1;
+}
+
+/* ── Header ── */
+
+header {
+ text-align: center;
+ border-bottom: 1px solid #2a1f0a;
+ margin-bottom: 0;
+ padding-bottom: 1.25rem;
+}
+
+.header-ornament {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ margin-bottom: 0.6rem;
+}
+
+.ornament-line {
+ flex: 1;
+ height: 1px;
+ background: linear-gradient(90deg, transparent, #c8973a66, #c8973a, #c8973a66, transparent);
+}
+
+.valknut {
+ width: 28px;
+ height: 28px;
+ flex-shrink: 0;
+}
+
+header h1 {
+ color: var(--runic-gold);
+ margin: 0;
+ font-size: 2rem;
+ font-weight: 400;
+ letter-spacing: 0.3em;
+ text-transform: uppercase;
+ text-shadow: 0 0 40px #c8973a33;
+}
+
+header p {
+ color: var(--silver-dim);
+ font-size: 0.8rem;
+ letter-spacing: 0.2em;
+ text-transform: uppercase;
+ margin: 0.5rem 0 0;
+}
+
+/* ── God selector ── */
+
+.hidden-select {
+ display: none;
+}
+
+.god-selector {
+ display: flex;
+ gap: 0.5rem;
+ padding: 0.85rem 0;
+ border-bottom: 1px solid #1a1f2e;
+ overflow-x: auto;
+ scrollbar-width: none;
+}
+
+.god-selector::-webkit-scrollbar {
+ display: none;
+}
+
+.god-chip {
+ display: flex;
+ align-items: center;
+ gap: 0.4rem;
+ padding: 0.35rem 0.9rem;
+ border-radius: 999px;
+ font-size: 0.78rem;
+ letter-spacing: 0.04em;
+ cursor: pointer;
+ border: 1px solid #2d2410;
+ color: var(--silver-dim);
+ background: transparent;
+ font-family: Georgia, serif;
+ white-space: nowrap;
+ transition: all 0.2s;
+ user-select: none;
+}
+
+.god-chip:hover {
+ border-color: #c8973a55;
+ color: var(--silver);
+}
+
+.god-chip.active {
+ background: #1a1000;
+ border-color: var(--runic-gold);
+ color: var(--runic-gold);
+}
+
+/* ── Main layout ── */
+
+main {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ gap: 1rem;
+}
+
+#chat-container {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+}
+
+#chat-window {
+ flex-grow: 1;
+ background: var(--abyss);
+ border-radius: 12px;
+ padding: 1.5rem;
+ overflow-y: auto;
+ height: 60vh;
+ border: 1px solid #1a1f2e;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+/* ── Messages ── */
+
+.message {
+ padding: 0.85rem 1.2rem;
+ border-radius: 12px;
+ max-width: 75%;
+ font-size: 0.9rem;
+ line-height: 1.65;
+ animation: fade-in 0.3s ease;
+}
+
+@keyframes fade-in {
+ from { opacity: 0; transform: translateY(6px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+.message.user {
+ background: var(--user-bubble);
+ color: #c8d8f0;
+ margin-left: auto;
+ border-bottom-right-radius: 3px;
+ border: 1px solid #2a4a7f;
+}
+
+.message.assistant {
+ background: var(--god-bubble);
+ color: var(--silver);
+ margin-right: auto;
+ border: 1px solid #2a1f0a;
+ border-left: 2px solid var(--runic-gold);
+ border-radius: 0;
+ border-top-right-radius: 12px;
+ border-bottom-right-radius: 12px;
+}
+
+.god-label {
+ font-size: 0.68rem;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: var(--runic-gold);
+ margin-bottom: 0.45rem;
+}
+
+#typing-indicator {
+ color: var(--silver-dim);
+ font-size: 0.82rem;
+ font-style: italic;
+ letter-spacing: 0.05em;
+ padding: 0.5rem 0 0;
+}
+
+/* ── Input area ── */
+
+.input-area {
+ display: flex;
+ gap: 0.75rem;
+ align-items: flex-end;
+}
+
+textarea {
+ flex: 1;
+ background: #111827;
+ border: 1px solid #2a1f0a;
+ color: var(--silver);
+ padding: 0.85rem 1rem;
+ border-radius: 10px;
+ outline: none;
+ font-family: Georgia, serif;
+ font-size: 0.9rem;
+ resize: none;
+ transition: border-color 0.2s;
+ line-height: 1.5;
+}
+
+textarea:focus {
+ border-color: var(--runic-gold);
+ box-shadow: 0 0 0 1px #c8973a33;
+}
+
+textarea::placeholder {
+ color: var(--silver-dim);
+ opacity: 0.6;
+}
+
+button {
+ background: #1a1000;
+ color: var(--runic-gold);
+ border: 1px solid var(--runic-gold);
+ padding: 0.75rem 1.5rem;
+ border-radius: 10px;
+ font-family: Georgia, serif;
+ font-size: 0.85rem;
+ font-weight: 400;
+ letter-spacing: 0.1em;
+ cursor: pointer;
+ transition: all 0.2s;
+ white-space: nowrap;
+}
+
+button:hover {
+ background: #2a1a00;
+ border-color: var(--runic-gold-light);
+ color: var(--runic-gold-light);
+}
+
+button:active {
+ transform: scale(0.98);
+}
+
+button:disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+ transform: none;
+}
+
+/* ── Footer ── */
+
+.rune-footer {
+ text-align: center;
+ padding: 0.6rem;
+ font-size: 0.8rem;
+ letter-spacing: 0.5em;
+ color: #2a2010;
+ margin-top: auto;
+}
+
+.hidden {
+ display: none;
+}
\ No newline at end of file