Spaces:
Running
Running
Update index.html
Browse files- index.html +41 -81
index.html
CHANGED
|
@@ -21,12 +21,10 @@
|
|
| 21 |
.generate-btn { background: linear-gradient(90deg,#ff93e9,#9b7cff); border:none; border-radius:12px; padding:12px 18px; font-size:14px; cursor:pointer; color:#fff; min-height:56px; display:inline-flex; align-items:center; justify-content:center; transition:opacity .15s; }
|
| 22 |
.generate-btn[disabled]{ opacity:0.6; cursor:default; }
|
| 23 |
|
| 24 |
-
/* Always-visible control panel (no width/height fields) */
|
| 25 |
.controls { display:flex; flex-wrap:wrap; gap:10px; padding:10px; border:1px solid rgba(255,255,255,0.06); background:#0f0f0f; border-radius:12px; }
|
| 26 |
.controls label { font-size:13px; color:#eaeaea; display:flex; align-items:center; gap:6px; }
|
| 27 |
.controls select, .controls input[type="number"] { background:#0c0c0c; border:1px solid rgba(255,255,255,0.04); color:#fff; padding:6px 8px; border-radius:8px; font-size:13px; min-width:90px; }
|
| 28 |
|
| 29 |
-
/* Styles grid */
|
| 30 |
.styles-wrap { margin-top:6px; }
|
| 31 |
.styles-grid { display:grid; grid-template-columns:repeat(2, minmax(140px,1fr)); gap:8px; }
|
| 32 |
@media (min-width:760px){ .styles-grid { grid-template-columns:repeat(5, minmax(120px,1fr)); } }
|
|
@@ -57,7 +55,6 @@
|
|
| 57 |
<button id="generateBtn" class="generate-btn" aria-label="Generate">Generate</button>
|
| 58 |
</div>
|
| 59 |
|
| 60 |
-
<!-- Always-visible controls (no width/height) -->
|
| 61 |
<div class="controls" id="controls">
|
| 62 |
<label>Model
|
| 63 |
<select id="model">
|
|
@@ -70,8 +67,8 @@
|
|
| 70 |
<select id="aspect">
|
| 71 |
<option value="1:1">1:1</option>
|
| 72 |
<option value="16:9">16:9</option>
|
| 73 |
-
<option value="19:6"
|
| 74 |
-
<option value="3:2">3:2</option>
|
| 75 |
<option value="2:3">2:3</option>
|
| 76 |
<option value="4:5">4:5</option>
|
| 77 |
<option value="9:16">9:16</option>
|
|
@@ -92,7 +89,6 @@
|
|
| 92 |
<label><input id="randomSeed" type="checkbox" checked /> Random</label>
|
| 93 |
</div>
|
| 94 |
|
| 95 |
-
<!-- Styles inline -->
|
| 96 |
<div class="styles-wrap">
|
| 97 |
<div class="note">Choose a style (optional):</div>
|
| 98 |
<div class="styles-grid" id="stylesGrid">
|
|
@@ -109,41 +105,52 @@
|
|
| 109 |
</div>
|
| 110 |
|
| 111 |
<script>
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
const framing = params.get('default_framing');
|
| 118 |
-
|
| 119 |
-
if (aspect) {
|
| 120 |
-
const aspectDropdown = document.getElementById('aspectRatio');
|
| 121 |
-
if (aspectDropdown) {
|
| 122 |
-
aspectDropdown.value = aspect;
|
| 123 |
-
aspectDropdown.dispatchEvent(new Event('change'));
|
| 124 |
-
}
|
| 125 |
}
|
| 126 |
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
}
|
| 132 |
}
|
|
|
|
|
|
|
| 133 |
|
| 134 |
-
|
| 135 |
-
saveSettingsToStorage();
|
| 136 |
-
}
|
| 137 |
-
|
| 138 |
-
// Apply defaults right after settings load
|
| 139 |
-
loadSettingsFromStorage();
|
| 140 |
-
applyDefaultsFromURL();
|
| 141 |
-
|
| 142 |
const baseUrl = 'https://image.pollinations.ai';
|
| 143 |
let selectedStyle = null;
|
| 144 |
let isGenerating = false;
|
| 145 |
|
| 146 |
-
// Presets (cinema without letterbox; photo wider lens)
|
| 147 |
const styleTemplates = {
|
| 148 |
cinema: " cinematic lighting, rich color grading",
|
| 149 |
realistic: " realistic stock photo",
|
|
@@ -158,7 +165,6 @@ applyDefaultsFromURL();
|
|
| 158 |
full: "full body, head-to-toe, wide shot, 35mm lens, subject fully in frame, feet visible"
|
| 159 |
};
|
| 160 |
|
| 161 |
-
// Persist minimal settings (no width/height in storage)
|
| 162 |
function saveSettings() {
|
| 163 |
const s = {
|
| 164 |
model: document.getElementById('model').value,
|
|
@@ -182,15 +188,14 @@ applyDefaultsFromURL();
|
|
| 182 |
} catch(e) {}
|
| 183 |
}
|
| 184 |
|
| 185 |
-
// Aspect helpers → always compute high-res size within 2048
|
| 186 |
function parseAspect(aspectStr){ const [w,h]=aspectStr.split(':').map(Number); return (!w||!h)?1:(w/h); }
|
| 187 |
function sizeForAspect(aspectStr, maxDim=2048){
|
| 188 |
const r = parseAspect(aspectStr);
|
| 189 |
-
if (r >= 1){
|
| 190 |
const width = maxDim;
|
| 191 |
const height = Math.max(64, Math.round(width / r));
|
| 192 |
return {width, height};
|
| 193 |
-
} else {
|
| 194 |
const height = maxDim;
|
| 195 |
const width = Math.max(64, Math.round(height * r));
|
| 196 |
return {width, height};
|
|
@@ -199,7 +204,6 @@ applyDefaultsFromURL();
|
|
| 199 |
|
| 200 |
loadSettings();
|
| 201 |
|
| 202 |
-
// Style selection
|
| 203 |
document.querySelectorAll('.style-card').forEach(card=>{
|
| 204 |
card.addEventListener('click',()=>{
|
| 205 |
document.querySelectorAll('.style-card').forEach(c=>c.classList.remove('selected'));
|
|
@@ -208,13 +212,11 @@ applyDefaultsFromURL();
|
|
| 208 |
});
|
| 209 |
});
|
| 210 |
|
| 211 |
-
// Seed toggle
|
| 212 |
const randomSeedEl = document.getElementById('randomSeed');
|
| 213 |
const seedEl = document.getElementById('seed');
|
| 214 |
randomSeedEl.addEventListener('change', ()=>{ seedEl.disabled = randomSeedEl.checked; saveSettings(); });
|
| 215 |
seedEl.disabled = randomSeedEl.checked;
|
| 216 |
|
| 217 |
-
// Prompt textarea UX
|
| 218 |
const promptEl = document.getElementById('prompt');
|
| 219 |
function autoResizeTextarea(){ promptEl.style.height='auto'; promptEl.style.height=Math.min(250, promptEl.scrollHeight)+'px'; }
|
| 220 |
promptEl.addEventListener('input', autoResizeTextarea); setTimeout(autoResizeTextarea,0);
|
|
@@ -245,45 +247,3 @@ applyDefaultsFromURL();
|
|
| 245 |
async function generateImage(){
|
| 246 |
if (isGenerating) return;
|
| 247 |
const rawPrompt = promptEl.value.trim();
|
| 248 |
-
if (!rawPrompt){ const temp=document.createElement('div'); temp.className='image-card'; temp.innerHTML='<div class="muted">Please enter a prompt</div>'; imagesContainer.prepend(temp); setTimeout(()=>temp.remove(),1200); return; }
|
| 249 |
-
|
| 250 |
-
isGenerating=true; generateBtn.disabled=true;
|
| 251 |
-
const card=document.createElement('div'); card.className='image-card';
|
| 252 |
-
card.innerHTML=`<div style="display:flex;align-items:center;"><div class="loader"></div><div class="muted">Generating...</div></div>`; imagesContainer.prepend(card);
|
| 253 |
-
|
| 254 |
-
// Build prompt with selected style + framing
|
| 255 |
-
const framingChoice=document.getElementById('framing').value;
|
| 256 |
-
const aspect=document.getElementById('aspect').value;
|
| 257 |
-
const enhanceOn=document.getElementById('enhance').checked;
|
| 258 |
-
|
| 259 |
-
let promptWithStyle=rawPrompt;
|
| 260 |
-
if (selectedStyle && styleTemplates[selectedStyle]) promptWithStyle += ' ' + styleTemplates[selectedStyle];
|
| 261 |
-
if (framingTemplates[framingChoice]) promptWithStyle += ' ' + framingTemplates[framingChoice];
|
| 262 |
-
|
| 263 |
-
const finalPrompt = enhanceOn ? await enhancePrompt(promptWithStyle) : promptWithStyle;
|
| 264 |
-
|
| 265 |
-
// Always compute high-res size from aspect (no UI fields)
|
| 266 |
-
const {width, height} = sizeForAspect(aspect, 2048);
|
| 267 |
-
|
| 268 |
-
const model = encodeURIComponent(document.getElementById('model').value || 'flux');
|
| 269 |
-
const seed = document.getElementById('randomSeed').checked ? randomInt(0, 4294967295) : (Number(seedEl.value) || 42);
|
| 270 |
-
|
| 271 |
-
try{
|
| 272 |
-
const url = `${baseUrl}/prompt/${encodeURIComponent(finalPrompt)}?model=${model}&width=${width}&height=${height}&seed=${seed}&nologo=true&safe=false`;
|
| 273 |
-
const res = await fetch(url); if(!res.ok) throw new Error('Network '+res.status);
|
| 274 |
-
const blob = await res.blob(); const imgUrl = URL.createObjectURL(blob);
|
| 275 |
-
const filename = `image_${width}x${height}_seed${seed}.png`;
|
| 276 |
-
card.innerHTML = `<img src="${imgUrl}" alt="generated image">`;
|
| 277 |
-
card.appendChild(makeActionsBar(imgUrl, filename, finalPrompt, seed, width, height));
|
| 278 |
-
}catch(err){
|
| 279 |
-
console.error(err); card.innerHTML = `<div class="muted" style="color:#ff6b6b;">Error generating image</div>`;
|
| 280 |
-
}finally{
|
| 281 |
-
isGenerating=false; generateBtn.disabled=false; saveSettings();
|
| 282 |
-
}
|
| 283 |
-
}
|
| 284 |
-
|
| 285 |
-
document.getElementById('generateBtn').addEventListener('click', generateImage);
|
| 286 |
-
promptEl.addEventListener('keydown', (e)=>{ if(e.key==='Enter'&&(e.ctrlKey||e.metaKey)) generateImage(); });
|
| 287 |
-
</script>
|
| 288 |
-
</body>
|
| 289 |
-
</html>
|
|
|
|
| 21 |
.generate-btn { background: linear-gradient(90deg,#ff93e9,#9b7cff); border:none; border-radius:12px; padding:12px 18px; font-size:14px; cursor:pointer; color:#fff; min-height:56px; display:inline-flex; align-items:center; justify-content:center; transition:opacity .15s; }
|
| 22 |
.generate-btn[disabled]{ opacity:0.6; cursor:default; }
|
| 23 |
|
|
|
|
| 24 |
.controls { display:flex; flex-wrap:wrap; gap:10px; padding:10px; border:1px solid rgba(255,255,255,0.06); background:#0f0f0f; border-radius:12px; }
|
| 25 |
.controls label { font-size:13px; color:#eaeaea; display:flex; align-items:center; gap:6px; }
|
| 26 |
.controls select, .controls input[type="number"] { background:#0c0c0c; border:1px solid rgba(255,255,255,0.04); color:#fff; padding:6px 8px; border-radius:8px; font-size:13px; min-width:90px; }
|
| 27 |
|
|
|
|
| 28 |
.styles-wrap { margin-top:6px; }
|
| 29 |
.styles-grid { display:grid; grid-template-columns:repeat(2, minmax(140px,1fr)); gap:8px; }
|
| 30 |
@media (min-width:760px){ .styles-grid { grid-template-columns:repeat(5, minmax(120px,1fr)); } }
|
|
|
|
| 55 |
<button id="generateBtn" class="generate-btn" aria-label="Generate">Generate</button>
|
| 56 |
</div>
|
| 57 |
|
|
|
|
| 58 |
<div class="controls" id="controls">
|
| 59 |
<label>Model
|
| 60 |
<select id="model">
|
|
|
|
| 67 |
<select id="aspect">
|
| 68 |
<option value="1:1">1:1</option>
|
| 69 |
<option value="16:9">16:9</option>
|
| 70 |
+
<option value="19:6">19:6</option>
|
| 71 |
+
<option value="3:2" selected>3:2</option>
|
| 72 |
<option value="2:3">2:3</option>
|
| 73 |
<option value="4:5">4:5</option>
|
| 74 |
<option value="9:16">9:16</option>
|
|
|
|
| 89 |
<label><input id="randomSeed" type="checkbox" checked /> Random</label>
|
| 90 |
</div>
|
| 91 |
|
|
|
|
| 92 |
<div class="styles-wrap">
|
| 93 |
<div class="note">Choose a style (optional):</div>
|
| 94 |
<div class="styles-grid" id="stylesGrid">
|
|
|
|
| 105 |
</div>
|
| 106 |
|
| 107 |
<script>
|
| 108 |
+
(function initDefaultsFromURL() {
|
| 109 |
+
if (document.readyState === 'loading') {
|
| 110 |
+
document.addEventListener('DOMContentLoaded', applyDefaultsFromURLSafe);
|
| 111 |
+
} else {
|
| 112 |
+
applyDefaultsFromURLSafe();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
}
|
| 114 |
|
| 115 |
+
function applyDefaultsFromURLSafe() {
|
| 116 |
+
try {
|
| 117 |
+
const params = new URLSearchParams(window.location.search);
|
| 118 |
+
const aspectParam = params.get('default_aspect');
|
| 119 |
+
const framingParam = params.get('default_framing');
|
| 120 |
+
const framingMap = { 'full-body': 'full', 'close-up': 'close' };
|
| 121 |
+
|
| 122 |
+
const aspectEl = document.getElementById('aspect');
|
| 123 |
+
const framingEl = document.getElementById('framing');
|
| 124 |
+
|
| 125 |
+
if (aspectEl) {
|
| 126 |
+
const defaultAspect = aspectParam || '3:2';
|
| 127 |
+
const hasOption = Array.from(aspectEl.options).some(o => o.value === defaultAspect);
|
| 128 |
+
if (hasOption) {
|
| 129 |
+
aspectEl.value = defaultAspect;
|
| 130 |
+
}
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
if (framingEl && (framingParam || framingMap[framingParam])) {
|
| 134 |
+
const normalized = framingMap[framingParam] || framingParam;
|
| 135 |
+
const hasOption = Array.from(framingEl.options).some(o => o.value === normalized);
|
| 136 |
+
if (hasOption) framingEl.value = normalized;
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
if (typeof saveSettings === 'function') {
|
| 140 |
+
saveSettings();
|
| 141 |
+
}
|
| 142 |
+
} catch (e) {
|
| 143 |
+
console.error('applyDefaultsFromURL failed:', e);
|
| 144 |
}
|
| 145 |
}
|
| 146 |
+
})();
|
| 147 |
+
</script>
|
| 148 |
|
| 149 |
+
<script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
const baseUrl = 'https://image.pollinations.ai';
|
| 151 |
let selectedStyle = null;
|
| 152 |
let isGenerating = false;
|
| 153 |
|
|
|
|
| 154 |
const styleTemplates = {
|
| 155 |
cinema: " cinematic lighting, rich color grading",
|
| 156 |
realistic: " realistic stock photo",
|
|
|
|
| 165 |
full: "full body, head-to-toe, wide shot, 35mm lens, subject fully in frame, feet visible"
|
| 166 |
};
|
| 167 |
|
|
|
|
| 168 |
function saveSettings() {
|
| 169 |
const s = {
|
| 170 |
model: document.getElementById('model').value,
|
|
|
|
| 188 |
} catch(e) {}
|
| 189 |
}
|
| 190 |
|
|
|
|
| 191 |
function parseAspect(aspectStr){ const [w,h]=aspectStr.split(':').map(Number); return (!w||!h)?1:(w/h); }
|
| 192 |
function sizeForAspect(aspectStr, maxDim=2048){
|
| 193 |
const r = parseAspect(aspectStr);
|
| 194 |
+
if (r >= 1){
|
| 195 |
const width = maxDim;
|
| 196 |
const height = Math.max(64, Math.round(width / r));
|
| 197 |
return {width, height};
|
| 198 |
+
} else {
|
| 199 |
const height = maxDim;
|
| 200 |
const width = Math.max(64, Math.round(height * r));
|
| 201 |
return {width, height};
|
|
|
|
| 204 |
|
| 205 |
loadSettings();
|
| 206 |
|
|
|
|
| 207 |
document.querySelectorAll('.style-card').forEach(card=>{
|
| 208 |
card.addEventListener('click',()=>{
|
| 209 |
document.querySelectorAll('.style-card').forEach(c=>c.classList.remove('selected'));
|
|
|
|
| 212 |
});
|
| 213 |
});
|
| 214 |
|
|
|
|
| 215 |
const randomSeedEl = document.getElementById('randomSeed');
|
| 216 |
const seedEl = document.getElementById('seed');
|
| 217 |
randomSeedEl.addEventListener('change', ()=>{ seedEl.disabled = randomSeedEl.checked; saveSettings(); });
|
| 218 |
seedEl.disabled = randomSeedEl.checked;
|
| 219 |
|
|
|
|
| 220 |
const promptEl = document.getElementById('prompt');
|
| 221 |
function autoResizeTextarea(){ promptEl.style.height='auto'; promptEl.style.height=Math.min(250, promptEl.scrollHeight)+'px'; }
|
| 222 |
promptEl.addEventListener('input', autoResizeTextarea); setTimeout(autoResizeTextarea,0);
|
|
|
|
| 247 |
async function generateImage(){
|
| 248 |
if (isGenerating) return;
|
| 249 |
const rawPrompt = promptEl.value.trim();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|