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> |