Elias207 commited on
Commit
9c6dcb5
·
verified ·
1 Parent(s): d1e79e3

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +714 -724
index.html CHANGED
@@ -1,775 +1,765 @@
1
  <!DOCTYPE html>
2
-
3
  <html lang="fa" dir="rtl">
4
  <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>خلق تصاویر کارتونی</title>
8
- <link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;500;600;700;800&display=swap" rel="stylesheet">
9
- <style>
10
- :root {
11
- --app-font: 'Vazirmatn', sans-serif;
12
- --app-bg: #F8F9FC;
13
- --panel-bg: #FFFFFF;
14
- --panel-border: #EAEFF7;
15
- --input-bg: #F6F8FB;
16
- --input-border: #E1E7EF;
17
- --text-primary: #1A202C;
18
- --text-secondary: #626F86;
19
- --text-tertiary: #8A94A6;
20
- --accent-primary: #4A6CFA;
21
- --accent-primary-hover: #3553D6;
22
- --accent-primary-glow: rgba(74, 108, 250, 0.25);
23
- --accent-secondary: #0FD4A8;
24
- --shadow-sm: 0 1px 2px 0 rgba(26, 32, 44, 0.03);
25
- --shadow-md: 0 4px 6px -1px rgba(26, 32, 44, 0.05), 0 2px 4px -2px rgba(26, 32, 44, 0.04);
26
- --shadow-lg: 0 10px 15px -3px rgba(26, 32, 44, 0.06), 0 4px 6px -4px rgba(26, 32, 44, 0.05);
27
- --shadow-xl: 0 20px 25px -5px rgba(26, 32, 44, 0.07), 0 8px 10px -6px rgba(26, 32, 44, 0.05);
28
- --radius-card: 24px;
29
- --radius-btn: 14px;
30
- --radius-input: 12px;
31
- --transition-smooth: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
32
-
33
- code
34
- Code
35
- download
36
- content_copy
37
- expand_less
38
- --guide-bg: rgba(255, 255, 255, 0.98);
39
- --guide-border: rgba(102, 126, 234, 0.2);
40
- --guide-text-title: #2d3748;
41
- --guide-text-body: #4a5568;
42
- --guide-accent: #667eea;
43
- --primary-gradient-guide: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
44
- --success-gradient-guide: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%);
45
- --radius-md-guide: 12px;
46
- --radius-lg-guide: 20px;
47
- }
48
-
49
- @keyframes fadeIn { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
50
- @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
51
- @keyframes pulse { 0% { box-shadow: 0 0 40px rgba(56, 189, 248, 0.3); } 50% { box-shadow: 0 0 60px rgba(56, 189, 248, 0.7); } 100% { box-shadow: 0 0 40px rgba(56, 189, 248, 0.3); } }
52
- @keyframes glow-text { 0% { opacity: 0.7; } 50% { opacity: 1; } 100% { opacity: 0.7; } }
53
- @keyframes float { 0%, 100% { transform: translateY(0px); } 50% { transform: translateY(-10px); } }
54
- @keyframes slideInUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
55
-
56
- body {
57
- font-family: var(--app-font);
58
- background-color: var(--app-bg);
59
- color: var(--text-primary);
60
- margin: 0;
61
- padding: 2.5rem 1rem;
62
- display: flex;
63
- justify-content: center;
64
- align-items: flex-start;
65
- min-height: 100vh;
66
- }
67
- .container { max-width: 820px; width: 100%; }
68
-
69
- header {
70
- position: relative;
71
- text-align: center;
72
- margin-bottom: 2.5rem;
73
- padding: 2rem 0;
74
- animation: fadeIn 0.8s 0.1s ease-out backwards;
75
- overflow: hidden;
76
- }
77
- #neural-network-canvas {
78
- position: absolute;
79
- top: 0;
80
- left: 0;
81
- width: 100%;
82
- height: 100%;
83
- z-index: 1;
84
- }
85
- h1, .subtitle { position: relative; z-index: 2; }
86
- h1 {
87
- font-size: 2.8rem;
88
- font-weight: 800;
89
- margin: 0 0 0.8rem 0;
90
- background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
91
- -webkit-background-clip: text;
92
- -webkit-text-fill-color: transparent;
93
- letter-spacing: -1px;
94
- }
95
- .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; }
96
-
97
- main {
98
- padding: 3rem;
99
- background-color: var(--panel-bg);
100
- border-radius: var(--radius-card);
101
- box-shadow: var(--shadow-xl);
102
- border: 1px solid var(--panel-border);
103
- animation: fadeIn 0.8s 0.3s ease-out backwards;
104
- }
105
-
106
- .form-group { margin-bottom: 2.5rem; }
107
- .form-group:last-child { margin-bottom: 0; }
108
-
109
- .form-label {
110
- display: flex;
111
- align-items: center;
112
- gap: 0.75rem;
113
- font-weight: 700;
114
- color: var(--text-primary);
115
- font-size: 1.2em;
116
- margin-bottom: 1.2rem;
117
- }
118
- .form-label svg { width: 24px; height: 24px; color: var(--accent-primary); }
119
-
120
- textarea {
121
- width: 100%; padding: 1rem 1.2rem; border-radius: var(--radius-input); border: 1px solid var(--input-border);
122
- background-color: var(--input-bg); color: var(--text-primary); box-shadow: var(--shadow-sm) inset;
123
- font-family: var(--app-font); font-size: 1rem; box-sizing: border-box; transition: var(--transition-smooth);
124
- min-height: 90px; resize: vertical;
125
- }
126
- textarea:focus {
127
- outline: none; border-color: var(--accent-primary);
128
- box-shadow: 0 0 0 3px var(--accent-primary-glow), var(--shadow-sm) inset; background-color: var(--panel-bg);
129
- }
130
-
131
- .aspect-ratio-selector {
132
- display: grid;
133
- grid-template-columns: repeat(4, 1fr);
134
- gap: 1rem;
135
- }
136
- .ratio-option {
137
- background-color: var(--input-bg);
138
- border: 2px solid var(--input-border);
139
- border-radius: var(--radius-input);
140
- padding: 1rem;
141
- cursor: pointer;
142
- transition: var(--transition-smooth);
143
- text-align: center;
144
- }
145
- .ratio-option:hover {
146
- border-color: var(--accent-primary);
147
- transform: translateY(-4px);
148
- box-shadow: var(--shadow-md);
149
- }
150
- .ratio-option.active {
151
- border-color: var(--accent-primary);
152
- background-color: #fff;
153
- box-shadow: 0 0 0 3px var(--accent-primary-glow);
154
- }
155
- .ratio-icon {
156
- height: 50px;
157
- display: flex;
158
- align-items: center;
159
- justify-content: center;
160
- margin: 0 auto 0.75rem;
161
- }
162
- .ratio-icon > div, .ratio-icon > svg {
163
- background-color: var(--panel-bg);
164
- border: 2px solid var(--text-tertiary);
165
- border-radius: 6px;
166
- transition: var(--transition-smooth);
167
- box-sizing: border-box;
168
- }
169
- .ratio-option.active .ratio-icon > div,
170
- .ratio-option.active .ratio-icon > svg {
171
- border-color: var(--accent-primary);
172
- background-color: var(--accent-primary-glow);
173
- }
174
- .ratio-label {
175
- font-weight: 600;
176
- color: var(--text-secondary);
177
- font-size: 0.9rem;
178
- transition: color 0.2s;
179
- }
180
- .ratio-option.active .ratio-label { color: var(--text-primary); }
181
-
182
- #icon-portrait > div { width: 28px; height: 50px; }
183
- #icon-landscape > div { width: 60px; height: 34px; }
184
- #icon-square > div { width: 45px; height: 45px; }
185
- #icon-custom > svg { width: 45px; height: 45px; padding: 8px; color: var(--text-tertiary); }
186
- .ratio-option.active #icon-custom > svg { color: var(--accent-primary); }
187
- .custom-ratio-value { font-size: 0.75rem; color: var(--text-secondary); margin-top: 2px; height: 1em; }
188
-
189
-
190
- #submit-btn {
191
- display: flex; align-items: center; justify-content: center; gap: 0.75rem; width: 100%; padding: 1.1rem;
192
- font-size: 1.2rem; font-weight: 700; background: linear-gradient(95deg, var(--accent-secondary) 0%, var(--accent-primary) 100%);
193
- color: #fff; border: none; border-radius: var(--radius-btn); cursor: pointer; transition: all 0.3s ease;
194
- box-shadow: 0 6px 12px -3px var(--accent-primary-glow), 0 6px 12px -3px var(--accent-secondary-glow); margin-top: 1.5rem;
195
- }
196
- #submit-btn svg { width: 24px; height: 24px; margin-left: 4px; filter: drop-shadow(0 0 5px rgba(255,255,255,0.5)); }
197
- #submit-btn:hover:not(:disabled) { transform: translateY(-5px) scale(1.02); box-shadow: 0 8px 20px -4px var(--accent-primary-glow), 0 8px 20px -4px var(--accent-secondary-glow); }
198
- #submit-btn:disabled { background: var(--text-tertiary); cursor: not-allowed; box-shadow: none; opacity: 0.7; }
199
- #submit-btn .spinner { width: 20px; height: 20px; border: 3px solid rgba(255, 255, 255, 0.4); border-top-color: #fff; border-radius: 50%; animation: spin 0.8s linear infinite; display: none; }
200
-
201
- #result-container { min-height: 250px; position: relative; padding: 1rem; background-color: var(--input-bg); border-radius: var(--radius-card); border: 2px dashed var(--input-border); box-shadow: var(--shadow-sm) inset; transition: var(--transition-smooth); display: flex; align-items: center; justify-content: center; flex-direction: column; box-sizing: border-box; }
202
- #result-container.loading,
203
- #result-container.has-error-guide {
204
- padding: 0;
205
- border: none;
206
- background-color: transparent;
207
- box-shadow: none;
208
- }
209
- #result-container.has-content { border-style: solid; border-color: var(--panel-border); padding: 1rem; }
210
- #loading-placeholder { display: none; }
211
- #result-container.loading #loading-placeholder { display: flex; animation: fadeIn 0.5s; justify-content: center; align-items: center; width: 100%; }
212
-
213
- .generator-container { position: relative; width: 100%; max-width: 500px; height: 200px; border: 2px solid #38bdf8; border-radius: 20px; overflow: hidden; box-shadow: 0 0 40px rgba(56, 189, 248, 0.3); animation: pulse 5s infinite cubic-bezier(0.4, 0, 0.6, 1); background-color: #161b22; color: #f0f6fc; display: flex; flex-direction: column; justify-content: center; align-items: center; padding: 1rem; }
214
- .text-overlay { font-size: 18px; font-weight: 700; text-shadow: 0 0 20px rgba(56, 189, 248, 0.8); animation: glow-text 7s infinite ease-in-out; text-align: center; }
215
- .progress-bar { position: absolute; bottom: 0; left: 0; width: 0%; height: 6px; background: linear-gradient(to right, #38bdf8, #bb86fc, #facc15); transition: width 1s linear; }
216
-
217
- #result-image-wrapper { display: none; width: 100%; }
218
- #result-container.has-content #result-image-wrapper { display: block; animation: fadeIn 0.5s; text-align: center; }
219
- #result-image-display { max-width: 100%; max-height: 500px; object-fit: contain; border-radius: var(--radius-input); box-shadow: var(--shadow-md); border: 1px solid var(--panel-border); }
220
- .error-message { color: #d93025; font-weight: bold; margin-top: 10px; background: #ffebee; padding: 1rem; border-radius: var(--radius-input); border: 1px solid #e57373; }
221
-
222
- .output-details { display: none !important; }
223
-
224
- .modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(18, 24, 38, 0.6); backdrop-filter: blur(8px); display: none; justify-content: center; align-items: center; z-index: 1000; animation: fadeIn 0.3s; }
225
- .modal-content-small { background: var(--panel-bg); padding: 2rem; border-radius: var(--radius-card); max-width: 350px; width: 90%; text-align: center; box-shadow: var(--shadow-xl); position: relative; }
226
- .modal-content-small h3 { margin-top: 0; margin-bottom: 1.5rem; font-size: 1.5rem; color: var(--text-primary); }
227
- .modal-content-small .input-group { margin-bottom: 1rem; text-align: right; }
228
- .modal-content-small label { display: block; margin-bottom: 0.5rem; font-weight: 600; color: var(--text-secondary); font-size: 0.9rem; }
229
- .modal-content-small input { width: 100%; padding: 0.8rem 1rem; 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: 1rem; box-sizing: border-box; transition: var(--transition-smooth); }
230
- .modal-content-small input: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); }
231
- #confirm-custom-size-btn { width: 100%; padding: 0.9rem; margin-top: 1rem; font-size: 1rem; font-weight: 700; background-color: var(--accent-primary); color: #fff; border: none; border-radius: var(--radius-btn); cursor: pointer; transition: var(--transition-smooth); }
232
- #confirm-custom-size-btn:hover { background-color: var(--accent-primary-hover); }
233
- #close-custom-modal-btn { position: absolute; top: 10px; left: 10px; background: transparent; border: none; font-size: 2rem; cursor: pointer; color: var(--text-tertiary); line-height: 1; padding: 0.5rem; }
234
-
235
- .ip-reset-guide-container { text-align: right; background: var(--panel-bg); padding: 10px; border-radius: var(--radius-lg-guide); animation: slideInUp 0.6s cubic-bezier(0.4, 0, 0.2, 1) both; max-width: 100%; width: 100%; position: relative; overflow: hidden; box-sizing: border-box; }
236
- .ip-reset-guide-container::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 5px; background: var(--primary-gradient-guide); }
237
- .guide-header { display: flex; align-items: center; margin-bottom: 20px; position: relative; }
238
- .guide-header-icon { width: 50px; height: 50px; margin-left: 15px; animation: float 3s ease-in-out infinite; flex-shrink: 0; }
239
- .guide-header h2 { font-size: 1.2rem; color: var(--guide-text-title); font-weight: 700; margin: 0; }
240
- .guide-header p { color: var(--guide-text-body); font-size: 0.85rem; margin-top: 5px; }
241
- .guide-content { font-size: 0.95rem; color: var(--guide-text-body); line-height: 1.8; }
242
- .info-card { background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%); border: 1px solid rgba(102, 126, 234, 0.2); border-radius: var(--radius-md-guide); padding: 15px; margin: 15px 0; position: relative; overflow: hidden; }
243
- .info-card p { font-size: 0.85rem; line-height: 1.7; }
244
- .info-card::before { content: ''; position: absolute; top: 0; right: 0; width: 4px; height: 100%; background: var(--primary-gradient-guide); }
245
- .info-card-header { display: flex; align-items: center; margin-bottom: 10px; }
246
- .info-card-icon { width: 22px; height: 22px; margin-left: 10px; }
247
- .info-card-title { font-weight: 600; color: var(--guide-text-title); font-size: 1rem; }
248
- .summary-section { margin-top: 20px; padding: 15px; border-radius: var(--radius-md-guide); background: linear-gradient(135deg, #56ab2f15 0%, #a8e06315 100%); border: 1px solid rgba(86, 171, 47, 0.2); position: relative; overflow: hidden; }
249
- .summary-section::before { content: ''; position: absolute; top: 0; right: 0; width: 4px; height: 100%; background: var(--success-gradient-guide); }
250
- .summary-header { display: flex; align-items: center; margin-bottom: 10px; }
251
- .summary-icon { width: 22px; height: 22px; margin-left: 10px; }
252
- .summary-title { font-weight: 600; color: #2f5a33; font-size: 1rem; }
253
- .summary-text { color: #2f5a33; font-size: 0.9rem; line-height: 1.7; }
254
- .guide-actions { display: flex; gap: 15px; margin-top: 25px; padding-top: 20px; border-top: 1px solid #e2e8f0; }
255
- .action-button { padding: 12px 20px; border: none; border-radius: var(--radius-md-guide); font-size: 0.9rem; font-weight: 600; cursor: pointer; font-family: inherit; flex: 1; transition: var(--transition-smooth); position: relative; overflow: hidden; display: flex; align-items: center; justify-content: center; }
256
- .action-button-icon { width: 18px; height: 18px; margin-right: 8px; margin-left: 0; }
257
- .back-button { background: white; color: var(--guide-text-body); border: 2px solid #e2e8f0; flex: 0.4; }
258
- .back-button:hover { background: #f7fafc; border-color: var(--guide-accent); transform: translateY(-2px); box-shadow: var(--shadow-md); }
259
- .retry-button { background: var(--primary-gradient-guide); color: white; flex: 0.6; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); }
260
- .retry-button:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4); }
261
- .video-button-container { text-align: center; margin: 25px 0 10px 0; }
262
- .elegant-video-button { display: inline-flex; align-items: center; justify-content: center; padding: 7px 18px; background-color: #f0f2f5; color: var(--guide-accent); border: 1px solid #e2e8f0; text-decoration: none; border-radius: var(--radius-md-guide); font-weight: 600; font-size: 0.8rem; cursor: pointer; font-family: inherit; transition: all 0.3s ease; box-shadow: var(--shadow-sm); }
263
- .elegant-video-button:hover { background: var(--primary-gradient-guide); color: white; border-color: transparent; transform: translateY(-2px); box-shadow: 0 6px 16px rgba(102, 126, 234, 0.3); }
264
- .elegant-video-button-icon { width: 16px; height: 16px; margin-left: 8px; fill: currentColor; }
265
-
266
- /* START: CSS styles for the download button */
267
- .download-actions {
268
- margin-top: 1.5rem;
269
- text-align: center;
270
- }
271
- .download-button {
272
- padding: 12px 25px;
273
- border: none;
274
- border-radius: var(--radius-btn);
275
- font-size: 1rem;
276
- font-weight: 600;
277
- cursor: pointer;
278
- font-family: inherit;
279
- transition: var(--transition-smooth);
280
- display: inline-flex;
281
- align-items: center;
282
- justify-content: center;
283
- gap: 0.7rem;
284
- background-color: var(--accent-primary);
285
- color: white;
286
- box-shadow: var(--shadow-md);
287
- }
288
- .download-button:hover:not(:disabled) {
289
- background-color: var(--accent-primary-hover);
290
- transform: translateY(-3px);
291
- box-shadow: var(--shadow-lg);
292
- }
293
- .download-button:disabled {
294
- background-color: var(--text-tertiary);
295
- cursor: not-allowed;
296
- opacity: 0.8;
297
- }
298
- .download-button svg {
299
- width: 20px;
300
- height: 20px;
301
- }
302
- /* END: CSS styles for the download button */
303
-
304
- @media (max-width: 768px) {
305
- main { padding: 1.5rem; } h1 { font-size: 2.2rem; }
306
- .aspect-ratio-selector { grid-template-columns: repeat(2, 1fr); }
307
- }
308
- </style>
309
  </head>
310
  <body>
311
- <div class="container">
312
- <header>
313
- <canvas id="neural-network-canvas"></canvas>
314
- <h1>خلق تصاویر انیمیشنی جذاب</h1>
315
- <p class="subtitle">ایده‌های خود را به سادگی به تصاویر انیمیشنی با سبک منحصر به فرد تبدیل کنید.</p>
316
- </header>
317
- <main>
318
- <div class="form-group">
319
- <div class="form-label">
320
- <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>
321
- ۱. ایده خود را توصیف کنید
322
- </div>
323
- <textarea id="prompt-input" rows="3" placeholder="مثال: یک فضانورد که روی ماه در حال موج‌سواری است"></textarea>
324
- </div>
325
-
326
- code
327
- Code
328
- download
329
- content_copy
330
- expand_less
331
- <div class="form-group">
332
- <div class="form-label">
333
- <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="M21 9V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h4"/><rect x="12" y="13" width="10" height="8" rx="2"/><path d="M17 12v-1a2 2 0 0 0-2-2h-1"/></svg>
334
- ۲. اندازه تصویر را انتخاب کنید
335
- </div>
336
- <div class="aspect-ratio-selector" id="aspect-ratio-selector">
337
- <div class="ratio-option" data-ratio="9:16">
338
- <div class="ratio-icon" id="icon-portrait"><div></div></div>
339
- <div class="ratio-label">عمودی ۹:۱۶</div>
340
  </div>
341
- <div class="ratio-option" data-ratio="16:9">
342
- <div class="ratio-icon" id="icon-landscape"><div></div></div>
343
- <div class="ratio-label">افقی ۱۶:۹</div>
 
 
 
 
344
  </div>
345
- <div class="ratio-option" data-ratio="custom">
346
- <div class="ratio-icon" id="icon-custom">
347
- <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">
348
- <path d="M21 16V8a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h8"/>
349
- <circle cx="18" cy="18" r="3"/>
350
- <path d="M18 15v-2"/><path d="M18 21v-2"/>
351
- <path d="M15 18h-2"/><path d="M21 18h-2"/>
352
- </svg>
353
  </div>
354
- <div class="ratio-label">
355
- اندازه دلخواه
356
- <div class="custom-ratio-value" id="custom-ratio-label-value"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
357
  </div>
358
  </div>
359
- <div class="ratio-option active" data-ratio="1:1">
360
- <div class="ratio-icon" id="icon-square"><div></div></div>
361
- <div class="ratio-label">مربع ۱:۱</div>
 
 
 
 
 
 
 
 
 
 
 
 
362
  </div>
363
  </div>
364
- </div>
365
-
366
- <button id="submit-btn" disabled>
367
- <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>
368
- <span id="btn-text">در حال اتصال...</span>
369
- <div class="spinner" style="display: inline-block;"></div>
370
- </button>
371
-
372
- <div class="form-group" style="margin-top: 3rem; margin-bottom: 0;">
373
- <div class="form-label">
374
- <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"/><path d="M21 14l-1.5 3-3-1.5 3-3 1.5 3z"/><path d="M19.5 2.5l-3 1.5 1.5 3 3-1.5-1.5-3z"/></svg>
375
- ۳. نتیجه را ببینید
 
376
  </div>
377
- <div id="result-container">
378
- <!-- محتوای این بخش توسط جاوااسکریپت مدیریت می‌شود -->
 
379
  </div>
 
380
  </div>
381
-
382
- <div class="output-details" id="output-details"></div>
383
- </main>
384
- </div>
385
-
386
- <!-- Modal for Custom Size -->
387
- <div class="modal-overlay" id="custom-size-modal">
388
- <div class="modal-content-small">
389
- <button id="close-custom-modal-btn">&times;</button>
390
- <h3>تنظیم اندازه دلخواه</h3>
391
- <div class="input-group">
392
- <label for="custom-width-input">عرض (Width)</label>
393
- <input type="number" id="custom-width-input" value="1024" min="512" max="1536" step="8" placeholder="بین ۵۱۲ تا ۱۵۳۶">
394
- </div>
395
- <div class="input-group">
396
- <label for="custom-height-input">طول (Height)</label>
397
- <input type="number" id="custom-height-input" value="1024" min="512" max="1536" step="8" placeholder="بین ۵۱۲ تا ۱۵۳۶">
398
- </div>
399
- <button id="confirm-custom-size-btn">تایید و اعمال</button>
400
  </div>
401
- </div>
402
 
403
 
404
- <script type="module">
405
- import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
406
-
407
- const selectedLora = {
408
- "title": "Long Toons",
409
- "repo": "prithivMLmods/Flux-Long-Toon-LoRA",
410
- "trigger_word": "Long toons"
411
- };
412
-
413
- const GPU_QUOTA_ERROR_HTML = `
414
- <div class="ip-reset-guide-container">
415
- <div class="guide-header">
416
- <svg class="guide-header-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10" stroke="#667eea" stroke-width="2" fill="rgba(102, 126, 234, 0.1)"/><path d="M8 12.5l2.5 2.5L16 9" stroke="#667eea" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
417
- <div><h2>یک قدم تا ساخت تصاویر جدید</h2><p>نیازمند تغییر نقطه دستیابی</p></div>
418
- </div>
419
- <div class="guide-content">
420
- <div class="info-card">
421
- <div class="info-card-header"><svg class="info-card-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" stroke="#667eea" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg><span class="info-card-title">راه حل سریع</span></div>
422
- <p>طبق ویدیو آموزشی پایین بین نقطه دستیابی جابجا شوید تلاش مجدد بزنید تا تصاویر مجدداً تولید بشه.</p>
423
  </div>
424
- <div class="summary-section">
425
- <div class="summary-header"><svg class="summary-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9 12l2 2 4-4" stroke="#2f5a33" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path><circle cx="12" cy="12" r="10" stroke="#2f5a33" stroke-width="2"></circle></svg><span class="summary-title">خلاصه راهنما</span></div>
426
- <div class="summary-text">هربار که این صفحه را مشاهده کردید: از اینترنت سیم‌کارت استفاده کنید، VPN را خاموش کرده و طبق ویدیو آموزشی پایین نقطه دستیابی رو تغییر دهید. «تلاش مجدد» کلیک کنید. با این روش ساده می‌توانید به صورت نامحدود تصویر بسازید! ☘️</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
427
  </div>
428
  </div>
429
- <div class="video-button-container">
430
- <button id="tutorialLinkBtn" class="elegant-video-button">
431
- <svg class="elegant-video-button-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"></path></svg>
432
- دیدن ویدیو آموزشی استفاده نامحدود
433
- </button>
434
- </div>
435
- <div class="guide-actions">
436
- <button class="action-button back-button"><svg class="action-button-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M19 12H5M12 19l-7-7 7-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>بازگشت</button>
437
- <button class="action-button retry-button"><svg class="action-button-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M23 4v6h-6M1 20v-6h6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path><path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg> تلاش مجدد</button>
438
- </div>
439
- </div>
440
- `;
441
 
442
- const promptInput = document.getElementById('prompt-input');
443
- const submitBtn = document.getElementById('submit-btn');
444
- const resultContainer = document.getElementById('result-container');
445
- const ratioSelector = document.getElementById('aspect-ratio-selector');
446
-
447
- const customSizeModal = document.getElementById('custom-size-modal');
448
- const closeCustomModalBtn = document.getElementById('close-custom-modal-btn');
449
- const confirmCustomSizeBtn = document.getElementById('confirm-custom-size-btn');
450
- const customWidthInput = document.getElementById('custom-width-input');
451
- const customHeightInput = document.getElementById('custom-height-input');
452
- const customRatioLabel = document.getElementById('custom-ratio-label-value');
453
-
454
- let selectedRatio = '1:1';
455
- let customWidth = 1024;
456
- let customHeight = 1024;
457
- let fluxClient = null;
458
- let countdownInterval = null;
459
-
460
- const loadingPlaceholderHTML = `<div id="loading-placeholder"><div class="generator-container"><div class="text-overlay" id="loading-status-text">در حال آماده‌سازی...</div><div class="progress-bar" id="loading-progress-bar"></div></div></div>`;
461
-
462
- // START: Modified resultWrapperHTML to include download button
463
- const resultWrapperHTML = `
464
- <div id="result-image-wrapper">
465
- <img id="result-image-display" src="" alt="تصویر خلق شده">
466
- <div class="download-actions">
467
- <button id="download-btn" class="download-button">
468
- <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">
469
- <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
470
- <polyline points="7 10 12 15 17 10"></polyline>
471
- <line x1="12" y1="15" x2="12" y2="3"></line>
472
- </svg>
473
- <span>دانلود تصویر</span>
474
- </button>
475
- </div>
476
- </div>`;
477
- // END: Modified resultWrapperHTML
478
-
479
- const startCountdown = (durationInSeconds) => {
480
- stopCountdown();
481
- let remainingTime = durationInSeconds;
482
- countdownInterval = setInterval(() => {
483
- const statusEl = document.getElementById('loading-status-text');
484
- const progressEl = document.getElementById('loading-progress-bar');
485
- if (remainingTime >= 0) {
486
- const progress = ((durationInSeconds - remainingTime) / durationInSeconds) * 100;
487
- if (statusEl) statusEl.textContent = `در حال ساخت... زمان باقی‌مانده: ${remainingTime} ثانیه`;
488
- if (progressEl) progressEl.style.width = `${progress}%`;
489
- remainingTime--;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
490
  } else {
491
- if (statusEl) statusEl.textContent = 'در حال نهایی‌سازی...';
492
- if (progressEl) progressEl.style.width = `100%`;
493
  stopCountdown();
 
 
 
 
 
494
  }
495
- }, 1000);
496
- };
497
 
498
- const stopCountdown = () => {
499
- if (countdownInterval) {
500
- clearInterval(countdownInterval);
501
- countdownInterval = null;
502
- }
503
- };
504
-
505
- const setLoadingState = (isLoading, message = 'در حال پردازش...') => {
506
- const btnSpinner = submitBtn.querySelector('.spinner');
507
- const btnText = document.getElementById('btn-text');
508
- const btnIcon = submitBtn.querySelector('svg');
509
- if (isLoading) {
510
- clearResult();
511
- resultContainer.classList.add('loading');
512
- resultContainer.innerHTML = loadingPlaceholderHTML;
513
- btnSpinner.style.display = 'inline-block';
514
- btnIcon.style.display = 'none';
515
- btnText.textContent = message;
516
- submitBtn.disabled = true;
517
- } else {
518
- stopCountdown();
519
- resultContainer.classList.remove('loading');
520
- btnSpinner.style.display = 'none';
521
- btnIcon.style.display = 'inline-block';
522
- btnText.textContent = 'خلق کن!';
523
- submitBtn.disabled = false;
524
- }
525
- };
526
-
527
- const displayResult = (imageUrl) => {
528
- resultContainer.classList.remove('loading', 'has-error-guide');
529
- resultContainer.classList.add('has-content');
530
- resultContainer.innerHTML = resultWrapperHTML;
531
- const resultImage = document.getElementById('result-image-display');
532
- resultImage.src = imageUrl;
533
-
534
- // START: Logic for download button
535
- const downloadBtn = document.getElementById('download-btn');
536
- downloadBtn.addEventListener('click', async () => {
537
- const buttonSpan = downloadBtn.querySelector('span');
538
- buttonSpan.textContent = 'در حال آماده‌سازی...';
539
- downloadBtn.disabled = true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
540
 
541
  try {
542
- // Fetch the image from its URL
543
- const response = await fetch(imageUrl);
544
- if (!response.ok) {
545
- throw new Error('خطا در دریافت فایل تصویر');
546
  }
547
- const imageBlob = await response.blob();
548
-
549
- // Define a filename
550
- const filename = `ai_cartoon_${Date.now()}.png`;
551
-
552
- // Send the blob and filename to the parent iframe via postMessage
553
- window.parent.postMessage({
554
- type: 'DOWNLOAD_IMAGE',
555
- payload: {
556
- blob: imageBlob,
557
- filename: filename
558
- }
559
- }, '*');
560
-
561
  } catch (error) {
562
- console.error('Download preparation failed:', error);
563
- alert('خطا در آماده‌سازی فایل برای دانلود. لطفاً اتصال اینترنت خود را بررسی کرده و دوباره تلاش کنید.');
564
- } finally {
565
- // Re-enable the button after a short delay
566
- setTimeout(() => {
567
- buttonSpan.textContent = 'دانلود تصویر';
568
- downloadBtn.disabled = false;
569
- }, 2000);
570
  }
571
- });
572
- // END: Logic for download button
573
- };
574
-
575
- const clearResult = () => {
576
- resultContainer.classList.remove('has-content', 'loading', 'has-error-guide');
577
- resultContainer.innerHTML = `<p>${'برای شروع، ایده و اندازه دلخواه خود را انتخاب کرده و دکمه "خلق کن" را بزنید.'}</p>`;
578
- };
579
-
580
- const displayError = (friendlyMessage, rawError) => {
581
- const rawErrorString = String(rawError?.message || rawError).toLowerCase();
582
- const gpuErrorKeywords = ["gpu capacity", "unavailable", "exceeded", "quota", "queue is full"];
583
- const isGpuError = gpuErrorKeywords.some(keyword => rawErrorString.includes(keyword));
584
- if (isGpuError) {
585
- resultContainer.classList.remove('loading');
586
- resultContainer.classList.add('has-error-guide');
587
- resultContainer.innerHTML = GPU_QUOTA_ERROR_HTML;
588
- resultContainer.querySelector('.back-button').addEventListener('click', clearResult);
589
- resultContainer.querySelector('.retry-button').addEventListener('click', generateImage);
590
- resultContainer.querySelector('#tutorialLinkBtn').addEventListener('click', () => {
591
- const tutorialUrl = '#/nav/online/news/getSingle/1149635/eyJpdiI6IjhHVGhPQWJwb3E0cjRXbnFWTW5BaUE9PSIsInZhbHVlIjoiS1V0dTdvT21wbXAwSXZaK1RCTG1pVXZqdlFJa1hXV1RKa2FLem9zU3pXMjd5MmlVOGc2YWY0NVdNR3h3Smp1aSIsIm1hYyI6IjY1NTA5ZDYzMjAzMTJhMGQyMWQ4NjA4ZDgyNGZjZDVlY2MyNjdiMjA2NWYzOWRjY2M4ZmVjYWRlMWNlMWQ3ODEiLCJ0YWciOiIifQ==/21135210';
592
- window.parent.postMessage({ type: 'NAVIGATE_TO_URL', url: tutorialUrl }, '*');
593
- });
594
- } else {
595
- resultContainer.classList.add('has-content');
596
- resultContainer.innerHTML = `<div class="error-message">${friendlyMessage}</div>`;
597
  }
598
- console.error("خطای کامل:", rawError);
599
- };
600
-
601
- async function initializeClient(isCalledFromGenerate = false) {
602
- if (!isCalledFromGenerate) {
603
- setLoadingState(true, 'در حال اتصال...');
604
- }
605
-
606
- try {
607
- fluxClient = await Client.connect("prithivMLmods/FLUX-LoRA-DLC");
608
- await fluxClient.predict("/add_custom_lora", { custom_lora: selectedLora.repo });
609
- if (!isCalledFromGenerate) {
610
- setLoadingState(false);
611
  }
612
- return true;
613
- } catch (error) {
614
- fluxClient = null;
615
- if (!isCalledFromGenerate) {
616
- displayError('ارتباط با سرور اصلی برقرار نشد. لطفاً اتصال را بررسی کنید یا دوباره تلاش کنید.', error);
617
- const btnText = document.getElementById('btn-text');
618
- btnText.textContent = 'تلاش مجدد برای اتصال';
619
- submitBtn.disabled = false;
620
  }
621
- return false;
622
- }
623
- }
624
-
625
- const getDimensions = (ratio) => {
626
- if (ratio === 'custom') {
627
- return { width: customWidth, height: customHeight };
628
- }
629
- switch (ratio) {
630
- case '9:16': return { width: 768, height: 1344 };
631
- case '16:9': return { width: 1344, height: 768 };
632
- case '1:1': return { width: 1024, height: 1024 };
633
- default: return { width: 1024, height: 1024 };
634
- }
635
- };
636
-
637
- async function generateImage() {
638
- if (submitBtn.disabled && !document.querySelector('.retry-button')) return;
639
- const userPrompt = promptInput.value.trim();
640
- if (!userPrompt) { alert('لطفاً یک متن برای ساخت تصویر بنویسید.'); return; }
641
-
642
- setLoadingState(true, 'در حال پردازش...');
643
-
644
- try {
645
- if (!fluxClient) {
646
- setLoadingState(true, 'در حال اتصال مجدد...');
647
- const connected = await initializeClient(true);
648
- if (!connected) {
649
- const friendlyMessage = 'اتصال مجدد با سرور برقرار نشد.';
650
- setLoadingState(false);
651
- displayError(friendlyMessage, { message: "Connection failed on retry." });
652
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
653
  }
 
 
 
 
 
 
 
 
 
654
  }
655
-
656
- setLoadingState(true, 'در حال پردازش...');
657
- const dimensions = getDimensions(selectedRatio);
658
-
659
- document.getElementById('loading-status-text').textContent = 'در حال ترجمه متن...';
660
-
661
- const translatorClient = await Client.connect("hamed744/translate-tts-aloha");
662
- const translationResult = await translatorClient.predict(1, [userPrompt + ' انمیشن سه بعدی', 'انگلیسی (آمریکا) - جنی (زن)', 0, 0, 0]);
663
- const translatedPrompt = translationResult.data[0];
664
- if (!translatedPrompt) throw new Error("سرویس ترجمه یک متن خالی برگرداند.");
665
-
666
- startCountdown(30);
667
-
668
- const finalPrompt = `${selectedLora.trigger_word} ${translatedPrompt}`;
669
-
670
- const result = await fluxClient.predict("/run_lora", {
671
- prompt: finalPrompt, image_input: null, cfg_scale: 3.5, steps: 28,
672
- randomize_seed: true, seed: 0,
673
- width: dimensions.width, height: dimensions.height, lora_scale: 0.8,
674
- });
675
 
676
- const imageData = result.data[0];
677
- if (imageData && imageData.url) {
678
- displayResult(imageData.url);
679
  } else {
680
- throw new Error('پاسخ سرور فاقد تصویر بود.');
 
 
 
 
 
 
 
 
 
 
 
 
681
  }
682
- setLoadingState(false);
 
 
 
 
683
 
684
- } catch (error) {
685
- let friendlyMessage = `خطا در ارتباط با سرور. لطفاً اتصال اینترنت خود را بررسی کرده و دوباره تلاش کنید.`;
686
- if (String(error.message).includes("hamed744/translate-tts-aloha")) {
687
- friendlyMessage = `خطا در سرویس ترجمه. لطفاً دقایقی دیگر دوباره امتحان کنید.`;
688
  }
689
 
690
- const rawErrorString = String(error?.message || error).toLowerCase();
691
- const gpuErrorKeywords = ["gpu capacity", "unavailable", "exceeded", "quota", "queue is full"];
692
- if (!gpuErrorKeywords.some(keyword => rawErrorString.includes(keyword))) {
693
- fluxClient = null;
694
  }
695
 
696
- setLoadingState(false);
697
- displayError(friendlyMessage, error);
698
- }
699
- }
700
-
701
- submitBtn.addEventListener('click', generateImage);
702
-
703
- ratioSelector.addEventListener('click', (event) => {
704
- const selectedOption = event.target.closest('.ratio-option');
705
- if (!selectedOption) return;
706
- const ratioValue = selectedOption.dataset.ratio;
707
-
708
- if (ratioValue === 'custom') {
709
- customSizeModal.style.display = 'flex';
710
- } else {
711
  ratioSelector.querySelectorAll('.ratio-option').forEach(opt => opt.classList.remove('active'));
712
- selectedOption.classList.add('active');
713
- selectedRatio = ratioValue;
714
- }
715
- });
 
716
 
717
- closeCustomModalBtn.addEventListener('click', () => {
718
- customSizeModal.style.display = 'none';
719
- });
720
 
721
- customSizeModal.addEventListener('click', (event) => {
722
- if (event.target === customSizeModal) {
723
- customSizeModal.style.display = 'none';
724
- }
725
- });
726
 
727
- confirmCustomSizeBtn.addEventListener('click', () => {
728
- const width = parseInt(customWidthInput.value, 10);
729
- const height = parseInt(customHeightInput.value, 10);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
730
 
731
- if (isNaN(width) || isNaN(height) || width < 512 || height < 512 || width > 1536 || height > 1536) {
732
- alert('مقادیر عرض و طول باید بین ۵۱۲ و ۱۵۳۶ پیکسل باشند.');
733
- return;
734
- }
735
-
736
- if (width % 8 !== 0 || height % 8 !== 0) {
737
- alert('مقادیر عرض و طول باید بر ۸ بخش‌پذیر باشند.');
738
- return;
739
- }
740
-
741
- customWidth = width;
742
- customHeight = height;
743
- selectedRatio = 'custom';
744
-
745
- ratioSelector.querySelectorAll('.ratio-option').forEach(opt => opt.classList.remove('active'));
746
- document.querySelector('.ratio-option[data-ratio="custom"]').classList.add('active');
747
-
748
- customRatioLabel.textContent = `${width} x ${height}`;
749
- customSizeModal.style.display = 'none';
750
- });
751
-
752
-
753
- clearResult();
754
- initializeClient();
755
-
756
- </script>
757
-
758
- <script>
759
- document.addEventListener('DOMContentLoaded', () => {
760
- const canvas = document.getElementById('neural-network-canvas');
761
- if(!canvas) return; const header = canvas.parentElement; const ctx = canvas.getContext('2d');
762
- let particles = []; const particleCount = 20; const maxDistance = 100;
763
- const computedStyles = getComputedStyle(document.documentElement);
764
- const particleColor = computedStyles.getPropertyValue('--accent-primary').trim();
765
- const lineColor = computedStyles.getPropertyValue('--text-tertiary').trim();
766
- function resizeCanvas() { canvas.width = header.clientWidth; canvas.height = header.clientHeight; init(); }
767
- 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(); } }
768
- function init() { particles = []; for (let i = 0; i < particleCount; i++) { particles.push(new Particle()); } }
769
- 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; }
770
- function animate() { if(!ctx) return; ctx.clearRect(0, 0, canvas.width, canvas.height); particles.forEach(particle => { particle.update(); particle.draw(); }); connectParticles(); requestAnimationFrame(animate); }
771
- window.addEventListener('resize', resizeCanvas); resizeCanvas(); animate();
772
- });
773
- </script>
774
  </body>
775
  </html>
 
1
  <!DOCTYPE html>
 
2
  <html lang="fa" dir="rtl">
3
  <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>خلق تصاویر کارتونی</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;500;600;700;800&display=swap" rel="stylesheet">
8
+ <style>
9
+ :root {
10
+ --app-font: 'Vazirmatn', sans-serif;
11
+ --app-bg: #F8F9FC;
12
+ --panel-bg: #FFFFFF;
13
+ --panel-border: #EAEFF7;
14
+ --input-bg: #F6F8FB;
15
+ --input-border: #E1E7EF;
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
+ --shadow-sm: 0 1px 2px 0 rgba(26, 32, 44, 0.03);
24
+ --shadow-md: 0 4px 6px -1px rgba(26, 32, 44, 0.05), 0 2px 4px -2px rgba(26, 32, 44, 0.04);
25
+ --shadow-lg: 0 10px 15px -3px rgba(26, 32, 44, 0.06), 0 4px 6px -4px rgba(26, 32, 44, 0.05);
26
+ --shadow-xl: 0 20px 25px -5px rgba(26, 32, 44, 0.07), 0 8px 10px -6px rgba(26, 32, 44, 0.05);
27
+ --radius-card: 24px;
28
+ --radius-btn: 14px;
29
+ --radius-input: 12px;
30
+ --transition-smooth: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
31
+
32
+ --guide-bg: rgba(255, 255, 255, 0.98);
33
+ --guide-border: rgba(102, 126, 234, 0.2);
34
+ --guide-text-title: #2d3748;
35
+ --guide-text-body: #4a5568;
36
+ --guide-accent: #667eea;
37
+ --primary-gradient-guide: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
38
+ --success-gradient-guide: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%);
39
+ --radius-md-guide: 12px;
40
+ --radius-lg-guide: 20px;
41
+ }
42
+
43
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
44
+ @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
45
+ @keyframes pulse { 0% { box-shadow: 0 0 40px rgba(56, 189, 248, 0.3); } 50% { box-shadow: 0 0 60px rgba(56, 189, 248, 0.7); } 100% { box-shadow: 0 0 40px rgba(56, 189, 248, 0.3); } }
46
+ @keyframes glow-text { 0% { opacity: 0.7; } 50% { opacity: 1; } 100% { opacity: 0.7; } }
47
+ @keyframes float { 0%, 100% { transform: translateY(0px); } 50% { transform: translateY(-10px); } }
48
+ @keyframes slideInUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
49
+
50
+ body {
51
+ font-family: var(--app-font);
52
+ background-color: var(--app-bg);
53
+ color: var(--text-primary);
54
+ margin: 0;
55
+ padding: 2.5rem 1rem;
56
+ display: flex;
57
+ justify-content: center;
58
+ align-items: flex-start;
59
+ min-height: 100vh;
60
+ }
61
+ .container { max-width: 820px; width: 100%; }
62
+
63
+ header {
64
+ position: relative;
65
+ text-align: center;
66
+ margin-bottom: 2.5rem;
67
+ padding: 2rem 0;
68
+ animation: fadeIn 0.8s 0.1s ease-out backwards;
69
+ overflow: hidden;
70
+ }
71
+ #neural-network-canvas {
72
+ position: absolute;
73
+ top: 0;
74
+ left: 0;
75
+ width: 100%;
76
+ height: 100%;
77
+ z-index: 1;
78
+ }
79
+ h1, .subtitle { position: relative; z-index: 2; }
80
+ h1 {
81
+ font-size: 2.8rem;
82
+ font-weight: 800;
83
+ margin: 0 0 0.8rem 0;
84
+ background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
85
+ -webkit-background-clip: text;
86
+ -webkit-text-fill-color: transparent;
87
+ letter-spacing: -1px;
88
+ }
89
+ .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; }
90
+
91
+ main {
92
+ padding: 3rem;
93
+ background-color: var(--panel-bg);
94
+ border-radius: var(--radius-card);
95
+ box-shadow: var(--shadow-xl);
96
+ border: 1px solid var(--panel-border);
97
+ animation: fadeIn 0.8s 0.3s ease-out backwards;
98
+ }
99
+
100
+ .form-group { margin-bottom: 2.5rem; }
101
+ .form-group:last-child { margin-bottom: 0; }
102
+
103
+ .form-label {
104
+ display: flex;
105
+ align-items: center;
106
+ gap: 0.75rem;
107
+ font-weight: 700;
108
+ color: var(--text-primary);
109
+ font-size: 1.2em;
110
+ margin-bottom: 1.2rem;
111
+ }
112
+ .form-label svg { width: 24px; height: 24px; color: var(--accent-primary); }
113
+
114
+ textarea {
115
+ width: 100%; padding: 1rem 1.2rem; border-radius: var(--radius-input); border: 1px solid var(--input-border);
116
+ background-color: var(--input-bg); color: var(--text-primary); box-shadow: var(--shadow-sm) inset;
117
+ font-family: var(--app-font); font-size: 1rem; box-sizing: border-box; transition: var(--transition-smooth);
118
+ min-height: 90px; resize: vertical;
119
+ }
120
+ textarea:focus {
121
+ outline: none; border-color: var(--accent-primary);
122
+ box-shadow: 0 0 0 3px var(--accent-primary-glow), var(--shadow-sm) inset; background-color: var(--panel-bg);
123
+ }
124
+
125
+ .aspect-ratio-selector {
126
+ display: grid;
127
+ grid-template-columns: repeat(4, 1fr);
128
+ gap: 1rem;
129
+ }
130
+ .ratio-option {
131
+ background-color: var(--input-bg);
132
+ border: 2px solid var(--input-border);
133
+ border-radius: var(--radius-input);
134
+ padding: 1rem;
135
+ cursor: pointer;
136
+ transition: var(--transition-smooth);
137
+ text-align: center;
138
+ }
139
+ .ratio-option:hover {
140
+ border-color: var(--accent-primary);
141
+ transform: translateY(-4px);
142
+ box-shadow: var(--shadow-md);
143
+ }
144
+ .ratio-option.active {
145
+ border-color: var(--accent-primary);
146
+ background-color: #fff;
147
+ box-shadow: 0 0 0 3px var(--accent-primary-glow);
148
+ }
149
+ .ratio-icon {
150
+ height: 50px;
151
+ display: flex;
152
+ align-items: center;
153
+ justify-content: center;
154
+ margin: 0 auto 0.75rem;
155
+ }
156
+ .ratio-icon > div, .ratio-icon > svg {
157
+ background-color: var(--panel-bg);
158
+ border: 2px solid var(--text-tertiary);
159
+ border-radius: 6px;
160
+ transition: var(--transition-smooth);
161
+ box-sizing: border-box;
162
+ }
163
+ .ratio-option.active .ratio-icon > div,
164
+ .ratio-option.active .ratio-icon > svg {
165
+ border-color: var(--accent-primary);
166
+ background-color: var(--accent-primary-glow);
167
+ }
168
+ .ratio-label {
169
+ font-weight: 600;
170
+ color: var(--text-secondary);
171
+ font-size: 0.9rem;
172
+ transition: color 0.2s;
173
+ }
174
+ .ratio-option.active .ratio-label { color: var(--text-primary); }
175
+
176
+ #icon-portrait > div { width: 28px; height: 50px; }
177
+ #icon-landscape > div { width: 60px; height: 34px; }
178
+ #icon-square > div { width: 45px; height: 45px; }
179
+ #icon-custom > svg { width: 45px; height: 45px; padding: 8px; color: var(--text-tertiary); }
180
+ .ratio-option.active #icon-custom > svg { color: var(--accent-primary); }
181
+ .custom-ratio-value { font-size: 0.75rem; color: var(--text-secondary); margin-top: 2px; height: 1em; }
182
+
183
+
184
+ #submit-btn {
185
+ display: flex; align-items: center; justify-content: center; gap: 0.75rem; width: 100%; padding: 1.1rem;
186
+ font-size: 1.2rem; font-weight: 700; background: linear-gradient(95deg, var(--accent-secondary) 0%, var(--accent-primary) 100%);
187
+ color: #fff; border: none; border-radius: var(--radius-btn); cursor: pointer; transition: all 0.3s ease;
188
+ box-shadow: 0 6px 12px -3px var(--accent-primary-glow), 0 6px 12px -3px var(--accent-secondary-glow); margin-top: 1.5rem;
189
+ }
190
+ #submit-btn svg { width: 24px; height: 24px; margin-left: 4px; filter: drop-shadow(0 0 5px rgba(255,255,255,0.5)); }
191
+ #submit-btn:hover:not(:disabled) { transform: translateY(-5px) scale(1.02); box-shadow: 0 8px 20px -4px var(--accent-primary-glow), 0 8px 20px -4px var(--accent-secondary-glow); }
192
+ #submit-btn:disabled { background: var(--text-tertiary); cursor: not-allowed; box-shadow: none; opacity: 0.7; }
193
+ #submit-btn .spinner { width: 20px; height: 20px; border: 3px solid rgba(255, 255, 255, 0.4); border-top-color: #fff; border-radius: 50%; animation: spin 0.8s linear infinite; display: none; }
194
+
195
+ #result-container { min-height: 250px; position: relative; padding: 1rem; background-color: var(--input-bg); border-radius: var(--radius-card); border: 2px dashed var(--input-border); box-shadow: var(--shadow-sm) inset; transition: var(--transition-smooth); display: flex; align-items: center; justify-content: center; flex-direction: column; box-sizing: border-box; }
196
+ #result-container.loading,
197
+ #result-container.has-error-guide {
198
+ padding: 0;
199
+ border: none;
200
+ background-color: transparent;
201
+ box-shadow: none;
202
+ }
203
+ #result-container.has-content { border-style: solid; border-color: var(--panel-border); padding: 1rem; }
204
+ #loading-placeholder { display: none; }
205
+ #result-container.loading #loading-placeholder { display: flex; animation: fadeIn 0.5s; justify-content: center; align-items: center; width: 100%; }
206
+
207
+ .generator-container { position: relative; width: 100%; max-width: 500px; height: 200px; border: 2px solid #38bdf8; border-radius: 20px; overflow: hidden; box-shadow: 0 0 40px rgba(56, 189, 248, 0.3); animation: pulse 5s infinite cubic-bezier(0.4, 0, 0.6, 1); background-color: #161b22; color: #f0f6fc; display: flex; flex-direction: column; justify-content: center; align-items: center; padding: 1rem; }
208
+ .text-overlay { font-size: 18px; font-weight: 700; text-shadow: 0 0 20px rgba(56, 189, 248, 0.8); animation: glow-text 7s infinite ease-in-out; text-align: center; }
209
+ .progress-bar { position: absolute; bottom: 0; left: 0; width: 0%; height: 6px; background: linear-gradient(to right, #38bdf8, #bb86fc, #facc15); transition: width 1s linear; }
210
+
211
+ #result-image-wrapper { display: none; width: 100%; }
212
+ #result-container.has-content #result-image-wrapper { display: block; animation: fadeIn 0.5s; text-align: center; }
213
+ #result-image-display { max-width: 100%; max-height: 500px; object-fit: contain; border-radius: var(--radius-input); box-shadow: var(--shadow-md); border: 1px solid var(--panel-border); }
214
+ .error-message { color: #d93025; font-weight: bold; margin-top: 10px; background: #ffebee; padding: 1rem; border-radius: var(--radius-input); border: 1px solid #e57373; }
215
+
216
+ .output-details { display: none !important; }
217
+
218
+ .modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(18, 24, 38, 0.6); backdrop-filter: blur(8px); display: none; justify-content: center; align-items: center; z-index: 1000; animation: fadeIn 0.3s; }
219
+ .modal-content-small { background: var(--panel-bg); padding: 2rem; border-radius: var(--radius-card); max-width: 350px; width: 90%; text-align: center; box-shadow: var(--shadow-xl); position: relative; }
220
+ .modal-content-small h3 { margin-top: 0; margin-bottom: 1.5rem; font-size: 1.5rem; color: var(--text-primary); }
221
+ .modal-content-small .input-group { margin-bottom: 1rem; text-align: right; }
222
+ .modal-content-small label { display: block; margin-bottom: 0.5rem; font-weight: 600; color: var(--text-secondary); font-size: 0.9rem; }
223
+ .modal-content-small input { width: 100%; padding: 0.8rem 1rem; 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: 1rem; box-sizing: border-box; transition: var(--transition-smooth); }
224
+ .modal-content-small input: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); }
225
+ #confirm-custom-size-btn { width: 100%; padding: 0.9rem; margin-top: 1rem; font-size: 1rem; font-weight: 700; background-color: var(--accent-primary); color: #fff; border: none; border-radius: var(--radius-btn); cursor: pointer; transition: var(--transition-smooth); }
226
+ #confirm-custom-size-btn:hover { background-color: var(--accent-primary-hover); }
227
+ #close-custom-modal-btn { position: absolute; top: 10px; left: 10px; background: transparent; border: none; font-size: 2rem; cursor: pointer; color: var(--text-tertiary); line-height: 1; padding: 0.5rem; }
228
+
229
+ .ip-reset-guide-container { text-align: right; background: var(--panel-bg); padding: 10px; border-radius: var(--radius-lg-guide); animation: slideInUp 0.6s cubic-bezier(0.4, 0, 0.2, 1) both; max-width: 100%; width: 100%; position: relative; overflow: hidden; box-sizing: border-box; }
230
+ .ip-reset-guide-container::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 5px; background: var(--primary-gradient-guide); }
231
+ .guide-header { display: flex; align-items: center; margin-bottom: 20px; position: relative; }
232
+ .guide-header-icon { width: 50px; height: 50px; margin-left: 15px; animation: float 3s ease-in-out infinite; flex-shrink: 0; }
233
+ .guide-header h2 { font-size: 1.2rem; color: var(--guide-text-title); font-weight: 700; margin: 0; }
234
+ .guide-header p { color: var(--guide-text-body); font-size: 0.85rem; margin-top: 5px; }
235
+ .guide-content { font-size: 0.95rem; color: var(--guide-text-body); line-height: 1.8; }
236
+ .info-card { background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%); border: 1px solid rgba(102, 126, 234, 0.2); border-radius: var(--radius-md-guide); padding: 15px; margin: 15px 0; position: relative; overflow: hidden; }
237
+ .info-card p { font-size: 0.85rem; line-height: 1.7; }
238
+ .info-card::before { content: ''; position: absolute; top: 0; right: 0; width: 4px; height: 100%; background: var(--primary-gradient-guide); }
239
+ .info-card-header { display: flex; align-items: center; margin-bottom: 10px; }
240
+ .info-card-icon { width: 22px; height: 22px; margin-left: 10px; }
241
+ .info-card-title { font-weight: 600; color: var(--guide-text-title); font-size: 1rem; }
242
+ .summary-section { margin-top: 20px; padding: 15px; border-radius: var(--radius-md-guide); background: linear-gradient(135deg, #56ab2f15 0%, #a8e06315 100%); border: 1px solid rgba(86, 171, 47, 0.2); position: relative; overflow: hidden; }
243
+ .summary-section::before { content: ''; position: absolute; top: 0; right: 0; width: 4px; height: 100%; background: var(--success-gradient-guide); }
244
+ .summary-header { display: flex; align-items: center; margin-bottom: 10px; }
245
+ .summary-icon { width: 22px; height: 22px; margin-left: 10px; }
246
+ .summary-title { font-weight: 600; color: #2f5a33; font-size: 1rem; }
247
+ .summary-text { color: #2f5a33; font-size: 0.9rem; line-height: 1.7; }
248
+ .guide-actions { display: flex; gap: 15px; margin-top: 25px; padding-top: 20px; border-top: 1px solid #e2e8f0; }
249
+ .action-button { padding: 12px 20px; border: none; border-radius: var(--radius-md-guide); font-size: 0.9rem; font-weight: 600; cursor: pointer; font-family: inherit; flex: 1; transition: var(--transition-smooth); position: relative; overflow: hidden; display: flex; align-items: center; justify-content: center; }
250
+ .action-button-icon { width: 18px; height: 18px; margin-right: 8px; margin-left: 0; }
251
+ .back-button { background: white; color: var(--guide-text-body); border: 2px solid #e2e8f0; flex: 0.4; }
252
+ .back-button:hover { background: #f7fafc; border-color: var(--guide-accent); transform: translateY(-2px); box-shadow: var(--shadow-md); }
253
+ .retry-button { background: var(--primary-gradient-guide); color: white; flex: 0.6; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); }
254
+ .retry-button:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4); }
255
+ .video-button-container { text-align: center; margin: 25px 0 10px 0; }
256
+ .elegant-video-button { display: inline-flex; align-items: center; justify-content: center; padding: 7px 18px; background-color: #f0f2f5; color: var(--guide-accent); border: 1px solid #e2e8f0; text-decoration: none; border-radius: var(--radius-md-guide); font-weight: 600; font-size: 0.8rem; cursor: pointer; font-family: inherit; transition: all 0.3s ease; box-shadow: var(--shadow-sm); }
257
+ .elegant-video-button:hover { background: var(--primary-gradient-guide); color: white; border-color: transparent; transform: translateY(-2px); box-shadow: 0 6px 16px rgba(102, 126, 234, 0.3); }
258
+ .elegant-video-button-icon { width: 16px; height: 16px; margin-left: 8px; fill: currentColor; }
259
+
260
+ /* START: CSS styles for the download button */
261
+ .download-actions {
262
+ margin-top: 1.5rem;
263
+ text-align: center;
264
+ }
265
+ .download-button {
266
+ padding: 12px 25px;
267
+ border: none;
268
+ border-radius: var(--radius-btn);
269
+ font-size: 1rem;
270
+ font-weight: 600;
271
+ cursor: pointer;
272
+ font-family: inherit;
273
+ transition: var(--transition-smooth);
274
+ display: inline-flex;
275
+ align-items: center;
276
+ justify-content: center;
277
+ gap: 0.7rem;
278
+ background-color: var(--accent-primary);
279
+ color: white;
280
+ box-shadow: var(--shadow-md);
281
+ }
282
+ .download-button:hover:not(:disabled) {
283
+ background-color: var(--accent-primary-hover);
284
+ transform: translateY(-3px);
285
+ box-shadow: var(--shadow-lg);
286
+ }
287
+ .download-button:disabled {
288
+ background-color: var(--text-tertiary);
289
+ cursor: not-allowed;
290
+ opacity: 0.8;
291
+ }
292
+ .download-button svg {
293
+ width: 20px;
294
+ height: 20px;
295
+ }
296
+ /* END: CSS styles for the download button */
297
+
298
+ @media (max-width: 768px) {
299
+ main { padding: 1.5rem; } h1 { font-size: 2.2rem; }
300
+ .aspect-ratio-selector { grid-template-columns: repeat(2, 1fr); }
301
+ }
302
+ </style>
 
 
 
 
 
303
  </head>
304
  <body>
305
+ <div class="container">
306
+ <header>
307
+ <canvas id="neural-network-canvas"></canvas>
308
+ <h1>خلق تصاویر انیمیشنی جذاب</h1>
309
+ <p class="subtitle">ایده‌های خود را به سادگی به تصاویر انیمیشنی با سبک منحصر به فرد تبدیل کنید.</p>
310
+ </header>
311
+ <main>
312
+ <div class="form-group">
313
+ <div class="form-label">
314
+ <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>
315
+ ۱. ایده خود را توصیف کنید
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
316
  </div>
317
+ <textarea id="prompt-input" rows="3" placeholder="مثال: یک فضانورد که روی ماه در حال موج‌سواری است"></textarea>
318
+ </div>
319
+
320
+ <div class="form-group">
321
+ <div class="form-label">
322
+ <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="M21 9V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h4"/><rect x="12" y="13" width="10" height="8" rx="2"/><path d="M17 12v-1a2 2 0 0 0-2-2h-1"/></svg>
323
+ ۲. اندازه تصویر را انتخاب کنید
324
  </div>
325
+ <div class="aspect-ratio-selector" id="aspect-ratio-selector">
326
+ <div class="ratio-option" data-ratio="9:16">
327
+ <div class="ratio-icon" id="icon-portrait"><div></div></div>
328
+ <div class="ratio-label">عمودی ۹:۱۶</div>
329
+ </div>
330
+ <div class="ratio-option" data-ratio="16:9">
331
+ <div class="ratio-icon" id="icon-landscape"><div></div></div>
332
+ <div class="ratio-label">افقی ۱۶:۹</div>
333
  </div>
334
+ <div class="ratio-option" data-ratio="custom">
335
+ <div class="ratio-icon" id="icon-custom">
336
+ <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">
337
+ <path d="M21 16V8a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h8"/>
338
+ <circle cx="18" cy="18" r="3"/>
339
+ <path d="M18 15v-2"/><path d="M18 21v-2"/>
340
+ <path d="M15 18h-2"/><path d="M21 18h-2"/>
341
+ </svg>
342
+ </div>
343
+ <div class="ratio-label">
344
+ اندازه دلخواه
345
+ <div class="custom-ratio-value" id="custom-ratio-label-value"></div>
346
+ </div>
347
+ </div>
348
+ <div class="ratio-option active" data-ratio="1:1">
349
+ <div class="ratio-icon" id="icon-square"><div></div></div>
350
+ <div class="ratio-label">مربع ۱:۱</div>
351
  </div>
352
  </div>
353
+ </div>
354
+
355
+ <button id="submit-btn" disabled>
356
+ <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>
357
+ <span id="btn-text">در حال اتصال...</span>
358
+ <div class="spinner" style="display: inline-block;"></div>
359
+ </button>
360
+
361
+ <div class="form-group" style="margin-top: 3rem; margin-bottom: 0;">
362
+ <div class="form-label">
363
+ <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"/><path d="M21 14l-1.5 3-3-1.5 3-3 1.5 3z"/><path d="M19.5 2.5l-3 1.5 1.5 3 3-1.5-1.5-3z"/></svg>
364
+ ۳. نتیجه را ببینید
365
+ </div>
366
+ <div id="result-container">
367
+ <!-- محتوای این بخش توسط جاوااسکریپت مدیریت می‌شود -->
368
  </div>
369
  </div>
370
+
371
+ <div class="output-details" id="output-details"></div>
372
+ </main>
373
+ </div>
374
+
375
+ <!-- Modal for Custom Size -->
376
+ <div class="modal-overlay" id="custom-size-modal">
377
+ <div class="modal-content-small">
378
+ <button id="close-custom-modal-btn">&times;</button>
379
+ <h3>تنظیم اندازه دلخواه</h3>
380
+ <div class="input-group">
381
+ <label for="custom-width-input">عرض (Width)</label>
382
+ <input type="number" id="custom-width-input" value="1024" min="512" max="1536" step="8" placeholder="بین ۵۱۲ تا ۱۵۳۶">
383
  </div>
384
+ <div class="input-group">
385
+ <label for="custom-height-input">طول (Height)</label>
386
+ <input type="number" id="custom-height-input" value="1024" min="512" max="1536" step="8" placeholder="بین ۵۱۲ تا ۱۵۳۶">
387
  </div>
388
+ <button id="confirm-custom-size-btn">تایید و اعمال</button>
389
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
390
  </div>
 
391
 
392
 
393
+ <script type="module">
394
+ import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
395
+
396
+ const selectedLora = {
397
+ "title": "Long Toons",
398
+ "repo": "prithivMLmods/Flux-Long-Toon-LoRA",
399
+ "trigger_word": "Long toons"
400
+ };
401
+
402
+ const GPU_QUOTA_ERROR_HTML = `
403
+ <div class="ip-reset-guide-container">
404
+ <div class="guide-header">
405
+ <svg class="guide-header-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10" stroke="#667eea" stroke-width="2" fill="rgba(102, 126, 234, 0.1)"/><path d="M8 12.5l2.5 2.5L16 9" stroke="#667eea" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
406
+ <div><h2>یک قدم تا ساخت تصاویر جدید</h2><p>نیازمند تغییر نقطه دستیابی</p></div>
 
 
 
 
 
407
  </div>
408
+ <div class="guide-content">
409
+ <div class="info-card">
410
+ <div class="info-card-header"><svg class="info-card-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" stroke="#667eea" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg><span class="info-card-title">راه حل سریع</span></div>
411
+ <p>طبق ویدیو آموزشی پایین بین نقطه دستیابی جابجا شوید تلاش مجدد بزنید تا تصاویر مجدداً تولید بشه.</p>
412
+ </div>
413
+ <div class="summary-section">
414
+ <div class="summary-header"><svg class="summary-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9 12l2 2 4-4" stroke="#2f5a33" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path><circle cx="12" cy="12" r="10" stroke="#2f5a33" stroke-width="2"></circle></svg><span class="summary-title">خلاصه راهنما</span></div>
415
+ <div class="summary-text">هربار که این صفحه را مشاهده کردید: از اینترنت سیم‌کارت استفاده کنید، VPN را خاموش کرده و طبق ویدیو آموزشی پایین نقطه دستیابی رو تغییر دهید. «تلاش مجدد» کلیک کنید. با این روش ساده می‌توانید به صورت نامحدود تصویر بسازید! ☘️</div>
416
+ </div>
417
+ </div>
418
+ <div class="video-button-container">
419
+ <button id="tutorialLinkBtn" class="elegant-video-button">
420
+ <svg class="elegant-video-button-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"></path></svg>
421
+ دیدن ویدیو آموزشی استفاده نامحدود
422
+ </button>
423
+ </div>
424
+ <div class="guide-actions">
425
+ <button class="action-button back-button"><svg class="action-button-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M19 12H5M12 19l-7-7 7-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>بازگشت</button>
426
+ <button class="action-button retry-button"><svg class="action-button-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M23 4v6h-6M1 20v-6h6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path><path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg> تلاش مجدد</button>
427
  </div>
428
  </div>
429
+ `;
 
 
 
 
 
 
 
 
 
 
 
430
 
431
+ const promptInput = document.getElementById('prompt-input');
432
+ const submitBtn = document.getElementById('submit-btn');
433
+ const resultContainer = document.getElementById('result-container');
434
+ const ratioSelector = document.getElementById('aspect-ratio-selector');
435
+
436
+ const customSizeModal = document.getElementById('custom-size-modal');
437
+ const closeCustomModalBtn = document.getElementById('close-custom-modal-btn');
438
+ const confirmCustomSizeBtn = document.getElementById('confirm-custom-size-btn');
439
+ const customWidthInput = document.getElementById('custom-width-input');
440
+ const customHeightInput = document.getElementById('custom-height-input');
441
+ const customRatioLabel = document.getElementById('custom-ratio-label-value');
442
+
443
+ let selectedRatio = '1:1';
444
+ let customWidth = 1024;
445
+ let customHeight = 1024;
446
+ let fluxClient = null;
447
+ let countdownInterval = null;
448
+
449
+ const loadingPlaceholderHTML = `<div id="loading-placeholder"><div class="generator-container"><div class="text-overlay" id="loading-status-text">در حال آماده‌سازی...</div><div class="progress-bar" id="loading-progress-bar"></div></div></div>`;
450
+
451
+ // START: Modified resultWrapperHTML to include download button
452
+ const resultWrapperHTML = `
453
+ <div id="result-image-wrapper">
454
+ <img id="result-image-display" src="" alt="تصویر خلق شده">
455
+ <div class="download-actions">
456
+ <button id="download-btn" class="download-button">
457
+ <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">
458
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
459
+ <polyline points="7 10 12 15 17 10"></polyline>
460
+ <line x1="12" y1="15" x2="12" y2="3"></line>
461
+ </svg>
462
+ <span>دانلود تصویر</span>
463
+ </button>
464
+ </div>
465
+ </div>`;
466
+ // END: Modified resultWrapperHTML
467
+
468
+ const startCountdown = (durationInSeconds) => {
469
+ stopCountdown();
470
+ let remainingTime = durationInSeconds;
471
+ countdownInterval = setInterval(() => {
472
+ const statusEl = document.getElementById('loading-status-text');
473
+ const progressEl = document.getElementById('loading-progress-bar');
474
+ if (remainingTime >= 0) {
475
+ const progress = ((durationInSeconds - remainingTime) / durationInSeconds) * 100;
476
+ if (statusEl) statusEl.textContent = `در حال ساخت... زمان باقی‌مانده: ${remainingTime} ثانیه`;
477
+ if (progressEl) progressEl.style.width = `${progress}%`;
478
+ remainingTime--;
479
+ } else {
480
+ if (statusEl) statusEl.textContent = 'در حال نهایی‌سازی...';
481
+ if (progressEl) progressEl.style.width = `100%`;
482
+ stopCountdown();
483
+ }
484
+ }, 1000);
485
+ };
486
+
487
+ const stopCountdown = () => {
488
+ if (countdownInterval) {
489
+ clearInterval(countdownInterval);
490
+ countdownInterval = null;
491
+ }
492
+ };
493
+
494
+ const setLoadingState = (isLoading, message = 'در حال پردازش...') => {
495
+ const btnSpinner = submitBtn.querySelector('.spinner');
496
+ const btnText = document.getElementById('btn-text');
497
+ const btnIcon = submitBtn.querySelector('svg');
498
+ if (isLoading) {
499
+ clearResult();
500
+ resultContainer.classList.add('loading');
501
+ resultContainer.innerHTML = loadingPlaceholderHTML;
502
+ btnSpinner.style.display = 'inline-block';
503
+ btnIcon.style.display = 'none';
504
+ btnText.textContent = message;
505
+ submitBtn.disabled = true;
506
  } else {
 
 
507
  stopCountdown();
508
+ resultContainer.classList.remove('loading');
509
+ btnSpinner.style.display = 'none';
510
+ btnIcon.style.display = 'inline-block';
511
+ btnText.textContent = 'خلق کن!';
512
+ submitBtn.disabled = false;
513
  }
514
+ };
 
515
 
516
+ const displayResult = (imageUrl) => {
517
+ resultContainer.classList.remove('loading', 'has-error-guide');
518
+ resultContainer.classList.add('has-content');
519
+ resultContainer.innerHTML = resultWrapperHTML;
520
+ const resultImage = document.getElementById('result-image-display');
521
+ resultImage.src = imageUrl;
522
+
523
+ // START: Logic for download button
524
+ const downloadBtn = document.getElementById('download-btn');
525
+ downloadBtn.addEventListener('click', async () => {
526
+ const buttonSpan = downloadBtn.querySelector('span');
527
+ buttonSpan.textContent = 'در حال آماده‌سازی...';
528
+ downloadBtn.disabled = true;
529
+
530
+ try {
531
+ // Fetch the image from its URL
532
+ const response = await fetch(imageUrl);
533
+ if (!response.ok) {
534
+ throw new Error('خطا در دریافت فایل تصویر');
535
+ }
536
+ const imageBlob = await response.blob();
537
+
538
+ // Define a filename
539
+ const filename = `ai_cartoon_${Date.now()}.png`;
540
+
541
+ // Send the blob and filename to the parent iframe via postMessage
542
+ window.parent.postMessage({
543
+ type: 'DOWNLOAD_IMAGE',
544
+ payload: {
545
+ blob: imageBlob,
546
+ filename: filename
547
+ }
548
+ }, '*');
549
+
550
+ } catch (error) {
551
+ console.error('Download preparation failed:', error);
552
+ alert('خطا در آماده‌سازی ��ایل برای دانلود. لطفاً اتصال اینترنت خود را بررسی کرده و دوباره تلاش کنید.');
553
+ } finally {
554
+ // Re-enable the button after a short delay
555
+ setTimeout(() => {
556
+ buttonSpan.textContent = 'دانلود تصویر';
557
+ downloadBtn.disabled = false;
558
+ }, 2000);
559
+ }
560
+ });
561
+ // END: Logic for download button
562
+ };
563
+
564
+ const clearResult = () => {
565
+ resultContainer.classList.remove('has-content', 'loading', 'has-error-guide');
566
+ resultContainer.innerHTML = `<p>${'برای شروع، ایده و اندازه دلخواه خود را انتخاب کرده و دکمه "خلق کن" را بزنید.'}</p>`;
567
+ };
568
+
569
+ const displayError = (friendlyMessage, rawError) => {
570
+ const rawErrorString = String(rawError?.message || rawError).toLowerCase();
571
+ const gpuErrorKeywords = ["gpu capacity", "unavailable", "exceeded", "quota", "queue is full"];
572
+ const isGpuError = gpuErrorKeywords.some(keyword => rawErrorString.includes(keyword));
573
+ if (isGpuError) {
574
+ resultContainer.classList.remove('loading');
575
+ resultContainer.classList.add('has-error-guide');
576
+ resultContainer.innerHTML = GPU_QUOTA_ERROR_HTML;
577
+ resultContainer.querySelector('.back-button').addEventListener('click', clearResult);
578
+ resultContainer.querySelector('.retry-button').addEventListener('click', generateImage);
579
+ resultContainer.querySelector('#tutorialLinkBtn').addEventListener('click', () => {
580
+ const tutorialUrl = '#/nav/online/news/getSingle/1149635/eyJpdiI6IjhHVGhPQWJwb3E0cjRXbnFWTW5BaUE9PSIsInZhbHVlIjoiS1V0dTdvT21wbXAwSXZaK1RCTG1pVXZqdlFJa1hXV1RKa2FLem9zU3pXMjd5MmlVOGc2YWY0NVdNR3h3Smp1aSIsIm1hYyI6IjY1NTA5ZDYzMjAzMTJhMGQyMWQ4NjA4ZDgyNGZjZDVlY2MyNjdiMjA2NWYzOWRjY2M4ZmVjYWRlMWNlMWQ3ODEiLCJ0YWciOiIifQ==/21135210';
581
+ window.parent.postMessage({ type: 'NAVIGATE_TO_URL', url: tutorialUrl }, '*');
582
+ });
583
+ } else {
584
+ resultContainer.classList.add('has-content');
585
+ resultContainer.innerHTML = `<div class="error-message">${friendlyMessage}</div>`;
586
+ }
587
+ console.error("خطای کامل:", rawError);
588
+ };
589
+
590
+ async function initializeClient(isCalledFromGenerate = false) {
591
+ if (!isCalledFromGenerate) {
592
+ setLoadingState(true, 'در حال اتصال...');
593
+ }
594
 
595
  try {
596
+ fluxClient = await Client.connect("prithivMLmods/FLUX-LoRA-DLC");
597
+ await fluxClient.predict("/add_custom_lora", { custom_lora: selectedLora.repo });
598
+ if (!isCalledFromGenerate) {
599
+ setLoadingState(false);
600
  }
601
+ return true;
 
 
 
 
 
 
 
 
 
 
 
 
 
602
  } catch (error) {
603
+ fluxClient = null;
604
+ if (!isCalledFromGenerate) {
605
+ displayError('ارتباط با سرور اصلی برقرار نشد. لطفاً اتصال را بررسی کنید یا دوباره تلاش کنید.', error);
606
+ const btnText = document.getElementById('btn-text');
607
+ btnText.textContent = 'تلاش مجدد برای اتصال';
608
+ submitBtn.disabled = false;
609
+ }
610
+ return false;
611
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
612
  }
613
+
614
+ const getDimensions = (ratio) => {
615
+ if (ratio === 'custom') {
616
+ return { width: customWidth, height: customHeight };
 
 
 
 
 
 
 
 
 
617
  }
618
+ switch (ratio) {
619
+ case '9:16': return { width: 768, height: 1344 };
620
+ case '16:9': return { width: 1344, height: 768 };
621
+ case '1:1': return { width: 1024, height: 1024 };
622
+ default: return { width: 1024, height: 1024 };
 
 
 
623
  }
624
+ };
625
+
626
+ async function generateImage() {
627
+ if (submitBtn.disabled && !document.querySelector('.retry-button')) return;
628
+ const userPrompt = promptInput.value.trim();
629
+ if (!userPrompt) { alert('لطفاً یک متن برای ساخت تصویر بنویسید.'); return; }
630
+
631
+ setLoadingState(true, 'در حال پردازش...');
632
+
633
+ try {
634
+ if (!fluxClient) {
635
+ setLoadingState(true, 'در حال اتصال مجدد...');
636
+ const connected = await initializeClient(true);
637
+ if (!connected) {
638
+ const friendlyMessage = 'اتصال مجدد با سرور برقرار نشد.';
639
+ setLoadingState(false);
640
+ displayError(friendlyMessage, { message: "Connection failed on retry." });
641
+ return;
642
+ }
643
+ }
644
+
645
+ setLoadingState(true, 'در حال پردازش...');
646
+ const dimensions = getDimensions(selectedRatio);
647
+
648
+ document.getElementById('loading-status-text').textContent = 'در حال ترجمه متن...';
649
+
650
+ const translatorClient = await Client.connect("hamed744/translate-tts-aloha");
651
+ const translationResult = await translatorClient.predict(1, [userPrompt + ' انمیشن سه بعدی', 'انگلیسی (آمریکا) - جنی (زن)', 0, 0, 0]);
652
+ const translatedPrompt = translationResult.data[0];
653
+ if (!translatedPrompt) throw new Error("سرویس ترجمه یک متن خالی برگرداند.");
654
+
655
+ startCountdown(30);
656
+
657
+ const finalPrompt = `${selectedLora.trigger_word} ${translatedPrompt}`;
658
+
659
+ const result = await fluxClient.predict("/run_lora", {
660
+ prompt: finalPrompt, image_input: null, cfg_scale: 3.5, steps: 28,
661
+ randomize_seed: true, seed: 0,
662
+ width: dimensions.width, height: dimensions.height, lora_scale: 0.8,
663
+ });
664
+
665
+ const imageData = result.data[0];
666
+ if (imageData && imageData.url) {
667
+ displayResult(imageData.url);
668
+ } else {
669
+ throw new Error('پاسخ سرور فاقد تصویر بود.');
670
+ }
671
+ setLoadingState(false);
672
+
673
+ } catch (error) {
674
+ let friendlyMessage = `خطا در ارتباط با سرور. لطفاً اتصال اینترنت خود را بررسی کرده و دوباره تلاش کنید.`;
675
+ if (String(error.message).includes("hamed744/translate-tts-aloha")) {
676
+ friendlyMessage = `خطا در سرویس ترجمه. لطفاً دقایقی دیگر دوباره امتحان کنید.`;
677
  }
678
+
679
+ const rawErrorString = String(error?.message || error).toLowerCase();
680
+ const gpuErrorKeywords = ["gpu capacity", "unavailable", "exceeded", "quota", "queue is full"];
681
+ if (!gpuErrorKeywords.some(keyword => rawErrorString.includes(keyword))) {
682
+ fluxClient = null;
683
+ }
684
+
685
+ setLoadingState(false);
686
+ displayError(friendlyMessage, error);
687
  }
688
+ }
689
+
690
+ submitBtn.addEventListener('click', generateImage);
691
+
692
+ ratioSelector.addEventListener('click', (event) => {
693
+ const selectedOption = event.target.closest('.ratio-option');
694
+ if (!selectedOption) return;
695
+ const ratioValue = selectedOption.dataset.ratio;
 
 
 
 
 
 
 
 
 
 
 
 
696
 
697
+ if (ratioValue === 'custom') {
698
+ customSizeModal.style.display = 'flex';
 
699
  } else {
700
+ ratioSelector.querySelectorAll('.ratio-option').forEach(opt => opt.classList.remove('active'));
701
+ selectedOption.classList.add('active');
702
+ selectedRatio = ratioValue;
703
+ }
704
+ });
705
+
706
+ closeCustomModalBtn.addEventListener('click', () => {
707
+ customSizeModal.style.display = 'none';
708
+ });
709
+
710
+ customSizeModal.addEventListener('click', (event) => {
711
+ if (event.target === customSizeModal) {
712
+ customSizeModal.style.display = 'none';
713
  }
714
+ });
715
+
716
+ confirmCustomSizeBtn.addEventListener('click', () => {
717
+ const width = parseInt(customWidthInput.value, 10);
718
+ const height = parseInt(customHeightInput.value, 10);
719
 
720
+ if (isNaN(width) || isNaN(height) || width < 512 || height < 512 || width > 1536 || height > 1536) {
721
+ alert('مقادیر عرض و طول باید بین ۵۱۲ و ۱۵۳۶ پیکسل باشند.');
722
+ return;
 
723
  }
724
 
725
+ if (width % 8 !== 0 || height % 8 !== 0) {
726
+ alert('مقادیر عرض و طول باید بر ۸ بخش‌پذیر باشند.');
727
+ return;
 
728
  }
729
 
730
+ customWidth = width;
731
+ customHeight = height;
732
+ selectedRatio = 'custom';
733
+
 
 
 
 
 
 
 
 
 
 
 
734
  ratioSelector.querySelectorAll('.ratio-option').forEach(opt => opt.classList.remove('active'));
735
+ document.querySelector('.ratio-option[data-ratio="custom"]').classList.add('active');
736
+
737
+ customRatioLabel.textContent = `${width} x ${height}`;
738
+ customSizeModal.style.display = 'none';
739
+ });
740
 
 
 
 
741
 
742
+ clearResult();
743
+ initializeClient();
 
 
 
744
 
745
+ </script>
746
+
747
+ <script>
748
+ document.addEventListener('DOMContentLoaded', () => {
749
+ const canvas = document.getElementById('neural-network-canvas');
750
+ if(!canvas) return; const header = canvas.parentElement; const ctx = canvas.getContext('2d');
751
+ let particles = []; const particleCount = 20; const maxDistance = 100;
752
+ const computedStyles = getComputedStyle(document.documentElement);
753
+ const particleColor = computedStyles.getPropertyValue('--accent-primary').trim();
754
+ const lineColor = computedStyles.getPropertyValue('--text-tertiary').trim();
755
+ function resizeCanvas() { canvas.width = header.clientWidth; canvas.height = header.clientHeight; init(); }
756
+ 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(); } }
757
+ function init() { particles = []; for (let i = 0; i < particleCount; i++) { particles.push(new Particle()); } }
758
+ 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; }
759
+ function animate() { if(!ctx) return; ctx.clearRect(0, 0, canvas.width, canvas.height); particles.forEach(particle => { particle.update(); particle.draw(); }); connectParticles(); requestAnimationFrame(animate); }
760
+ window.addEventListener('resize', resizeCanvas); resizeCanvas(); animate();
761
+ });
762
+ </script>
763
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
764
  </body>
765
  </html>