Sada8888 commited on
Commit
a173d7c
·
verified ·
1 Parent(s): 1a0b2fd

Update templates/image_studio.html

Browse files
Files changed (1) hide show
  1. templates/image_studio.html +277 -386
templates/image_studio.html CHANGED
@@ -16,299 +16,211 @@
16
  --text-primary: #1A202C;
17
  --text-secondary: #626F86;
18
  --text-tertiary: #8A94A6;
19
- --accent-primary: #8B5CF6;
20
- --accent-primary-hover: #7C3AED;
21
- --accent-primary-glow: rgba(139, 92, 246, 0.25);
22
- --accent-secondary: #EC4899;
23
- --premium-gold: #F59E0B;
24
- --success-color: #38A169;
25
  --danger-color: #e53e3e;
26
-
 
27
  --shadow-sm: 0 1px 2px 0 rgba(26, 32, 44, 0.03);
28
  --shadow-md: 0 4px 6px -1px rgba(26, 32, 44, 0.05);
29
  --shadow-lg: 0 10px 15px -3px rgba(26, 32, 44, 0.06);
30
  --shadow-xl: 0 20px 25px -5px rgba(26, 32, 44, 0.07);
31
-
32
  --radius-card: 24px;
33
  --radius-btn: 14px;
34
  --radius-input: 12px;
35
  --transition-smooth: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
36
  }
37
 
38
- @keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
39
- @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
40
- @keyframes shimmer { 0% { background-position: -1000px 0; } 100% { background-position: 1000px 0; } }
41
- @keyframes pulse-border { 0% { box-shadow: 0 0 0 0 rgba(139, 92, 246, 0.4); } 70% { box-shadow: 0 0 0 15px rgba(139, 92, 246, 0); } 100% { box-shadow: 0 0 0 0 rgba(139, 92, 246, 0); } }
42
-
43
- body {
44
- font-family: var(--app-font);
45
- background-color: var(--app-bg);
46
- color: var(--text-primary);
47
- margin: 0;
48
- padding: 2.5rem 1rem;
49
- display: flex;
50
- justify-content: center;
51
- min-height: 100vh;
52
- }
53
-
54
- .container { max-width: 850px; width: 100%; }
55
-
56
- /* Header */
57
- header { position: relative; text-align: center; margin-bottom: 2.5rem; padding: 2rem 0; animation: fadeIn 0.8s 0.1s ease-out backwards; overflow: hidden; }
58
- #neural-network-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; }
59
- .header-content { position: relative; z-index: 2; }
60
- h1 { font-size: 2.8rem; font-weight: 800; margin: 0 0 0.8rem 0; background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
61
- .subtitle { font-size: 1.1rem; color: var(--text-secondary); margin-top: 0; max-width: 650px; margin-left: auto; margin-right: auto; line-height: 1.8; }
62
-
63
- #subscription-status-badge { display: none; padding: 6px 16px; border-radius: 20px; font-size: 0.9em; font-weight: 700; margin-top: 1rem; }
64
- .free-badge { background: linear-gradient(45deg, #6c757d, #495057); color: white; }
65
- .paid-badge { background: linear-gradient(45deg, var(--premium-gold), #FFC107); color: #1E3A8A; }
66
-
67
- /* Main Panel */
68
- main { padding: 3rem; background-color: var(--panel-bg); border-radius: var(--radius-card); box-shadow: var(--shadow-xl); border: 1px solid var(--panel-border); animation: fadeIn 0.8s 0.3s ease-out backwards; }
69
-
70
- .form-group { margin-bottom: 2.5rem; }
71
- .form-group:last-child { margin-bottom: 0; }
72
- .form-label { display: flex; align-items: center; gap: 0.75rem; font-weight: 700; color: var(--text-primary); font-size: 1.2em; margin-bottom: 1.2rem; }
73
- .form-label svg { width: 24px; height: 24px; color: var(--accent-primary); }
74
-
75
- /* Square Model Selector */
76
- .model-selector { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.2rem; }
77
- .model-card {
78
- aspect-ratio: 1 / 1; /* کاملا مربع */
79
- background-color: var(--input-bg);
80
- border: 2px solid var(--input-border);
81
- border-radius: var(--radius-input);
82
- cursor: pointer;
83
- transition: var(--transition-smooth);
84
- position: relative;
85
- overflow: hidden;
86
- display: flex;
87
- flex-direction: column;
88
- }
89
- .model-card:hover { transform: translateY(-5px); box-shadow: var(--shadow-md); border-color: var(--accent-primary); }
90
- .model-card.active { border-color: var(--accent-primary); box-shadow: 0 0 0 4px var(--accent-primary-glow); }
91
-
92
- .model-image {
93
- width: 100%;
94
- height: 100%;
95
- background-size: cover;
96
- background-position: center;
97
- background-repeat: no-repeat;
98
- transition: transform 0.5s;
99
- }
100
- .model-card:hover .model-image { transform: scale(1.05); }
101
-
102
- .model-info {
103
- position: absolute;
104
- bottom: 0;
105
- left: 0;
106
- width: 100%;
107
- background: linear-gradient(to top, rgba(0,0,0,0.85) 0%, rgba(0,0,0,0.4) 70%, transparent 100%);
108
- color: white;
109
- padding: 15px 10px 10px;
110
- text-align: center;
111
- }
112
- .model-title { font-weight: 800; font-size: 1.1rem; text-shadow: 0 1px 3px rgba(0,0,0,0.8); }
113
 
114
- /* Ratio Selector */
115
- .aspect-ratio-selector { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; }
116
- .ratio-option { background-color: var(--input-bg); border: 2px solid var(--input-border); border-radius: var(--radius-input); padding: 1rem; cursor: pointer; transition: var(--transition-smooth); text-align: center; }
117
- .ratio-option:hover { border-color: var(--accent-primary); transform: translateY(-3px); }
118
- .ratio-option.active { border-color: var(--accent-primary); background-color: #fff; box-shadow: 0 0 0 3px var(--accent-primary-glow); }
119
- .ratio-icon { height: 40px; display: flex; align-items: center; justify-content: center; margin: 0 auto 0.5rem; }
120
- .ratio-icon > div { background-color: var(--panel-bg); border: 2px solid var(--text-tertiary); border-radius: 4px; transition: var(--transition-smooth); box-sizing: border-box; }
121
- .ratio-option.active .ratio-icon > div { border-color: var(--accent-primary); background-color: var(--accent-primary-glow); }
122
- .ratio-label { font-weight: 600; color: var(--text-secondary); font-size: 0.9rem; }
123
- .ratio-option.active .ratio-label { color: var(--text-primary); }
124
 
125
- #icon-portrait > div { width: 22px; height: 40px; }
126
- #icon-landscape > div { width: 48px; height: 27px; }
127
- #icon-square > div { width: 35px; height: 35px; }
128
- #icon-classic > div { width: 40px; height: 30px; }
129
-
130
- /* Textarea */
131
- textarea { width: 100%; padding: 1.2rem; border-radius: var(--radius-input); border: 1px solid var(--input-border); background-color: var(--input-bg); color: var(--text-primary); box-shadow: var(--shadow-sm) inset; font-family: var(--app-font); font-size: 1.05rem; box-sizing: border-box; transition: var(--transition-smooth); min-height: 120px; resize: vertical; line-height: 1.6;}
132
- textarea:focus { outline: none; border-color: var(--accent-primary); box-shadow: 0 0 0 3px var(--accent-primary-glow), var(--shadow-sm) inset; background-color: var(--panel-bg); }
133
-
134
- /* Big Button */
135
- #submit-btn { display: flex; align-items: center; justify-content: center; gap: 0.75rem; width: 100%; padding: 1.2rem; font-size: 1.3rem; font-weight: 800; background: linear-gradient(95deg, var(--accent-secondary) 0%, var(--accent-primary) 100%); color: #fff; border: none; border-radius: var(--radius-btn); cursor: pointer; transition: all 0.3s ease; box-shadow: 0 8px 20px -4px var(--accent-primary-glow); margin-top: 1.5rem; }
136
- #submit-btn svg { width: 26px; height: 26px; filter: drop-shadow(0 0 5px rgba(255,255,255,0.5)); }
137
- #submit-btn:hover:not(:disabled) { transform: translateY(-5px) scale(1.02); box-shadow: 0 12px 25px -4px var(--accent-primary-glow); }
138
- #submit-btn:disabled { background: var(--text-tertiary); cursor: not-allowed; box-shadow: none; opacity: 0.7; transform: none; }
139
- #submit-btn .spinner { width: 24px; height: 24px; border: 3px solid rgba(255, 255, 255, 0.4); border-top-color: #fff; border-radius: 50%; animation: spin 0.8s linear infinite; display: none; }
140
-
141
- /* Credit Info */
142
- #credit-info-section { margin-top: 1rem; padding: 1rem; background-color: var(--input-bg); border-radius: var(--radius-input); text-align: center; font-size: 0.95rem; font-weight: 600; color: var(--text-secondary); border: 1px solid var(--input-border); display: none; }
143
- #upgrade-button { margin-top: 1rem; width: 100%; padding: 1rem; font-size: 1.1rem; font-weight: 700; background: linear-gradient(95deg, #FFD54F, #FFC107 100%); color: #212529; border: none; border-radius: var(--radius-btn); cursor: pointer; box-shadow: 0 8px 20px -5px rgba(255, 193, 7, 0.3); display: none; }
144
-
145
- /* Results History Area */
146
- #results-area {
147
- margin-top: 3rem;
148
- display: flex;
149
- flex-direction: column;
150
- gap: 2rem;
151
- }
152
 
153
- .result-card {
154
- background-color: var(--panel-bg);
155
- border: 1px solid var(--panel-border);
156
- border-radius: var(--radius-card);
157
- padding: 1.5rem;
158
- box-shadow: var(--shadow-md);
159
- animation: fadeIn 0.5s ease-out;
160
- text-align: center;
161
- }
162
 
163
- .result-message-top {
164
- font-size: 1.05rem;
165
- font-weight: 700;
166
- color: var(--text-primary);
167
- margin-bottom: 1.5rem;
168
- }
169
 
170
- /* Dynamic Loading Box */
171
- .dynamic-loader-box {
172
- width: 100%;
173
- max-width: 500px;
174
- margin: 0 auto 1.5rem;
175
- border-radius: var(--radius-input);
176
- background: linear-gradient(90deg, var(--input-bg) 25%, #E2E8F0 50%, var(--input-bg) 75%);
177
- background-size: 1000px 100%;
178
- animation: shimmer 2s infinite linear, pulse-border 2s infinite;
179
- border: 2px solid var(--accent-primary);
180
- display: flex;
181
- align-items: center;
182
- justify-content: center;
183
- flex-direction: column;
184
- color: var(--accent-primary);
185
- font-weight: 700;
186
- }
187
- .dynamic-loader-box svg { width: 40px; height: 40px; animation: spin 2s linear infinite; margin-bottom: 10px; }
188
-
189
- /* Output Image */
190
- .result-image-container { margin-bottom: 1.5rem; }
191
- .result-image-container img {
192
- max-width: 100%;
193
- max-height: 600px;
194
- border-radius: var(--radius-input);
195
- box-shadow: var(--shadow-lg);
196
- border: 1px solid var(--panel-border);
197
- }
198
 
199
- /* Meta Data inside card */
200
- .result-meta {
201
- display: flex;
202
- justify-content: center;
203
- gap: 15px;
204
- margin-bottom: 1.5rem;
205
- flex-wrap: wrap;
206
- }
207
- .meta-badge {
208
- background-color: var(--input-bg);
209
- padding: 6px 14px;
210
- border-radius: 20px;
211
- font-size: 0.85rem;
212
- font-weight: 600;
213
- color: var(--text-secondary);
214
- border: 1px solid var(--input-border);
215
- }
216
 
217
- .download-button { display: inline-flex; align-items: center; justify-content: center; gap: 0.75rem; width: 100%; max-width: 300px; padding: 1rem; font-size: 1.05rem; font-weight: 700; background: var(--accent-primary); color: #fff; border: none; border-radius: var(--radius-btn); cursor: pointer; transition: all 0.3s ease; box-shadow: var(--shadow-md); text-decoration: none; margin: 0 auto;}
218
- .download-button:hover { background: var(--accent-primary-hover); transform: translateY(-3px); box-shadow: var(--shadow-lg); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
 
220
- @media (max-width: 768px) {
221
- main { padding: 1.5rem; }
222
- h1 { font-size: 2.2rem; }
223
- .model-selector { grid-template-columns: 1fr; }
224
- .model-card { aspect-ratio: auto; height: 120px; } /* در موبایل مستطیل شود تا جای کمتری بگیرد */
225
- .aspect-ratio-selector { grid-template-columns: repeat(2, 1fr); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
  }
227
  </style>
228
  </head>
229
  <body>
 
230
  <div class="container">
 
 
231
  <header>
232
  <canvas id="neural-network-canvas"></canvas>
233
  <div class="header-content">
234
- <h1>آتلیه هوش مصنوعی</h1>
235
- <p class="subtitle">ایده‌های خود را با پیشرفته‌ترین مدل‌های هوش مصنوعی (Flux, Midjourney, Cartoon) به واقعیت تبدیل کنید.</p>
236
  <div id="subscription-status-badge">درحال بررسی...</div>
237
  </div>
238
  </header>
239
 
240
- <main>
241
- <!-- ۱. انتخاب مدل -->
242
- <div class="form-group">
243
- <div class="form-label">
244
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
245
- ۱. انتخاب مدل هوش مصنوعی
 
 
 
 
 
246
  </div>
247
- <div class="model-selector" id="model-selector">
248
  <div class="model-card active" data-action="generate-flux" data-name="Flux Pro">
249
- <div class="model-image" style="background-image: url('https://app.puzzley.net/uploads/user/Jydo/Gpt4/pro.webp')"></div>
250
- <div class="model-info"><div class="model-title">Flux Pro</div></div>
251
  </div>
252
  <div class="model-card" data-action="generate-midjourney" data-name="Midjourney">
253
- <div class="model-image" style="background-image: url('https://app.puzzley.net/uploads/user/Jydo/%D9%87%D9%88%D8%B4%20%D9%85%D8%B5%D9%86%D9%88%D8%B9%DB%8C/Midjourney_Emblem.png')"></div>
254
- <div class="model-info"><div class="model-title">Midjourney</div></div>
255
  </div>
256
  <div class="model-card" data-action="generate-cartoon" data-name="Cartoon 3D">
257
- <div class="model-image" style="background-image: url('https://app.puzzley.net/uploads/user/Jydo/Ghibli/image%20(28).webp')"></div>
258
- <div class="model-info"><div class="model-title">Cartoon 3D</div></div>
259
  </div>
260
  </div>
261
  </div>
262
 
263
- <!-- ۲. انتخاب سایز -->
264
- <div class="form-group">
265
- <div class="form-label">
266
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="3" y1="9" x2="21" y2="9"></line><line x1="9" y1="21" x2="9" y2="9"></line></svg>
267
- ۲. اندازه تصویر را انتخاب کنید
268
  </div>
269
- <div class="aspect-ratio-selector" id="aspect-ratio-selector">
270
- <div class="ratio-option" data-ratio="9:16" data-label="عمودی ۹:۱۶">
271
- <div class="ratio-icon" id="icon-portrait"><div></div></div>
272
- <div class="ratio-label">عمودی ۹:۱۶</div>
273
- </div>
274
- <div class="ratio-option" data-ratio="16:9" data-label="افقی ۱۶:۹">
275
- <div class="ratio-icon" id="icon-landscape"><div></div></div>
276
- <div class="ratio-label">افقی ۱۶:۹</div>
277
- </div>
278
- <div class="ratio-option" data-ratio="4:3" data-label="کلاسیک ۴:۳">
279
- <div class="ratio-icon" id="icon-classic"><div></div></div>
280
- <div class="ratio-label">کلاسیک ۴:۳</div>
281
- </div>
282
- <div class="ratio-option active" data-ratio="1:1" data-label="مربع ۱:۱">
283
- <div class="ratio-icon" id="icon-square"><div></div></div>
284
- <div class="ratio-label">مربع ۱:۱</div>
285
- </div>
286
  </div>
287
  </div>
288
 
289
- <!-- ۳. کادر متن -->
290
- <div class="form-group">
291
- <div class="form-label">
292
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>
293
  ۳. ایده خود را بنویسید
294
  </div>
295
- <textarea id="prompt-input" placeholder="مثال: پرتره یک پیرمرد در باران با نورپردازی نئونی، کیفیت 8k، فوق‌العاده طبیعی..."></textarea>
296
- </div>
297
-
298
- <!-- دکمه‌ها و اعتبار -->
299
- <div id="credit-info-section">درحال بررسی اعتبار...</div>
300
- <button id="upgrade-button">⭐️ ارتقا به نسخه کامل و نامحدود</button>
301
 
302
- <button id="submit-btn">
303
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 3L12 8L17 10L12 12L10 17L8 12L3 10L8 8L10 3z"/></svg>
304
- <span id="btn-text">خلق تصویر جادویی</span>
305
- <div class="spinner"></div>
306
- </button>
307
-
308
- <!-- کانتینر نمایش نتایج (فید) -->
309
- <div id="results-area"></div>
310
 
311
- </main>
312
  </div>
313
 
314
  <script>
@@ -335,9 +247,9 @@
335
 
336
  function updateUIForSubscriptionStatus(status) {
337
  userSubscriptionStatus = status;
338
- const creditInfoDiv = document.getElementById('credit-info-section');
339
  const upgradeBtn = document.getElementById('upgrade-button');
340
- const genBtn = document.getElementById('submit-btn');
341
  const badge = document.getElementById('subscription-status-badge');
342
 
343
  badge.style.display = 'inline-block';
@@ -347,7 +259,7 @@
347
  badge.className = 'paid-badge';
348
  creditInfoDiv.style.display = 'none';
349
  upgradeBtn.style.display = 'none';
350
- genBtn.disabled = false;
351
  if(countdownInterval) clearInterval(countdownInterval);
352
  } else {
353
  badge.textContent = 'کاربر رایگان';
@@ -358,9 +270,9 @@
358
 
359
  async function checkFreeUserCredit() {
360
  if (!userFingerprint || userSubscriptionStatus === 'paid') return;
361
- const creditInfoDiv = document.getElementById('credit-info-section');
362
  const upgradeBtn = document.getElementById('upgrade-button');
363
- const genBtn = document.getElementById('submit-btn');
364
  try {
365
  const response = await fetch('/api/check-image-credit', {
366
  method: 'POST',
@@ -372,24 +284,24 @@
372
  creditInfoDiv.style.display = 'block';
373
 
374
  if (result.limit_reached) {
375
- genBtn.disabled = true;
376
  upgradeBtn.style.display = 'block';
377
  startCountdown(result.reset_timestamp);
378
  } else {
379
- genBtn.disabled = false;
380
  upgradeBtn.style.display = 'none';
381
  if(countdownInterval) clearInterval(countdownInterval);
382
  creditInfoDiv.innerHTML = `شما <b>${result.credits_remaining}</b> اعتبار ساخت تصویر برای امروز دارید.`;
383
  }
384
  } catch (error) {
385
  creditInfoDiv.textContent = "خطا در ارتباط با سرور اعتبار.";
386
- genBtn.disabled = true;
387
  }
388
  }
389
 
390
  function startCountdown(resetTimestamp) {
391
  if (countdownInterval) clearInterval(countdownInterval);
392
- const creditInfoDiv = document.getElementById('credit-info-section');
393
  const updateTimer = () => {
394
  const timeLeft = Math.max(0, resetTimestamp - (Date.now() / 1000));
395
  if (timeLeft === 0) {
@@ -404,7 +316,7 @@
404
  updateTimer(); countdownInterval = setInterval(updateTimer, 60000);
405
  }
406
 
407
- // ارتباط با اپلیکیشن والد
408
  window.addEventListener('message', (event) => {
409
  if (event.data?.type === 'USER_DATA_RESPONSE') {
410
  if (event.data.error || !event.data.payload) { updateUIForSubscriptionStatus('free'); return; }
@@ -424,20 +336,18 @@
424
  parent.postMessage({ type: 'REQUEST_USER_DATA' }, '*');
425
  });
426
 
427
- // --- بخش تعاملی UI ---
428
  const promptInput = document.getElementById('prompt-input');
429
- const submitBtn = document.getElementById('submit-btn');
430
- const btnText = document.getElementById('btn-text');
431
- const spinner = submitBtn.querySelector('.spinner');
432
- const btnIcon = submitBtn.querySelector('svg');
433
  const modelSelector = document.getElementById('model-selector');
434
- const ratioSelector = document.getElementById('aspect-ratio-selector');
435
- const resultsArea = document.getElementById('results-area');
436
 
437
  let selectedModelAction = 'generate-flux';
438
  let selectedModelName = 'Flux Pro';
439
  let selectedRatio = '1:1';
440
- let selectedRatioLabel = 'مربع ۱:۱';
 
441
 
442
  // انتخاب مدل
443
  modelSelector.addEventListener('click', (e) => {
@@ -451,12 +361,13 @@
451
 
452
  // انتخاب سایز
453
  ratioSelector.addEventListener('click', (e) => {
454
- const opt = e.target.closest('.ratio-option');
455
- if (!opt) return;
456
- ratioSelector.querySelectorAll('.ratio-option').forEach(c => c.classList.remove('active'));
457
- opt.classList.add('active');
458
- selectedRatio = opt.dataset.ratio;
459
- selectedRatioLabel = opt.dataset.label;
 
460
  });
461
 
462
  const getDimensions = (ratio) => {
@@ -468,114 +379,83 @@
468
  }
469
  };
470
 
471
- // تبدیل نسبت انتخاب شده به aspect-ratio برای انیمیشن لودینگ
472
- const getCssAspectRatio = (ratio) => {
473
- switch(ratio) {
474
- case '9:16': return '9 / 16';
475
- case '16:9': return '16 / 9';
476
- case '4:3': return '4 / 3';
477
- case '1:1': default: return '1 / 1';
478
- }
479
- };
480
-
481
- const setLoadingState = (isLoading) => {
482
- if (isLoading) {
483
- submitBtn.disabled = true;
484
- btnIcon.style.display = 'none';
485
- spinner.style.display = 'inline-block';
486
- btnText.textContent = 'در حال پردازش...';
487
- } else {
488
- submitBtn.disabled = false;
489
- btnIcon.style.display = 'inline-block';
490
- spinner.style.display = 'none';
491
- btnText.textContent = 'خلق تصویر جادویی';
492
- }
493
- };
494
 
495
- // ایجاد کارت پردازش اولیه (جعبه انیمیشنی)
496
- function createLoadingCard(ratio, modelName) {
497
- const card = document.createElement('div');
498
- card.className = 'result-card';
499
-
500
- const cssRatio = getCssAspectRatio(ratio);
501
-
502
- card.innerHTML = `
503
- <div class="result-message-top">در حال طراحی رویای شما با ${modelName}...</div>
504
- <div class="dynamic-loader-box" style="aspect-ratio: ${cssRatio};">
505
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
506
- <line x1="12" y1="2" x2="12" y2="6"></line>
507
- <line x1="12" y1="18" x2="12" y2="22"></line>
508
- <line x1="4.93" y1="4.93" x2="7.76" y2="7.76"></line>
509
- <line x1="16.24" y1="16.24" x2="19.07" y2="19.07"></line>
510
- <line x1="2" y1="12" x2="6" y2="12"></line>
511
- <line x1="18" y1="12" x2="22" y2="12"></line>
512
- <line x1="4.93" y1="19.07" x2="7.76" y2="16.24"></line>
513
- <line x1="16.24" y1="4.93" x2="19.07" y2="7.76"></line>
514
- </svg>
515
- <span>درحال ساخت...</span>
516
  </div>
517
  `;
518
-
519
- // اضافه کردن به بالای لیست
520
- resultsArea.insertBefore(card, resultsArea.firstChild);
521
-
522
- // اسکرول نرم به این کارت
523
- setTimeout(() => {
524
- card.scrollIntoView({ behavior: 'smooth', block: 'center' });
525
- }, 100);
526
-
527
- return card;
528
  }
529
 
530
- // جایگزین کردن باکس لودینگ با تصویر نهایی
531
- function finishCardWithImage(cardElement, imageUrl, modelName, ratioLabel) {
 
 
 
 
532
  const absoluteUrl = window.location.origin + imageUrl;
533
 
534
- cardElement.innerHTML = `
535
- <div class="result-message-top" style="color: var(--success-color);">
536
- تصویر شما آماده شد! امیدوارم لذت ببرید. 🎨
537
- </div>
538
- <div class="result-image-container">
539
- <img src="${imageUrl}" alt="تصویر ساخته شده">
540
- </div>
541
- <div class="result-meta">
542
- <span class="meta-badge">مدل: ${modelName}</span>
543
- <span class="meta-badge">ابعاد: ${ratioLabel}</span>
544
- </div>
545
- <button class="download-button" onclick="triggerDownload('${absoluteUrl}')">
546
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
547
- <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
548
- <polyline points="7 10 12 15 17 10"></polyline>
549
- <line x1="12" y1="15" x2="12" y2="3"></line>
550
- </svg>
551
- دانلود تصویر
552
- </button>
553
- `;
554
- }
555
 
556
- // عملکرد خطا در کارت
557
- function errorCard(cardElement, errorMsg) {
558
- cardElement.innerHTML = `
559
- <div class="result-message-top" style="color: var(--danger-color);">
560
- ❌ خطا در ساخت تصویر
561
  </div>
562
- <div style="color: var(--text-secondary); margin-bottom: 1rem;">${errorMsg}</div>
563
- <button class="download-button" style="background: var(--text-tertiary);" onclick="this.parentElement.remove()">بستن</button>
564
  `;
 
 
 
 
565
  }
566
 
567
- // تابع گلوبال برای دکمه‌های دانلود داخل کارت‌ها
568
  window.triggerDownload = function(url) {
569
- parent.postMessage({
570
- type: 'DOWNLOAD_REQUEST',
571
- url: url
572
- }, '*');
573
  };
574
 
575
- // هندل کردن ارسال
576
- submitBtn.addEventListener('click', async () => {
 
 
 
 
 
 
 
 
577
  const userPrompt = promptInput.value.trim();
578
- if (!userPrompt) return alert('لطفاً ایده خود را بنویسید.');
 
 
 
579
 
580
  // چک کردن کلمات ممنوعه محلی
581
  const forbiddenWords = ["sex", "sexy", "porn", "nude", "erotic", "سکس", "سکسی", "پورن", "شهوانی", "برهنه", "لخت"];
@@ -584,7 +464,7 @@
584
  return;
585
  }
586
 
587
- // چک کردن اعتبار قبل از ارسال
588
  if (userSubscriptionStatus !== 'paid') {
589
  try {
590
  const response = await fetch('/api/check-image-credit', {
@@ -593,7 +473,7 @@
593
  const result = await response.json();
594
  if (result.limit_reached) {
595
  updateUIForSubscriptionStatus('free');
596
- alert("محدودیت ساخت عکس روزانه شما به پایان رسیده است.");
597
  return;
598
  }
599
  } catch (err) {
@@ -601,21 +481,27 @@
601
  }
602
  }
603
 
604
- // قفل کردن و ساخت کارت لودینگ
605
- setLoadingState(true);
 
 
 
 
 
 
 
606
  isResultProcessed = false;
607
 
608
- // ذخیره مقادیر فعلی (در صورت تغییر توسط کاربر حین لودینگ، کارت فعلی به هم نریزد)
609
- const currentModelAction = selectedModelAction;
610
  const currentModelName = selectedModelName;
611
- const currentRatio = selectedRatio;
612
- const currentRatioLabel = selectedRatioLabel;
613
 
614
- const activeCard = createLoadingCard(currentRatio, currentModelName);
 
615
 
616
- const dims = getDimensions(currentRatio);
617
  let finalPrompt = userPrompt;
618
- if(currentModelAction === 'generate-cartoon') {
619
  finalPrompt += " کارتونی انیمیشن سه بعدی";
620
  }
621
 
@@ -627,14 +513,13 @@
627
  prompt: finalPrompt,
628
  width: dims.width,
629
  height: dims.height,
630
- action_name: currentModelAction
631
  })
632
  });
633
 
634
  const data = await res.json();
635
 
636
  if (data.status === 'success') {
637
- // شروع Polling برای همین کارت خاص
638
  let pollTimer = setInterval(async () => {
639
  try {
640
  const statusRes = await fetch(`/api/status/${data.run_id}`);
@@ -643,34 +528,40 @@
643
  if (statusData.status === 'ready' && !isResultProcessed) {
644
  isResultProcessed = true;
645
  clearInterval(pollTimer);
646
- setLoadingState(false);
647
 
648
- // کسر اعتبار فقط 1 بار
649
  if (userSubscriptionStatus === 'free') {
650
  try {
651
  await fetch('/api/use-image-credit', {
652
- method: 'POST',
653
- headers: {'Content-Type': 'application/json'},
654
- body: JSON.stringify({ fingerprint: userFingerprint })
655
  });
656
  checkFreeUserCredit();
657
  } catch (e) { console.error("Error using credit", e); }
658
  }
659
 
660
- // اتمام موفقیت آمیز کارت
661
- finishCardWithImage(activeCard, statusData.url, currentModelName, currentRatioLabel);
 
 
 
 
 
662
  }
663
  } catch(e) {
664
  console.error('Polling error', e);
665
  }
666
- }, 3000);
667
  } else {
668
- errorCard(activeCard, 'خطا در برقراری ارتباط با هسته هوش مصنوعی.');
669
- setLoadingState(false);
 
 
670
  }
671
  } catch (e) {
672
- errorCard(activeCard, 'عدم ارتباط با سرور.');
673
- setLoadingState(false);
 
 
674
  }
675
  });
676
 
@@ -678,10 +569,10 @@
678
  document.addEventListener('DOMContentLoaded', () => {
679
  const canvas = document.getElementById('neural-network-canvas');
680
  if(!canvas) return; const header = canvas.parentElement; const ctx = canvas.getContext('2d');
681
- let particles = []; const particleCount = 25; const maxDistance = 110;
682
- const particleColor = '#8B5CF6'; const lineColor = '#8A94A6';
683
  function resizeCanvas() { canvas.width = header.clientWidth; canvas.height = header.clientHeight; init(); }
684
- class Particle { constructor() { this.x = Math.random() * canvas.width; this.y = Math.random() * canvas.height; this.vx = (Math.random() - 0.5) * 0.4; this.vy = (Math.random() - 0.5) * 0.4; this.radius = 1.5; } update() { this.x += this.vx; this.y += this.vy; if (this.x < 0 || this.x > canvas.width) this.vx *= -1; if (this.y < 0 || this.y > canvas.height) this.vy *= -1; } draw() { ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); ctx.fillStyle = particleColor; ctx.fill(); } }
685
  function init() { particles = []; for (let i = 0; i < particleCount; i++) { particles.push(new Particle()); } }
686
  function connectParticles() { for (let i = 0; i < particles.length; i++) { for (let j = i + 1; j < particles.length; j++) { const dx = particles[i].x - particles[j].x; const dy = particles[i].y - particles[j].y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance < maxDistance) { ctx.beginPath(); ctx.moveTo(particles[i].x, particles[i].y); ctx.lineTo(particles[j].x, particles[j].y); ctx.strokeStyle = lineColor; ctx.lineWidth = 0.2; ctx.globalAlpha = 1 - distance / maxDistance; ctx.stroke(); } } } ctx.globalAlpha = 1; }
687
  function animate() { if(!ctx) return; ctx.clearRect(0, 0, canvas.width, canvas.height); particles.forEach(particle => { particle.update(); particle.draw(); }); connectParticles(); requestAnimationFrame(animate); }
 
16
  --text-primary: #1A202C;
17
  --text-secondary: #626F86;
18
  --text-tertiary: #8A94A6;
19
+ --accent-primary: #4A6CFA; /* تم آبی روشن قدیمی */
20
+ --accent-primary-hover: #3553D6;
21
+ --accent-primary-glow: rgba(74, 108, 250, 0.25);
22
+ --accent-secondary: #0FD4A8;
23
+ --accent-premium: #FFC107;
 
24
  --danger-color: #e53e3e;
25
+ --chat-user-bg: #4A6CFA;
26
+ --chat-ai-bg: #FFFFFF;
27
  --shadow-sm: 0 1px 2px 0 rgba(26, 32, 44, 0.03);
28
  --shadow-md: 0 4px 6px -1px rgba(26, 32, 44, 0.05);
29
  --shadow-lg: 0 10px 15px -3px rgba(26, 32, 44, 0.06);
30
  --shadow-xl: 0 20px 25px -5px rgba(26, 32, 44, 0.07);
 
31
  --radius-card: 24px;
32
  --radius-btn: 14px;
33
  --radius-input: 12px;
34
  --transition-smooth: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
35
  }
36
 
37
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
38
+ @keyframes badge-fade-in { from { opacity: 0; transform: translateY(-10px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } }
39
+ @keyframes spin-border { 100% { transform: rotate(360deg); } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
+ body { font-family: var(--app-font); background-color: var(--app-bg); color: var(--text-primary); margin: 0; padding: 2.5rem 1rem; display: flex; justify-content: center; min-height: 100vh; overflow-y: auto; }
42
+ .container { max-width: 820px; width: 100%; display: flex; flex-direction: column; gap: 2rem; }
43
+
44
+ header { position: relative; text-align: center; padding: 2rem 0; animation: fadeIn 0.8s 0.1s ease-out backwards; overflow: hidden; background: var(--panel-bg); border-radius: var(--radius-card); box-shadow: var(--shadow-md); border: 1px solid var(--panel-border); }
45
+ #neural-network-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; opacity: 0.6; }
46
+ .header-content { position: relative; z-index: 2; padding: 0 1.5rem; }
47
+ h1 { font-size: 2.5rem; font-weight: 800; margin: 0 0 0.8rem 0; background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
48
+ .subtitle { font-size: 1.1rem; color: var(--text-secondary); margin-top: 0; line-height: 1.8; }
 
 
49
 
50
+ #subscription-status-badge { display: none; padding: 6px 16px; border-radius: 20px; font-size: 0.9em; font-weight: 700; margin-top: 1rem; letter-spacing: 0.5px; animation: badge-fade-in 0.6s 0.5s ease-out backwards; }
51
+ #subscription-status-badge.free-badge { background: linear-gradient(45deg, #6c757d, #495057); color: white; box-shadow: 0 4px 10px rgba(108, 117, 125, 0.3); }
52
+ #subscription-status-badge.paid-badge { background: linear-gradient(45deg, var(--accent-premium), #ffca2c); color: #333; box-shadow: 0 4px 10px rgba(255, 193, 7, 0.3); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
+ /* Chat History Section */
55
+ #chat-history { display: flex; flex-direction: column; gap: 1.5rem; width: 100%; }
56
+
57
+ .message-wrapper { display: flex; flex-direction: column; max-width: 90%; animation: fadeIn 0.4s ease-out; }
58
+ .message-wrapper.user { align-self: flex-start; }
59
+ .message-wrapper.ai { align-self: center; width: 100%; max-width: 100%; align-items: center;}
 
 
 
60
 
61
+ .message-bubble { padding: 1rem 1.2rem; border-radius: var(--radius-md); font-size: 1rem; line-height: 1.6; }
62
+ .user .message-bubble { background: var(--chat-user-bg); color: #fff; border-top-right-radius: 4px; box-shadow: 0 4px 15px rgba(74, 108, 250, 0.3); }
63
+
64
+ .ai .message-bubble { background: var(--chat-ai-bg); color: var(--text-primary); border-radius: var(--radius-card); border: 1px solid var(--panel-border); box-shadow: var(--shadow-lg); padding: 1.5rem; width: 100%; display: flex; flex-direction: column; align-items: center; }
 
 
65
 
66
+ /* Dynamic Loading Animation matching Aspect Ratio */
67
+ .loading-frame { position: relative; border-radius: var(--radius-input); overflow: hidden; display: flex; align-items: center; justify-content: center; background: var(--input-bg); width: 100%; max-width: 400px; box-shadow: var(--shadow-md); }
68
+ .loading-frame::before { content: ''; position: absolute; width: 200%; height: 200%; background: conic-gradient(transparent, transparent, transparent, var(--accent-primary)); animation: spin-border 2s linear infinite; }
69
+ .loading-frame::after { content: 'در حال ساخت تصویر...'; position: absolute; inset: 4px; background: var(--panel-bg); border-radius: calc(var(--radius-input) - 4px); display: flex; align-items: center; justify-content: center; font-weight: 700; color: var(--accent-primary); font-family: var(--app-font); z-index: 1; box-shadow: inset 0 0 20px rgba(74, 108, 250, 0.05); }
70
+
71
+ .ratio-1-1 { aspect-ratio: 1 / 1; }
72
+ .ratio-16-9 { aspect-ratio: 16 / 9; }
73
+ .ratio-9-16 { aspect-ratio: 9 / 16; }
74
+ .ratio-4-3 { aspect-ratio: 4 / 3; }
75
+
76
+ /* Generated Image Style */
77
+ .generated-image-box { width: 100%; display: flex; flex-direction: column; align-items: center; gap: 1rem; }
78
+ .generated-image-box img { max-width: 100%; border-radius: var(--radius-input); border: 1px solid var(--panel-border); box-shadow: var(--shadow-md); }
79
+
80
+ .image-meta { display: flex; gap: 15px; justify-content: center; background: var(--input-bg); padding: 8px 16px; border-radius: 20px; font-size: 0.85rem; font-weight: 600; color: var(--text-secondary); border: 1px solid var(--input-border); margin-top: 5px; }
81
+ .image-meta span { display: flex; align-items: center; gap: 5px; }
82
+ .image-meta svg { width: 16px; height: 16px; color: var(--accent-primary); }
 
 
 
 
 
 
 
 
 
 
 
83
 
84
+ .download-btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; width: 100%; max-width: 300px; padding: 0.9rem; background: var(--accent-primary); color: #fff; border: none; border-radius: var(--radius-btn); font-family: inherit; font-weight: 700; cursor: pointer; transition: var(--transition-smooth); text-decoration: none; box-shadow: 0 4px 10px rgba(74, 108, 250, 0.3); }
85
+ .download-btn:hover { background: var(--accent-primary-hover); transform: translateY(-3px); box-shadow: 0 6px 15px rgba(74, 108, 250, 0.4); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
 
87
+ /* Control Panel */
88
+ .control-panel { background: var(--panel-bg); border-radius: var(--radius-card); padding: 2rem; box-shadow: var(--shadow-xl); border: 1px solid var(--panel-border); display: flex; flex-direction: column; gap: 2rem; }
89
+
90
+ .section-title { font-size: 1.1rem; font-weight: 700; color: var(--text-primary); margin-bottom: 1rem; display: flex; align-items: center; gap: 8px; }
91
+ .section-title svg { width: 22px; height: 22px; color: var(--accent-primary); }
92
+
93
+ /* Model Selector Grid */
94
+ .model-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; }
95
+ .model-card { position: relative; border-radius: var(--radius-input); overflow: hidden; cursor: pointer; border: 3px solid transparent; background-color: var(--input-bg); aspect-ratio: 1; transition: var(--transition-smooth); box-shadow: var(--shadow-sm); }
96
+ .model-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); }
97
+ .model-card.active { border-color: var(--accent-primary); box-shadow: 0 0 0 2px var(--accent-primary-glow), var(--shadow-lg); transform: translateY(-5px); }
98
+
99
+ .model-image { width: 100%; height: 100%; object-fit: cover; transition: transform 0.5s; opacity: 0.85; }
100
+ .model-card:hover .model-image, .model-card.active .model-image { transform: scale(1.08); opacity: 1; }
101
+
102
+ .model-name { position: absolute; bottom: 0; left: 0; width: 100%; padding: 8px; background: linear-gradient(to top, rgba(0,0,0,0.8), transparent); color: #fff; font-size: 0.95rem; font-weight: 700; text-align: center; pointer-events: none; }
103
 
104
+ /* Ratio Selector */
105
+ .ratio-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; }
106
+ .ratio-btn { background: var(--input-bg); border: 2px solid var(--input-border); border-radius: var(--radius-input); padding: 1rem 0.5rem; color: var(--text-secondary); font-size: 0.9rem; font-weight: 700; cursor: pointer; transition: var(--transition-smooth); display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; box-shadow: var(--shadow-sm); }
107
+ .ratio-btn:hover { border-color: var(--accent-primary); transform: translateY(-3px); }
108
+ .ratio-btn.active { background: #fff; border-color: var(--accent-primary); color: var(--accent-primary); box-shadow: 0 0 0 3px var(--accent-primary-glow); }
109
+
110
+ .ratio-icon-box { background-color: var(--panel-bg); border: 2px solid var(--text-tertiary); border-radius: 4px; transition: var(--transition-smooth); }
111
+ .ratio-btn.active .ratio-icon-box { border-color: var(--accent-primary); background-color: var(--accent-primary-glow); }
112
+ .icon-1-1 { width: 28px; height: 28px; }
113
+ .icon-16-9 { width: 40px; height: 22px; }
114
+ .icon-9-16 { width: 22px; height: 40px; }
115
+ .icon-4-3 { width: 34px; height: 25px; }
116
+
117
+ /* Input Area & Big Button */
118
+ textarea { width: 100%; padding: 1.2rem; border-radius: var(--radius-input); border: 2px solid var(--input-border); background-color: var(--input-bg); color: var(--text-primary); font-family: var(--app-font); font-size: 1rem; box-sizing: border-box; transition: var(--transition-smooth); min-height: 100px; resize: vertical; box-shadow: inset 0 1px 3px rgba(0,0,0,0.05); }
119
+ textarea:focus { outline: none; border-color: var(--accent-primary); box-shadow: 0 0 0 3px var(--accent-primary-glow), inset 0 1px 3px rgba(0,0,0,0.05); background-color: #fff; }
120
+
121
+ .big-send-btn { display: flex; align-items: center; justify-content: center; gap: 0.75rem; width: 100%; padding: 1.2rem; font-size: 1.25rem; font-weight: 800; background: linear-gradient(95deg, var(--accent-secondary) 0%, var(--accent-primary) 100%); color: #fff; border: none; border-radius: var(--radius-btn); cursor: pointer; transition: all 0.3s ease; box-shadow: 0 6px 15px -3px var(--accent-primary-glow); margin-top: 1rem; }
122
+ .big-send-btn svg { width: 26px; height: 26px; filter: drop-shadow(0 0 5px rgba(255,255,255,0.4)); }
123
+ .big-send-btn:hover:not(:disabled) { transform: translateY(-5px) scale(1.01); box-shadow: 0 10px 20px -4px var(--accent-primary-glow); }
124
+ .big-send-btn:disabled { background: var(--text-tertiary); cursor: not-allowed; box-shadow: none; opacity: 0.7; transform: none; }
125
+
126
+ .credit-info { font-size: 0.9rem; color: var(--text-tertiary); text-align: center; margin-top: 15px; font-weight: 600; background: var(--input-bg); padding: 10px; border-radius: var(--radius-input); border: 1px solid var(--input-border); display: none; }
127
+ .upgrade-btn { display: none; width: 100%; margin-top: 10px; padding: 1rem; background: linear-gradient(90deg, #FFD54F, var(--accent-premium)); border: none; border-radius: var(--radius-btn); color: #212529; font-weight: 800; font-size: 1.1rem; cursor: pointer; text-align: center; font-family: inherit; box-shadow: 0 6px 15px rgba(255, 193, 7, 0.3); transition: transform 0.3s; }
128
+ .upgrade-btn:hover { transform: translateY(-3px); }
129
+
130
+ @media (max-width: 600px) {
131
+ .model-grid { grid-template-columns: 1fr; }
132
+ .model-card { aspect-ratio: auto; height: 120px; }
133
+ .ratio-grid { grid-template-columns: repeat(2, 1fr); }
134
+ h1 { font-size: 1.8rem; }
135
  }
136
  </style>
137
  </head>
138
  <body>
139
+
140
  <div class="container">
141
+
142
+ <!-- Header -->
143
  <header>
144
  <canvas id="neural-network-canvas"></canvas>
145
  <div class="header-content">
146
+ <h1>استودیو پیشرفته ساخت تصویر</h1>
147
+ <p class="subtitle">ایده‌های خود را توصیف کنید تا مدل‌های قدرتمند هوش مصنوعی آن را برای شما به تصویر بکشند.</p>
148
  <div id="subscription-status-badge">درحال بررسی...</div>
149
  </div>
150
  </header>
151
 
152
+ <!-- Chat History -->
153
+ <div id="chat-history"></div>
154
+
155
+ <!-- Controls Form -->
156
+ <div class="control-panel">
157
+
158
+ <!-- Model Selection -->
159
+ <div>
160
+ <div class="section-title">
161
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 2 7 12 12 22 7 12 2"></polygon><polyline points="2 17 12 22 22 17"></polyline><polyline points="2 12 12 17 22 12"></polyline></svg>
162
+ ۱. مدل هوش مصنوعی را ا��تخاب کنید
163
  </div>
164
+ <div class="model-grid" id="model-selector">
165
  <div class="model-card active" data-action="generate-flux" data-name="Flux Pro">
166
+ <img class="model-image" src="https://app.puzzley.net/uploads/user/Jydo/Gpt4/pro.webp" alt="Flux Pro">
167
+ <div class="model-name">Flux Pro</div>
168
  </div>
169
  <div class="model-card" data-action="generate-midjourney" data-name="Midjourney">
170
+ <img class="model-image" src="https://app.puzzley.net/uploads/user/Jydo/%D9%87%D9%88%D8%B4%20%D9%85%D8%B5%D9%86%D9%88%D8%B9%DB%8C/Midjourney_Emblem.png" alt="Midjourney">
171
+ <div class="model-name">Midjourney</div>
172
  </div>
173
  <div class="model-card" data-action="generate-cartoon" data-name="Cartoon 3D">
174
+ <img class="model-image" src="https://app.puzzley.net/uploads/user/Jydo/Ghibli/image%20(28).webp" alt="Cartoon">
175
+ <div class="model-name">Cartoon 3D</div>
176
  </div>
177
  </div>
178
  </div>
179
 
180
+ <!-- Ratio Selection -->
181
+ <div>
182
+ <div class="section-title">
183
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="3" y1="9" x2="21" y2="9"></line><line x1="9" y1="21" x2="9" y2="9"></line></svg>
184
+ ۲. ابعاد تصویر را مشخص کنید
185
  </div>
186
+ <div class="ratio-grid" id="ratio-selector">
187
+ <button class="ratio-btn active" data-ratio="1:1" data-name="۱:۱ مربع">
188
+ <div class="ratio-icon-box icon-1-1"></div>
189
+ <span>۱:۱ مربع</span>
190
+ </button>
191
+ <button class="ratio-btn" data-ratio="16:9" data-name="۱۶:۹ افقی">
192
+ <div class="ratio-icon-box icon-16-9"></div>
193
+ <span>۱۶:۹ افقی</span>
194
+ </button>
195
+ <button class="ratio-btn" data-ratio="9:16" data-name="۹:۱۶ استوری">
196
+ <div class="ratio-icon-box icon-9-16"></div>
197
+ <span>۹:۱۶ استوری</span>
198
+ </button>
199
+ <button class="ratio-btn" data-ratio="4:3" data-name="۴:۳ کلاسیک">
200
+ <div class="ratio-icon-box icon-4-3"></div>
201
+ <span>۴:۳ کلاسیک</span>
202
+ </button>
203
  </div>
204
  </div>
205
 
206
+ <!-- Prompt & Button -->
207
+ <div>
208
+ <div class="section-title">
209
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>
210
  ۳. ایده خود را بنویسید
211
  </div>
212
+ <textarea id="prompt-input" placeholder="ایده خود را با جزئیات توصیف کنید... (مثال: یک شوالیه با زره درخشان در حال قدم زدن در یک جنگل سایبرپانک، کیفیت 8k)"></textarea>
213
+
214
+ <div id="credit-info" class="credit-info">درحال بررسی اعتبار...</div>
215
+ <button id="upgrade-button" class="upgrade-btn">⭐️ ارتقا به نسخه پرمیوم و نامحدود</button>
 
 
216
 
217
+ <button id="send-btn" class="big-send-btn">
218
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>
219
+ ساخت تصویر جادویی
220
+ </button>
221
+ </div>
 
 
 
222
 
223
+ </div>
224
  </div>
225
 
226
  <script>
 
247
 
248
  function updateUIForSubscriptionStatus(status) {
249
  userSubscriptionStatus = status;
250
+ const creditInfoDiv = document.getElementById('credit-info');
251
  const upgradeBtn = document.getElementById('upgrade-button');
252
+ const sendBtn = document.getElementById('send-btn');
253
  const badge = document.getElementById('subscription-status-badge');
254
 
255
  badge.style.display = 'inline-block';
 
259
  badge.className = 'paid-badge';
260
  creditInfoDiv.style.display = 'none';
261
  upgradeBtn.style.display = 'none';
262
+ sendBtn.disabled = false;
263
  if(countdownInterval) clearInterval(countdownInterval);
264
  } else {
265
  badge.textContent = 'کاربر رایگان';
 
270
 
271
  async function checkFreeUserCredit() {
272
  if (!userFingerprint || userSubscriptionStatus === 'paid') return;
273
+ const creditInfoDiv = document.getElementById('credit-info');
274
  const upgradeBtn = document.getElementById('upgrade-button');
275
+ const sendBtn = document.getElementById('send-btn');
276
  try {
277
  const response = await fetch('/api/check-image-credit', {
278
  method: 'POST',
 
284
  creditInfoDiv.style.display = 'block';
285
 
286
  if (result.limit_reached) {
287
+ sendBtn.disabled = true;
288
  upgradeBtn.style.display = 'block';
289
  startCountdown(result.reset_timestamp);
290
  } else {
291
+ sendBtn.disabled = false;
292
  upgradeBtn.style.display = 'none';
293
  if(countdownInterval) clearInterval(countdownInterval);
294
  creditInfoDiv.innerHTML = `شما <b>${result.credits_remaining}</b> اعتبار ساخت تصویر برای امروز دارید.`;
295
  }
296
  } catch (error) {
297
  creditInfoDiv.textContent = "خطا در ارتباط با سرور اعتبار.";
298
+ sendBtn.disabled = true;
299
  }
300
  }
301
 
302
  function startCountdown(resetTimestamp) {
303
  if (countdownInterval) clearInterval(countdownInterval);
304
+ const creditInfoDiv = document.getElementById('credit-info');
305
  const updateTimer = () => {
306
  const timeLeft = Math.max(0, resetTimestamp - (Date.now() / 1000));
307
  if (timeLeft === 0) {
 
316
  updateTimer(); countdownInterval = setInterval(updateTimer, 60000);
317
  }
318
 
319
+ // ارتباط با اپلیکیشن
320
  window.addEventListener('message', (event) => {
321
  if (event.data?.type === 'USER_DATA_RESPONSE') {
322
  if (event.data.error || !event.data.payload) { updateUIForSubscriptionStatus('free'); return; }
 
336
  parent.postMessage({ type: 'REQUEST_USER_DATA' }, '*');
337
  });
338
 
339
+ // --- بخش کنترل UI ---
340
  const promptInput = document.getElementById('prompt-input');
341
+ const sendBtn = document.getElementById('send-btn');
342
+ const chatHistory = document.getElementById('chat-history');
 
 
343
  const modelSelector = document.getElementById('model-selector');
344
+ const ratioSelector = document.getElementById('ratio-selector');
 
345
 
346
  let selectedModelAction = 'generate-flux';
347
  let selectedModelName = 'Flux Pro';
348
  let selectedRatio = '1:1';
349
+ let selectedRatioName = '۱:۱ مربع';
350
+ let selectedRatioClass = 'ratio-1-1';
351
 
352
  // انتخاب مدل
353
  modelSelector.addEventListener('click', (e) => {
 
361
 
362
  // انتخاب سایز
363
  ratioSelector.addEventListener('click', (e) => {
364
+ const btn = e.target.closest('.ratio-btn');
365
+ if (!btn) return;
366
+ ratioSelector.querySelectorAll('.ratio-btn').forEach(c => c.classList.remove('active'));
367
+ btn.classList.add('active');
368
+ selectedRatio = btn.dataset.ratio;
369
+ selectedRatioName = btn.dataset.name;
370
+ selectedRatioClass = 'ratio-' + btn.dataset.ratio.replace(':', '-');
371
  });
372
 
373
  const getDimensions = (ratio) => {
 
379
  }
380
  };
381
 
382
+ // توابع اضافه کردن پیام به History
383
+ function appendUserMessage(text) {
384
+ const wrapper = document.createElement('div');
385
+ wrapper.className = 'message-wrapper user';
386
+ wrapper.innerHTML = `<div class="message-bubble">${text.replace(/\n/g, '<br>')}</div>`;
387
+ chatHistory.appendChild(wrapper);
388
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
389
 
390
+ function appendAILoadingMessage(ratioClass) {
391
+ const wrapper = document.createElement('div');
392
+ wrapper.className = 'message-wrapper ai';
393
+ wrapper.innerHTML = `
394
+ <div class="message-bubble">
395
+ <div class="loading-frame ${ratioClass}"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
396
  </div>
397
  `;
398
+ chatHistory.appendChild(wrapper);
399
+ wrapper.scrollIntoView({ behavior: 'smooth', block: 'center' });
400
+ return wrapper;
 
 
 
 
 
 
 
401
  }
402
 
403
+ function updateAIMessageWithError(wrapper, errorMsg) {
404
+ wrapper.querySelector('.message-bubble').innerHTML = `<span style="color: var(--danger-color); font-weight:bold;">❌ خطا:</span> ${errorMsg}`;
405
+ }
406
+
407
+ function updateAIMessageWithImage(wrapper, imageUrl, modelName, ratioName) {
408
+ const bubble = wrapper.querySelector('.message-bubble');
409
  const absoluteUrl = window.location.origin + imageUrl;
410
 
411
+ bubble.innerHTML = `
412
+ <div class="generated-image-box">
413
+ <img src="${imageUrl}" alt="تصویر تولید شده توسط هوش مصنوعی">
414
+
415
+ <div class="image-meta">
416
+ <span title="مدل استفاده شده">
417
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
418
+ ${modelName}
419
+ </span>
420
+ <span>|</span>
421
+ <span title="ابعاد تصویر">
422
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="3" y1="9" x2="21" y2="9"></line><line x1="9" y1="21" x2="9" y2="9"></line></svg>
423
+ ${ratioName}
424
+ </span>
425
+ </div>
 
 
 
 
 
 
426
 
427
+ <button class="download-btn" onclick="triggerDownload('${absoluteUrl}')">
428
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
429
+ دانلود مستقیم تصویر
430
+ </button>
 
431
  </div>
 
 
432
  `;
433
+
434
+ // پس از لود شدن کامل عکس اسکرول کنیم که قشنگ دیده بشه
435
+ const img = bubble.querySelector('img');
436
+ img.onload = () => wrapper.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
437
  }
438
 
439
+ // عملکرد دکمه دانلود
440
  window.triggerDownload = function(url) {
441
+ parent.postMessage({ type: 'DOWNLOAD_REQUEST', url: url }, '*');
 
 
 
442
  };
443
 
444
+ // هندل ارسال با Enter
445
+ promptInput.addEventListener('keydown', function(e) {
446
+ if (e.key === 'Enter' && !e.shiftKey) {
447
+ e.preventDefault();
448
+ sendBtn.click();
449
+ }
450
+ });
451
+
452
+ // فرایند اصلی ساخت تصویر
453
+ sendBtn.addEventListener('click', async () => {
454
  const userPrompt = promptInput.value.trim();
455
+ if (!userPrompt) {
456
+ promptInput.focus();
457
+ return;
458
+ }
459
 
460
  // چک کردن کلمات ممنوعه محلی
461
  const forbiddenWords = ["sex", "sexy", "porn", "nude", "erotic", "سکس", "سکسی", "پورن", "شهوانی", "برهنه", "لخت"];
 
464
  return;
465
  }
466
 
467
+ // چک کردن اعتبار رایگان
468
  if (userSubscriptionStatus !== 'paid') {
469
  try {
470
  const response = await fetch('/api/check-image-credit', {
 
473
  const result = await response.json();
474
  if (result.limit_reached) {
475
  updateUIForSubscriptionStatus('free');
476
+ alert("محدودیت ساخت عکس امروز شما به پایان رسیده است.");
477
  return;
478
  }
479
  } catch (err) {
 
481
  }
482
  }
483
 
484
+ // افزودن پیام کاربر
485
+ appendUserMessage(userPrompt);
486
+ promptInput.value = '';
487
+
488
+ // قفل کردن فرم
489
+ sendBtn.disabled = true;
490
+ promptInput.disabled = true;
491
+ sendBtn.innerHTML = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="animation: spin-border 1s linear infinite;"><line x1="12" y1="2" x2="12" y2="6"></line><line x1="12" y1="18" x2="12" y2="22"></line><line x1="4.93" y1="4.93" x2="7.76" y2="7.76"></line><line x1="16.24" y1="16.24" x2="19.07" y2="19.07"></line><line x1="2" y1="12" x2="6" y2="12"></line><line x1="18" y1="12" x2="22" y2="12"></line><line x1="4.93" y1="19.07" x2="7.76" y2="16.24"></line><line x1="16.24" y1="7.76" x2="19.07" y2="4.93"></line></svg> در حال ساخت...`;
492
+
493
  isResultProcessed = false;
494
 
495
+ // ذخیره مقادیر انتخاب شده فعلی برای این درخواست (در صورت تغییر دادن در حین لود شدن)
 
496
  const currentModelName = selectedModelName;
497
+ const currentRatioName = selectedRatioName;
 
498
 
499
+ // ساخت حباب لودینگ
500
+ const aiMessageElement = appendAILoadingMessage(selectedRatioClass);
501
 
502
+ const dims = getDimensions(selectedRatio);
503
  let finalPrompt = userPrompt;
504
+ if(selectedModelAction === 'generate-cartoon') {
505
  finalPrompt += " کارتونی انیمیشن سه بعدی";
506
  }
507
 
 
513
  prompt: finalPrompt,
514
  width: dims.width,
515
  height: dims.height,
516
+ action_name: selectedModelAction
517
  })
518
  });
519
 
520
  const data = await res.json();
521
 
522
  if (data.status === 'success') {
 
523
  let pollTimer = setInterval(async () => {
524
  try {
525
  const statusRes = await fetch(`/api/status/${data.run_id}`);
 
528
  if (statusData.status === 'ready' && !isResultProcessed) {
529
  isResultProcessed = true;
530
  clearInterval(pollTimer);
 
531
 
532
+ // کسر اعتبار قطعی
533
  if (userSubscriptionStatus === 'free') {
534
  try {
535
  await fetch('/api/use-image-credit', {
536
+ method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ fingerprint: userFingerprint })
 
 
537
  });
538
  checkFreeUserCredit();
539
  } catch (e) { console.error("Error using credit", e); }
540
  }
541
 
542
+ // نمایش عکس و دکمه دانلود
543
+ updateAIMessageWithImage(aiMessageElement, statusData.url, currentModelName, currentRatioName);
544
+
545
+ // باز کردن فرم
546
+ sendBtn.disabled = false;
547
+ promptInput.disabled = false;
548
+ sendBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg> ساخت تصویر جادویی`;
549
  }
550
  } catch(e) {
551
  console.error('Polling error', e);
552
  }
553
+ }, 3500);
554
  } else {
555
+ updateAIMessageWithError(aiMessageElement, 'متاسفانه در ارسال درخواست به سرور مشکلی پیش آمد.');
556
+ sendBtn.disabled = false;
557
+ promptInput.disabled = false;
558
+ sendBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg> ساخت تصویر جادویی`;
559
  }
560
  } catch (e) {
561
+ updateAIMessageWithError(aiMessageElement, 'خطا در برقراری ارتباط با سرور.');
562
+ sendBtn.disabled = false;
563
+ promptInput.disabled = false;
564
+ sendBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg> ساخت تصویر جادویی`;
565
  }
566
  });
567
 
 
569
  document.addEventListener('DOMContentLoaded', () => {
570
  const canvas = document.getElementById('neural-network-canvas');
571
  if(!canvas) return; const header = canvas.parentElement; const ctx = canvas.getContext('2d');
572
+ let particles = []; const particleCount = 20; const maxDistance = 100;
573
+ const particleColor = '#4A6CFA'; const lineColor = '#8A94A6';
574
  function resizeCanvas() { canvas.width = header.clientWidth; canvas.height = header.clientHeight; init(); }
575
+ class Particle { constructor() { this.x = Math.random() * canvas.width; this.y = Math.random() * canvas.height; this.vx = (Math.random() - 0.5) * 0.3; this.vy = (Math.random() - 0.5) * 0.3; this.radius = 1.2; } update() { this.x += this.vx; this.y += this.vy; if (this.x < 0 || this.x > canvas.width) this.vx *= -1; if (this.y < 0 || this.y > canvas.height) this.vy *= -1; } draw() { ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); ctx.fillStyle = particleColor; ctx.fill(); } }
576
  function init() { particles = []; for (let i = 0; i < particleCount; i++) { particles.push(new Particle()); } }
577
  function connectParticles() { for (let i = 0; i < particles.length; i++) { for (let j = i + 1; j < particles.length; j++) { const dx = particles[i].x - particles[j].x; const dy = particles[i].y - particles[j].y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance < maxDistance) { ctx.beginPath(); ctx.moveTo(particles[i].x, particles[i].y); ctx.lineTo(particles[j].x, particles[j].y); ctx.strokeStyle = lineColor; ctx.lineWidth = 0.2; ctx.globalAlpha = 1 - distance / maxDistance; ctx.stroke(); } } } ctx.globalAlpha = 1; }
578
  function animate() { if(!ctx) return; ctx.clearRect(0, 0, canvas.width, canvas.height); particles.forEach(particle => { particle.update(); particle.draw(); }); connectParticles(); requestAnimationFrame(animate); }