Elias207 commited on
Commit
0fd2356
·
verified ·
1 Parent(s): 3d7f82d

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +166 -123
index.html CHANGED
@@ -6,6 +6,34 @@
6
  <title>آلفا QR کد | ساخت و خواندن</title>
7
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@v33.003/Vazirmatn-font-face.css">
8
  <style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  * {
10
  margin: 0;
11
  padding: 0;
@@ -13,51 +41,64 @@
13
  }
14
 
15
  body {
16
- font-family: 'Vazirmatn', sans-serif;
17
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
 
18
  min-height: 100vh;
19
- color: #fff;
20
  overflow-x: hidden;
 
 
 
 
 
 
 
21
  }
22
 
23
  .container {
24
- max-width: 400px;
 
25
  margin: 0 auto;
26
- padding: 15px;
27
- min-height: 100vh;
28
  display: flex;
29
  flex-direction: column;
30
  }
31
-
 
32
  .header {
33
  text-align: center;
34
- padding: 20px 0 30px;
35
  }
36
 
37
  .logo {
38
  font-size: 3rem;
39
  margin-bottom: 10px;
40
- filter: drop-shadow(0 4px 8px rgba(0,0,0,0.3));
41
  }
42
 
43
  .title {
44
- font-size: 1.5rem;
45
- font-weight: 600;
46
- margin-bottom: 5px;
 
 
 
 
47
  }
48
 
49
  .subtitle {
50
- font-size: 0.9rem;
51
- opacity: 0.8;
 
52
  }
53
-
 
54
  .tabs {
55
  display: flex;
56
- background: rgba(255,255,255,0.15);
57
- border-radius: 15px;
58
- padding: 5px;
59
  margin-bottom: 20px;
60
- backdrop-filter: blur(10px);
61
  }
62
 
63
  .tab {
@@ -66,24 +107,26 @@
66
  text-align: center;
67
  border-radius: 10px;
68
  cursor: pointer;
69
- transition: all 0.3s ease;
70
- font-weight: 500;
71
  font-size: 0.9rem;
 
72
  }
73
 
74
  .tab.active {
75
- background: rgba(255,255,255,0.3);
 
76
  transform: translateY(-2px);
77
- box-shadow: 0 4px 12px rgba(0,0,0,0.2);
78
  }
79
 
 
80
  .card {
81
- background: rgba(255,255,255,0.2);
82
- border-radius: 20px;
83
- padding: 25px;
84
- backdrop-filter: blur(15px);
85
- border: 1px solid rgba(255,255,255,0.3);
86
- box-shadow: 0 8px 32px rgba(0,0,0,0.1);
87
  flex: 1;
88
  display: none;
89
  flex-direction: column;
@@ -94,50 +137,49 @@
94
  }
95
 
96
  .form-group {
97
- margin-bottom: 20px;
98
  }
99
 
100
  .label {
101
  display: block;
102
- margin-bottom: 8px;
103
- font-size: 0.9rem;
104
- opacity: 0.9;
105
- font-weight: 500;
106
  }
107
 
 
108
  .input {
109
  width: 100%;
110
  padding: 15px;
111
- border: none;
112
- border-radius: 12px;
113
- background: rgba(255,255,255,0.9);
114
- color: #333;
115
  font-size: 1rem;
116
- min-height: 100px;
 
117
  resize: vertical;
118
  outline: none;
119
- transition: all 0.3s ease;
 
120
  }
121
 
122
  .input:focus {
123
- background: white;
124
- transform: translateY(-2px);
125
- box-shadow: 0 6px 20px rgba(0,0,0,0.15);
126
- }
127
-
128
- .input::placeholder {
129
- color: #666;
130
  }
131
 
132
  .file-upload {
133
  position: relative;
134
- background: rgba(255,255,255,0.9);
135
- border: 2px dashed #667eea;
136
- border-radius: 12px;
137
  padding: 30px 20px;
138
  text-align: center;
139
  cursor: pointer;
140
- transition: all 0.3s ease;
141
  min-height: 140px;
142
  display: flex;
143
  flex-direction: column;
@@ -146,12 +188,15 @@
146
  }
147
 
148
  .file-upload.has-preview {
149
- padding: 5px;
150
  border-style: solid;
 
151
  }
152
 
153
- .file-upload:hover {
154
  background: white;
 
 
155
  transform: translateY(-2px);
156
  }
157
 
@@ -171,127 +216,131 @@
171
  .upload-icon {
172
  font-size: 2.5rem;
173
  margin-bottom: 10px;
174
- color: #667eea;
175
  }
176
 
177
  .upload-text {
178
- color: #333;
179
  font-size: 0.9rem;
180
  font-weight: 500;
181
  }
182
 
 
183
  .btn {
184
  width: 100%;
185
  padding: 16px;
186
  border: none;
187
- border-radius: 12px;
188
  font-size: 1.1rem;
189
- font-weight: 600;
190
  cursor: pointer;
191
  margin-top: auto;
192
- margin-bottom: 20px;
193
- transition: all 0.3s ease;
194
- position: relative;
195
- overflow: hidden;
196
- }
197
-
198
- .btn-generate, .btn-read {
199
- background: linear-gradient(45deg, #00d2d3, #54a0ff);
200
  color: white;
201
- box-shadow: 0 6px 20px rgba(84, 160, 255, 0.4);
202
  }
203
 
204
  .btn:hover:not(:disabled) {
205
- transform: translateY(-3px);
206
- box-shadow: 0 8px 25px rgba(0,0,0,0.3);
207
  }
208
 
209
  .btn:disabled {
210
- opacity: 0.6;
 
211
  cursor: not-allowed;
212
  transform: none;
 
213
  }
214
 
215
- .btn:active {
216
  transform: translateY(0);
217
  }
218
 
 
219
  .result {
220
  flex: 1;
221
- background: rgba(255,255,255,0.15);
222
- border-radius: 12px;
 
223
  padding: 20px;
224
  min-height: 150px;
225
  display: flex;
226
  align-items: center;
227
  justify-content: center;
228
  text-align: center;
229
- backdrop-filter: blur(5px);
230
- margin-bottom: 20px;
231
  }
232
 
233
  .result-empty {
234
- opacity: 0.7;
235
  font-style: italic;
236
  }
237
 
238
  .result img {
239
  max-width: 100%;
240
  height: auto;
241
- border-radius: 8px;
242
  background: white;
243
  padding: 10px;
244
- box-shadow: 0 4px 15px rgba(0,0,0,0.2);
245
  animation: zoomIn 0.4s ease;
 
246
  }
247
 
248
  .result-text-wrapper {
249
  width: 100%;
250
- background: rgba(255,255,255,0.2);
251
- border-radius: 8px;
252
- padding: 10px 15px;
 
253
  display: flex;
254
  align-items: center;
255
  justify-content: space-between;
256
  animation: slideUp 0.4s ease;
 
257
  }
258
  .result-text {
259
  flex-grow: 1;
260
  margin-left: 10px;
261
  word-break: break-all;
262
- line-height: 1.5;
263
  text-align: right;
 
 
264
  }
265
 
266
- /* ===== CHANGE HERE: Styles for the copy button and its animation ===== */
267
  .copy-btn {
268
- background: #fff;
269
- color: #54a0ff;
270
  border: none;
271
- border-radius: 6px;
272
- padding: 8px 12px;
273
- font-family: 'Vazirmatn', sans-serif;
274
  font-weight: 600;
275
  cursor: pointer;
276
- transition: all 0.3s ease; /* Smooth transition for all properties */
277
  flex-shrink: 0;
278
  font-size: 0.9rem;
 
279
  }
280
  .copy-btn:hover {
281
- background: #eef;
 
282
  }
283
  .copy-btn.copied {
284
- background-color: #28a745; /* Success green */
285
- color: white;
286
- transform: scale(1.05); /* Slightly enlarge */
287
  }
288
 
289
-
290
  .loader {
291
  width: 40px;
292
  height: 40px;
293
- border: 3px solid rgba(255,255,255,0.3);
294
- border-top: 3px solid white;
295
  border-radius: 50%;
296
  animation: spin 1s linear infinite;
297
  }
@@ -303,6 +352,7 @@
303
  .icon {
304
  display: inline-block;
305
  margin-left: 8px;
 
306
  }
307
  </style>
308
  </head>
@@ -310,13 +360,13 @@
310
  <div class="container">
311
  <div class="header">
312
  <div class="logo">📱</div>
313
- <h1 class="title">ساخت Qr Code آلفا </h1>
314
  <p class="subtitle">ساخت و خواندن آسان کیوآر کد</p>
315
  </div>
316
 
317
  <div class="tabs">
318
  <div class="tab active" data-tab="generate"><span class="icon">➕</span>ساخت</div>
319
- <div class="tab" data-tab="read"><span class="icon">👁</span>خواندن</div>
320
  </div>
321
 
322
  <div class="card tab-content active" id="generate-content">
@@ -328,14 +378,14 @@
328
  <div id="generate-loader" class="loader" style="display: none;"></div>
329
  <div id="qr-display"><span class="result-empty">QR کد شما اینجا ظاهر می‌شود</span></div>
330
  </div>
331
- <button id="generate-btn" class="btn btn-generate"><span class="icon">✨</span>ساخت QR کد</button>
332
  </div>
333
 
334
  <div class="card tab-content" id="read-content">
335
  <div class="form-group">
336
  <label class="label">تصویر QR کد را انتخاب کنید:</label>
337
  <div class="file-upload" onclick="document.getElementById('file-input').click()">
338
- <input type="file" id="file-input">
339
  <div class="upload-content">
340
  <div class="upload-icon">📷</div>
341
  <div class="upload-text">انتخاب تصویر</div>
@@ -346,11 +396,12 @@
346
  <div id="read-loader" class="loader" style="display: none;"></div>
347
  <div id="decoded-text"><span class="result-empty">نتیجه اینجا نمایش داده می‌شود</span></div>
348
  </div>
349
- <button id="read-btn" class="btn btn-read"><span class="icon">🔍</span>خواندن QR کد</button>
350
  </div>
351
  </div>
352
 
353
  <script>
 
354
  const SPACE_URL = "https://cultrix-qrcode-read-generate.hf.space";
355
 
356
  const tabs = document.querySelectorAll('.tab');
@@ -366,13 +417,12 @@
366
  const fileUpload = document.querySelector('.file-upload');
367
  const uploadContent = document.querySelector('.upload-content');
368
 
369
- // ===== CHANGE HERE: New copy function with animation =====
370
  function copyToClipboard(button, text) {
371
- if (button.classList.contains('copied')) return; // Prevent re-triggering animation
372
 
373
  navigator.clipboard.writeText(text).then(() => {
374
  const originalText = button.innerHTML;
375
- button.innerHTML = '✓';
376
  button.classList.add('copied');
377
  button.disabled = true;
378
 
@@ -380,7 +430,7 @@
380
  button.innerHTML = originalText;
381
  button.classList.remove('copied');
382
  button.disabled = false;
383
- }, 2000); // 2 seconds
384
  }).catch(err => {
385
  console.error('Failed to copy text: ', err);
386
  alert('کپی کردن با خطا مواجه شد.');
@@ -411,11 +461,8 @@
411
  }
412
  reader.readAsDataURL(file);
413
  } else {
414
- if(fileUpload.querySelector('img')) fileUpload.querySelector('img').remove();
415
- uploadContent.style.display = 'block';
416
- fileUpload.classList.remove('has-preview');
417
- uploadContent.querySelector('.upload-icon').textContent = '📄';
418
- uploadContent.querySelector('.upload-text').textContent = file.name;
419
  }
420
  } else {
421
  if(fileUpload.querySelector('img')) fileUpload.querySelector('img').remove();
@@ -428,7 +475,7 @@
428
 
429
  const generateSessionHash = () => Math.random().toString(36).substring(2, 15);
430
 
431
- const listenForData = (sessionHash, onResult, onError) => {
432
  const eventSource = new EventSource(`${SPACE_URL}/gradio_api/queue/data?session_hash=${sessionHash}`);
433
  eventSource.onmessage = (event) => {
434
  const data = JSON.parse(event.data);
@@ -436,14 +483,17 @@
436
  eventSource.close();
437
  if (data.output.error) onError(data.output.error);
438
  else onResult(data.output.data);
 
439
  } else if (data.msg === "process_failed") {
440
  eventSource.close();
441
  onError("پردازش با خطا مواجه شد.");
 
442
  }
443
  };
444
  eventSource.onerror = (err) => {
445
  eventSource.close();
446
  onError("خطا در ارتباط با سرور.");
 
447
  };
448
  };
449
 
@@ -463,12 +513,12 @@
463
  if (!res.ok) throw new Error('خطا در ارسال درخواست');
464
  listenForData(sessionHash,
465
  (result) => qrDisplay.innerHTML = (result && result[0]) ? result[0] : '<span class="result-empty">خطا در ساخت</span>',
466
- (error) => { alert(`خطا: ${error}`); qrDisplay.innerHTML = '<span class="result-empty">خطا</span>'; }
 
467
  );
468
  } catch (error) {
469
  alert(`خطا: ${error.message}`);
470
  qrDisplay.innerHTML = '<span class="result-empty">خطا در ساخت</span>';
471
- } finally {
472
  generateLoader.style.display = 'none';
473
  generateBtn.disabled = false;
474
  }
@@ -501,7 +551,6 @@
501
  listenForData(sessionHash,
502
  (result) => {
503
  const textToCopy = result[0];
504
- // ===== CHANGE HERE: onclick now passes `this` to the function =====
505
  const escapedText = textToCopy.replace(/'/g, "\\'").replace(/"/g, '&quot;');
506
  decodedText.innerHTML = `
507
  <div class="result-text-wrapper">
@@ -509,12 +558,12 @@
509
  <button class="copy-btn" onclick="copyToClipboard(this, '${escapedText}')">کپی</button>
510
  </div>`;
511
  },
512
- (error) => { alert(`خطا: ${error}`); decodedText.innerHTML = '<span class="result-empty">خطا در خواندن</span>'; }
 
513
  );
514
  } catch (error) {
515
  alert(`خطا: ${error.message}`);
516
  decodedText.innerHTML = '<span class="result-empty">خطا در پردازش</span>';
517
- } finally {
518
  readLoader.style.display = 'none';
519
  readBtn.disabled = false;
520
  }
@@ -524,16 +573,10 @@
524
  fileUpload.addEventListener(eventName, e => { e.preventDefault(); e.stopPropagation(); });
525
  });
526
  ['dragenter', 'dragover'].forEach(eventName => {
527
- fileUpload.addEventListener(eventName, () => {
528
- fileUpload.style.background = 'white';
529
- fileUpload.style.transform = 'translateY(-2px)';
530
- });
531
  });
532
  ['dragleave', 'drop'].forEach(eventName => {
533
- fileUpload.addEventListener(eventName, () => {
534
- fileUpload.style.background = 'rgba(255,255,255,0.9)';
535
- fileUpload.style.transform = 'translateY(0)';
536
- });
537
  });
538
  fileUpload.addEventListener('drop', e => {
539
  if (e.dataTransfer.files.length > 0) {
 
6
  <title>آلفا QR کد | ساخت و خواندن</title>
7
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@v33.003/Vazirmatn-font-face.css">
8
  <style>
9
+ /* ===== THEME VARIABLES FROM TTS APP ===== */
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
+ --accent-secondary-hover: #0DA986;
25
+ --accent-secondary-glow: rgba(15, 212, 168, 0.2);
26
+ --shadow-sm: 0 1px 2px 0 rgba(26, 32, 44, 0.03);
27
+ --shadow-md: 0 4px 6px -1px rgba(26, 32, 44, 0.05), 0 2px 4px -2px rgba(26, 32, 44, 0.04);
28
+ --shadow-lg: 0 10px 15px -3px rgba(26, 32, 44, 0.06), 0 4px 6px -4px rgba(26, 32, 44, 0.05);
29
+ --shadow-xl: 0 20px 25px -5px rgba(26, 32, 44, 0.07), 0 8px 10px -6px rgba(26, 32, 44, 0.05);
30
+ --radius-card: 24px;
31
+ --radius-btn: 14px;
32
+ --radius-input: 12px;
33
+ --transition-smooth: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
34
+ }
35
+
36
+ /* ===== BASE STYLES ===== */
37
  * {
38
  margin: 0;
39
  padding: 0;
 
41
  }
42
 
43
  body {
44
+ font-family: var(--app-font);
45
+ background-color: var(--app-bg);
46
+ color: var(--text-primary);
47
  min-height: 100vh;
 
48
  overflow-x: hidden;
49
+ display: flex;
50
+ justify-content: center;
51
+ align-items: flex-start;
52
+ padding: 2.5rem 1rem;
53
+ background-image: radial-gradient(var(--text-tertiary) 0.5px, transparent 0.5px);
54
+ background-size: 20px 20px;
55
+ background-position: -10px -10px;
56
  }
57
 
58
  .container {
59
+ max-width: 480px;
60
+ width: 100%;
61
  margin: 0 auto;
 
 
62
  display: flex;
63
  flex-direction: column;
64
  }
65
+
66
+ /* ===== HEADER ===== */
67
  .header {
68
  text-align: center;
69
+ padding: 1rem 0 2rem;
70
  }
71
 
72
  .logo {
73
  font-size: 3rem;
74
  margin-bottom: 10px;
75
+ filter: drop-shadow(0 4px 8px rgba(0,0,0,0.1));
76
  }
77
 
78
  .title {
79
+ font-size: 2.2rem;
80
+ font-weight: 900;
81
+ margin-bottom: 0.8rem;
82
+ background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
83
+ -webkit-background-clip: text;
84
+ -webkit-text-fill-color: transparent;
85
+ letter-spacing: -1px;
86
  }
87
 
88
  .subtitle {
89
+ font-size: 1rem;
90
+ color: var(--text-secondary);
91
+ opacity: 0.9;
92
  }
93
+
94
+ /* ===== TABS ===== */
95
  .tabs {
96
  display: flex;
97
+ background: var(--input-bg);
98
+ border-radius: var(--radius-btn);
99
+ padding: 6px;
100
  margin-bottom: 20px;
101
+ border: 1px solid var(--panel-border);
102
  }
103
 
104
  .tab {
 
107
  text-align: center;
108
  border-radius: 10px;
109
  cursor: pointer;
110
+ transition: var(--transition-smooth);
111
+ font-weight: 600;
112
  font-size: 0.9rem;
113
+ color: var(--text-secondary);
114
  }
115
 
116
  .tab.active {
117
+ background: var(--panel-bg);
118
+ color: var(--text-primary);
119
  transform: translateY(-2px);
120
+ box-shadow: var(--shadow-lg);
121
  }
122
 
123
+ /* ===== CARD ===== */
124
  .card {
125
+ background: var(--panel-bg);
126
+ border-radius: var(--radius-card);
127
+ padding: 30px;
128
+ border: 1px solid var(--panel-border);
129
+ box-shadow: var(--shadow-xl);
 
130
  flex: 1;
131
  display: none;
132
  flex-direction: column;
 
137
  }
138
 
139
  .form-group {
140
+ margin-bottom: 25px;
141
  }
142
 
143
  .label {
144
  display: block;
145
+ margin-bottom: 12px;
146
+ font-size: 1rem;
147
+ font-weight: 700;
148
+ color: var(--text-primary);
149
  }
150
 
151
+ /* ===== INPUTS & UPLOAD AREA ===== */
152
  .input {
153
  width: 100%;
154
  padding: 15px;
155
+ border: 1px solid var(--input-border);
156
+ border-radius: var(--radius-input);
157
+ background: var(--input-bg);
158
+ color: var(--text-primary);
159
  font-size: 1rem;
160
+ font-family: var(--app-font);
161
+ min-height: 120px;
162
  resize: vertical;
163
  outline: none;
164
+ transition: var(--transition-smooth);
165
+ box-shadow: var(--shadow-sm) inset;
166
  }
167
 
168
  .input:focus {
169
+ background: var(--panel-bg);
170
+ border-color: var(--accent-primary);
171
+ box-shadow: 0 0 0 3px var(--accent-primary-glow), var(--shadow-sm) inset;
 
 
 
 
172
  }
173
 
174
  .file-upload {
175
  position: relative;
176
+ background: var(--input-bg);
177
+ border: 2px dashed var(--input-border);
178
+ border-radius: var(--radius-input);
179
  padding: 30px 20px;
180
  text-align: center;
181
  cursor: pointer;
182
+ transition: var(--transition-smooth);
183
  min-height: 140px;
184
  display: flex;
185
  flex-direction: column;
 
188
  }
189
 
190
  .file-upload.has-preview {
191
+ padding: 10px;
192
  border-style: solid;
193
+ border-color: var(--accent-primary);
194
  }
195
 
196
+ .file-upload:hover, .file-upload.drag-over {
197
  background: white;
198
+ border-color: var(--accent-primary);
199
+ box-shadow: 0 0 15px var(--accent-primary-glow);
200
  transform: translateY(-2px);
201
  }
202
 
 
216
  .upload-icon {
217
  font-size: 2.5rem;
218
  margin-bottom: 10px;
219
+ color: var(--accent-primary);
220
  }
221
 
222
  .upload-text {
223
+ color: var(--text-secondary);
224
  font-size: 0.9rem;
225
  font-weight: 500;
226
  }
227
 
228
+ /* ===== BUTTONS ===== */
229
  .btn {
230
  width: 100%;
231
  padding: 16px;
232
  border: none;
233
+ border-radius: var(--radius-btn);
234
  font-size: 1.1rem;
235
+ font-weight: 700;
236
  cursor: pointer;
237
  margin-top: auto;
238
+ transition: var(--transition-smooth);
239
+ background: linear-gradient(95deg, var(--accent-secondary) 0%, var(--accent-primary) 100%);
 
 
 
 
 
 
240
  color: white;
241
+ box-shadow: 0 6px 12px -3px var(--accent-primary-glow), 0 6px 12px -3px var(--accent-secondary-glow);
242
  }
243
 
244
  .btn:hover:not(:disabled) {
245
+ transform: translateY(-5px) scale(1.02);
246
+ box-shadow: 0 8px 20px -4px var(--accent-primary-glow), 0 8px 20px -4px var(--accent-secondary-glow);
247
  }
248
 
249
  .btn:disabled {
250
+ background: var(--text-tertiary);
251
+ color: var(--text-secondary);
252
  cursor: not-allowed;
253
  transform: none;
254
+ box-shadow: none;
255
  }
256
 
257
+ .btn:active:not(:disabled) {
258
  transform: translateY(0);
259
  }
260
 
261
+ /* ===== RESULT AREA ===== */
262
  .result {
263
  flex: 1;
264
+ background: var(--input-bg);
265
+ border: 2px dashed var(--input-border);
266
+ border-radius: var(--radius-card);
267
  padding: 20px;
268
  min-height: 150px;
269
  display: flex;
270
  align-items: center;
271
  justify-content: center;
272
  text-align: center;
273
+ margin-bottom: 25px;
274
+ box-shadow: var(--shadow-sm) inset;
275
  }
276
 
277
  .result-empty {
278
+ color: var(--text-secondary);
279
  font-style: italic;
280
  }
281
 
282
  .result img {
283
  max-width: 100%;
284
  height: auto;
285
+ border-radius: var(--radius-input);
286
  background: white;
287
  padding: 10px;
288
+ box-shadow: var(--shadow-lg);
289
  animation: zoomIn 0.4s ease;
290
+ border: 1px solid var(--panel-border);
291
  }
292
 
293
  .result-text-wrapper {
294
  width: 100%;
295
+ background: var(--panel-bg);
296
+ border: 1px solid var(--panel-border);
297
+ border-radius: var(--radius-input);
298
+ padding: 12px 18px;
299
  display: flex;
300
  align-items: center;
301
  justify-content: space-between;
302
  animation: slideUp 0.4s ease;
303
+ box-shadow: var(--shadow-md);
304
  }
305
  .result-text {
306
  flex-grow: 1;
307
  margin-left: 10px;
308
  word-break: break-all;
309
+ line-height: 1.6;
310
  text-align: right;
311
+ color: var(--text-primary);
312
+ font-weight: 500;
313
  }
314
 
 
315
  .copy-btn {
316
+ background: var(--accent-primary);
317
+ color: white;
318
  border: none;
319
+ border-radius: 8px;
320
+ padding: 8px 14px;
321
+ font-family: var(--app-font);
322
  font-weight: 600;
323
  cursor: pointer;
324
+ transition: var(--transition-smooth);
325
  flex-shrink: 0;
326
  font-size: 0.9rem;
327
+ box-shadow: var(--shadow-sm);
328
  }
329
  .copy-btn:hover {
330
+ background: var(--accent-primary-hover);
331
+ transform: scale(1.05);
332
  }
333
  .copy-btn.copied {
334
+ background-color: var(--accent-secondary);
335
+ transform: scale(1.05);
 
336
  }
337
 
338
+ /* ===== LOADER & ANIMATIONS ===== */
339
  .loader {
340
  width: 40px;
341
  height: 40px;
342
+ border: 4px solid var(--input-border);
343
+ border-top: 4px solid var(--accent-primary);
344
  border-radius: 50%;
345
  animation: spin 1s linear infinite;
346
  }
 
352
  .icon {
353
  display: inline-block;
354
  margin-left: 8px;
355
+ vertical-align: middle;
356
  }
357
  </style>
358
  </head>
 
360
  <div class="container">
361
  <div class="header">
362
  <div class="logo">📱</div>
363
+ <h1 class="title">ساخت Qr Code آلفا</h1>
364
  <p class="subtitle">ساخت و خواندن آسان کیوآر کد</p>
365
  </div>
366
 
367
  <div class="tabs">
368
  <div class="tab active" data-tab="generate"><span class="icon">➕</span>ساخت</div>
369
+ <div class="tab" data-tab="read"><span class="icon">👁️</span>خواندن</div>
370
  </div>
371
 
372
  <div class="card tab-content active" id="generate-content">
 
378
  <div id="generate-loader" class="loader" style="display: none;"></div>
379
  <div id="qr-display"><span class="result-empty">QR کد شما اینجا ظاهر می‌شود</span></div>
380
  </div>
381
+ <button id="generate-btn" class="btn"><span class="icon">✨</span>ساخت QR کد</button>
382
  </div>
383
 
384
  <div class="card tab-content" id="read-content">
385
  <div class="form-group">
386
  <label class="label">تصویر QR کد را انتخاب کنید:</label>
387
  <div class="file-upload" onclick="document.getElementById('file-input').click()">
388
+ <input type="file" id="file-input" accept="image/*">
389
  <div class="upload-content">
390
  <div class="upload-icon">📷</div>
391
  <div class="upload-text">انتخاب تصویر</div>
 
396
  <div id="read-loader" class="loader" style="display: none;"></div>
397
  <div id="decoded-text"><span class="result-empty">نتیجه اینجا نمایش داده می‌شود</span></div>
398
  </div>
399
+ <button id="read-btn" class="btn"><span class="icon">🔍</span>خواندن QR کد</button>
400
  </div>
401
  </div>
402
 
403
  <script>
404
+ // JAVASCRIPT IS UNCHANGED
405
  const SPACE_URL = "https://cultrix-qrcode-read-generate.hf.space";
406
 
407
  const tabs = document.querySelectorAll('.tab');
 
417
  const fileUpload = document.querySelector('.file-upload');
418
  const uploadContent = document.querySelector('.upload-content');
419
 
 
420
  function copyToClipboard(button, text) {
421
+ if (button.classList.contains('copied')) return;
422
 
423
  navigator.clipboard.writeText(text).then(() => {
424
  const originalText = button.innerHTML;
425
+ button.innerHTML = '✓ کپی شد';
426
  button.classList.add('copied');
427
  button.disabled = true;
428
 
 
430
  button.innerHTML = originalText;
431
  button.classList.remove('copied');
432
  button.disabled = false;
433
+ }, 2000);
434
  }).catch(err => {
435
  console.error('Failed to copy text: ', err);
436
  alert('کپی کردن با خطا مواجه شد.');
 
461
  }
462
  reader.readAsDataURL(file);
463
  } else {
464
+ alert('لطفا یک فایل تصویری انتخاب کنید.');
465
+ fileInput.value = ''; // Reset input
 
 
 
466
  }
467
  } else {
468
  if(fileUpload.querySelector('img')) fileUpload.querySelector('img').remove();
 
475
 
476
  const generateSessionHash = () => Math.random().toString(36).substring(2, 15);
477
 
478
+ const listenForData = (sessionHash, onResult, onError, onFinally) => {
479
  const eventSource = new EventSource(`${SPACE_URL}/gradio_api/queue/data?session_hash=${sessionHash}`);
480
  eventSource.onmessage = (event) => {
481
  const data = JSON.parse(event.data);
 
483
  eventSource.close();
484
  if (data.output.error) onError(data.output.error);
485
  else onResult(data.output.data);
486
+ onFinally();
487
  } else if (data.msg === "process_failed") {
488
  eventSource.close();
489
  onError("پردازش با خطا مواجه شد.");
490
+ onFinally();
491
  }
492
  };
493
  eventSource.onerror = (err) => {
494
  eventSource.close();
495
  onError("خطا در ارتباط با سرور.");
496
+ onFinally();
497
  };
498
  };
499
 
 
513
  if (!res.ok) throw new Error('خطا در ارسال درخواست');
514
  listenForData(sessionHash,
515
  (result) => qrDisplay.innerHTML = (result && result[0]) ? result[0] : '<span class="result-empty">خطا در ساخت</span>',
516
+ (error) => { alert(`خطا: ${error}`); qrDisplay.innerHTML = '<span class="result-empty">خطا</span>'; },
517
+ () => { generateLoader.style.display = 'none'; generateBtn.disabled = false; }
518
  );
519
  } catch (error) {
520
  alert(`خطا: ${error.message}`);
521
  qrDisplay.innerHTML = '<span class="result-empty">خطا در ساخت</span>';
 
522
  generateLoader.style.display = 'none';
523
  generateBtn.disabled = false;
524
  }
 
551
  listenForData(sessionHash,
552
  (result) => {
553
  const textToCopy = result[0];
 
554
  const escapedText = textToCopy.replace(/'/g, "\\'").replace(/"/g, '&quot;');
555
  decodedText.innerHTML = `
556
  <div class="result-text-wrapper">
 
558
  <button class="copy-btn" onclick="copyToClipboard(this, '${escapedText}')">کپی</button>
559
  </div>`;
560
  },
561
+ (error) => { alert(`خطا: ${error}`); decodedText.innerHTML = '<span class="result-empty">خطا در خواندن</span>'; },
562
+ () => { readLoader.style.display = 'none'; readBtn.disabled = false; }
563
  );
564
  } catch (error) {
565
  alert(`خطا: ${error.message}`);
566
  decodedText.innerHTML = '<span class="result-empty">خطا در پردازش</span>';
 
567
  readLoader.style.display = 'none';
568
  readBtn.disabled = false;
569
  }
 
573
  fileUpload.addEventListener(eventName, e => { e.preventDefault(); e.stopPropagation(); });
574
  });
575
  ['dragenter', 'dragover'].forEach(eventName => {
576
+ fileUpload.addEventListener(eventName, () => fileUpload.classList.add('drag-over'));
 
 
 
577
  });
578
  ['dragleave', 'drop'].forEach(eventName => {
579
+ fileUpload.addEventListener(eventName, () => fileUpload.classList.remove('drag-over'));
 
 
 
580
  });
581
  fileUpload.addEventListener('drop', e => {
582
  if (e.dataTransfer.files.length > 0) {