mainmenuguiii / index.html
Wavetype's picture
Update index.html
a91566b verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GODSRODS AI Visualizer</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
/* Custom styles for a premium, dark 'GODSRODS' theme */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;500;700;900&display=swap');
body {
font-family: 'Inter', sans-serif;
background-color: #121212; /* Deep Black */
min-height: 100vh;
color: #ffffff;
padding: 0;
}
/* === MODAL STYLES === */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.9);
display: none;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-content {
background-color: #121212;
border-radius: 1rem;
max-width: 1250px; /* Increased max width to fit sidebar */
width: 95%;
height: 95vh;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.8);
display: flex;
flex-direction: column;
overflow: hidden;
border: 2px solid #3a3a3a;
}
.modal-header {
padding: 1rem 1.5rem;
border-bottom: 1px solid #3a3a3a;
display: flex;
justify-content: space-between;
align-items: center;
background-color: #212121;
}
.modal-body-flex {
flex-grow: 1;
display: flex;
overflow: hidden; /* Prevent horizontal scroll from inner content */
}
/* === HISTORY SIDEBAR STYLES === */
#historySidebar {
background-color: #1a1a1a;
width: 4rem; /* Initial narrow width (64px) */
transition: width 0.3s ease-in-out;
overflow: hidden;
border-right: 1px solid #3a3a3a;
flex-shrink: 0;
padding: 0.5rem 0;
}
#historySidebar.is-expanded {
width: 14rem; /* Expanded width (224px) */
}
.sidebar-header {
display: flex;
justify-content: center;
align-items: center;
padding: 0.5rem 0;
cursor: pointer;
color: #fbc02d;
border-bottom: 1px solid #333;
margin-bottom: 0.5rem;
}
.sidebar-icon-text {
display: none;
font-size: 0.875rem;
font-weight: 600;
}
#historySidebar.is-expanded .sidebar-icon-text {
display: block;
margin-left: 0.5rem;
}
.sidebar-list {
overflow-y: auto;
height: calc(100% - 3rem); /* Adjust based on header height */
padding: 0 0.5rem;
}
.car-thumbnail-container {
margin-bottom: 0.5rem;
cursor: pointer;
transition: transform 0.1s, box-shadow 0.2s;
border-radius: 0.375rem;
overflow: hidden;
background-color: #2c2c2c;
border: 2px solid transparent;
}
.car-thumbnail-container:hover {
transform: scale(1.02);
border-color: #fbc02d;
}
.car-thumbnail-img {
aspect-ratio: 16 / 9;
object-fit: cover;
width: 100%;
}
.car-thumbnail-name {
display: none;
padding: 0.25rem;
font-size: 0.75rem;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #ddd;
}
#historySidebar.is-expanded .car-thumbnail-name {
display: block;
}
/* === MAIN CONTENT STYLES === */
.modal-main-content {
flex-grow: 1;
overflow-y: auto;
padding: 1.5rem;
}
.container-inner {
max-width: 100%;
margin: 0 auto;
}
/* === APP COMPONENT STYLES === */
.panel {
background-color: #212121;
border-radius: 0.75rem;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4);
border: 1px solid #3a3a3a;
}
.accordion-header {
background-color: #2c2c2c;
cursor: pointer;
transition: background-color 0.2s;
}
.accordion-header:hover {
background-color: #383838;
}
.accordion-content {
background-color: #212121;
max-height: 0;
overflow: hidden;
transition: max-height 0.4s ease-out;
}
.accordion-content.active {
max-height: 800px; /* Increased max-height for content visibility */
}
.input-style {
background-color: #333333;
border: 1px solid #4f4f4f;
color: #ffffff;
border-radius: 0.375rem;
}
.input-style:focus {
ring-color: #fbc02d; /* Gold accent */
border-color: #fbc02d;
}
.btn-gold {
background-color: #fbc02d; /* Gold accent */
color: #121212;
transition: all 0.2s;
font-weight: 700;
}
.btn-gold:hover:not(:disabled) {
background-color: #e0a800;
transform: translateY(-1px);
}
.btn-gold:disabled {
background-color: #ffeb3b;
cursor: not-allowed;
opacity: 0.6;
}
.accent-text {
color: #fbc02d;
}
/* Responsive Grid for Media-Text Layout */
@media (min-width: 768px) {
.media-text-grid {
display: grid;
grid-template-columns: 1fr 1.5fr; /* 1:1.5 ratio for controls and visualizer */
gap: 1.5rem;
}
}
</style>
</head>
<body>
<!-- Floating Button to Open the Garage -->
<button id="openGarageBtn"
class="fixed bottom-4 right-4 z-[1001] bg-gray-800 text-white p-3 rounded-full shadow-lg font-bold hover:bg-gray-700 transition duration-200 focus:outline-none focus:ring-4 focus:ring-yellow-500 focus:ring-opacity-50">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="#fbc02d" class="w-6 h-6 inline-block mr-1">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 18.75a1.5 1.5 0 0 1-3 0 1.5 1.5 0 0 1 3 0ZM15.75 18.75a1.5 1.5 0 0 1-3 0 1.5 1.5 0 0 1 3 0ZM19.5 7.5a1.5 1.5 0 0 0-3 0m3 0a1.5 1.5 0 0 1-3 0m3 0h-3m-3 0h-3m-1.5 0h-3V6a3 3 0 0 1 3-3h15.5a1.5 1.5 0 0 1 1.5 1.5v3.627a1.5 1.5 0 0 1-.988 1.411L18 13.5m-9 3.75h3M3 13.5H.75v-6h11.559L12 7.5a1.5 1.5 0 0 1 1.5-1.5h1.5M10.5 7.5V6a1.5 1.5 0 0 0-1.5-1.5h-3A1.5 1.5 0 0 0 4.5 6v1.5" />
</svg>
Garage
</button>
<!-- Full-Screen Modal/Garage Wrapper -->
<div id="garageModal" class="modal-overlay">
<div class="modal-content">
<!-- Modal Header -->
<div class="modal-header">
<h1 class="text-3xl font-black accent-text">GODSRODS ⚡ VISUALIZER GARAGE</h1>
<button id="closeModalBtn" class="text-4xl text-gray-400 hover:text-white transition duration-200 p-2 leading-none">
&times;
</button>
</div>
<!-- Modal Body (History Sidebar + Main Content) -->
<div class="modal-body-flex">
<!-- HISTORY SIDEBAR -->
<div id="historySidebar">
<div id="sidebarHeader" class="sidebar-header">
<!-- Car Icon -->
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 21v-8.25m0 0l-3 3m3-3l3 3m-11.25 0V3m14.5 18v-8.25m0 0l3-3m-3 3l-3-3m-4.5 3h6m-12 0h.008v.008H3v-.008Zm12 0h.008v.008h-.008v-.008Zm4.5 4.5h.008v.008H19.5v-.008Z" />
</svg>
<span class="sidebar-icon-text">BUILD HISTORY</span>
</div>
<div id="historyList" class="sidebar-list">
<!-- Thumbnails will be injected here -->
<p id="historyPlaceholder" class="text-gray-500 text-xs text-center p-2 hidden">
Generate your first car to start your history!
</p>
</div>
</div>
<!-- MAIN VISUALIZER CONTENT -->
<div class="modal-main-content">
<div id="app" class="container-inner">
<p class="text-gray-400 mt-2 mb-8 text-center">Design your ultimate fantasy machine and render it with AI.</p>
<div class="media-text-grid">
<!-- CONTROL PANEL (LEFT/TOP) -->
<div class="space-y-4">
<div class="panel">
<div class="accordion-header p-4 flex justify-between items-center rounded-t-xl" data-target="exteriorContent">
<h2 class="text-xl font-semibold accent-text">1. Exterior Customization</h2>
<span class="text-3xl font-bold">+</span>
</div>
<div id="exteriorContent" class="accordion-content active">
<div class="p-4 grid grid-cols-2 gap-4">
<div>
<label for="bodyStyle" class="block text-xs font-medium mb-1">Body Style</label>
<select id="bodyStyle" class="w-full p-2 input-style">
<option value="Extreme Widebody Kit">Extreme Widebody Kit</option>
<option value="Sleek Hypercar">Sleek Hypercar</option>
<option value="Off-Road Rally Truck">Off-Road Rally Truck</option>
<option value="1969 Classic Muscle Car">1969 Classic Muscle Car</option>
</select>
</div>
<div>
<label for="color" class="block text-xs font-medium mb-1">Primary Color</label>
<select id="color" class="w-full p-2 input-style">
<option value="Matte Dark Chrome">Matte Dark Chrome</option>
<option value="Deep Candy Red">Deep Candy Red</option>
<option value="Molten Gold Metallic">Molten Gold Metallic</option>
<option value="Battle-Scarred Grey">Battle-Scarred Grey</option>
</select>
</div>
<div>
<label for="aero" class="block text-xs font-medium mb-1">Aero/Spoiler</label>
<select id="aero" class="w-full p-2 input-style">
<option value="Twin Blade GT Wing">Twin Blade GT Wing</option>
<option value="Aggressive Diffuser">Aggressive Diffuser</option>
<option value="No Spoiler">No Spoiler</option>
</select>
</div>
<div>
<label for="finish" class="block text-xs font-medium mb-1">Finish/Material</label>
<select id="finish" class="w-full p-2 input-style">
<option value="Forged Carbon Fiber panels">Forged Carbon Fiber panels</option>
<option value="High-Gloss Wet Paint">High-Gloss Wet Paint</option>
<option value="Worn, Rusty Metal">Worn, Rusty Metal</option>
</select>
</div>
</div>
</div>
</div>
<div class="panel">
<div class="accordion-header p-4 flex justify-between items-center" data-target="performanceContent">
<h2 class="text-xl font-semibold accent-text">2. Performance & Wheels</h2>
<span class="text-3xl font-bold">+</span>
</div>
<div id="performanceContent" class="accordion-content">
<div class="p-4 grid grid-cols-2 gap-4">
<div>
<label for="wheels" class="block text-xs font-medium mb-1">Wheel Type</label>
<select id="wheels" class="w-full p-2 input-style">
<option value="Forged Bronze Monoblock Rims">Forged Bronze Monoblock Rims</option>
<option value="Wide Drag Slicks">Wide Drag Slicks</option>
<option value="Chunky All-Terrain Tires">Chunky All-Terrain Tires</option>
</select>
</div>
<div>
<label for="suspension" class="block text-xs font-medium mb-1">Ride Height</label>
<select id="suspension" class="w-full p-2 input-style">
<option value="Slammed on Air Suspension">Slammed on Air Suspension</option>
<option value="High Suspension Lift">High Suspension Lift</option>
<option value="Perfect Track Stance">Perfect Track Stance</option>
</select>
</div>
<div>
<label for="exhaust" class="block text-xs font-medium mb-1">Exhaust System</label>
<select id="exhaust" class="w-full p-2 input-style">
<option value="Quad Tips, Backfiring">Quad Tips, Backfiring</option>
<option value="Massive Upward Stacks">Massive Upward Stacks</option>
<option value="Single Cannon Exit">Single Cannon Exit</option>
</select>
</div>
<div>
<label for="brakes" class="block text-xs font-medium mb-1">Brake Calipers</label>
<select id="brakes" class="w-full p-2 input-style">
<option value="Gold Carbon Ceramic Brakes">Gold Carbon Ceramic Brakes</option>
<option value="Blood Red Calipers">Blood Red Calipers</option>
<option value="Oversized Drilled Rotors">Oversized Drilled Rotors</option>
</select>
</div>
</div>
</div>
</div>
<div class="panel">
<div class="accordion-header p-4 flex justify-between items-center rounded-b-xl" data-target="stagingContent">
<h2 class="text-xl font-semibold accent-text">3. Staging & Environment</h2>
<span class="text-3xl font-bold">+</span>
</div>
<div id="stagingContent" class="accordion-content">
<div class="p-4 grid grid-cols-2 gap-4">
<div>
<label for="setting" class="block text-xs font-medium mb-1">Scene Setting</label>
<select id="setting" class="w-full p-2 input-style">
<option value="Abandoned Neo-Tokyo Alley at Night">Abandoned Neo-Tokyo Alley at Night</option>
<option value="Mountain Pass, Heavy Fog">Mountain Pass, Heavy Fog</option>
<option value="Modern Art Gallery">Modern Art Gallery</option>
<option value="Dusty Post-Apocalyptic Road">Dusty Post-Apocalyptic Road</option>
</select>
</div>
<div>
<label for="style" class="block text-xs font-medium mb-1">Art Style</label>
<select id="style" class="w-full p-2 input-style">
<option value="Cinematic Photo, Nikon Z9, F/1.4">Cinematic Photo, Nikon Z9, F/1.4</option>
<option value="Concept Art, Digital Painting">Concept Art, Digital Painting</option>
<option value="Vaporwave, Synthwave lighting">Vaporwave, Synthwave lighting</option>
</select>
</div>
<div>
<label for="camera" class="block text-xs font-medium mb-1">Camera Angle</label>
<select id="camera" class="w-full p-2 input-style">
<option value="Ground Level, dramatic wide-angle">Ground Level, dramatic wide-angle</option>
<option value="Dynamic Action Shot">Dynamic Action Shot</option>
<option value="Front 3/4 View, bokeh">Front 3/4 View, bokeh</option>
</select>
</div>
<div>
<label for="artist" class="block text-xs font-medium mb-1">Artistic Influence</label>
<select id="artist" class="w-full p-2 input-style">
<option value="by Mike Winkelmann, highly detailed">by Mike Winkelmann, highly detailed</option>
<option value="by Simon Stålenhag, retro sci-fi">by Simon Stålenhag, retro sci-fi</option>
<option value="Unreal Engine 5 render, ray tracing">Unreal Engine 5 render, ray tracing</option>
</select>
</div>
</div>
</div>
</div>
<!-- Generation Button -->
<button id="generateBtn"
class="btn-gold w-full px-8 py-3 rounded-lg font-bold shadow-2xl"
onclick="generateImage()">
GENERATE GODSROD (AI Render)
</button>
</div>
<!-- VISUALIZER PANEL (RIGHT/BOTTOM) -->
<div class="panel p-6 space-y-4">
<h2 class="text-2xl font-black accent-text border-b border-gray-700 pb-2">Visualization Output</h2>
<!-- Image Result -->
<div id="imageResult" class="text-center">
<img id="generatedImage" class="w-full h-auto max-h-[70vh] object-contain rounded-lg border border-gray-600 shadow-xl"
src="https://placehold.co/800x450/212121/fbc02d?text=Configure+and+Generate+Your+GODSROD"
alt="Generated AI Image of Custom Car">
<p id="errorMsg" class="text-red-500 mt-4 hidden font-semibold">Image generation failed. Please check your network or try a different combination.</p>
<!-- Loading Indicator -->
<div id="loading" class="text-center text-gray-400 font-medium pt-4 hidden">
<svg class="animate-spin -ml-1 mr-3 h-6 w-6 accent-text inline-block" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Generating image (approx. 10-20 seconds)...
</div>
</div>
<!-- Prompt Output -->
<div class="pt-4 border-t border-gray-700">
<h3 class="text-sm font-semibold mb-2 text-gray-400">Generated Prompt for AI:</h3>
<textarea id="outputPrompt" rows="4" readonly class="w-full p-2 input-style text-xs opacity-70"></textarea>
</div>
</div>
</div>
</div>
</div>
<!-- End Main Content -->
</div>
<!-- End Modal Body -->
</div>
</div>
<!-- End Modal/Garage Wrapper -->
<script>
// In-memory array to store car history (prompt, config, image URL).
// This data is lost when the page is reloaded.
let generatedCars = [];
// --- Modal Logic ---
const garageModal = document.getElementById('garageModal');
const openBtn = document.getElementById('openGarageBtn');
const closeBtn = document.getElementById('closeModalBtn');
function openModal() {
garageModal.style.display = 'flex';
openBtn.style.display = 'none';
// Ensure history sidebar is rendered when modal opens
renderHistorySidebar();
}
function closeModal() {
garageModal.style.display = 'none';
openBtn.style.display = 'block';
}
openBtn.addEventListener('click', openModal);
closeBtn.addEventListener('click', closeModal);
garageModal.addEventListener('click', (e) => {
if (e.target === garageModal) {
closeModal();
}
});
// --- Sidebar Logic ---
const historySidebar = document.getElementById('historySidebar');
const sidebarHeader = document.getElementById('sidebarHeader');
const historyList = document.getElementById('historyList');
const historyPlaceholder = document.getElementById('historyPlaceholder');
sidebarHeader.addEventListener('click', () => {
historySidebar.classList.toggle('is-expanded');
// Re-render to show/hide text content correctly
renderHistorySidebar();
});
function saveCarToHistory(prompt, imageUrl, config) {
const timestamp = new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
const newCar = {
id: Date.now(),
name: `${config.bodyStyle.split(' ')[0]}-${config.color.split(' ')[0]}`,
prompt: prompt,
imageUrl: imageUrl,
config: config,
timestamp: timestamp
};
// Add new car to the beginning of the array
generatedCars.unshift(newCar);
renderHistorySidebar();
}
function renderHistorySidebar() {
historyList.innerHTML = ''; // Clear previous content
if (generatedCars.length === 0) {
historyPlaceholder.classList.remove('hidden');
return;
}
historyPlaceholder.classList.add('hidden');
generatedCars.forEach((car, index) => {
const container = document.createElement('div');
container.className = 'car-thumbnail-container';
container.setAttribute('data-index', index);
container.onclick = () => loadCarFromHistory(index);
const img = document.createElement('img');
img.className = 'car-thumbnail-img';
img.src = car.imageUrl;
img.alt = car.name;
const name = document.createElement('p');
name.className = 'car-thumbnail-name';
name.textContent = car.name;
container.appendChild(img);
container.appendChild(name);
historyList.appendChild(container);
});
}
function loadCarFromHistory(index) {
const car = generatedCars[index];
if (!car) return;
// 1. Update form controls
// Map the stored config back to the select elements
Object.keys(car.config).forEach(key => {
const selectEl = document.getElementById(key);
if (selectEl) {
selectEl.value = car.config[key];
}
});
// 2. Update visualizer output
generatedImageEl.src = car.imageUrl;
outputPromptEl.value = car.prompt;
// 3. Close and reopen the first accordion to visually confirm load
setupAccordions(true);
}
// --- Accordion Logic ---
const headers = document.querySelectorAll('.accordion-header');
function setupAccordions(forceOpenFirst = false) {
headers.forEach(header => {
// Remove existing listener to prevent duplicates after re-initialization
header.onclick = null;
const targetId = header.getAttribute('data-target');
const content = document.getElementById(targetId);
if (forceOpenFirst && header.getAttribute('data-target') === 'exteriorContent') {
// Force open the first accordion
content.classList.add('active');
content.style.maxHeight = (content.scrollHeight + 50) + "px";
header.querySelector('span').textContent = '—';
} else if (forceOpenFirst) {
// Ensure others are closed
content.classList.remove('active');
content.style.maxHeight = null;
header.querySelector('span').textContent = '+';
}
// Re-add the click listener
header.addEventListener('click', () => {
const isActive = content.classList.contains('active');
// Close all contents first
document.querySelectorAll('.accordion-content').forEach(c => {
c.style.maxHeight = null;
c.classList.remove('active');
const h = c.previousElementSibling;
if (h) h.querySelector('span').textContent = '+';
});
if (!isActive) {
// Open the clicked content
content.classList.add('active');
content.style.maxHeight = (content.scrollHeight + 50) + "px";
header.querySelector('span').textContent = '—';
}
});
});
}
// --- Image Generation Logic ---
const generateBtn = document.getElementById('generateBtn');
const loadingEl = document.getElementById('loading');
const outputPromptEl = document.getElementById('outputPrompt');
const generatedImageEl = document.getElementById('generatedImage');
const errorMsgEl = document.getElementById('errorMsg');
function getCurrentConfig() {
return {
bodyStyle: document.getElementById('bodyStyle')?.value || '',
color: document.getElementById('color')?.value || '',
aero: document.getElementById('aero')?.value || '',
finish: document.getElementById('finish')?.value || '',
wheels: document.getElementById('wheels')?.value || '',
suspension: document.getElementById('suspension')?.value || '',
exhaust: document.getElementById('exhaust')?.value || '',
brakes: document.getElementById('brakes')?.value || '',
setting: document.getElementById('setting')?.value || '',
style: document.getElementById('style')?.value || '',
camera: document.getElementById('camera')?.value || '',
artist: document.getElementById('artist')?.value || ''
};
}
/**
* Gathers all selected values and formats them into a high-quality prompt string.
*/
function collectPromptParts(config) {
// Construct the detailed, high-quality prompt string
const promptParts = [
'A custom GODSROD vehicle',
config.color,
config.bodyStyle,
config.finish,
config.aero,
config.wheels,
config.suspension,
config.exhaust,
config.brakes,
'highly detailed',
'photorealistic',
config.camera,
config.setting,
config.style,
'cinematic lighting, film grain',
config.artist
];
// Clean up the prompt by removing empty or duplicate values and joining with commas
return promptParts.filter(p => p).join(', ');
}
/**
* Triggers the Pollinations.ai API call by setting the image source URL.
*/
window.generateImage = function() {
const config = getCurrentConfig();
const rawPrompt = collectPromptParts(config);
outputPromptEl.value = rawPrompt;
// Encode the prompt for the URL, replacing spaces with underscores (common Pollinations practice)
const encodedPrompt = encodeURIComponent(rawPrompt).replace(/%20/g, '_');
// Pollinations.ai API URL structure
const imageUrl = `https://image.pollinations.ai/prompt/${encodedPrompt}?width=800&height=450&model=sdxl_turbo&nologo=true`;
// UI state management
generateBtn.disabled = true;
loadingEl.classList.remove('hidden');
errorMsgEl.classList.add('hidden');
// Show a loading image
generatedImageEl.src = "https://placehold.co/800x450/212121/fbc02d?text=GENERATING...+(Please+Wait)";
const tempImage = new Image();
tempImage.crossOrigin = "anonymous";
tempImage.onload = () => {
const finalImageUrl = tempImage.src; // Use the final loaded URL (which might be redirected)
generatedImageEl.src = finalImageUrl;
// Save successful generation to history
saveCarToHistory(rawPrompt, finalImageUrl, config);
generateBtn.disabled = false;
loadingEl.classList.add('hidden');
};
tempImage.onerror = () => {
generatedImageEl.src = "https://placehold.co/800x450/331a1a/ff0000?text=GENERATION+ERROR";
errorMsgEl.classList.remove('hidden');
generateBtn.disabled = false;
loadingEl.classList.add('hidden');
};
// This initiates the image generation process via the URL
tempImage.src = imageUrl;
};
// Initialize: setup accordions and set default prompt
window.onload = () => {
setupAccordions(true); // Open first accordion on load
outputPromptEl.value = collectPromptParts(getCurrentConfig()); // Show the default prompt
};
</script>
</body>
</html>