Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<title>Particle Corn-Field Animation</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<style> | |
/* Full-screen canvas sits behind everything */ | |
#particleCanvas { | |
position: fixed; | |
inset: 0; | |
z-index: -1; | |
background: linear-gradient(135deg, #0f172a, #312e81, #581c87); | |
} | |
/* From Uiverse.io by krokettenkoal */ | |
:root { | |
--bg-col: #010101; | |
--space-col: #2d093a; | |
--galaxy-col: #460a42; | |
} | |
.card { | |
--bg-col: #010101; | |
--space-col: #2d093a; | |
--galaxy-col: #460a42; | |
--space-gradient: radial-gradient(ellipse at top, var(--bg-col), transparent), | |
radial-gradient(ellipse at bottom, var(--galaxy-col) 10%, transparent 60%), | |
radial-gradient(ellipse at bottom right, var(--space-col), transparent); | |
--space-gradient-alt: radial-gradient(ellipse at top left, var(--space-col), transparent), | |
radial-gradient(ellipse at bottom, var(--galaxy-col) 10%, transparent 60%), | |
radial-gradient(ellipse at bottom right, var(--bg-col), transparent); | |
--default-left: 50%; | |
--default-top: 50%; | |
--stars: radial-gradient(circle at 52% 54%, rgba(255, 255, 255, 0.582) 3px, transparent 4px), | |
radial-gradient(circle at 22% 24%, rgba(255, 255, 255, 0.582) 2px, transparent 3px), | |
radial-gradient(circle at 14% 18%, rgba(255, 255, 255, 0.582) 3px, transparent 8px), | |
radial-gradient(circle at 18% 21%, rgba(255, 255, 255, 0.582) 4px, transparent 5px), | |
radial-gradient(circle at 36% 9%, rgba(255, 255, 255, 0.582) 3px, transparent 5px), | |
radial-gradient(circle at 28% 31%, rgba(255, 255, 255, 0.39) 2px, transparent 3px), | |
radial-gradient(circle at 62% 61%, rgba(255, 255, 255, 0.532) 3px, transparent 4px), | |
radial-gradient(circle at 57% 66%, rgba(255, 255, 255, 0.842) 6px, transparent 8px), | |
radial-gradient(circle at 65% 71%, rgba(255, 255, 255, 0.534) 1px, transparent 3px), | |
radial-gradient(circle at 67% 68%, rgba(255, 255, 255, 0.651) 3px, transparent 3px), | |
radial-gradient(circle at 43% 44%, rgba(255, 255, 255, 0.74) 2px, transparent 6px), | |
radial-gradient(circle at 40% 39%, rgba(183, 243, 255, 0.842) 4px, transparent 10px), | |
radial-gradient(circle at 41% 40%, rgba(255, 255, 255, 0.699) 5px, transparent 6px), | |
radial-gradient(circle at 38% 38%, rgba(255, 255, 255, 0.349) 2px, transparent 4px), | |
radial-gradient(circle at 39% 42%, rgba(255, 255, 255, 0.747) 5px, transparent 7px), | |
radial-gradient(circle at 80% 31%, rgba(255, 255, 255, 0.781) 4px, transparent 6px), | |
radial-gradient(circle at 25% 64%, rgba(255, 255, 255, 0.425) 3px, transparent 4px), | |
radial-gradient(circle at 41% 49%, rgba(255, 255, 255, 0.678) 3px, transparent 6px), | |
radial-gradient(circle at 50% 37%, rgba(255, 255, 255, 0.336) 1px, transparent 3px), | |
radial-gradient(circle at 4% 37%, rgba(255, 255, 255, 0.336) 1px, transparent 3px), | |
radial-gradient(circle at 8% 60%, rgba(255, 255, 255, 0.336) 1px, transparent 4px), | |
radial-gradient(circle at 12% 54%, rgba(255, 255, 255, 0.336) 1px, transparent 5px), | |
radial-gradient(circle at 6% 59%, rgba(255, 255, 255, 0.336) 2px, transparent 10px), | |
radial-gradient(circle at 9% 57%, rgba(255, 255, 255, 0.336) 1px, transparent 2px), | |
radial-gradient(circle at 14% 61%, rgba(255, 255, 255, 0.336) 2px, transparent 6px); | |
width: 700px; | |
height: 700px; | |
padding: 0; | |
border-radius: 2rem; | |
left: var(--default-left); | |
top: var(--default-top); | |
transform: translate(-50%, -50%); | |
background-color: #010101; | |
background-image: var(--space-gradient), var(--stars); | |
background-size: 175% 200%; | |
background-repeat: no-repeat; | |
box-shadow: 5px 7px 20px var(--bg-col); | |
overflow: clip; | |
animation: space-drift 180s ease-in-out infinite; | |
position: fixed; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
z-index: 10; | |
cursor: grab; | |
user-select: none; | |
} | |
.heading { | |
font-size: 0.9rem; | |
text-align: center; | |
color: rgb(189, 188, 141); | |
} | |
.heading span { | |
font-size: 2.2rem; | |
font-weight: bold; | |
display: block; | |
font-style: italic; | |
margin-top: 0.25rem; | |
background-clip: text; | |
-webkit-background-clip: text; | |
-moz-background-clip: text; | |
-webkit-text-fill-color: transparent; | |
text-shadow: 15px 10px 55px plum; | |
text-transform: uppercase; | |
letter-spacing: 1rem; | |
z-index: 99; | |
animation: heading-stretch 0.7s forwards ease-out; | |
} | |
.heading span::before, | |
.heading span::after { | |
content: '—'; | |
} | |
.content { | |
display: grid; | |
place-items: center; | |
padding: 2rem; | |
z-index: 1; | |
} | |
.item { | |
--item-duration: 8s; | |
--idx: 0; | |
display: flex; | |
grid-area: 1 / 1; | |
flex-flow: column nowrap; | |
justify-content: center; | |
align-items: center; | |
gap: 0.5rem; | |
font-size: 1.1rem; | |
text-transform: lowercase; | |
font-style: italic; | |
opacity: 0; | |
animation: item-fade var(--item-duration) infinite ease-in-out; | |
animation-delay: calc(var(--idx) * var(--item-duration) / 3); | |
} | |
.item svg { | |
width: 3rem; | |
height: 3rem; | |
} | |
.item--create { | |
--idx: 0; | |
} | |
.item--post { | |
--idx: 1; | |
} | |
.item--inspire { | |
--idx: 2; | |
} | |
/* Animation keyframes for card */ | |
@keyframes space-drift { | |
0% { background-position: 0% 50%; } | |
33% { background-position: 80% 0%; } | |
67% { background-position: 80% 100%; } | |
100% { background-position: 0% 50%; } | |
} | |
@keyframes heading-stretch { | |
from { | |
opacity: 0.8; | |
transform: scale(0.8); | |
letter-spacing: normal; | |
filter: blur(50px); | |
text-shadow: none; | |
} | |
to { | |
opacity: unset; | |
transform: unset; | |
letter-spacing: 1rem; | |
filter: unset; | |
} | |
} | |
@keyframes item-fade { | |
0%, 20% { | |
opacity: 0; | |
transform: translateX(10px); | |
filter: blur(5px); | |
} | |
40%, 60% { | |
opacity: 1; | |
transform: unset; | |
filter: unset; | |
} | |
70%, 100% { | |
opacity: 0; | |
transform: translateX(-10px); | |
filter: blur(5px); | |
} | |
} | |
:root { | |
--bg-col: #010101; | |
--space-col: #2d093a; | |
--galaxy-col: #460a42; | |
} | |
/* Tiny Settings Panel */ | |
#settingsPanel { | |
position: fixed; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
z-index: 20; | |
width: 240px; | |
background: rgba(15,15,15,0.95); | |
border-radius: 0.5rem; | |
padding: 0.75rem; | |
display: none; | |
flex-direction: column; | |
gap: 0.5rem; | |
font-size: 0.6rem; | |
color: #fff; | |
backdrop-filter: blur(4px); | |
} | |
#settingsPanel label { | |
display: flex; | |
flex-direction: column; | |
gap: 0.25rem; | |
} | |
#settingsPanel input[type="range"], | |
#settingsPanel input[type="number"] { | |
width: 100%; | |
height: 0.5rem; | |
font-size: 0.6rem; | |
padding: 0.1rem; | |
} | |
/* Modern colour pellets */ | |
.colourPellet { | |
width: 0.8rem; | |
height: 0.8rem; | |
border-radius: 50%; | |
border: none; | |
cursor: pointer; | |
display: inline-block; | |
margin-right: 0.15rem; | |
} | |
/* ---------- 2-D Red-Orange Glowing Orb ---------- */ | |
.spinner { | |
position: absolute; | |
width: 130px; | |
height: 130px; | |
border-radius: 50%; | |
background: radial-gradient(circle at 30% 30%, #ff1a1a, #cc0000 40%, #990000 100%); | |
box-shadow: | |
0 0 30px rgba(255, 26, 26, 0.4), | |
0 0 60px rgba(255, 26, 26, 0.3), | |
0 0 90px rgba(255, 26, 26, 0.2), | |
0 0 120px rgba(255, 26, 26, 0.1), | |
inset 0 0 20px rgba(255, 255, 255, 0.3); | |
filter: blur(0.5px); | |
cursor: grab; | |
z-index: 20; | |
animation: pulseGlow 2s ease-in-out infinite; | |
} | |
@keyframes pulseGlow { | |
0%, 100% { | |
box-shadow: | |
0 0 30px rgba(255, 26, 26, 0.4), | |
0 0 60px rgba(255, 26, 26, 0.3), | |
0 0 90px rgba(255, 26, 26, 0.2), | |
0 0 120px rgba(255, 26, 26, 0.1), | |
inset 0 0 20px rgba(255, 255, 255, 0.3); | |
} | |
50% { | |
box-shadow: | |
0 0 40px rgba(255, 26, 26, 0.6), | |
0 0 80px rgba(255, 26, 26, 0.5), | |
0 0 110px rgba(255, 26, 26, 0.4), | |
0 0 150px rgba(255, 26, 26, 0.3), | |
inset 0 0 25px rgba(255, 255, 255, 0.4); | |
} | |
} | |
</style> | |
</head> | |
<body class="relative min-h-screen text-white flex flex-col items-center justify-center font-sans"> | |
<canvas id="particleCanvas"></canvas> | |
<!-- 3-D Red-Orange Glowing Orb --> | |
<div id="orb" class="spinner" style="top:50%; left:50%; transform:translate(-50%,-50%);"></div> | |
<!-- From Uiverse.io by krokettenkoal --> | |
<div id="draggableCard" class="card relative flex flex-col min-w-0 w-full mx-auto !min-h-[28rem] !rounded-xl !p-0"> | |
<!-- Header --> | |
<header class="flex items-center justify-between px-4 py-2 border-b border-white/20"> | |
<div class="flex items-center gap-2"> | |
<button id="newChatBtn" title="New Chat" class="p-1.5 rounded-full hover:bg-white/10 transition"> | |
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M12 4v16m8-8H4"/></svg> | |
</button> | |
<button id="historyBtn" title="History" class="p-1.5 rounded-full hover:bg-white/10 transition"> | |
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M12 8v4l3 3m7-7a9 9 0 11-18 0 9 9 0 0118 0z"/></svg> | |
</button> | |
</div> | |
<div class="flex items-center gap-2"> | |
<select id="modelSelect" class="bg-slate-900/80 text-sm border border-white/20 rounded px-2 py-1 outline-none"> | |
<option value="gpt-4o-mini">GPT-4o-mini</option> | |
<option value="gpt-4o">GPT-4o</option> | |
<option value="gpt-4-turbo">GPT-4-turbo</option> | |
<option value="claude-3-5-sonnet">Claude-3.5-Sonnet</option> | |
<option value="claude-3-5-haiku">Claude-3.5-Haiku</option> | |
<option value="claude-3-opus">Claude-3-Opus</option> | |
<option value="gemini-1.5-pro">Gemini-1.5-Pro</option> | |
<option value="gemini-1.5-flash">Gemini-1.5-Flash</option> | |
<option value="gemini-2-flash">Gemini-2-Flash</option> | |
<option value="llama-3.1-8b">Llama-3.1-8B</option> | |
<option value="llama-3.1-70b">Llama-3.1-70B</option> | |
<option value="llama-3.3-70b">Llama-3.3-70B</option> | |
<option value="mistral-large">Mistral-Large</option> | |
<option value="mistral-nemo">Mistral-Nemo</option> | |
<option value="command-r-plus">Cohere-Command-R+</option> | |
<option value="command-r">Cohere-Command-R</option> | |
<option value="gpt-3.5-turbo">GPT-3.5-Turbo</option> | |
<option value="claude-3-haiku">Claude-3-Haiku</option> | |
<option value="gemini-1.0-pro">Gemini-1.0-Pro</option> | |
<option value="llama-3-8b">Llama-3-8B</option> | |
<option value="mistral-7b">Mistral-7B</option> | |
<option value="command-light">Cohere-Command-Light</option> | |
</select> | |
<button id="settingsButton" class="p-1.5 rounded-full hover:bg-white/10 transition"> | |
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M12 1v6m0 6v6m9-9h-6m-6 0H3m15.364-6.364l-4.242 4.242m-4.242 4.242l-4.242 4.242m12.728 0l-4.242-4.242m-4.242-4.242l-4.242-4.242"/></svg> | |
</button> | |
<button id="lockButton" class="p-1.5 rounded-full hover:bg-white/10 transition"> | |
<svg id="lockIcon" class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><circle cx="12" cy="16" r="1"/><path d="M7 11V7a5 5 0 0110 0v4"/></svg> | |
<svg id="unlockIcon" class="w-5 h-5 hidden" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><circle cx="12" cy="16" r="1"/><path d="M7 11V7a5 5 0 019.9-1"/></svg> | |
</button> | |
</div> | |
</header> | |
<!-- Message list --> | |
<main id="chatWindow" class="flex-1 flex flex-col gap-3 p-3 overflow-y-auto text-sm"> | |
<!-- messages appear here --> | |
</main> | |
<!-- Input row --> | |
<footer class="border-t border-white/20 p-3"> | |
<div class="flex items-center gap-2"> | |
<button id="micBtn" class="p-2 rounded-full hover:bg-white/10 transition shrink-0"> | |
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M12 1a3 3 0 00-3 3v8a3 3 0 006 0V4a3 3 0 00-3-3z"/><path d="M19 10v2a7 7 0 01-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></svg> | |
</button> | |
<button id="attachBtn" class="p-2 rounded-full hover:bg-white/10 transition shrink-0"> | |
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66l-9.2 9.19a2 2 0 01-2.83-2.83l8.49-8.48"/></svg> | |
</button> | |
<div class="flex-1 relative"> | |
<input id="chatInput" type="text" placeholder="Type your message…" class="w-full bg-transparent border border-white/20 rounded-xl px-3 py-2 outline-none focus:border-white/40 transition"/> | |
</div> | |
<button id="sendBtn" class="p-2 rounded-full hover:bg-white/10 transition shrink-0"> | |
<svg class="w-5 h-5 rotate-90" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/></svg> | |
</button> | |
</div> | |
</footer> | |
<script> | |
const chatInput = document.getElementById('chatInput'); | |
const sendBtn = document.getElementById('sendBtn'); | |
const chatWindow = document.getElementById('chatWindow'); | |
sendBtn.addEventListener('click', () => { | |
const text = chatInput.value.trim(); | |
if (!text) return; | |
const msgDiv = document.createElement('div'); | |
msgDiv.className = 'self-end bg-sky-600/80 rounded-lg px-3 py-2 max-w-xs break-words'; | |
msgDiv.textContent = text; | |
chatWindow.appendChild(msgDiv); | |
chatInput.value = ''; | |
chatWindow.scrollTop = chatWindow.scrollHeight; | |
}); | |
chatInput.addEventListener('keydown', (e) => { | |
if (e.key === 'Enter' && !e.shiftKey) { | |
e.preventDefault(); | |
sendBtn.click(); | |
} | |
}); | |
</script> | |
<!-- History panel --> | |
<div id="historyPanel" class="hidden absolute top-12 left-3 w-64 max-h-60 border border-white/20 rounded-lg shadow-2xl z-20 overflow-y-auto text-sm"> | |
<ul class="divide-y divide-white/10"> | |
<li class="px-3 py-2 hover:bg-white/5 cursor-pointer">Chat 1 – Yesterday</li> | |
<li class="px-3 py-2 hover:bg-white/5 cursor-pointer">Chat 2 – 3 days ago</li> | |
</ul> | |
</div> | |
<!-- Resize handles --> | |
<!-- Corners --> | |
<div class="absolute top-0 left-0 w-3 h-3 cursor-nw-resize" data-corner="nw"></div> | |
<div class="absolute top-0 right-0 w-3 h-3 cursor-ne-resize" data-corner="ne"></div> | |
<div class="absolute bottom-0 right-0 w-3 h-3 cursor-se-resize" data-corner="se"></div> | |
<div class="absolute bottom-0 left-0 w-3 h-3 cursor-sw-resize" data-corner="sw"></div> | |
<!-- Edges --> | |
<div class="absolute top-0 left-3 right-3 h-2 cursor-n-resize" data-edge="n"></div> | |
<div class="absolute right-0 top-3 bottom-3 w-2 cursor-e-resize" data-edge="e"></div> | |
<div class="absolute bottom-0 left-3 right-3 h-2 cursor-s-resize" data-edge="s"></div> | |
<div class="absolute left-0 top-3 bottom-3 w-2 cursor-w-resize" data-edge="w"></div> | |
</div> | |
<!-- API Key Modal --> | |
<div id="apiModal" class="fixed inset-0 bg-black/50 z-30 flex items-center justify-center hidden"> | |
<div class="bg-slate-800 rounded-lg p-6 w-80 text-white"> | |
<h2 class="text-base font-semibold mb-2">Enter API Key</h2> | |
<p id="apiModelName" class="text-sm text-slate-300 mb-3"></p> | |
<input id="apiKeyInput" type="password" placeholder="Paste your API key here…" | |
class="w-full bg-slate-700 border border-slate-600 rounded px-3 py-2 mb-3 text-sm outline-none focus:border-sky-500"> | |
<div class="flex justify-end gap-2"> | |
<button id="apiCancel" class="px-3 py-1 text-sm rounded bg-slate-600 hover:bg-slate-500">Cancel</button> | |
<button id="apiSave" class="px-3 py-1 text-sm rounded bg-sky-600 hover:bg-sky-500">Save</button> | |
</div> | |
<p id="apiSavedMsg" class="hidden text-xs text-green-400 mt-2">✓ API saved successfully</p> | |
</div> | |
</div> | |
<!-- Tiny Settings Panel --> | |
<div id="settingsPanel"> | |
<label> | |
Opacity | |
<input type="range" id="opacitySlider" min="0" max="1" step="0.05" value="1"> | |
</label> | |
<label> | |
Shadow Blur | |
<input type="range" id="shadowSlider" min="0" max="50" step="1" value="0"> | |
</label> | |
<label> | |
Corner Curve | |
<input type="range" id="cornerSlider" min="0" max="50" step="1" value="24"> | |
</label> | |
<label> | |
Colour Gradient | |
<div id="colourPalette"> | |
<button class="colourPellet" style="background:#ff0055"></button> | |
<button class="colourPellet" style="background:#ff6b35"></button> | |
<button class="colourPellet" style="background:#ffcf40"></button> | |
<button class="colourPellet" style="background:#44d62c"></button> | |
<button class="colourPellet" style="background:#00bfff"></button> | |
<button class="colourPellet" style="background:#0047ab"></button> | |
<button class="colourPellet" style="background:#9d00ff"></button> | |
<button class="colourPellet" style="background:#ff00ff"></button> | |
<button class="colourPellet" style="background:#fd79a8"></button> | |
<button class="colourPellet" style="background:#a29bfe"></button> | |
</div> | |
</label> | |
</div> | |
<script> | |
/* ---------- DRAGGABLE & RESIZABLE CARD ---------- */ | |
const card = document.getElementById('draggableCard'); | |
const resizeHandles = document.querySelectorAll('[data-corner], [data-edge]'); | |
// Dragging variables | |
let isDragging = false; | |
let isResizing = false; | |
let startMouseX, startMouseY, startLeft, startTop; | |
// Resizing variables | |
let startWidth, startHeight, startX, startY, resizeDirection; | |
const lockButton = document.getElementById('lockButton'); | |
const lockIcon = document.getElementById('lockIcon'); | |
const unlockIcon = document.getElementById('unlockIcon'); | |
let isLocked = false; | |
card.addEventListener('mousedown', dragStart); | |
resizeHandles.forEach(h => h.addEventListener('mousedown', resizeStart)); | |
document.addEventListener('mousemove', dragMove); | |
document.addEventListener('mouseup', dragEnd); | |
lockButton.addEventListener('click', () => { | |
isLocked = !isLocked; | |
const handles = document.querySelectorAll('[data-corner], [data-edge]'); | |
if (isLocked) { | |
lockIcon.classList.add('hidden'); | |
unlockIcon.classList.remove('hidden'); | |
card.style.cursor = 'default'; | |
handles.forEach(h => h.style.display = 'none'); | |
} else { | |
lockIcon.classList.remove('hidden'); | |
unlockIcon.classList.add('hidden'); | |
card.style.cursor = 'grab'; | |
handles.forEach(h => h.style.display = 'block'); | |
} | |
}); | |
function dragStart(e) { | |
if (isLocked || e.target.tagName === 'BUTTON') return; | |
const rect = card.getBoundingClientRect(); | |
// Remove centering transform once at start | |
if (card.style.transform.includes('translate')) { | |
card.style.transform = 'none'; | |
card.style.left = `${rect.left}px`; | |
card.style.top = `${rect.top}px`; | |
} | |
startMouseX = e.clientX; | |
startMouseY = e.clientY; | |
startLeft = parseFloat(card.style.left) || rect.left; | |
startTop = parseFloat(card.style.top) || rect.top; | |
isDragging = true; | |
} | |
function resizeStart(e) { | |
if (isLocked) return; | |
e.stopPropagation(); | |
const rect = card.getBoundingClientRect(); | |
// Remove centering transform once at start | |
if (card.style.transform.includes('translate')) { | |
card.style.transform = 'none'; | |
card.style.left = `${rect.left}px`; | |
card.style.top = `${rect.top}px`; | |
} | |
startX = e.clientX; | |
startY = e.clientY; | |
startLeft = parseFloat(card.style.left) || rect.left; | |
startTop = parseFloat(card.style.top) || rect.top; | |
startWidth = parseInt(getComputedStyle(card).width, 10); | |
startHeight = parseInt(getComputedStyle(card).height, 10); | |
resizeDirection = e.target.dataset.corner || e.target.dataset.edge; | |
isResizing = true; | |
} | |
function dragMove(e) { | |
if (!isLocked && isDragging) { | |
e.preventDefault(); | |
const dx = e.clientX - startMouseX; | |
const dy = e.clientY - startMouseY; | |
card.style.left = `${startLeft + dx}px`; | |
card.style.top = `${startTop + dy}px`; | |
} else if (!isLocked && isResizing) { | |
e.preventDefault(); | |
// Remove transform-centering on first resize | |
if (card.style.transform.includes('translate')) { | |
const rect = card.getBoundingClientRect(); | |
card.style.transform = 'none'; | |
card.style.left = `${rect.left}px`; | |
card.style.top = `${rect.top}px`; | |
} | |
let newWidth = startWidth; | |
let newHeight = startHeight; | |
let newLeft = startLeft; | |
let newTop = startTop; | |
const dx = e.clientX - startX; | |
const dy = e.clientY - startY; | |
if (resizeDirection.includes('e')) newWidth = startWidth + dx; | |
if (resizeDirection.includes('w')) { | |
newWidth = startWidth - dx; | |
newLeft = startLeft + dx; | |
} | |
if (resizeDirection.includes('s')) newHeight = startHeight + dy; | |
if (resizeDirection.includes('n')) { | |
newHeight = startHeight - dy; | |
newTop = startTop + dy; | |
} | |
newWidth = Math.max(300, newWidth); | |
newHeight = Math.max(250, newHeight); | |
card.style.width = `${newWidth}px`; | |
card.style.height = `${newHeight}px`; | |
card.style.left = `${newLeft}px`; | |
card.style.top = `${newTop}px`; | |
} | |
} | |
function dragEnd() { | |
isDragging = false; | |
isResizing = false; | |
} | |
/* ---------- TINY SETTINGS ---------- */ | |
const settingsButton = document.getElementById('settingsButton'); | |
const settingsPanel = document.getElementById('settingsPanel'); | |
settingsButton.addEventListener('click', () => { | |
settingsPanel.style.display = settingsPanel.style.display === 'flex' ? 'none' : 'flex'; | |
}); | |
/* ---------- API-KEY MODAL LOGIC ---------- */ | |
const modelSelect = document.getElementById('modelSelect'); | |
const apiModal = document.getElementById('apiModal'); | |
const apiModelName= document.getElementById('apiModelName'); | |
const apiKeyInput = document.getElementById('apiKeyInput'); | |
const apiSaveBtn = document.getElementById('apiSave'); | |
const apiCancelBtn= document.getElementById('apiCancel'); | |
const apiSavedMsg = document.getElementById('apiSavedMsg'); | |
modelSelect.addEventListener('change', () => { | |
apiModelName.textContent = `Model: ${modelSelect.value}`; | |
apiKeyInput.value = ''; | |
apiSavedMsg.classList.add('hidden'); | |
apiModal.classList.remove('hidden'); | |
}); | |
apiSaveBtn.addEventListener('click', () => { | |
localStorage.setItem(`api-${modelSelect.value}`, apiKeyInput.value.trim()); | |
apiSavedMsg.classList.remove('hidden'); | |
setTimeout(() => apiModal.classList.add('hidden'), 800); | |
}); | |
apiCancelBtn.addEventListener('click', () => apiModal.classList.add('hidden')); | |
// Persist current size & position as the default | |
const cardRect = card.getBoundingClientRect(); | |
document.documentElement.style.setProperty('--default-left', `${cardRect.left}px`); | |
document.documentElement.style.setProperty('--default-top', `${cardRect.top}px`); | |
document.documentElement.style.setProperty('--default-width', `${cardRect.width}px`); | |
document.documentElement.style.setProperty('--default-height', `${cardRect.height}px`); | |
card.style.left = `${cardRect.left}px`; | |
card.style.top = `${cardRect.top}px`; | |
card.style.transform = 'none'; | |
/* ---------- CONFIG ---------- */ | |
const CONFIG = { | |
particleCount: 120, | |
particleRadius: () => Math.random() * 2.5 + 1, // 1–3.5 | |
speed: () => Math.random() * 0.4 + 0.1, // 0.1–0.5 | |
hueShiftSpeed: 0.8, | |
}; | |
/* ---------- CANVAS ---------- */ | |
const canvas = document.getElementById('particleCanvas'); | |
const ctx = canvas.getContext('2d'); | |
let width, height, particles = []; | |
let hue = 200; // Start on a blue tone instead of yellow | |
/* ---------- RESIZE HANDLER ---------- */ | |
function resize() { | |
width = canvas.width = window.innerWidth; | |
height = canvas.height = window.innerHeight; | |
if (particles.length === 0) initParticles(); | |
} | |
/* ---------- PARTICLE ---------- */ | |
class Particle { | |
constructor() { | |
this.reset(); | |
this.hue = Math.random() * 360; | |
this.saturation = 75 + Math.random() * 25; | |
this.lightness = 50 + Math.random() * 30; | |
this.shiny = Math.random() > 0.7; | |
this.blurred = Math.random() > 0.8; | |
} | |
reset() { | |
this.x = Math.random() * width; | |
this.y = Math.random() * height; | |
this.radius = CONFIG.particleRadius(); | |
this.speed = CONFIG.speed(); | |
this.angle = Math.random() * Math.PI * 2; | |
} | |
update() { | |
this.x += Math.cos(this.angle) * this.speed; | |
this.y += Math.sin(this.angle) * this.speed; | |
// Wrap around edges | |
if (this.x < -this.radius) this.x = width + this.radius; | |
if (this.x > width + this.radius) this.x = -this.radius; | |
if (this.y < -this.radius) this.y = height + this.radius; | |
if (this.y > height + this.radius) this.y = -this.radius; | |
} | |
draw() { | |
ctx.save(); | |
if (this.blurred) { | |
ctx.filter = 'blur(2px)'; | |
} | |
if (this.shiny) { | |
ctx.shadowBlur = 10; | |
ctx.shadowColor = `hsl(${this.hue}, ${this.saturation}%, ${this.lightness + 20}%)`; | |
} | |
ctx.beginPath(); | |
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); | |
ctx.fillStyle = `hsl(${this.hue}, ${this.saturation}%, ${this.lightness}%)`; | |
ctx.fill(); | |
ctx.restore(); | |
} | |
} | |
/* ---------- INITIALIZE PARTICLES ---------- */ | |
function initParticles() { | |
particles = []; | |
for (let i = 0; i < CONFIG.particleCount; i++) { | |
particles.push(new Particle()); | |
} | |
} | |
/* ---------- ANIMATION LOOP ---------- */ | |
function animate() { | |
ctx.clearRect(0, 0, width, height); | |
particles.forEach(p => { | |
p.update(); | |
p.draw(); | |
}); | |
requestAnimationFrame(animate); | |
} | |
/* ---------- EVENT LISTENERS ---------- */ | |
window.addEventListener('resize', resize); | |
/* ---------- START ---------- */ | |
resize(); | |
animate(); | |
/* ---------- Settings Controls ---------- */ | |
const opacitySlider = document.getElementById('opacitySlider'); | |
const shadowSlider = document.getElementById('shadowSlider'); | |
const cornerSlider = document.getElementById('cornerSlider'); | |
const colourPalette = document.getElementById('colourPalette'); | |
opacitySlider.addEventListener('input', () => { | |
card.style.opacity = opacitySlider.value; | |
}); | |
shadowSlider.addEventListener('input', () => { | |
card.style.boxShadow = `0 0 ${shadowSlider.value}px rgba(255,255,255,0.3)`; | |
}); | |
cornerSlider.addEventListener('input', () => { | |
card.style.borderRadius = cornerSlider.value + 'px'; | |
}); | |
colourPalette.addEventListener('click', (e) => { | |
if (e.target.classList.contains('colourPellet')) { | |
const colour = e.target.style.background; | |
card.style.background = `linear-gradient(135deg, ${colour}, rgba(0,0,0,0.8))`; | |
} | |
}); | |
/* ---------- ORB DRAG ---------- */ | |
const orb = document.getElementById('orb'); | |
let orbDragging = false; | |
let orbStartX, orbStartY, orbStartLeft, orbStartTop; | |
orb.addEventListener('mousedown', (e) => { | |
orbDragging = true; | |
orbStartX = e.clientX; | |
orbStartY = e.clientY; | |
const rect = orb.getBoundingClientRect(); | |
// remove centering | |
if (orb.style.transform.includes('translate')) { | |
orb.style.transform = 'none'; | |
orb.style.left = `${rect.left}px`; | |
orb.style.top = `${rect.top}px`; | |
} | |
orbStartLeft = parseFloat(orb.style.left) || rect.left; | |
orbStartTop = parseFloat(orb.style.top) || rect.top; | |
e.preventDefault(); | |
}); | |
document.addEventListener('mousemove', (e) => { | |
if (!orbDragging) return; | |
const dx = e.clientX - orbStartX; | |
const dy = e.clientY - orbStartY; | |
orb.style.left = `${orbStartLeft + dx}px`; | |
orb.style.top = `${orbStartTop + dy}px`; | |
}); | |
document.addEventListener('mouseup', () => { | |
orbDragging = false; | |
}); | |
</script> | |
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=basheer1414/aiui" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |