0 Comments

#spark-root, #spark-root * { box-sizing: border-box; } #spark-root { --spark-ink: #0c1815; --spark-panel: #132420; --spark-panel-2: #1d3630; --spark-parchment: #f4efdd; --spark-sage: #9fb3a8; --spark-gold: #e8b84b; --spark-gold-dim: #c79a3d; --spark-rust: #b5654a; --spark-radius: 18px; --spark-font-display: 'Fraunces', Georgia, serif; --spark-font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; --spark-font-mono: 'IBM Plex Mono', 'SFMono-Regular', Consolas, monospace; position: fixed; z-index: 999999; bottom: 24px; right: 24px; font-family: var(--spark-font-body); color: var(--spark-parchment); } @media (max-width: 480px) { #spark-root { bottom: 16px; right: 16px; } } /* ---------- Toggle orb ---------- */ .spark-orb { width: 60px; height: 60px; border-radius: 50%; background: radial-gradient(circle at 32% 28%, #24443c, var(--spark-panel) 70%); border: 1px solid rgba(232, 184, 75, 0.35); box-shadow: 0 8px 24px rgba(0,0,0,0.35), 0 0 0 0 rgba(232,184,75,0.4); display: flex; align-items: center; justify-content: center; cursor: pointer; position: relative; transition: transform .2s ease, box-shadow .2s ease; } .spark-orb:hover { transform: scale(1.06); } .spark-orb:active { transform: scale(0.97); } .spark-orb.spark-pulse { animation: spark-breathe 2.6s ease-in-out infinite; } @keyframes spark-breathe { 0%, 100% { box-shadow: 0 8px 24px rgba(0,0,0,0.35), 0 0 0 0 rgba(232,184,75,0.35); } 50% { box-shadow: 0 8px 24px rgba(0,0,0,0.35), 0 0 0 10px rgba(232,184,75,0); } } .spark-bars { display: flex; align-items: flex-end; gap: 3px; height: 20px; } .spark-bars span { width: 3px; background: var(--spark-gold); border-radius: 2px; display: block; animation: spark-eq 1.4s ease-in-out infinite; } .spark-bars span:nth-child(1) { height: 8px; animation-delay: 0s; } .spark-bars span:nth-child(2) { height: 18px; animation-delay: .18s; } .spark-bars span:nth-child(3) { height: 12px; animation-delay: .36s; } .spark-bars span:nth-child(4) { height: 16px; animation-delay: .54s; } .spark-bars span:nth-child(5) { height: 7px; animation-delay: .72s; } @keyframes spark-eq { 0%, 100% { transform: scaleY(0.4); } 50% { transform: scaleY(1); } } .spark-orb.spark-active .spark-bars span { animation-duration: .5s; } /* ---------- Panel ---------- */ .spark-panel { position: absolute; bottom: 74px; right: 0; width: 380px; max-width: calc(100vw - 32px); height: 580px; max-height: calc(100vh - 120px); background: var(--spark-panel); border-radius: var(--spark-radius); border: 1px solid rgba(232,184,75,0.18); box-shadow: 0 24px 60px rgba(0,0,0,0.45); display: none; flex-direction: column; overflow: hidden; opacity: 0; transform: translateY(12px); transition: opacity .18s ease, transform .18s ease; } .spark-panel.spark-open { display: flex; opacity: 1; transform: translateY(0); } @media (max-width: 480px) { .spark-panel { position: fixed; left: 12px; right: 12px; bottom: 88px; width: auto; height: calc(100vh - 140px); } } .spark-header { background: var(--spark-ink); padding: 14px 16px; display: flex; align-items: center; gap: 10px; border-bottom: 1px solid rgba(232,184,75,0.15); flex-shrink: 0; } .spark-header-orb { width: 30px; height: 30px; border-radius: 50%; background: radial-gradient(circle at 32% 28%, #24443c, var(--spark-ink) 70%); display:flex; align-items:center; justify-content:center; flex-shrink:0; } .spark-header-orb .spark-bars { height: 12px; } .spark-header-orb .spark-bars span:nth-child(1) { height: 5px; } .spark-header-orb .spark-bars span:nth-child(2) { height: 11px; } .spark-header-orb .spark-bars span:nth-child(3) { height: 7px; } .spark-header-orb .spark-bars span:nth-child(4) { height: 10px; } .spark-header-orb .spark-bars span:nth-child(5) { height: 4px; } .spark-header-title { flex: 1; min-width: 0; } .spark-header-title h1 { font-family: var(--spark-font-display); font-style: italic; font-weight: 600; font-size: 19px; margin: 0; color: var(--spark-parchment); line-height: 1.1; } .spark-header-title p { font-family: var(--spark-font-mono); font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--spark-sage); margin: 2px 0 0; } .spark-icon-btn { background: transparent; border: none; color: var(--spark-sage); cursor: pointer; width: 28px; height: 28px; border-radius: 8px; display: flex; align-items: center; justify-content: center; transition: background .15s ease, color .15s ease; flex-shrink: 0; } .spark-icon-btn:hover { background: rgba(232,184,75,0.12); color: var(--spark-gold); } .spark-icon-btn.spark-on { color: var(--spark-gold); } /* ---------- Mode segmented control ---------- */ .spark-modes { display: flex; gap: 6px; padding: 10px 14px; border-bottom: 1px solid rgba(232,184,75,0.1); flex-shrink: 0; } .spark-mode-btn { flex: 1; background: var(--spark-ink); border: 1px solid transparent; color: var(--spark-sage); font-family: var(--spark-font-mono); font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; padding: 7px 0; border-radius: 9px; cursor: pointer; transition: all .15s ease; } .spark-mode-btn.spark-active { background: rgba(232,184,75,0.14); border-color: rgba(232,184,75,0.4); color: var(--spark-gold); } /* ---------- Messages ---------- */ .spark-messages { flex: 1; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 12px; } .spark-messages::-webkit-scrollbar { width: 6px; } .spark-messages::-webkit-scrollbar-thumb { background: rgba(232,184,75,0.25); border-radius: 3px; } .spark-msg { max-width: 86%; font-size: 14px; line-height: 1.5; } .spark-msg-user { align-self: flex-end; } .spark-msg-ai { align-self: flex-start; } .spark-bubble { padding: 10px 13px; border-radius: 14px; white-space: pre-wrap; word-wrap: break-word; } .spark-msg-user .spark-bubble { background: var(--spark-gold); color: var(--spark-ink); border-bottom-right-radius: 4px; } .spark-msg-ai .spark-bubble { background: var(--spark-panel-2); color: var(--spark-parchment); border-bottom-left-radius: 4px; } .spark-msg img.spark-generated { max-width: 100%; border-radius: 12px; display: block; margin-top: 4px; border: 1px solid rgba(232,184,75,0.25); } .spark-caption { font-family: var(--spark-font-mono); font-size: 10px; color: var(--spark-sage); margin-top: 4px; letter-spacing: 0.03em; } .spark-empty { margin: auto; text-align: center; color: var(--spark-sage); font-family: var(--spark-font-mono); font-size: 12px; padding: 20px; line-height: 1.6; } .spark-empty strong { display: block; font-family: var(--spark-font-display); font-style: italic; font-size: 20px; color: var(--spark-parchment); margin-bottom: 6px; } .spark-typing { display: flex; gap: 4px; padding: 4px 2px; } .spark-typing span { width: 6px; height: 6px; border-radius: 50%; background: var(--spark-gold); animation: spark-eq 1s ease-in-out infinite; } .spark-typing span:nth-child(2) { animation-delay: .15s; } .spark-typing span:nth-child(3) { animation-delay: .3s; } /* ---------- Footer / input ---------- */ .spark-footer { border-top: 1px solid rgba(232,184,75,0.15); padding: 10px; display: flex; align-items: flex-end; gap: 8px; flex-shrink: 0; background: var(--spark-ink); } .spark-input { flex: 1; background: var(--spark-panel); border: 1px solid rgba(232,184,75,0.2); color: var(--spark-parchment); border-radius: 12px; padding: 9px 12px; font-family: var(--spark-font-body); font-size: 14px; resize: none; max-height: 90px; line-height: 1.4; outline: none; } .spark-input::placeholder { color: var(--spark-sage); } .spark-input:focus { border-color: var(--spark-gold); } .spark-round-btn { width: 36px; height: 36px; border-radius: 50%; border: 1px solid rgba(232,184,75,0.25); background: var(--spark-panel); color: var(--spark-sage); display: flex; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0; transition: all .15s ease; } .spark-round-btn:hover { color: var(--spark-gold); border-color: var(--spark-gold); } .spark-round-btn.spark-listening { background: var(--spark-rust); color: var(--spark-parchment); border-color: var(--spark-rust); animation: spark-breathe 1.2s ease-in-out infinite; } .spark-round-btn.spark-send { background: var(--spark-gold); color: var(--spark-ink); border-color: var(--spark-gold); } .spark-round-btn:disabled { opacity: 0.4; cursor: not-allowed; } .spark-round-btn svg { width: 16px; height: 16px; } .spark-visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); } @media (prefers-reduced-motion: reduce) { .spark-orb.spark-pulse, .spark-bars span, .spark-round-btn.spark-listening, .spark-typing span { animation: none !important; } } (function(){ if (window.__sparkWidgetLoaded) return; window.__sparkWidgetLoaded = true; // ---------------- CONFIG ---------------- var CONFIG = { name: "Spark", tagline: "your on-page AI", greeting: "Ask me anything, or switch to Image mode to generate a picture.", systemPrompt: "You are Spark, a friendly, concise AI assistant embedded on a website. Keep answers helpful and to the point.", startOpen: false }; var FONT_LINK = document.createElement('link'); FONT_LINK.rel = 'stylesheet'; FONT_LINK.href = 'https://fonts.googleapis.com/css2?family=Fraunces:ital,wght@0,600;1,600&family=Inter:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap'; document.head.appendChild(FONT_LINK); var root = document.getElementById('spark-root'); var barsHTML = ''; root.innerHTML += '' + '
' + '
' + barsHTML + '
' + '
' + ''; function svgMic(){ return ''; } function svgSend(){ return ''; } function svgClose(){ return ''; } function svgTrash(){ return ''; } function svgSpeaker(){ return ''; } var els = { toggle: document.getElementById('spark-toggle'), panel: document.getElementById('spark-panel'), close: document.getElementById('spark-close'), clear: document.getElementById('spark-clear'), voiceToggle: document.getElementById('spark-voice-toggle'), messages: document.getElementById('spark-messages'), empty: document.getElementById('spark-empty'), input: document.getElementById('spark-input'), send: document.getElementById('spark-send'), mic: document.getElementById('spark-mic'), modeBtns: root.querySelectorAll('.spark-mode-btn') }; var state = { open: CONFIG.startOpen, mode: 'chat', speakReplies: false, history: [{ role: 'system', content: CONFIG.systemPrompt }], busy: false }; function openPanel(){ state.open = true; els.panel.classList.add('spark-open'); els.toggle.classList.remove('spark-pulse'); els.input.focus(); } function closePanel(){ state.open = false; els.panel.classList.remove('spark-open'); els.toggle.classList.add('spark-pulse'); } els.toggle.addEventListener('click', function(){ state.open ? closePanel() : openPanel(); }); els.toggle.addEventListener('keydown', function(e){ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); state.open ? closePanel() : openPanel(); } }); els.close.addEventListener('click', closePanel); els.clear.addEventListener('click', function(){ state.history = [{ role: 'system', content: CONFIG.systemPrompt }]; els.messages.innerHTML = ''; els.messages.appendChild(els.empty); }); els.voiceToggle.addEventListener('click', function(){ state.speakReplies = !state.speakReplies; els.voiceToggle.classList.toggle('spark-on', state.speakReplies); if (!state.speakReplies && window.speechSynthesis) window.speechSynthesis.cancel(); }); els.modeBtns.forEach(function(btn){ btn.addEventListener('click', function(){ els.modeBtns.forEach(function(b){ b.classList.remove('spark-active'); }); btn.classList.add('spark-active'); state.mode = btn.getAttribute('data-mode'); els.input.placeholder = state.mode === 'image' ? 'Describe an image to generate…' : 'Type a message…'; }); }); function clearEmpty(){ if (els.empty && els.empty.parentNode) els.empty.remove(); } function addMessage(role, contentEl){ clearEmpty(); var wrap = document.createElement('div'); wrap.className = 'spark-msg ' + (role === 'user' ? 'spark-msg-user' : 'spark-msg-ai'); wrap.appendChild(contentEl); els.messages.appendChild(wrap); els.messages.scrollTop = els.messages.scrollHeight; return wrap; } function bubble(text){ var b = document.createElement('div'); b.className = 'spark-bubble'; b.textContent = text; return b; } function typingBubble(){ var b = document.createElement('div'); b.className = 'spark-bubble spark-typing'; b.innerHTML = ''; return b; } function speak(text){ if (!state.speakReplies || !window.speechSynthesis) return; window.speechSynthesis.cancel(); var u = new SpeechSynthesisUtterance(text); u.rate = 1.02; window.speechSynthesis.speak(u); } els.input.addEventListener('input', function(){ els.input.style.height = 'auto'; els.input.style.height = Math.min(els.input.scrollHeight, 90) + 'px'; }); els.input.addEventListener('keydown', function(e){ if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); } }); els.send.addEventListener('click', handleSend); function handleSend(){ var text = els.input.value.trim(); if (!text || state.busy) return; els.input.value = ''; els.input.style.height = 'auto'; addMessage('user', bubble(text)); if (state.mode === 'image') { handleImage(text); } else { handleChat(text); } } function handleChat(text){ state.history.push({ role: 'user', content: text }); state.busy = true; els.send.disabled = true; var aiWrap = addMessage('ai', typingBubble()); fetch('https://text.pollinations.ai/openai', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'openai', messages: state.history }) }) .then(function(res){ return res.json(); }) .then(function(data){ var reply = (data && data.choices && data.choices[0] && data.choices[0].message && data.choices[0].message.content) || "Sorry, I couldn't generate a reply just now."; state.history.push({ role: 'assistant', content: reply }); aiWrap.innerHTML = ''; aiWrap.appendChild(bubble(reply)); els.messages.scrollTop = els.messages.scrollHeight; speak(reply); }) .catch(function(){ aiWrap.innerHTML = ''; aiWrap.appendChild(bubble("I couldn't reach the AI service. Please try again in a moment.")); }) .finally(function(){ state.busy = false; els.send.disabled = false; }); } function handleImage(prompt){ state.busy = true; els.send.disabled = true; var aiWrap = addMessage('ai', typingBubble()); var seed = Math.floor(Math.random() * 1000000); var url = 'https://image.pollinations.ai/prompt/' + encodeURIComponent(prompt) + '?width=768&height=768&nologo=true&seed=' + seed; var img = new Image(); img.className = 'spark-generated'; img.alt = prompt; img.onload = function(){ aiWrap.innerHTML = ''; aiWrap.appendChild(img); var cap = document.createElement('div'); cap.className = 'spark-caption'; cap.textContent = 'generated for: "' + prompt + '"'; aiWrap.appendChild(cap); els.messages.scrollTop = els.messages.scrollHeight; state.busy = false; els.send.disabled = false; }; img.onerror = function(){ aiWrap.innerHTML = ''; aiWrap.appendChild(bubble("I couldn't generate that image. Please try again.")); state.busy = false; els.send.disabled = false; }; img.src = url; } // ---------------- Voice input ---------------- var SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; var recognition = null; var listening = false; if (SpeechRecognition) { recognition = new SpeechRecognition(); recognition.continuous = false; recognition.interimResults = false; recognition.lang = (navigator.language || 'en-US'); recognition.onresult = function(e){ var transcript = e.results[0][0].transcript; els.input.value = transcript; handleSend(); }; recognition.onend = function(){ listening = false; els.mic.classList.remove('spark-listening'); }; recognition.onerror = function(){ listening = false; els.mic.classList.remove('spark-listening'); }; els.mic.addEventListener('click', function(){ if (listening) { recognition.stop(); return; } try { recognition.start(); listening = true; els.mic.classList.add('spark-listening'); } catch (err) { /* already started */ } }); } else { els.mic.disabled = true; els.mic.title = 'Voice input is not supported in this browser'; } if (CONFIG.startOpen) openPanel(); })();

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Posts