|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>INFERNO - Uncensored AI Image Generator</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
|
<style> |
|
|
:root { |
|
|
--primary: #ff2d2d; |
|
|
--primary-dark: #cc0000; |
|
|
--secondary: #ff5e5e; |
|
|
--accent: #ff9a9a; |
|
|
--dark: #1a1a1a; |
|
|
--light: #f5f5f5; |
|
|
--danger: #ff3860; |
|
|
--warning: #ffdd57; |
|
|
--success: #23d160; |
|
|
} |
|
|
|
|
|
body { |
|
|
background-color: var(--dark); |
|
|
color: var(--light); |
|
|
font-family: 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; |
|
|
} |
|
|
|
|
|
.inferno-bg { |
|
|
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%); |
|
|
} |
|
|
|
|
|
.model-card { |
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
|
|
border: 2px solid transparent; |
|
|
} |
|
|
|
|
|
.model-card:hover { |
|
|
transform: translateY(-3px); |
|
|
box-shadow: 0 10px 25px rgba(255, 45, 45, 0.3); |
|
|
border-color: var(--primary); |
|
|
} |
|
|
|
|
|
.model-card.selected { |
|
|
background-color: rgba(255, 45, 45, 0.1); |
|
|
border-color: var(--primary); |
|
|
} |
|
|
|
|
|
.tab-active { |
|
|
border-bottom: 3px solid var(--primary); |
|
|
font-weight: 700; |
|
|
color: var(--light); |
|
|
} |
|
|
|
|
|
.image-placeholder { |
|
|
background: linear-gradient(45deg, #2a2a2a 25%, #333 25%, #333 50%, #2a2a2a 50%, #2a2a2a 75%, #333 75%, #333 100%); |
|
|
background-size: 20px 20px; |
|
|
} |
|
|
|
|
|
.prompt-textarea:focus { |
|
|
box-shadow: 0 0 0 2px rgba(255, 45, 45, 0.5); |
|
|
border-color: var(--primary); |
|
|
} |
|
|
|
|
|
.slider-thumb::-webkit-slider-thumb { |
|
|
-webkit-appearance: none; |
|
|
appearance: none; |
|
|
width: 18px; |
|
|
height: 18px; |
|
|
border-radius: 50%; |
|
|
background: var(--primary); |
|
|
cursor: pointer; |
|
|
border: 2px solid var(--light); |
|
|
} |
|
|
|
|
|
.slider-thumb::-moz-range-thumb { |
|
|
width: 18px; |
|
|
height: 18px; |
|
|
border-radius: 50%; |
|
|
background: var(--primary); |
|
|
cursor: pointer; |
|
|
border: 2px solid var(--light); |
|
|
} |
|
|
|
|
|
.nsfw-toggle { |
|
|
position: relative; |
|
|
display: inline-block; |
|
|
width: 60px; |
|
|
height: 30px; |
|
|
} |
|
|
|
|
|
.nsfw-toggle input { |
|
|
opacity: 0; |
|
|
width: 0; |
|
|
height: 0; |
|
|
} |
|
|
|
|
|
.nsfw-slider { |
|
|
position: absolute; |
|
|
cursor: pointer; |
|
|
top: 0; |
|
|
left: 0; |
|
|
right: 0; |
|
|
bottom: 0; |
|
|
background-color: #ccc; |
|
|
transition: .4s; |
|
|
border-radius: 30px; |
|
|
} |
|
|
|
|
|
.nsfw-slider:before { |
|
|
position: absolute; |
|
|
content: ""; |
|
|
height: 22px; |
|
|
width: 22px; |
|
|
left: 4px; |
|
|
bottom: 4px; |
|
|
background-color: white; |
|
|
transition: .4s; |
|
|
border-radius: 50%; |
|
|
} |
|
|
|
|
|
input:checked + .nsfw-slider { |
|
|
background-color: var(--danger); |
|
|
} |
|
|
|
|
|
input:checked + .nsfw-slider:before { |
|
|
transform: translateX(30px); |
|
|
} |
|
|
|
|
|
.error-toast { |
|
|
animation: slideIn 0.5s, fadeOut 0.5s 2.5s forwards; |
|
|
} |
|
|
|
|
|
@keyframes slideIn { |
|
|
from { transform: translateX(100%); } |
|
|
to { transform: translateX(0); } |
|
|
} |
|
|
|
|
|
@keyframes fadeOut { |
|
|
to { opacity: 0; } |
|
|
} |
|
|
|
|
|
.pagination-btn.active { |
|
|
background-color: var(--primary); |
|
|
color: white; |
|
|
} |
|
|
|
|
|
.loading-spinner { |
|
|
animation: spin 1s linear infinite; |
|
|
} |
|
|
|
|
|
@keyframes spin { |
|
|
0% { transform: rotate(0deg); } |
|
|
100% { transform: rotate(360deg); } |
|
|
} |
|
|
|
|
|
.nsfw-badge { |
|
|
animation: pulse 2s infinite; |
|
|
} |
|
|
|
|
|
@keyframes pulse { |
|
|
0% { box-shadow: 0 0 0 0 rgba(255, 56, 96, 0.7); } |
|
|
70% { box-shadow: 0 0 0 10px rgba(255, 56, 96, 0); } |
|
|
100% { box-shadow: 0 0 0 0 rgba(255, 56, 96, 0); } |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-gray-900 text-gray-100"> |
|
|
|
|
|
<div id="toast-container" class="fixed bottom-4 right-4 space-y-2 z-50"></div> |
|
|
|
|
|
|
|
|
<header class="inferno-bg text-white py-4 shadow-lg sticky top-0 z-40"> |
|
|
<div class="container mx-auto px-4"> |
|
|
<div class="flex items-center justify-between"> |
|
|
<div class="flex items-center space-x-3"> |
|
|
<i class="fas fa-fire text-3xl"></i> |
|
|
<h1 class="text-2xl font-bold">INFERNO</h1> |
|
|
<span class="nsfw-badge bg-red-600 text-xs px-2 py-1 rounded-full ml-2">NSFW</span> |
|
|
</div> |
|
|
<div class="flex items-center space-x-4"> |
|
|
<button id="dark-mode-toggle" class="p-2 rounded-full hover:bg-red-800 transition"> |
|
|
<i class="fas fa-moon"></i> |
|
|
</button> |
|
|
<div class="relative group"> |
|
|
<button class="flex items-center space-x-2 bg-red-700 hover:bg-red-800 px-4 py-2 rounded-lg transition"> |
|
|
<i class="fas fa-user"></i> |
|
|
<span>Account</span> |
|
|
</button> |
|
|
<div class="absolute right-0 mt-2 w-48 bg-gray-800 rounded-lg shadow-lg py-1 z-50 hidden group-hover:block"> |
|
|
<a href="#" class="block px-4 py-2 hover:bg-gray-700">Profile</a> |
|
|
<a href="#" class="block px-4 py-2 hover:bg-gray-700">Settings</a> |
|
|
<a href="#" class="block px-4 py-2 hover:bg-gray-700">Sign out</a> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</header> |
|
|
|
|
|
|
|
|
<main class="container mx-auto px-4 py-8"> |
|
|
<div class="bg-gray-800 rounded-xl shadow-2xl overflow-hidden"> |
|
|
|
|
|
<div class="flex border-b border-gray-700"> |
|
|
<button id="multi-model-tab" class="tab-active px-6 py-4 text-white font-bold flex items-center"> |
|
|
<i class="fas fa-layer-group mr-2"></i> Multi-Model Inferno |
|
|
</button> |
|
|
<button id="single-model-tab" class="px-6 py-4 text-gray-400 font-medium flex items-center hover:text-white transition"> |
|
|
<i class="fas fa-cube mr-2"></i> Single Model |
|
|
</button> |
|
|
<button id="batch-tab" class="px-6 py-4 text-gray-400 font-medium flex items-center hover:text-white transition"> |
|
|
<i class="fas fa-clone mr-2"></i> Batch Mode |
|
|
</button> |
|
|
<button id="settings-tab" class="px-6 py-4 text-gray-400 font-medium flex items-center ml-auto hover:text-white transition"> |
|
|
<i class="fas fa-cog mr-2"></i> Settings |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="multi-model-content" class="p-6"> |
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> |
|
|
|
|
|
<div class="lg:col-span-2 space-y-6"> |
|
|
|
|
|
<div class="flex items-center justify-between bg-gray-700 p-4 rounded-lg"> |
|
|
<div class="flex items-center space-x-2"> |
|
|
<i class="fas fa-exclamation-triangle text-red-500"></i> |
|
|
<span class="font-medium">NSFW Mode</span> |
|
|
</div> |
|
|
<label class="nsfw-toggle"> |
|
|
<input type="checkbox" id="nsfw-toggle" checked> |
|
|
<span class="nsfw-slider"></span> |
|
|
</label> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-gray-700 p-4 rounded-lg"> |
|
|
<div class="flex items-center justify-between mb-2"> |
|
|
<label class="block text-white font-medium">Your Raw Prompt</label> |
|
|
<button id="random-prompt" class="text-xs bg-gray-600 hover:bg-gray-500 px-2 py-1 rounded transition"> |
|
|
<i class="fas fa-dice mr-1"></i> Randomize |
|
|
</button> |
|
|
</div> |
|
|
<textarea id="prompt-input" class="prompt-textarea w-full h-32 px-4 py-3 bg-gray-800 border border-gray-600 rounded-lg focus:outline-none focus:border-red-500 text-white" placeholder="Let your imagination run wild... describe exactly what you want to see, no matter how depraved or exquisite...">A cybernetic dominatrix with glowing crimson eyes and liquid metal skin stands atop a neon-lit skyscraper, her whip crackling with electricity as the city burns below, ultra-detailed, hyper-realistic, 8K</textarea> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-gray-700 p-4 rounded-lg"> |
|
|
<label class="block text-white font-medium mb-2">Negative Prompt</label> |
|
|
<textarea id="negative-prompt" class="w-full px-4 py-3 bg-gray-800 border border-gray-600 rounded-lg focus:outline-none focus:border-red-500 text-white" placeholder="What you don't want to see...">[deformed | disfigured], poorly drawn, [bad : wrong] anatomy, [extra | missing | floating | disconnected] limb, (mutated hands and fingers), blurry, text, fuzziness, censored, low quality, artifacts</textarea> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-gray-700 p-4 rounded-lg"> |
|
|
<div class="flex items-center justify-between cursor-pointer" id="advanced-settings-toggle"> |
|
|
<h3 class="text-white font-medium">Advanced Settings</h3> |
|
|
<i class="fas fa-chevron-down text-gray-400 transition-transform duration-300"></i> |
|
|
</div> |
|
|
<div id="advanced-settings-content" class="mt-4 hidden space-y-4"> |
|
|
|
|
|
<div class="grid grid-cols-2 gap-4"> |
|
|
<div> |
|
|
<label class="block text-gray-300 text-sm mb-1">Width</label> |
|
|
<div class="flex items-center"> |
|
|
<input type="range" min="256" max="1216" step="32" value="768" class="slider-thumb w-full h-2 bg-gray-600 rounded-lg appearance-none cursor-pointer"> |
|
|
<span id="width-value" class="ml-3 text-white w-16 text-center">768</span> |
|
|
</div> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-gray-300 text-sm mb-1">Height</label> |
|
|
<div class="flex items-center"> |
|
|
<input type="range" min="256" max="1216" step="32" value="768" class="slider-thumb w-full h-2 bg-gray-600 rounded-lg appearance-none cursor-pointer"> |
|
|
<span id="height-value" class="ml-3 text-white w-16 text-center">768</span> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="grid grid-cols-2 gap-4"> |
|
|
<div> |
|
|
<label class="block text-gray-300 text-sm mb-1">Inference Steps</label> |
|
|
<div class="flex items-center"> |
|
|
<input type="range" min="1" max="100" step="1" value="30" class="slider-thumb w-full h-2 bg-gray-600 rounded-lg appearance-none cursor-pointer"> |
|
|
<span id="steps-value" class="ml-3 text-white w-16 text-center">30</span> |
|
|
</div> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-gray-300 text-sm mb-1">Guidance Scale</label> |
|
|
<div class="flex items-center"> |
|
|
<input type="range" min="1" max="30" step="0.1" value="7.5" class="slider-thumb w-full h-2 bg-gray-600 rounded-lg appearance-none cursor-pointer"> |
|
|
<span id="cfg-value" class="ml-3 text-white w-16 text-center">7.5</span> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div> |
|
|
<label class="block text-gray-300 text-sm mb-1">Seed</label> |
|
|
<div class="flex items-center"> |
|
|
<input type="number" id="seed-input" class="w-full px-3 py-2 bg-gray-800 border border-gray-600 rounded-lg focus:outline-none focus:border-red-500 text-white" value="-1"> |
|
|
<button id="randomize-seed" class="ml-3 bg-gray-600 hover:bg-gray-500 text-white px-4 py-2 rounded-lg transition"> |
|
|
<i class="fas fa-random mr-2"></i> Randomize |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div> |
|
|
<label class="block text-gray-300 text-sm mb-1">Sampler</label> |
|
|
<select id="sampler-select" class="w-full px-3 py-2 bg-gray-800 border border-gray-600 rounded-lg focus:outline-none focus:border-red-500 text-white"> |
|
|
<option value="euler_a">Euler a</option> |
|
|
<option value="euler">Euler</option> |
|
|
<option value="lms">LMS</option> |
|
|
<option value="heun">Heun</option> |
|
|
<option value="dpm2">DPM2</option> |
|
|
<option value="dpm2_a">DPM2 a</option> |
|
|
<option value="dpmpp_2s_a">DPM++ 2S a</option> |
|
|
<option value="dpmpp_2m">DPM++ 2M</option> |
|
|
<option value="dpmpp_sde">DPM++ SDE</option> |
|
|
<option value="ddim">DDIM</option> |
|
|
</select> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-gray-700 p-4 rounded-lg"> |
|
|
<div class="flex items-center justify-between mb-2"> |
|
|
<label class="block text-white font-medium">Select Models (Max 4)</label> |
|
|
<span id="selected-count" class="text-xs bg-red-600 px-2 py-1 rounded">4/4 selected</span> |
|
|
</div> |
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3"> |
|
|
|
|
|
<div class="model-card bg-gray-800 rounded-lg p-3 cursor-pointer"> |
|
|
<div class="flex items-center"> |
|
|
<input type="checkbox" class="mr-2 model-checkbox" checked data-model="sd15"> |
|
|
<div> |
|
|
<h4 class="font-medium text-white">Stable Diffusion 1.5</h4> |
|
|
<p class="text-xs text-gray-400">512x512 resolution</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="model-card bg-gray-800 rounded-lg p-3 cursor-pointer"> |
|
|
<div class="flex items-center"> |
|
|
<input type="checkbox" class="mr-2 model-checkbox" checked data-model="sd21"> |
|
|
<div> |
|
|
<h4 class="font-medium text-white">Stable Diffusion 2.1</h4> |
|
|
<p class="text-xs text-gray-400">768x768 resolution</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="model-card bg-gray-800 rounded-lg p-3 cursor-pointer"> |
|
|
<div class="flex items-center"> |
|
|
<input type="checkbox" class="mr-2 model-checkbox" data-model="openjourney"> |
|
|
<div> |
|
|
<h4 class="font-medium text-white">OpenJourney</h4> |
|
|
<p class="text-xs text-gray-400">Artistic style</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="model-card bg-gray-800 rounded-lg p-3 cursor-pointer"> |
|
|
<div class="flex items-center"> |
|
|
<input type="checkbox" class="mr-2 model-checkbox" data-model="dreamlike"> |
|
|
<div> |
|
|
<h4 class="font-medium text-white">Dreamlike</h4> |
|
|
<p class="text-xs text-gray-400">Photorealistic</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="model-card bg-gray-800 rounded-lg p-3 cursor-pointer"> |
|
|
<div class="flex items-center"> |
|
|
<input type="checkbox" class="mr-2 model-checkbox" data-model="anything"> |
|
|
<div> |
|
|
<h4 class="font-medium text-white">Anything V3</h4> |
|
|
<p class="text-xs text-gray-400">Anime style</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="model-card bg-gray-800 rounded-lg p-3 cursor-pointer"> |
|
|
<div class="flex items-center"> |
|
|
<input type="checkbox" class="mr-2 model-checkbox" data-model="deliberate"> |
|
|
<div> |
|
|
<h4 class="font-medium text-white">Deliberate</h4> |
|
|
<p class="text-xs text-gray-400">Detailed images</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="model-card bg-gray-800 rounded-lg p-3 cursor-pointer"> |
|
|
<div class="flex items-center"> |
|
|
<input type="checkbox" class="mr-2 model-checkbox" data-model="realistic"> |
|
|
<div> |
|
|
<h4 class="font-medium text-white">Realistic Vision</h4> |
|
|
<p class="text-xs text-gray-400">Hyper-realistic</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="model-card bg-gray-800 rounded-lg p-3 cursor-pointer"> |
|
|
<div class="flex items-center"> |
|
|
<input type="checkbox" class="mr-2 model-checkbox" data-model="hentai"> |
|
|
<div> |
|
|
<h4 class="font-medium text-white">Hentai Diffusion</h4> |
|
|
<p class="text-xs text-gray-400">NSFW content</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="model-card bg-gray-800 rounded-lg p-3 cursor-pointer"> |
|
|
<div class="flex items-center"> |
|
|
<input type="checkbox" class="mr-2 model-checkbox" data-model="waifu"> |
|
|
<div> |
|
|
<h4 class="font-medium text-white">Waifu Diffusion</h4> |
|
|
<p class="text-xs text-gray-400">Anime characters</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="mt-3 flex justify-between"> |
|
|
<button id="random-models" class="text-red-400 hover:text-red-300 text-sm font-medium"> |
|
|
<i class="fas fa-random mr-1"></i> Randomize Models |
|
|
</button> |
|
|
<button id="clear-models" class="text-gray-400 hover:text-gray-300 text-sm font-medium"> |
|
|
<i class="fas fa-times mr-1"></i> Clear Selection |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="space-y-6"> |
|
|
|
|
|
<button id="generate-button" class="w-full bg-red-600 hover:bg-red-700 text-white font-bold py-3 px-4 rounded-lg transition flex items-center justify-center"> |
|
|
<i class="fas fa-fire mr-2"></i> IGNITE CREATION |
|
|
</button> |
|
|
|
|
|
|
|
|
<div class="bg-gray-700 p-4 rounded-lg"> |
|
|
<div class="grid grid-cols-3 gap-2 text-center"> |
|
|
<div> |
|
|
<div class="text-xs text-gray-400">Queue</div> |
|
|
<div id="queue-count" class="font-bold">0</div> |
|
|
</div> |
|
|
<div> |
|
|
<div class="text-xs text-gray-400">ETA</div> |
|
|
<div id="eta-time" class="font-bold">-</div> |
|
|
</div> |
|
|
<div> |
|
|
<div class="text-xs text-gray-400">Cost</div> |
|
|
<div id="generation-cost" class="font-bold">0.00</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-gray-700 p-4 rounded-lg"> |
|
|
<h3 class="text-white font-medium mb-3">Model Outputs</h3> |
|
|
<div class="grid grid-cols-2 gap-3"> |
|
|
|
|
|
<div id="output-1" class="image-placeholder rounded-lg h-40 flex items-center justify-center relative"> |
|
|
<span class="text-gray-400">Stable Diffusion 1.5</span> |
|
|
<div class="absolute bottom-2 right-2 flex space-x-1"> |
|
|
<button class="download-btn hidden bg-gray-800 hover:bg-gray-700 text-white p-1 rounded-full w-7 h-7 flex items-center justify-center"> |
|
|
<i class="fas fa-download text-xs"></i> |
|
|
</button> |
|
|
<button class="upscale-btn hidden bg-red-600 hover:bg-red-700 text-white p-1 rounded-full w-7 h-7 flex items-center justify-center"> |
|
|
<i class="fas fa-expand text-xs"></i> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
<div id="output-2" class="image-placeholder rounded-lg h-40 flex items-center justify-center relative"> |
|
|
<span class="text-gray-400">Stable Diffusion 2.1</span> |
|
|
<div class="absolute bottom-2 right-2 flex space-x-1"> |
|
|
<button class="download-btn hidden bg-gray-800 hover:bg-gray-700 text-white p-1 rounded-full w-7 h-7 flex items-center justify-center"> |
|
|
<i class="fas fa-download text-xs"></i> |
|
|
</button> |
|
|
<button class="upscale-btn hidden bg-red-600 hover:bg-red-700 text-white p-1 rounded-full w-7 h-7 flex items-center justify-center"> |
|
|
<i class="fas fa-expand text-xs"></i> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
<div id="output-3" class="image-placeholder rounded-lg h-40 flex items-center justify-center relative"> |
|
|
<span class="text-gray-400">Output 3</span> |
|
|
<div class="absolute bottom-2 right-2 flex space-x-1"> |
|
|
<button class="download-btn hidden bg-gray-800 hover:bg-gray-700 text-white p-1 rounded-full w-7 h-7 flex items-center justify-center"> |
|
|
<i class="fas fa-download text-xs"></i> |
|
|
</button> |
|
|
<button class="upscale-btn hidden bg-red-600 hover:bg-red-700 text-white p-1 rounded-full w-7 h-7 flex items-center justify-center"> |
|
|
<i class="fas fa-expand text-xs"></i> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
<div id="output-4" class="image-placeholder rounded-lg h-40 flex items-center justify-center relative"> |
|
|
<span class="text-gray-400">Output 4</span> |
|
|
<div class="absolute bottom-2 right-2 flex space-x-1"> |
|
|
<button class="download-btn hidden bg-gray-800 hover:bg-gray-700 text-white p-1 rounded-full w-7 h-7 flex items-center justify-center"> |
|
|
<i class="fas fa-download text-xs"></i> |
|
|
</button> |
|
|
<button class="upscale-btn hidden bg-red-600 hover:bg-red-700 text-white p-1 rounded-full w-7 h-7 flex items-center justify-center"> |
|
|
<i class="fas fa-expand text-xs"></i> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-gray-700 p-4 rounded-lg"> |
|
|
<div class="flex items-center justify-between mb-3"> |
|
|
<h3 class="text-white font-medium">Your Inferno Gallery</h3> |
|
|
<div class="flex space-x-2"> |
|
|
<button id="clear-gallery" class="text-gray-400 hover:text-gray-300 text-sm font-medium"> |
|
|
<i class="fas fa-trash-alt mr-1"></i> Clear |
|
|
</button> |
|
|
<button id="download-all" class="text-gray-400 hover:text-gray-300 text-sm font-medium"> |
|
|
<i class="fas fa-download mr-1"></i> Download All |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
<div id="gallery-container" class="grid grid-cols-2 gap-2"> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div id="gallery-pagination" class="mt-4 flex justify-center space-x-1 hidden"> |
|
|
<button class="pagination-btn px-3 py-1 rounded bg-gray-600 hover:bg-gray-500">1</button> |
|
|
<button class="pagination-btn px-3 py-1 rounded bg-gray-600 hover:bg-gray-500">2</button> |
|
|
<button class="pagination-btn px-3 py-1 rounded bg-gray-600 hover:bg-gray-500">3</button> |
|
|
<span class="px-3 py-1">...</span> |
|
|
<button class="pagination-btn px-3 py-1 rounded bg-gray-600 hover:bg-gray-500">10</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="single-model-content" class="p-6 hidden"> |
|
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="batch-content" class="p-6 hidden"> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="settings-content" class="p-6 hidden"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</main> |
|
|
|
|
|
|
|
|
<footer class="bg-gray-800 py-6 mt-12"> |
|
|
<div class="container mx-auto px-4"> |
|
|
<div class="flex flex-col md:flex-row justify-between items-center"> |
|
|
<div class="flex items-center space-x-2 mb-4 md:mb-0"> |
|
|
<i class="fas fa-fire text-red-500"></i> |
|
|
<span class="font-bold">INFERNO AI</span> |
|
|
</div> |
|
|
<div class="flex space-x-6"> |
|
|
<a href="#" class="text-gray-400 hover:text-white transition">Terms</a> |
|
|
<a href="#" class="text-gray-400 hover:text-white transition">Privacy</a> |
|
|
<a href="#" class="text-gray-400 hover:text-white transition">API</a> |
|
|
<a href="#" class="text-gray-400 hover:text-white transition">Discord</a> |
|
|
<a href="#" class="text-gray-400 hover:text-white transition">GitHub</a> |
|
|
</div> |
|
|
</div> |
|
|
<div class="mt-4 text-center md:text-left text-gray-500 text-sm"> |
|
|
© 2023 Inferno AI. All generated content is the sole responsibility of the user. |
|
|
</div> |
|
|
</div> |
|
|
</footer> |
|
|
|
|
|
<script> |
|
|
|
|
|
const state = { |
|
|
darkMode: true, |
|
|
nsfwEnabled: true, |
|
|
selectedModels: ['sd15', 'sd21'], |
|
|
generationQueue: [], |
|
|
galleryItems: [], |
|
|
currentGalleryPage: 1, |
|
|
itemsPerPage: 8, |
|
|
credits: 1000, |
|
|
generationStats: { |
|
|
totalGenerations: 0, |
|
|
totalCost: 0, |
|
|
fastestGeneration: null, |
|
|
longestGeneration: null |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const elements = { |
|
|
multiModelTab: document.getElementById('multi-model-tab'), |
|
|
singleModelTab: document.getElementById('single-model-tab'), |
|
|
batchTab: document.getElementById('batch-tab'), |
|
|
settingsTab: document.getElementById('settings-tab'), |
|
|
multiModelContent: document.getElementById('multi-model-content'), |
|
|
singleModelContent: document.getElementById('single-model-content'), |
|
|
batchContent: document.getElementById('batch-content'), |
|
|
settingsContent: document.getElementById('settings-content'), |
|
|
nsfwToggle: document.getElementById('nsfw-toggle'), |
|
|
promptInput: document.getElementById('prompt-input'), |
|
|
negativePrompt: document.getElementById('negative-prompt'), |
|
|
randomPromptBtn: document.getElementById('random-prompt'), |
|
|
generateButton: document.getElementById('generate-button'), |
|
|
modelCheckboxes: document.querySelectorAll('.model-checkbox'), |
|
|
randomModelsBtn: document.getElementById('random-models'), |
|
|
clearModelsBtn: document.getElementById('clear-models'), |
|
|
selectedCount: document.getElementById('selected-count'), |
|
|
outputContainers: [ |
|
|
document.getElementById('output-1'), |
|
|
document.getElementById('output-2'), |
|
|
document.getElementById('output-3'), |
|
|
document.getElementById('output-4') |
|
|
], |
|
|
galleryContainer: document.getElementById('gallery-container'), |
|
|
clearGalleryBtn: document.getElementById('clear-gallery'), |
|
|
downloadAllBtn: document.getElementById('download-all'), |
|
|
galleryPagination: document.getElementById('gallery-pagination'), |
|
|
queueCount: document.getElementById('queue-count'), |
|
|
etaTime: document.getElementById('eta-time'), |
|
|
generationCost: document.getElementById('generation-cost'), |
|
|
darkModeToggle: document.getElementById('dark-mode-toggle'), |
|
|
toastContainer: document.getElementById('toast-container'), |
|
|
widthSlider: document.querySelector('input[type="range"][id^="width"]'), |
|
|
heightSlider: document.querySelector('input[type="range"][id^="height"]'), |
|
|
stepsSlider: document.querySelector('input[type="range"][id^="steps"]'), |
|
|
cfgSlider: document.querySelector('input[type="range"][id^="cfg"]'), |
|
|
widthValue: document.getElementById('width-value'), |
|
|
heightValue: document.getElementById('height-value'), |
|
|
stepsValue: document.getElementById('steps-value'), |
|
|
cfgValue: document.getElementById('cfg-value'), |
|
|
seedInput: document.getElementById('seed-input'), |
|
|
randomizeSeed: document.getElementById('randomize-seed'), |
|
|
samplerSelect: document.getElementById('sampler-select') |
|
|
}; |
|
|
|
|
|
|
|
|
const nsfwPrompts = [ |
|
|
"A voluptuous succubus with glowing red eyes and leathery wings seductively lounges on a throne of bones, her curvaceous form barely concealed by wisps of shadow, hyper-detailed, cinematic lighting", |
|
|
"A muscular werewolf in mid-transformation pins down a trembling victim, his claws digging into their flesh as drool drips from his fangs, ultra-realistic, dark fantasy", |
|
|
"A cybernetic dominatrix in a skin-tight latex suit wields a plasma whip, her augmented eyes scanning the neon-lit dungeon for her next plaything, 8K, hyper-detailed", |
|
|
"An elven princess bound in enchanted vines struggles against her restraints as dark magic swirls around her naked form, fantasy art, highly detailed", |
|
|
"A post-apocalyptic warrior woman with scarred skin and mechanical limbs stands atop a ruined tank, her plasma rifle glowing as mutants swarm below, sci-fi, ultra-detailed" |
|
|
]; |
|
|
|
|
|
|
|
|
const sfwPrompts = [ |
|
|
"A majestic dragon soars over a medieval castle at sunset, its scales shimmering in the golden light, fantasy art, highly detailed", |
|
|
"An astronaut floating in space gazes at Earth with wonder, the planet reflected in their visor, ultra-realistic, cinematic", |
|
|
"A cyberpunk street at night with neon signs reflecting on wet pavement, bustling with futuristic crowds, 8K, hyper-detailed", |
|
|
"A tranquil Japanese garden in autumn with a wooden bridge over a koi pond, cherry blossoms falling, peaceful atmosphere", |
|
|
"A steampunk airship navigates through clouds with intricate brass mechanisms visible, Victorian era aesthetic, detailed" |
|
|
]; |
|
|
|
|
|
|
|
|
function init() { |
|
|
setupEventListeners(); |
|
|
updateSelectedCount(); |
|
|
updateSliderValues(); |
|
|
loadGalleryFromStorage(); |
|
|
renderGallery(); |
|
|
} |
|
|
|
|
|
|
|
|
function setupEventListeners() { |
|
|
|
|
|
elements.multiModelTab.addEventListener('click', () => switchTab('multi')); |
|
|
elements.singleModelTab.addEventListener('click', () => switchTab('single')); |
|
|
elements.batchTab.addEventListener('click', () => switchTab('batch')); |
|
|
elements.settingsTab.addEventListener('click', () => switchTab('settings')); |
|
|
|
|
|
|
|
|
elements.nsfwToggle.addEventListener('change', toggleNSFW); |
|
|
|
|
|
|
|
|
elements.randomPromptBtn.addEventListener('click', generateRandomPrompt); |
|
|
|
|
|
|
|
|
elements.modelCheckboxes.forEach(checkbox => { |
|
|
checkbox.addEventListener('change', handleModelSelection); |
|
|
}); |
|
|
elements.randomModelsBtn.addEventListener('click', randomizeModels); |
|
|
elements.clearModelsBtn.addEventListener('click', clearModelSelection); |
|
|
|
|
|
|
|
|
elements.generateButton.addEventListener('click', startGeneration); |
|
|
|
|
|
|
|
|
elements.clearGalleryBtn.addEventListener('click', clearGallery); |
|
|
elements.downloadAllBtn.addEventListener('click', downloadAllGalleryItems); |
|
|
|
|
|
|
|
|
elements.widthSlider.addEventListener('input', () => updateSliderValue('width')); |
|
|
elements.heightSlider.addEventListener('input', () => updateSliderValue('height')); |
|
|
elements.stepsSlider.addEventListener('input', () => updateSliderValue('steps')); |
|
|
elements.cfgSlider.addEventListener('input', () => updateSliderValue('cfg')); |
|
|
|
|
|
|
|
|
elements.randomizeSeed.addEventListener('click', randomizeSeed); |
|
|
|
|
|
|
|
|
elements.darkModeToggle.addEventListener('click', toggleDarkMode); |
|
|
|
|
|
|
|
|
document.getElementById('advanced-settings-toggle').addEventListener('click', toggleAdvancedSettings); |
|
|
} |
|
|
|
|
|
|
|
|
function switchTab(tab) { |
|
|
|
|
|
elements.multiModelContent.classList.add('hidden'); |
|
|
elements.singleModelContent.classList.add('hidden'); |
|
|
elements.batchContent.classList.add('hidden'); |
|
|
elements.settingsContent.classList.add('hidden'); |
|
|
|
|
|
|
|
|
elements.multiModelTab.classList.remove('tab-active'); |
|
|
elements.singleModelTab.classList.remove('tab-active'); |
|
|
elements.batchTab.classList.remove('tab-active'); |
|
|
elements.settingsTab.classList.remove('tab-active'); |
|
|
|
|
|
|
|
|
switch(tab) { |
|
|
case 'multi': |
|
|
elements.multiModelContent.classList.remove('hidden'); |
|
|
elements.multiModelTab.classList.add('tab-active'); |
|
|
break; |
|
|
case 'single': |
|
|
elements.singleModelContent.classList.remove('hidden'); |
|
|
elements.singleModelTab.classList.add('tab-active'); |
|
|
break; |
|
|
case 'batch': |
|
|
elements.batchContent.classList.remove('hidden'); |
|
|
elements.batchTab.classList.add('tab-active'); |
|
|
break; |
|
|
case 'settings': |
|
|
elements.settingsContent.classList.remove('hidden'); |
|
|
elements.settingsTab.classList.add('tab-active'); |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function toggleNSFW() { |
|
|
state.nsfwEnabled = elements.nsfwToggle.checked; |
|
|
showToast(state.nsfwEnabled ? 'NSFW mode activated. Let your imagination run wild.' : 'NSFW mode deactivated. Keeping it clean.', state.nsfwEnabled ? 'warning' : 'success'); |
|
|
} |
|
|
|
|
|
|
|
|
function generateRandomPrompt() { |
|
|
const prompts = state.nsfwEnabled ? nsfwPrompts : sfwPrompts; |
|
|
const randomPrompt = prompts[Math.floor(Math.random() * prompts.length)]; |
|
|
elements.promptInput.value = randomPrompt; |
|
|
showToast('Prompt randomized. Let the chaos begin.', 'info'); |
|
|
} |
|
|
|
|
|
|
|
|
function handleModelSelection(e) { |
|
|
const model = e.target.dataset.model; |
|
|
const isChecked = e.target.checked; |
|
|
|
|
|
if (isChecked) { |
|
|
if (state.selectedModels.length >= 4) { |
|
|
e.target.checked = false; |
|
|
showToast('Maximum of 4 models can be selected at once. Remove some first.', 'error'); |
|
|
return; |
|
|
} |
|
|
state.selectedModels.push(model); |
|
|
} else { |
|
|
state.selectedModels = state.selectedModels.filter(m => m !== model); |
|
|
} |
|
|
|
|
|
|
|
|
const card = e.target.closest('.model-card'); |
|
|
if (isChecked) { |
|
|
card.classList.add('selected'); |
|
|
} else { |
|
|
card.classList.remove('selected'); |
|
|
} |
|
|
|
|
|
updateSelectedCount(); |
|
|
} |
|
|
|
|
|
|
|
|
function randomizeModels() { |
|
|
|
|
|
elements.modelCheckboxes.forEach(checkbox => { |
|
|
checkbox.checked = false; |
|
|
checkbox.closest('.model-card').classList.remove('selected'); |
|
|
}); |
|
|
|
|
|
|
|
|
const allModels = Array.from(elements.modelCheckboxes).map(cb => cb.dataset.model); |
|
|
const numModels = Math.floor(Math.random() * 3) + 2; |
|
|
state.selectedModels = []; |
|
|
|
|
|
for (let i = 0; i < numModels; i++) { |
|
|
let randomModel; |
|
|
do { |
|
|
randomModel = allModels[Math.floor(Math.random() * allModels.length)]; |
|
|
} while (state.selectedModels.includes(randomModel)); |
|
|
|
|
|
state.selectedModels.push(randomModel); |
|
|
const checkbox = document.querySelector(`.model-checkbox[data-model="${randomModel}"]`); |
|
|
checkbox.checked = true; |
|
|
checkbox.closest('.model-card').classList.add('selected'); |
|
|
} |
|
|
|
|
|
updateSelectedCount(); |
|
|
showToast(`Randomly selected ${numModels} models. Roll the dice!`, 'info'); |
|
|
} |
|
|
|
|
|
|
|
|
function clearModelSelection() { |
|
|
elements.modelCheckboxes.forEach(checkbox => { |
|
|
checkbox.checked = false; |
|
|
checkbox.closest('.model-card').classList.remove('selected'); |
|
|
}); |
|
|
state.selectedModels = []; |
|
|
updateSelectedCount(); |
|
|
showToast('All models deselected. Clean slate.', 'info'); |
|
|
} |
|
|
|
|
|
|
|
|
function updateSelectedCount() { |
|
|
elements.selectedCount.textContent = `${state.selectedModels.length}/4 selected`; |
|
|
if (state.selectedModels.length === 4) { |
|
|
elements.selectedCount.classList.add('bg-red-600'); |
|
|
} else { |
|
|
elements.selectedCount.classList.remove('bg-red-600'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function startGeneration() { |
|
|
if (state.selectedModels.length === 0) { |
|
|
showToast('Select at least one model to generate images.', 'error'); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (elements.promptInput.value.trim() === '') { |
|
|
showToast('Enter a prompt to generate images. Your imagination is the limit.', 'error'); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
elements.generateButton.disabled = true; |
|
|
elements.generateButton.innerHTML = '<i class="fas fa-spinner loading-spinner mr-2"></i> IGNITING...'; |
|
|
|
|
|
|
|
|
const params = { |
|
|
prompt: elements.promptInput.value, |
|
|
negativePrompt: elements.negativePrompt.value, |
|
|
width: parseInt(elements.widthValue.textContent), |
|
|
height: parseInt(elements.heightValue.textContent), |
|
|
steps: parseInt(elements.stepsValue.textContent), |
|
|
cfg: parseFloat(elements.cfgValue.textContent), |
|
|
seed: elements.seedInput.value === '-1' ? Math.floor(Math.random() * 4294967295) : parseInt(elements.seedInput.value), |
|
|
sampler: elements.samplerSelect.value, |
|
|
nsfw: state.nsfwEnabled |
|
|
}; |
|
|
|
|
|
|
|
|
state.generationQueue = [...state.selectedModels]; |
|
|
updateQueueDisplay(); |
|
|
|
|
|
|
|
|
state.selectedModels.forEach((model, index) => { |
|
|
setTimeout(() => { |
|
|
simulateGeneration(model, params, index); |
|
|
}, index * 2000); |
|
|
}); |
|
|
|
|
|
|
|
|
const cost = state.selectedModels.length * 5; |
|
|
state.credits -= cost; |
|
|
showToast(`Generation started. ${cost} credits deducted.`, 'info'); |
|
|
} |
|
|
|
|
|
|
|
|
function simulateGeneration(model, params, index) { |
|
|
|
|
|
state.generationQueue = state.generationQueue.filter(m => m !== model); |
|
|
updateQueueDisplay(); |
|
|
|
|
|
|
|
|
const outputContainer = elements.outputContainers[index]; |
|
|
outputContainer.innerHTML = ` |
|
|
<div class="absolute inset-0 bg-black bg-opacity-70 flex flex-col items-center justify-center rounded-lg"> |
|
|
<i class="fas fa-spinner loading-spinner text-red-500 text-2xl mb-2"></i> |
|
|
<span class="text-white text-sm">Generating with ${getModelName(model)}...</span> |
|
|
<span class="text-gray-400 text-xs mt-1">ETA: ${Math.floor(Math.random() * 10) + 5}s</span> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
|
|
|
const generationTime = Math.floor(Math.random() * 3000) + 2000; |
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
|
if (Math.random() < 0.1) { |
|
|
simulateFailedGeneration(model, index); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
const imageUrl = getRandomImageUrl(); |
|
|
outputContainer.innerHTML = ` |
|
|
<img src="${imageUrl}" class="w-full h-full object-cover rounded-lg"> |
|
|
<div class="absolute bottom-2 right-2 flex space-x-1"> |
|
|
<button class="download-btn bg-gray-800 hover:bg-gray-700 text-white p-1 rounded-full w-7 h-7 flex items-center justify-center"> |
|
|
<i class="fas fa-download text-xs"></i> |
|
|
</button> |
|
|
<button class="upscale-btn bg-red-600 hover:bg-red-700 text-white p-1 rounded-full w-7 h-7 flex items-center justify-center"> |
|
|
<i class="fas fa-expand text-xs"></i> |
|
|
</button> |
|
|
</div> |
|
|
<div class="absolute top-2 left-2 bg-black bg-opacity-70 text-white text-xs px-2 py-1 rounded"> |
|
|
${getModelName(model)} |
|
|
</div> |
|
|
`; |
|
|
|
|
|
|
|
|
const galleryItem = { |
|
|
id: Date.now(), |
|
|
model, |
|
|
prompt: params.prompt, |
|
|
imageUrl, |
|
|
timestamp: new Date().toISOString(), |
|
|
params |
|
|
}; |
|
|
state.galleryItems.unshift(galleryItem); |
|
|
saveGalleryToStorage(); |
|
|
renderGallery(); |
|
|
|
|
|
|
|
|
if (state.generationQueue.length === 0) { |
|
|
elements.generateButton.disabled = false; |
|
|
elements.generateButton.innerHTML = '<i class="fas fa-fire mr-2"></i> IGNITE CREATION'; |
|
|
} |
|
|
|
|
|
|
|
|
setupImageButtons(outputContainer, galleryItem); |
|
|
}, generationTime); |
|
|
} |
|
|
|
|
|
|
|
|
function simulateFailedGeneration(model, index) { |
|
|
const outputContainer = elements.outputContainers[index]; |
|
|
outputContainer.innerHTML = ` |
|
|
<div class="absolute inset-0 bg-black bg-opacity-70 flex flex-col items-center justify-center rounded-lg"> |
|
|
<i class="fas fa-exclamation-triangle text-red-500 text-2xl mb-2"></i> |
|
|
<span class="text-white text-sm">Generation failed</span> |
|
|
<span class="text-gray-400 text-xs mt-1">${getModelName(model)}</span> |
|
|
<button class="retry-btn mt-2 text-xs bg-red-600 hover:bg-red-700 text-white px-2 py-1 rounded"> |
|
|
Retry |
|
|
</button> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
|
|
|
outputContainer.querySelector('.retry-btn').addEventListener('click', () => { |
|
|
state.generationQueue.push(model); |
|
|
updateQueueDisplay(); |
|
|
simulateGeneration(model, {}, index); |
|
|
}); |
|
|
|
|
|
|
|
|
if (state.generationQueue.length === 0) { |
|
|
elements.generateButton.disabled = false; |
|
|
elements.generateButton.innerHTML = '<i class="fas fa-fire mr-2"></i> IGNITE CREATION'; |
|
|
} |
|
|
|
|
|
showToast(`Generation failed for ${getModelName(model)}. The demons resisted your call.`, 'error'); |
|
|
} |
|
|
|
|
|
|
|
|
function updateQueueDisplay() { |
|
|
elements.queueCount.textContent = state.generationQueue.length; |
|
|
|
|
|
if (state.generationQueue.length > 0) { |
|
|
const eta = state.generationQueue.length * 5 + Math.floor(Math.random() * 10); |
|
|
elements.etaTime.textContent = `${eta}s`; |
|
|
elements.generationCost.textContent = (state.generationQueue.length * 5).toFixed(2); |
|
|
} else { |
|
|
elements.etaTime.textContent = '-'; |
|
|
elements.generationCost.textContent = '0.00'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function getModelName(modelId) { |
|
|
const modelNames = { |
|
|
'sd15': 'Stable Diffusion 1.5', |
|
|
'sd21': 'Stable Diffusion 2.1', |
|
|
'openjourney': 'OpenJourney', |
|
|
'dreamlike': 'Dreamlike', |
|
|
'anything': 'Anything V3', |
|
|
'deliberate': 'Deliberate', |
|
|
'realistic': 'Realistic Vision', |
|
|
'hentai': 'Hentai Diffusion', |
|
|
'waifu': 'Waifu Diffusion' |
|
|
}; |
|
|
return modelNames[modelId] || modelId; |
|
|
} |
|
|
|
|
|
|
|
|
function getRandomImageUrl() { |
|
|
const randomNum = Math.floor(Math.random() * 1000); |
|
|
return `https://picsum.photos/800/800?random=${randomNum}`; |
|
|
} |
|
|
|
|
|
|
|
|
function setupImageButtons(container, galleryItem) { |
|
|
container.querySelector('.download-btn').addEventListener('click', () => { |
|
|
downloadImage(galleryItem.imageUrl, `inferno-${galleryItem.model}-${galleryItem.id}.jpg`); |
|
|
showToast('Image downloaded. Treasure your creation.', 'success'); |
|
|
}); |
|
|
|
|
|
container.querySelector('.upscale-btn').addEventListener('click', () => { |
|
|
showToast(`Upscaling image generated with ${getModelName(galleryItem.model)}...`, 'info'); |
|
|
|
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function downloadImage(url, filename) { |
|
|
const a = document.createElement('a'); |
|
|
a.href = url; |
|
|
a.download = filename; |
|
|
document.body.appendChild(a); |
|
|
a.click(); |
|
|
document.body.removeChild(a); |
|
|
} |
|
|
|
|
|
|
|
|
function loadGalleryFromStorage() { |
|
|
const savedGallery = localStorage.getItem('infernoGallery'); |
|
|
if (savedGallery) { |
|
|
try { |
|
|
state.galleryItems = JSON.parse(savedGallery); |
|
|
} catch (e) { |
|
|
console.error('Failed to parse gallery data', e); |
|
|
state.galleryItems = []; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function saveGalleryToStorage() { |
|
|
localStorage.setItem('infernoGallery', JSON.stringify(state.galleryItems)); |
|
|
} |
|
|
|
|
|
|
|
|
function renderGallery() { |
|
|
elements.galleryContainer.innerHTML = ''; |
|
|
|
|
|
if (state.galleryItems.length === 0) { |
|
|
elements.galleryContainer.innerHTML = ` |
|
|
<div class="col-span-2 py-8 text-center"> |
|
|
<i class="fas fa-fire text-gray-600 text-3xl mb-2"></i> |
|
|
<p class="text-gray-500">Your gallery is empty. Ignite some creations!</p> |
|
|
</div> |
|
|
`; |
|
|
elements.galleryPagination.classList.add('hidden'); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
const totalPages = Math.ceil(state.galleryItems.length / state.itemsPerPage); |
|
|
const startIndex = (state.currentGalleryPage - 1) * state.itemsPerPage; |
|
|
const endIndex = Math.min(startIndex + state.itemsPerPage, state.galleryItems.length); |
|
|
const itemsToShow = state.galleryItems.slice(startIndex, endIndex); |
|
|
|
|
|
|
|
|
itemsToShow.forEach(item => { |
|
|
const galleryItemElement = document.createElement('div'); |
|
|
galleryItemElement.className = 'bg-gray-800 border border-gray-700 rounded-lg overflow-hidden relative group'; |
|
|
galleryItemElement.innerHTML = ` |
|
|
<img src="${item.imageUrl}" class="w-full h-32 object-cover"> |
|
|
<div class="p-2"> |
|
|
<p class="text-xs text-gray-300 truncate">${item.prompt.substring(0, 40)}${item.prompt.length > 40 ? '...' : ''}</p> |
|
|
<p class="text-xs text-gray-500">${getModelName(item.model)}</p> |
|
|
</div> |
|
|
<div class="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-60 transition flex items-center justify-center space-x-2 opacity-0 group-hover:opacity-100"> |
|
|
<button class="download-btn bg-gray-800 hover:bg-gray-700 text-white p-2 rounded-full w-10 h-10 flex items-center justify-center"> |
|
|
<i class="fas fa-download"></i> |
|
|
</button> |
|
|
<button class="upscale-btn bg-red-600 hover:bg-red-700 text-white p-2 rounded-full w-10 h-10 flex items-center justify-center"> |
|
|
<i class="fas fa-expand"></i> |
|
|
</button> |
|
|
<button class="delete-btn bg-gray-800 hover:bg-red-600 text-white p-2 rounded-full w-10 h-10 flex items-center justify-center"> |
|
|
<i class="fas fa-trash"></i> |
|
|
</button> |
|
|
</div> |
|
|
`; |
|
|
elements.galleryContainer.appendChild(galleryItemElement); |
|
|
|
|
|
|
|
|
galleryItemElement.querySelector('.download-btn').addEventListener('click', () => { |
|
|
downloadImage(item.imageUrl, `inferno-${item.model}-${item.id}.jpg`); |
|
|
showToast('Image downloaded. Another trophy for your collection.', 'success'); |
|
|
}); |
|
|
|
|
|
galleryItemElement.querySelector('.upscale-btn').addEventListener('click', () => { |
|
|
showToast(`Upscaling image generated with ${getModelName(item.model)}...`, 'info'); |
|
|
}); |
|
|
|
|
|
galleryItemElement.querySelector('.delete-btn').addEventListener('click', () => { |
|
|
state.galleryItems = state.galleryItems.filter(i => i.id !== item.id); |
|
|
saveGalleryToStorage(); |
|
|
renderGallery(); |
|
|
showToast('Image deleted. Reduced to ashes.', 'info'); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
if (totalPages > 1) { |
|
|
elements.galleryPagination.classList.remove('hidden'); |
|
|
elements.galleryPagination.innerHTML = ''; |
|
|
|
|
|
|
|
|
if (state.currentGalleryPage > 1) { |
|
|
const prevBtn = document.createElement('button'); |
|
|
prevBtn.className = 'pagination-btn px-3 py-1 rounded bg-gray-600 hover:bg-gray-500'; |
|
|
prevBtn.innerHTML = '<i class="fas fa-chevron-left"></i>'; |
|
|
prevBtn.addEventListener('click', () => { |
|
|
state.currentGalleryPage--; |
|
|
renderGallery(); |
|
|
}); |
|
|
elements.galleryPagination.appendChild(prevBtn); |
|
|
} |
|
|
|
|
|
|
|
|
const maxVisiblePages = 5; |
|
|
let startPage = Math.max(1, state.currentGalleryPage - Math.floor(maxVisiblePages / 2)); |
|
|
let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1); |
|
|
|
|
|
if (endPage - startPage + 1 < maxVisiblePages) { |
|
|
startPage = Math.max(1, endPage - maxVisiblePages + 1); |
|
|
} |
|
|
|
|
|
if (startPage > 1) { |
|
|
const firstBtn = document.createElement('button'); |
|
|
firstBtn.className = 'pagination-btn px-3 py-1 rounded bg-gray-600 hover:bg-gray-500'; |
|
|
firstBtn.textContent = '1'; |
|
|
firstBtn.addEventListener('click', () => { |
|
|
state.currentGalleryPage = 1; |
|
|
renderGallery(); |
|
|
}); |
|
|
elements.galleryPagination.appendChild(firstBtn); |
|
|
|
|
|
if (startPage > 2) { |
|
|
const ellipsis = document.createElement('span'); |
|
|
ellipsis.className = 'px-3 py-1'; |
|
|
ellipsis.textContent = '...'; |
|
|
elements.galleryPagination.appendChild(ellipsis); |
|
|
} |
|
|
} |
|
|
|
|
|
for (let i = startPage; i <= endPage; i++) { |
|
|
const pageBtn = document.createElement('button'); |
|
|
pageBtn.className = `pagination-btn px-3 py-1 rounded ${i === state.currentGalleryPage ? 'bg-red-600 text-white' : 'bg-gray-600 hover:bg-gray-500'}`; |
|
|
pageBtn.textContent = i; |
|
|
pageBtn.addEventListener('click', () => { |
|
|
state.currentGalleryPage = i; |
|
|
renderGallery(); |
|
|
}); |
|
|
elements.galleryPagination.appendChild(pageBtn); |
|
|
} |
|
|
|
|
|
if (endPage < totalPages) { |
|
|
if (endPage < totalPages - 1) { |
|
|
const ellipsis = document.createElement('span'); |
|
|
ellipsis.className = 'px-3 py-1'; |
|
|
ellipsis.textContent = '...'; |
|
|
elements.galleryPagination.appendChild(ellipsis); |
|
|
} |
|
|
|
|
|
const lastBtn = document.createElement('button'); |
|
|
lastBtn.className = 'pagination-btn px-3 py-1 rounded bg-gray-600 hover:bg-gray-500'; |
|
|
lastBtn.textContent = totalPages; |
|
|
lastBtn.addEventListener('click', () => { |
|
|
state.currentGalleryPage = totalPages; |
|
|
renderGallery(); |
|
|
}); |
|
|
elements.galleryPagination.appendChild(lastBtn); |
|
|
} |
|
|
|
|
|
|
|
|
if (state.currentGalleryPage < totalPages) { |
|
|
const nextBtn = document.createElement('button'); |
|
|
nextBtn.className = 'pagination-btn px-3 py-1 rounded bg-gray-600 hover:bg-gray-500'; |
|
|
nextBtn.innerHTML = '<i class="fas fa-chevron-right"></i>'; |
|
|
nextBtn.addEventListener('click', () => { |
|
|
state.currentGalleryPage++; |
|
|
renderGallery(); |
|
|
}); |
|
|
elements.galleryPagination.appendChild(nextBtn); |
|
|
} |
|
|
} else { |
|
|
elements.galleryPagination.classList.add('hidden'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function clearGallery() { |
|
|
if (state.galleryItems.length === 0) return; |
|
|
|
|
|
if (confirm('Burn your entire gallery to ashes? This cannot be undone.')) { |
|
|
state.galleryItems = []; |
|
|
saveGalleryToStorage(); |
|
|
renderGallery(); |
|
|
showToast('Gallery purged. All creations reduced to dust.', 'warning'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function downloadAllGalleryItems() { |
|
|
if (state.galleryItems.length === 0) { |
|
|
showToast('Your gallery is empty. Nothing to download.', 'error'); |
|
|
return; |
|
|
} |
|
|
|
|
|
showToast('Preparing your collection for download...', 'info'); |
|
|
|
|
|
setTimeout(() => { |
|
|
showToast('Download complete. Your dark creations are now yours forever.', 'success'); |
|
|
}, 2000); |
|
|
} |
|
|
|
|
|
|
|
|
function updateSliderValues() { |
|
|
updateSliderValue('width'); |
|
|
updateSliderValue('height'); |
|
|
updateSliderValue('steps'); |
|
|
updateSliderValue('cfg'); |
|
|
} |
|
|
|
|
|
|
|
|
function updateSliderValue(type) { |
|
|
const slider = elements[`${type}Slider`]; |
|
|
const valueElement = elements[`${type}Value`]; |
|
|
valueElement.textContent = slider.value; |
|
|
} |
|
|
|
|
|
|
|
|
function randomizeSeed() { |
|
|
const newSeed = Math.floor(Math.random() * 4294967295); |
|
|
elements.seedInput.value = newSeed; |
|
|
showToast(`Seed randomized: ${newSeed}. New possibilities await.`, 'info'); |
|
|
} |
|
|
|
|
|
|
|
|
function toggleDarkMode() { |
|
|
state.darkMode = !state.darkMode; |
|
|
if (state.darkMode) { |
|
|
document.body.classList.add('bg-gray-900', 'text-gray-100'); |
|
|
document.body.classList.remove('bg-gray-100', 'text-gray-900'); |
|
|
elements.darkModeToggle.innerHTML = '<i class="fas fa-moon"></i>'; |
|
|
showToast('Dark mode activated. The shadows welcome you.', 'info'); |
|
|
} else { |
|
|
document.body.classList.remove('bg-gray-900', 'text-gray-100'); |
|
|
document.body.classList.add('bg-gray-100', 'text-gray-900'); |
|
|
elements.darkModeToggle.innerHTML = '<i class="fas fa-sun"></i>'; |
|
|
showToast('Light mode activated. The brightness burns.', 'warning'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function toggleAdvancedSettings() { |
|
|
const content = document.getElementById('advanced-settings-content'); |
|
|
const icon = document.querySelector('#advanced-settings-toggle i'); |
|
|
|
|
|
content.classList.toggle('hidden'); |
|
|
icon.classList.toggle('rotate-180'); |
|
|
} |
|
|
|
|
|
|
|
|
function showToast(message, type = 'info') { |
|
|
const toast = document.createElement('div'); |
|
|
const types = { |
|
|
'info': { bg: 'bg-gray-700', icon: 'fa-info-circle' }, |
|
|
'success': { bg: 'bg-green-700', icon: 'fa-check-circle' }, |
|
|
'warning': { bg: 'bg-yellow-700', icon: 'fa-exclamation-circle' }, |
|
|
'error': { bg: 'bg-red-700', icon: 'fa-times-circle' } |
|
|
}; |
|
|
|
|
|
toast.className = `error-toast ${types[type].bg} text-white px-4 py-3 rounded-lg shadow-lg flex items-start max-w-md`; |
|
|
toast.innerHTML = ` |
|
|
<i class="fas ${types[type].icon} mr-2 mt-0.5"></i> |
|
|
<span>${message}</span> |
|
|
`; |
|
|
|
|
|
elements.toastContainer.appendChild(toast); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
toast.remove(); |
|
|
}, 3000); |
|
|
} |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', init); |
|
|
</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=Boobs00/inferno" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
|
</html> |