Simonc-44 commited on
Commit
4b88f58
·
verified ·
1 Parent(s): 7a6bdbf

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +388 -257
index.html CHANGED
@@ -6,345 +6,458 @@
6
  <title>Cygnis Image Studio</title>
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
10
  <script src="https://unpkg.com/lucide@latest"></script>
11
  <style>
12
  :root {
13
- --bg-app: #1e1e1e;
14
- --bg-panel: #252526;
15
- --bg-canvas: #111111;
16
- --border: #3e3e42;
17
- --accent: #0f64d2;
18
- --text: #cccccc;
19
- --text-light: #ffffff;
20
- --toolbar-width: 50px;
21
- --panel-width: 300px;
22
  }
23
 
24
  * { margin: 0; padding: 0; box-sizing: border-box; }
25
 
26
  body {
27
- background-color: var(--bg-app);
28
- color: var(--text);
29
  font-family: 'Inter', sans-serif;
30
  height: 100vh;
31
  display: flex;
32
- flex-direction: column;
33
  overflow: hidden;
34
- font-size: 12px;
35
- user-select: none;
 
36
  }
37
 
38
- /* --- MENU BAR --- */
39
- .menubar {
40
- height: 30px;
41
- background-color: #2d2d2d;
 
 
42
  display: flex;
43
- align-items: center;
44
- padding: 0 10px;
45
- border-bottom: 1px solid #000;
 
46
  }
47
 
48
- .menu-item { padding: 0 10px; cursor: pointer; }
49
- .menu-item:hover { background: #3e3e42; color: white; }
50
-
51
- /* --- MAIN LAYOUT --- */
52
- .workspace {
53
  display: flex;
54
- flex: 1;
55
- overflow: hidden;
 
 
56
  }
 
57
 
58
- /* --- TOOLBAR (LEFT) --- */
59
- .toolbar {
60
- width: var(--toolbar-width);
61
- background-color: var(--bg-panel);
62
- border-right: 1px solid #000;
63
  display: flex;
64
- flex-direction: column;
65
  align-items: center;
66
- padding-top: 10px;
67
- gap: 5px;
68
- }
69
-
70
- .tool-btn {
71
- width: 36px; height: 36px;
72
- display: flex; align-items: center; justify-content: center;
73
- border-radius: 3px;
74
  cursor: pointer;
75
- color: #a0a0a0;
76
- transition: all 0.1s;
 
 
 
 
 
 
 
 
 
 
77
  }
78
 
79
- .tool-btn:hover { background: #3e3e42; color: white; }
80
- .tool-btn.active { background: #3e3e42; color: var(--accent); border-left: 2px solid var(--accent); }
81
-
82
- /* --- CENTER AREA (CANVAS + GALLERY) --- */
83
- .center-area {
84
  flex: 1;
85
  display: flex;
86
  flex-direction: column;
87
- background-color: var(--bg-canvas);
88
  }
89
 
90
- .canvas-area {
91
  flex: 1;
92
- display: flex;
 
 
 
 
93
  align-items: center;
94
- justify-content: center;
95
- position: relative;
96
- overflow: hidden;
97
- border-bottom: 1px solid #000;
98
  }
 
99
 
100
- .canvas-wrapper {
101
- width: 512px; height: 512px;
102
- background-color: white;
103
- box-shadow: 0 0 20px rgba(0,0,0,0.5);
104
- background-image:
105
- linear-gradient(45deg, #ccc 25%, transparent 25%),
106
- linear-gradient(-45deg, #ccc 25%, transparent 25%),
107
- linear-gradient(45deg, transparent 75%, #ccc 75%),
108
- linear-gradient(-45deg, transparent 75%, #ccc 75%);
109
- background-size: 20px 20px;
110
- background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  display: flex; align-items: center; justify-content: center;
 
112
  }
113
 
114
- .generated-image {
115
- width: 100%; height: 100%;
116
- object-fit: contain;
117
- display: none;
118
  }
119
 
120
- /* --- GALLERY PANEL (BOTTOM) --- */
121
- .gallery-panel {
122
- height: 150px;
123
- background-color: var(--bg-panel);
124
  display: flex;
125
- flex-direction: column;
 
 
126
  }
127
 
128
- .gallery-content {
 
 
 
 
129
  flex: 1;
130
- padding: 10px;
 
 
131
  display: flex;
132
- gap: 10px;
133
- overflow-x: auto;
134
  }
135
 
136
- .gallery-item {
137
- width: 100px; height: 100px;
138
- background: #333;
139
- border: 1px solid #444;
 
 
 
140
  cursor: pointer;
141
- position: relative;
 
 
 
 
 
 
142
  }
143
- .gallery-item:hover { border-color: var(--accent); }
144
- .gallery-item img { width: 100%; height: 100%; object-fit: cover; }
145
 
146
- /* --- PROPERTIES PANEL (RIGHT) --- */
147
- .properties {
148
- width: var(--panel-width);
149
- background-color: var(--bg-panel);
150
- border-left: 1px solid #000;
151
- display: flex;
152
- flex-direction: column;
153
  }
154
 
155
- .panel-header {
156
- padding: 8px 12px;
157
- background: #333;
158
- font-weight: 600;
159
- border-bottom: 1px solid #000;
160
- border-top: 1px solid #444;
161
- color: #eee;
162
- font-size: 11px;
163
- text-transform: uppercase;
164
- letter-spacing: 0.5px;
165
  }
 
 
166
 
167
- .panel-content {
168
- padding: 15px;
 
169
  display: flex;
170
- flex-direction: column;
171
- gap: 15px;
 
 
172
  }
173
 
174
- .control-group {
175
- display: flex; flex-direction: column; gap: 6px;
 
 
 
 
 
 
 
 
 
 
 
176
  }
177
 
178
- label { color: #ccc; font-size: 11px; font-weight: 600; }
 
 
 
 
179
 
180
- textarea {
181
- background: #181818;
182
- border: 1px solid #444;
 
183
  color: white;
184
- padding: 8px;
185
- border-radius: 2px;
186
- resize: none;
187
- height: 100px;
188
  font-family: inherit;
189
- font-size: 12px;
190
  }
191
- textarea:focus { border-color: var(--accent); outline: none; }
 
192
 
193
- .btn-primary {
194
- background: var(--accent);
 
195
  color: white;
196
  border: none;
197
- padding: 8px;
198
- border-radius: 2px;
199
- font-weight: 600;
 
200
  cursor: pointer;
201
- transition: background 0.2s;
 
 
 
 
202
  }
203
- .btn-primary:hover { background: #1473e6; }
204
- .btn-primary:disabled { background: #444; color: #888; cursor: not-allowed; }
205
 
206
- /* --- LOADER --- */
207
- .loader {
208
- width: 40px; height: 40px;
209
- border: 3px solid rgba(255,255,255,0.1);
210
- border-top-color: var(--accent);
211
- border-radius: 50%;
212
- animation: spin 1s linear infinite;
213
- display: none;
214
  }
215
- @keyframes spin { to { transform: rotate(360deg); } }
216
 
217
- .status-bar {
218
- height: 24px;
219
- background: #2d2d2d;
220
- border-top: 1px solid #000;
221
- display: flex; align-items: center;
222
- padding: 0 10px;
223
- font-size: 11px;
224
- color: #aaa;
225
- justify-content: space-between;
226
  }
227
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
  </style>
229
  </head>
230
  <body>
231
 
232
- <!-- MENU BAR -->
233
- <div class="menubar">
234
- <div class="menu-item" style="font-weight:700; color:#0f64d2;">Ps</div>
235
- <div class="menu-item">Fichier</div>
236
- <div class="menu-item">Édition</div>
237
- <div class="menu-item">Image</div>
238
- <div class="menu-item">Calque</div>
239
- <div class="menu-item">Sélection</div>
240
- <div class="menu-item">Filtre</div>
241
- <div class="menu-item">Affichage</div>
242
- <div class="menu-item">Fenêtre</div>
243
- <div class="menu-item">Aide</div>
244
- </div>
245
-
246
- <div class="workspace">
247
- <!-- TOOLBAR -->
248
- <div class="toolbar">
249
- <div class="tool-btn active"><i data-lucide="move"></i></div>
250
- <div class="tool-btn"><i data-lucide="crop"></i></div>
251
- <div class="tool-btn"><i data-lucide="lasso"></i></div>
252
- <div class="tool-btn"><i data-lucide="brush"></i></div>
253
- <div class="tool-btn"><i data-lucide="eraser"></i></div>
254
- <div class="tool-btn"><i data-lucide="type"></i></div>
255
- <div class="tool-btn"><i data-lucide="paint-bucket"></i></div>
256
- <div class="tool-btn"><i data-lucide="zoom-in"></i></div>
257
- <div style="flex:1"></div>
258
- <div class="tool-btn"><i data-lucide="palette"></i></div>
259
  </div>
260
-
261
- <!-- CENTER AREA -->
262
- <div class="center-area">
263
- <div class="canvas-area">
264
- <div class="canvas-wrapper">
265
- <div class="loader" id="loader"></div>
266
- <img id="result-img" class="generated-image" alt="Résultat">
267
- </div>
268
- </div>
269
-
270
- <div class="gallery-panel">
271
- <div class="panel-header">Bibliothèque / Historique</div>
272
- <div class="gallery-content" id="gallery">
273
- <!-- Images will be added here -->
 
274
  </div>
275
  </div>
276
  </div>
277
 
278
- <!-- PROPERTIES -->
279
- <div class="properties">
280
- <div class="panel-header">Propriétés</div>
281
- <div class="panel-content">
282
- <div class="control-group">
283
- <label>PROMPT (DESCRIPTION)</label>
284
- <textarea id="prompt" placeholder="Décrivez l'image à générer..."></textarea>
285
- </div>
286
-
287
- <div class="control-group">
288
- <label>MODÈLE</label>
289
- <select style="background:#181818; border:1px solid #444; color:white; padding:5px;">
290
- <option>Stable Diffusion XL (Cloud)</option>
291
- </select>
292
- </div>
293
-
294
- <button id="generate-btn" class="btn-primary">Générer</button>
295
  </div>
 
296
 
297
- <div class="panel-header" style="margin-top:auto;">Calques</div>
298
- <div class="layers-panel" style="flex:1; padding:10px;">
299
- <div style="display:flex; align-items:center; gap:10px; padding:5px; background:#3e3e42;">
300
- <div style="width:30px; height:30px; background:white; border:1px solid #000;"></div>
301
- <div style="color:white; font-weight:500;">Arrière-plan</div>
302
- <i data-lucide="eye" size="14" style="margin-left:auto; color:#ccc;"></i>
303
- </div>
304
  </div>
305
  </div>
306
- </div>
307
-
308
- <div class="status-bar">
309
- <span id="status-text">Prêt</span>
310
- <span>100%</span>
311
- </div>
312
 
313
  <script>
314
  lucide.createIcons();
315
 
316
  const btn = document.getElementById('generate-btn');
317
- const prompt = document.getElementById('prompt');
318
- const img = document.getElementById('result-img');
319
- const loader = document.getElementById('loader');
320
- const status = document.getElementById('status-text');
321
- const gallery = document.getElementById('gallery');
322
-
323
- // Load gallery from local storage
324
- const savedImages = JSON.parse(localStorage.getItem('cygnis_images') || '[]');
325
- savedImages.forEach(url => addToGallery(url));
326
-
327
- function addToGallery(url) {
328
- const div = document.createElement('div');
329
- div.className = 'gallery-item';
330
- div.innerHTML = `<img src="${url}" onclick="showImage('${url}')">`;
331
- gallery.prepend(div);
332
- }
333
-
334
- window.showImage = (url) => {
335
- img.src = url;
336
- img.style.display = 'block';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
  };
338
 
339
  btn.addEventListener('click', async () => {
340
- const text = prompt.value;
341
  if (!text) return;
342
 
 
343
  btn.disabled = true;
344
- btn.textContent = "Génération...";
345
- loader.style.display = 'block';
346
- img.style.display = 'none';
347
- status.textContent = "Calcul en cours (SDXL)...";
 
 
 
 
 
 
 
 
 
348
 
349
  try {
350
  const res = await fetch('/generate', {
@@ -356,25 +469,43 @@
356
  if (!res.ok) throw new Error("Erreur serveur");
357
  const data = await res.json();
358
 
359
- img.src = data.image_url;
360
- img.onload = () => {
361
- loader.style.display = 'none';
362
- img.style.display = 'block';
363
- status.textContent = "Terminé.";
364
-
365
- // Save to gallery
366
- addToGallery(data.image_url);
367
- savedImages.unshift(data.image_url);
368
- localStorage.setItem('cygnis_images', JSON.stringify(savedImages));
369
- };
 
 
 
 
 
 
 
370
  } catch (e) {
371
- status.textContent = "Erreur : " + e.message;
372
- loader.style.display = 'none';
373
  } finally {
374
  btn.disabled = false;
375
- btn.textContent = "Générer";
 
 
 
376
  }
377
  });
 
 
 
 
 
 
 
 
 
378
  </script>
379
  </body>
380
  </html>
 
6
  <title>Cygnis Image Studio</title>
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
10
  <script src="https://unpkg.com/lucide@latest"></script>
11
  <style>
12
  :root {
13
+ --bg-dark: #030014;
14
+ --panel-bg: #0a0a12;
15
+ --primary: #8b5cf6;
16
+ --primary-hover: #7c3aed;
17
+ --text-main: #ffffff;
18
+ --text-muted: #94a3b8;
19
+ --border: rgba(255, 255, 255, 0.08);
20
+ --card-bg: rgba(20, 20, 30, 0.6);
 
21
  }
22
 
23
  * { margin: 0; padding: 0; box-sizing: border-box; }
24
 
25
  body {
26
+ background-color: var(--bg-dark);
27
+ color: var(--text-main);
28
  font-family: 'Inter', sans-serif;
29
  height: 100vh;
30
  display: flex;
 
31
  overflow: hidden;
32
+ background-image:
33
+ radial-gradient(circle at 0% 0%, rgba(188, 19, 254, 0.15), transparent 40%),
34
+ radial-gradient(circle at 100% 100%, rgba(0, 243, 255, 0.1), transparent 40%);
35
  }
36
 
37
+ /* --- SIDEBAR --- */
38
+ .sidebar {
39
+ width: 260px;
40
+ background: rgba(10, 10, 18, 0.8);
41
+ backdrop-filter: blur(20px);
42
+ border-right: 1px solid var(--border);
43
  display: flex;
44
+ flex-direction: column;
45
+ padding: 2rem;
46
+ gap: 2rem;
47
+ z-index: 10;
48
  }
49
 
50
+ .logo {
51
+ font-size: 1.5rem;
52
+ font-weight: 700;
 
 
53
  display: flex;
54
+ align-items: center;
55
+ gap: 0.8rem;
56
+ color: white;
57
+ letter-spacing: -0.5px;
58
  }
59
+ .logo svg { color: var(--primary); filter: drop-shadow(0 0 10px var(--primary)); }
60
 
61
+ .nav-btn {
 
 
 
 
62
  display: flex;
 
63
  align-items: center;
64
+ gap: 1rem;
65
+ padding: 1rem;
66
+ border-radius: 12px;
67
+ color: var(--text-muted);
68
+ text-decoration: none;
69
+ font-size: 0.95rem;
70
+ font-weight: 500;
 
71
  cursor: pointer;
72
+ transition: all 0.3s;
73
+ border: 1px solid transparent;
74
+ }
75
+ .nav-btn:hover {
76
+ background: rgba(255,255,255,0.03);
77
+ color: white;
78
+ border-color: rgba(255,255,255,0.05);
79
+ }
80
+ .nav-btn.active {
81
+ background: linear-gradient(90deg, rgba(139, 92, 246, 0.1), transparent);
82
+ color: var(--primary);
83
+ border-left: 3px solid var(--primary);
84
  }
85
 
86
+ /* --- MAIN CONTENT --- */
87
+ .main {
 
 
 
88
  flex: 1;
89
  display: flex;
90
  flex-direction: column;
91
+ position: relative;
92
  }
93
 
94
+ .content-view {
95
  flex: 1;
96
+ overflow-y: auto;
97
+ padding: 2rem;
98
+ display: none;
99
+ flex-direction: column;
100
+ gap: 2rem;
101
  align-items: center;
102
+ scroll-behavior: smooth;
 
 
 
103
  }
104
+ .content-view.active { display: flex; }
105
 
106
+ .welcome-message {
107
+ text-align: center;
108
+ margin-top: 10vh;
109
+ opacity: 1;
110
+ transition: opacity 0.5s;
111
+ }
112
+ .welcome-message h1 {
113
+ font-size: 2.5rem;
114
+ font-weight: 700;
115
+ background: linear-gradient(to right, #fff, #a5b4fc);
116
+ -webkit-background-clip: text;
117
+ -webkit-text-fill-color: transparent;
118
+ margin-bottom: 1rem;
119
+ }
120
+ .welcome-message p { color: var(--text-muted); font-size: 1.1rem; }
121
+
122
+ /* --- IMAGE CARD --- */
123
+ .image-card {
124
+ background: var(--card-bg);
125
+ border: 1px solid var(--border);
126
+ border-radius: 24px;
127
+ padding: 1.5rem;
128
+ width: 100%;
129
+ max-width: 700px;
130
+ display: flex;
131
+ flex-direction: column;
132
+ gap: 1.5rem;
133
+ animation: slideUp 0.6s cubic-bezier(0.16, 1, 0.3, 1);
134
+ box-shadow: 0 20px 50px rgba(0,0,0,0.3);
135
+ backdrop-filter: blur(10px);
136
+ transition: transform 0.3s;
137
+ }
138
+
139
+ .image-wrapper {
140
+ width: 100%;
141
+ border-radius: 16px;
142
+ overflow: hidden;
143
+ position: relative;
144
+ background: #000;
145
+ min-height: 400px;
146
  display: flex; align-items: center; justify-content: center;
147
+ box-shadow: 0 10px 30px rgba(0,0,0,0.5);
148
  }
149
 
150
+ .image-wrapper img {
151
+ width: 100%; height: auto; display: block;
152
+ transition: transform 0.5s;
 
153
  }
154
 
155
+ .image-wrapper:hover img { transform: scale(1.02); }
156
+
157
+ .card-footer {
 
158
  display: flex;
159
+ justify-content: space-between;
160
+ align-items: flex-start;
161
+ gap: 1rem;
162
  }
163
 
164
+ .prompt-text {
165
+ font-size: 1rem;
166
+ color: #e0e0e0;
167
+ font-weight: 400;
168
+ line-height: 1.5;
169
  flex: 1;
170
+ }
171
+
172
+ .card-actions {
173
  display: flex;
174
+ gap: 0.8rem;
 
175
  }
176
 
177
+ .icon-btn {
178
+ background: rgba(255,255,255,0.05);
179
+ border: 1px solid rgba(255,255,255,0.1);
180
+ border-radius: 10px;
181
+ width: 42px; height: 42px;
182
+ display: flex; align-items: center; justify-content: center;
183
+ color: var(--text-muted);
184
  cursor: pointer;
185
+ transition: all 0.2s;
186
+ }
187
+ .icon-btn:hover {
188
+ background: rgba(255,255,255,0.1);
189
+ color: white;
190
+ border-color: rgba(255,255,255,0.2);
191
+ transform: translateY(-2px);
192
  }
 
 
193
 
194
+ /* --- GALLERY VIEW --- */
195
+ .gallery-grid {
196
+ display: grid;
197
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
198
+ gap: 1.5rem;
199
+ width: 100%;
 
200
  }
201
 
202
+ .gallery-item {
203
+ position: relative;
204
+ border-radius: 16px;
205
+ overflow: hidden;
206
+ cursor: pointer;
207
+ aspect-ratio: 1 / 1;
208
+ box-shadow: 0 10px 20px rgba(0,0,0,0.3);
 
 
 
209
  }
210
+ .gallery-item img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.3s; }
211
+ .gallery-item:hover img { transform: scale(1.05); }
212
 
213
+ /* --- INPUT AREA --- */
214
+ .input-container {
215
+ padding: 2rem;
216
  display: flex;
217
+ justify-content: center;
218
+ background: linear-gradient(to top, var(--bg-dark) 60%, transparent);
219
+ position: relative;
220
+ z-index: 20;
221
  }
222
 
223
+ .input-box {
224
+ width: 100%;
225
+ max-width: 700px;
226
+ background: rgba(20, 20, 30, 0.8);
227
+ backdrop-filter: blur(20px);
228
+ border: 1px solid var(--border);
229
+ border-radius: 20px;
230
+ padding: 0.8rem;
231
+ display: flex;
232
+ align-items: center;
233
+ gap: 1rem;
234
+ box-shadow: 0 10px 40px rgba(0,0,0,0.5);
235
+ transition: all 0.3s;
236
  }
237
 
238
+ .input-box:focus-within {
239
+ border-color: var(--primary);
240
+ box-shadow: 0 0 30px rgba(0, 243, 255, 0.15);
241
+ transform: translateY(-2px);
242
+ }
243
 
244
+ input {
245
+ flex: 1;
246
+ background: transparent;
247
+ border: none;
248
  color: white;
249
+ font-size: 1.1rem;
 
 
 
250
  font-family: inherit;
251
+ padding: 0.5rem 1rem;
252
  }
253
+ input:focus { outline: none; }
254
+ input::placeholder { color: rgba(255,255,255,0.3); }
255
 
256
+ /* --- WAOW BUTTON --- */
257
+ .generate-btn {
258
+ background: linear-gradient(135deg, var(--primary), var(--secondary));
259
  color: white;
260
  border: none;
261
+ border-radius: 14px;
262
+ padding: 0.8rem 1.5rem;
263
+ font-weight: 700;
264
+ font-size: 1rem;
265
  cursor: pointer;
266
+ display: flex; align-items: center; gap: 0.6rem;
267
+ transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
268
+ position: relative;
269
+ overflow: hidden;
270
+ box-shadow: 0 5px 15px rgba(188, 19, 254, 0.3);
271
  }
 
 
272
 
273
+ .generate-btn::before {
274
+ content: '';
275
+ position: absolute;
276
+ top: 0; left: -100%; width: 100%; height: 100%;
277
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
278
+ transition: 0.5s;
 
 
279
  }
 
280
 
281
+ .generate-btn:hover {
282
+ transform: scale(1.05);
283
+ box-shadow: 0 10px 25px rgba(0, 243, 255, 0.4);
 
 
 
 
 
 
284
  }
285
 
286
+ .generate-btn:hover::before { left: 100%; }
287
+
288
+ .generate-btn:disabled {
289
+ background: #1e293b;
290
+ color: #64748b;
291
+ cursor: not-allowed;
292
+ transform: none;
293
+ box-shadow: none;
294
+ }
295
+
296
+ /* --- LOADER --- */
297
+ .shimmer {
298
+ position: absolute;
299
+ top: 0; left: 0; width: 100%; height: 100%;
300
+ background: linear-gradient(90deg, #1a1a1a 25%, #2a2a2a 50%, #1a1a1a 75%);
301
+ background-size: 200% 100%;
302
+ animation: shimmer 1.5s infinite;
303
+ }
304
+
305
+ @keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
306
+ @keyframes slideUp { from { opacity: 0; transform: translateY(40px); } to { opacity: 1; transform: translateY(0); } }
307
+
308
  </style>
309
  </head>
310
  <body>
311
 
312
+ <aside class="sidebar">
313
+ <div class="logo">
314
+ <svg class="w-12 h-12 text-white" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path d="M12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3ZM12 19C8.13401 19 5 15.866 5 12C5 8.13401 8.13401 5 12 5C15.866 5 19 8.13401 19 12C19 15.866 15.866 19 12 19Z" fill="currentColor" fill-opacity="0.2"></path><path d="M16.5414 10.3541C15.8617 9.68729 14.974 9.24988 14 9.10241V12.1873L16.5414 10.3541Z" fill="currentColor"></path><path d="M12.0001 14.8129L9.45874 16.6461C10.1384 17.3129 11.0261 17.7503 12.0001 17.8978V14.8129Z" fill="currentColor"></path><path d="M12.0001 6.10254C12.974 6.24995 13.8617 6.68735 14.5414 7.35413L12.0001 9.18734V6.10254Z" fill="currentColor"></path><path d="M15.5 12C15.5 13.933 13.933 15.5 12 15.5C10.067 15.5 8.5 13.933 8.5 12C8.5 10.067 10.067 8.5 12 8.5C13.933 8.5 15.5 10.067 15.5 12Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>
315
+ Cygnis Image
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
316
  </div>
317
+ <nav style="flex:1; display:flex; flex-direction:column; gap:0.5rem;">
318
+ <div class="nav-btn active" onclick="showView('home')"><i data-lucide="sparkles"></i> Créer</div>
319
+ <div class="nav-btn" onclick="showView('gallery')"><i data-lucide="grid"></i> Galerie</div>
320
+ </nav>
321
+ </aside>
322
+
323
+ <main class="main">
324
+ <div id="view-home" class="content-view active">
325
+ <div class="gallery-container" id="gallery-home">
326
+ <div class="welcome-message" id="welcome">
327
+ <div style="width:80px; height:80px; background:linear-gradient(135deg, var(--primary), var(--secondary)); border-radius:24px; display:flex; align-items:center; justify-content:center; margin:0 auto 2rem; box-shadow:0 0 40px rgba(188,19,254,0.4);">
328
+ <i data-lucide="image" size="40" color="white"></i>
329
+ </div>
330
+ <h1 style="font-size:2.5rem; margin-bottom:1rem; font-weight:700;">Imaginez l'impossible</h1>
331
+ <p style="color:var(--text-muted); font-size:1.1rem;">Tapez votre idée ci-dessous pour commencer la création.</p>
332
  </div>
333
  </div>
334
  </div>
335
 
336
+ <div id="view-gallery" class="content-view">
337
+ <div class="gallery-grid" id="gallery-grid">
338
+ <!-- Gallery items will be injected here -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
339
  </div>
340
+ </div>
341
 
342
+ <div class="input-container">
343
+ <div class="input-box">
344
+ <input type="text" id="prompt" placeholder="Un chat cybernétique dans une ville néon..." autocomplete="off">
345
+ <button id="generate-btn" class="generate-btn">
346
+ <i data-lucide="wand-2" size="18"></i> Générer
347
+ </button>
 
348
  </div>
349
  </div>
350
+ </main>
 
 
 
 
 
351
 
352
  <script>
353
  lucide.createIcons();
354
 
355
  const btn = document.getElementById('generate-btn');
356
+ const promptInput = document.getElementById('prompt');
357
+ const galleryHome = document.getElementById('gallery-home');
358
+ const galleryGrid = document.getElementById('gallery-grid');
359
+ const welcome = document.getElementById('welcome');
360
+
361
+ // --- NAVIGATION ---
362
+ window.showView = (view) => {
363
+ document.querySelectorAll('.content-view').forEach(el => el.classList.remove('active'));
364
+ document.querySelectorAll('.nav-btn').forEach(el => el.classList.remove('active'));
365
+
366
+ if (view === 'home') {
367
+ document.getElementById('view-home').classList.add('active');
368
+ document.querySelector('.nav-btn:nth-child(1)').classList.add('active');
369
+ } else if (view === 'gallery') {
370
+ document.getElementById('view-gallery').classList.add('active');
371
+ document.querySelector('.nav-btn:nth-child(2)').classList.add('active');
372
+ renderGalleryGrid();
373
+ }
374
+ };
375
+
376
+ // --- DATA & STATE ---
377
+ let history = JSON.parse(localStorage.getItem('cygnis_img_history') || '[]');
378
+ if (history.length > 0) {
379
+ welcome.style.display = 'none';
380
+ history.forEach(item => addImageToHome(item.url, item.prompt, false));
381
+ }
382
+
383
+ function addImageToHome(url, promptText, prepend = true) {
384
+ const card = document.createElement('div');
385
+ card.className = 'image-card';
386
+ card.innerHTML = `
387
+ <div class="image-wrapper">
388
+ <img src="${url}" alt="${promptText}" onload="this.style.opacity=1" style="opacity:0; transition:opacity 0.5s;">
389
+ </div>
390
+ <div class="card-footer">
391
+ <div class="prompt-text">${promptText}</div>
392
+ <div class="card-actions">
393
+ <button class="icon-btn" onclick="downloadImage('${url}')" title="Télécharger"><i data-lucide="download" size="18"></i></button>
394
+ <button class="icon-btn" title="Copier le prompt"><i data-lucide="copy" size="18"></i></button>
395
+ </div>
396
+ </div>
397
+ `;
398
+
399
+ if (prepend) galleryHome.prepend(card);
400
+ else galleryHome.appendChild(card);
401
+
402
+ lucide.createIcons();
403
+ if (prepend) card.scrollIntoView({ behavior: 'smooth', block: 'center' });
404
+ }
405
+
406
+ function renderGalleryGrid() {
407
+ galleryGrid.innerHTML = history.map(item => `
408
+ <div class="gallery-item" onclick="showImageInHome('${item.url}', '${item.prompt}')">
409
+ <img src="${item.url}" alt="${item.prompt}">
410
+ </div>
411
+ `).join('');
412
+ }
413
+
414
+ window.showImageInHome = (url, prompt) => {
415
+ showView('home');
416
+ // Remove welcome message if it's there
417
+ if (welcome) welcome.style.display = 'none';
418
+
419
+ // Check if card already exists
420
+ let existingCard = false;
421
+ document.querySelectorAll('.image-card').forEach(card => {
422
+ if (card.querySelector('img')?.src.includes(url)) {
423
+ card.scrollIntoView({ behavior: 'smooth', block: 'center' });
424
+ existingCard = true;
425
+ }
426
+ });
427
+
428
+ if (!existingCard) {
429
+ addImageToHome(url, prompt, true);
430
+ }
431
+ };
432
+
433
+ window.downloadImage = (url) => {
434
+ const a = document.createElement('a');
435
+ a.href = url;
436
+ a.download = 'cygnis-creation.png';
437
+ document.body.appendChild(a);
438
+ a.click();
439
+ document.body.removeChild(a);
440
  };
441
 
442
  btn.addEventListener('click', async () => {
443
+ const text = promptInput.value;
444
  if (!text) return;
445
 
446
+ welcome.style.display = 'none';
447
  btn.disabled = true;
448
+ btn.innerHTML = '<div style="width:20px;height:20px;border:3px solid white;border-top-color:transparent;border-radius:50%;animation:spin 1s linear infinite;"></div>';
449
+
450
+ const placeholder = document.createElement('div');
451
+ placeholder.className = 'image-card';
452
+ placeholder.innerHTML = `
453
+ <div class="image-wrapper">
454
+ <div class="shimmer"></div>
455
+ </div>
456
+ <div class="card-footer">
457
+ <div class="prompt-text">${text}</div>
458
+ </div>
459
+ `;
460
+ galleryHome.prepend(placeholder);
461
 
462
  try {
463
  const res = await fetch('/generate', {
 
469
  if (!res.ok) throw new Error("Erreur serveur");
470
  const data = await res.json();
471
 
472
+ placeholder.innerHTML = `
473
+ <div class="image-wrapper">
474
+ <img src="${data.image_url}" alt="${text}">
475
+ </div>
476
+ <div class="card-footer">
477
+ <div class="prompt-text">${text}</div>
478
+ <div class="card-actions">
479
+ <button class="icon-btn" onclick="downloadImage('${data.image_url}')"><i data-lucide="download" size="18"></i></button>
480
+ <button class="icon-btn"><i data-lucide="copy" size="18"></i></button>
481
+ </div>
482
+ </div>
483
+ `;
484
+ lucide.createIcons();
485
+
486
+ const newHistory = [{ url: data.image_url, prompt: text }, ...history].slice(0, 50);
487
+ localStorage.setItem('cygnis_img_history', JSON.stringify(newHistory));
488
+ history = newHistory;
489
+
490
  } catch (e) {
491
+ placeholder.innerHTML = `<p style="color:#ef4444; text-align:center; padding:2rem;">Erreur : ${e.message}</p>`;
 
492
  } finally {
493
  btn.disabled = false;
494
+ btn.innerHTML = '<i data-lucide="wand-2" size="18"></i> Générer';
495
+ lucide.createIcons();
496
+ promptInput.value = '';
497
+ promptInput.focus();
498
  }
499
  });
500
+
501
+ promptInput.addEventListener('keypress', (e) => {
502
+ if (e.key === 'Enter') btn.click();
503
+ });
504
+
505
+ const style = document.createElement('style');
506
+ style.innerHTML = '@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }';
507
+ document.head.appendChild(style);
508
+
509
  </script>
510
  </body>
511
  </html>