Elias207 commited on
Commit
d1ee207
·
verified ·
1 Parent(s): bf863df

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +436 -623
index.html CHANGED
@@ -1,641 +1,454 @@
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>ابزار مدرن QR Code</title>
7
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@v33.003/Vazirmatn-font-face.css">
8
- <style>
9
- :root {
10
- --primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
11
- --secondary-gradient: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
12
- --success-gradient: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
13
- --bg-pattern: linear-gradient(45deg, #1e3c72 0%, #2a5298 50%, #1e3c72 100%);
14
- --text-primary: #ffffff;
15
- --text-secondary: #e2e8f0;
16
- --card-bg: rgba(255, 255, 255, 0.1);
17
- --card-border: rgba(255, 255, 255, 0.2);
18
- --input-bg: rgba(255, 255, 255, 0.15);
19
- --shadow-light: 0 8px 32px rgba(31, 38, 135, 0.37);
20
- --shadow-heavy: 0 15px 35px rgba(31, 38, 135, 0.5);
21
- }
22
-
23
- * {
24
- margin: 0;
25
- padding: 0;
26
- box-sizing: border-box;
27
- }
28
-
29
- body {
30
- font-family: 'Vazirmatn', sans-serif;
31
- background: var(--bg-pattern);
32
- background-attachment: fixed;
33
- min-height: 100vh;
34
- color: var(--text-primary);
35
- overflow-x: hidden;
36
- }
37
-
38
- /* QR Pattern Background */
39
- body::before {
40
- content: '';
41
- position: fixed;
42
- top: 0;
43
- left: 0;
44
- width: 100%;
45
- height: 100%;
46
- background-image:
47
- radial-gradient(circle at 25% 25%, rgba(255,255,255,0.05) 2px, transparent 2px),
48
- radial-gradient(circle at 75% 75%, rgba(255,255,255,0.05) 2px, transparent 2px);
49
- background-size: 50px 50px;
50
- z-index: -1;
51
- animation: float 20s ease-in-out infinite;
52
- }
53
-
54
- @keyframes float {
55
- 0%, 100% { transform: translateY(0px) rotate(0deg); }
56
- 50% { transform: translateY(-20px) rotate(1deg); }
57
- }
58
-
59
- .header {
60
- text-align: center;
61
- padding: 40px 20px;
62
- background: rgba(255, 255, 255, 0.05);
63
- backdrop-filter: blur(20px);
64
- margin-bottom: 40px;
65
- }
66
-
67
- .main-title {
68
- font-size: clamp(2rem, 5vw, 3.5rem);
69
- font-weight: 300;
70
- margin-bottom: 10px;
71
- background: linear-gradient(135deg, #ffffff, #a8edea);
72
- -webkit-background-clip: text;
73
- -webkit-text-fill-color: transparent;
74
- background-clip: text;
75
- text-shadow: 0 2px 10px rgba(0,0,0,0.3);
76
- }
77
-
78
- .main-title span {
79
- font-weight: 700;
80
- background: var(--secondary-gradient);
81
- -webkit-background-clip: text;
82
- -webkit-text-fill-color: transparent;
83
- background-clip: text;
84
- }
85
-
86
- .subtitle {
87
- font-size: 1.1rem;
88
- color: var(--text-secondary);
89
- opacity: 0.9;
90
- }
91
-
92
- .main-container {
93
- display: grid;
94
- grid-template-columns: 1fr 1fr;
95
- gap: 30px;
96
- max-width: 1200px;
97
- margin: 0 auto;
98
- padding: 0 20px;
99
- }
100
-
101
- @media (max-width: 768px) {
102
- .main-container {
103
- grid-template-columns: 1fr;
104
- gap: 25px;
105
- }
106
- }
107
-
108
- .card {
109
- background: var(--card-bg);
110
- backdrop-filter: blur(20px);
111
- border: 1px solid var(--card-border);
112
- border-radius: 20px;
113
- padding: 30px;
114
- box-shadow: var(--shadow-light);
115
- transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
116
- position: relative;
117
- overflow: hidden;
118
- }
119
-
120
- .card::before {
121
- content: '';
122
- position: absolute;
123
- top: 0;
124
- left: 0;
125
- right: 0;
126
- height: 4px;
127
- background: var(--primary-gradient);
128
- transform: scaleX(0);
129
- transition: transform 0.3s ease;
130
- }
131
-
132
- .card:hover {
133
- transform: translateY(-10px);
134
- box-shadow: var(--shadow-heavy);
135
- }
136
-
137
- .card:hover::before {
138
- transform: scaleX(1);
139
- }
140
-
141
- .card-header {
142
- display: flex;
143
- align-items: center;
144
- gap: 15px;
145
- margin-bottom: 25px;
146
- padding-bottom: 15px;
147
- border-bottom: 1px solid rgba(255, 255, 255, 0.1);
148
- }
149
-
150
- .card-icon {
151
- width: 50px;
152
- height: 50px;
153
- background: var(--primary-gradient);
154
- border-radius: 12px;
155
- display: flex;
156
- align-items: center;
157
- justify-content: center;
158
- box-shadow: 0 4px 15px rgba(0,0,0,0.2);
159
- }
160
-
161
- .card-title {
162
- font-size: 1.4rem;
163
- font-weight: 600;
164
- color: var(--text-primary);
165
- }
166
-
167
- .form-group {
168
- margin-bottom: 20px;
169
- }
170
-
171
- .form-label {
172
- display: block;
173
- margin-bottom: 8px;
174
- font-size: 0.95rem;
175
- color: var(--text-secondary);
176
- font-weight: 500;
177
- }
178
-
179
- .form-input {
180
- width: 100%;
181
- padding: 15px;
182
- background: var(--input-bg);
183
- border: 2px solid transparent;
184
- border-radius: 12px;
185
- color: var(--text-primary);
186
- font-size: 1rem;
187
- transition: all 0.3s ease;
188
- backdrop-filter: blur(10px);
189
- }
190
-
191
- .form-input:focus {
192
- outline: none;
193
- border-color: #667eea;
194
- background: rgba(255, 255, 255, 0.2);
195
- box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
196
- }
197
-
198
- .form-input::placeholder {
199
- color: rgba(255, 255, 255, 0.6);
200
- }
201
-
202
- .file-input-wrapper {
203
- position: relative;
204
- cursor: pointer;
205
- }
206
-
207
- .file-input {
208
- display: none;
209
- }
210
-
211
- .file-input-label {
212
- display: flex;
213
- align-items: center;
214
- justify-content: center;
215
- gap: 12px;
216
- padding: 15px;
217
- background: var(--input-bg);
218
- border: 2px dashed rgba(255, 255, 255, 0.3);
219
- border-radius: 12px;
220
- transition: all 0.3s ease;
221
- min-height: 60px;
222
- }
223
-
224
- .file-input-label:hover {
225
- border-color: #667eea;
226
- background: rgba(255, 255, 255, 0.2);
227
- }
228
-
229
- .file-name {
230
- margin-top: 10px;
231
- padding: 8px 12px;
232
- background: rgba(255, 255, 255, 0.1);
233
- border-radius: 8px;
234
- font-size: 0.9rem;
235
- color: var(--text-secondary);
236
- text-align: center;
237
- min-height: 1.5em;
238
- }
239
-
240
- .btn {
241
- width: 100%;
242
- padding: 16px;
243
- background: var(--primary-gradient);
244
- border: none;
245
- border-radius: 12px;
246
- color: white;
247
- font-size: 1.1rem;
248
- font-weight: 600;
249
- cursor: pointer;
250
- transition: all 0.3s ease;
251
- display: flex;
252
- align-items: center;
253
- justify-content: center;
254
- gap: 10px;
255
- box-shadow: 0 4px 15px rgba(0,0,0,0.2);
256
- position: relative;
257
- overflow: hidden;
258
- }
259
-
260
- .btn::before {
261
- content: '';
262
- position: absolute;
263
- top: 0;
264
- left: -100%;
265
- width: 100%;
266
- height: 100%;
267
- background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
268
- transition: left 0.5s;
269
- }
270
-
271
- .btn:hover:not(:disabled) {
272
- transform: translateY(-2px);
273
- box-shadow: 0 6px 20px rgba(0,0,0,0.3);
274
- }
275
-
276
- .btn:hover:not(:disabled)::before {
277
- left: 100%;
278
- }
279
-
280
- .btn:disabled {
281
- background: rgba(255, 255, 255, 0.1);
282
- cursor: not-allowed;
283
- transform: none;
284
- }
285
-
286
- .btn-secondary {
287
- background: var(--success-gradient);
288
- }
289
-
290
- .result-container {
291
- margin-top: 20px;
292
- padding: 20px;
293
- background: var(--input-bg);
294
- border-radius: 12px;
295
- min-height: 120px;
296
- display: flex;
297
- align-items: center;
298
- justify-content: center;
299
- text-align: center;
300
- position: relative;
301
- backdrop-filter: blur(10px);
302
- }
303
-
304
- .result-container img {
305
- max-width: 100%;
306
- height: auto;
307
- border-radius: 8px;
308
- box-shadow: 0 4px 15px rgba(0,0,0,0.3);
309
- animation: zoomIn 0.5s ease-out;
310
- }
311
-
312
- .result-text {
313
- word-wrap: break-word;
314
- line-height: 1.6;
315
- color: var(--text-primary);
316
- }
317
-
318
- .loader {
319
- width: 40px;
320
- height: 40px;
321
- border: 4px solid rgba(255, 255, 255, 0.1);
322
- border-top: 4px solid #667eea;
323
- border-radius: 50%;
324
- animation: spin 1s linear infinite;
325
- }
326
-
327
- @keyframes spin {
328
- 0% { transform: rotate(0deg); }
329
- 100% { transform: rotate(360deg); }
330
- }
331
-
332
- @keyframes zoomIn {
333
- from {
334
- opacity: 0;
335
- transform: scale(0.5);
336
- }
337
- to {
338
- opacity: 1;
339
- transform: scale(1);
340
- }
341
- }
342
-
343
- .floating-elements {
344
- position: fixed;
345
- top: 0;
346
- left: 0;
347
- width: 100%;
348
- height: 100%;
349
- pointer-events: none;
350
- z-index: -1;
351
- }
352
-
353
- .floating-qr {
354
- position: absolute;
355
- width: 20px;
356
- height: 20px;
357
- background: rgba(255, 255, 255, 0.05);
358
- animation: float-qr 15s infinite linear;
359
- }
360
-
361
- .floating-qr:nth-child(1) { top: 20%; left: 10%; animation-delay: 0s; }
362
- .floating-qr:nth-child(2) { top: 80%; left: 90%; animation-delay: 5s; }
363
- .floating-qr:nth-child(3) { top: 40%; left: 80%; animation-delay: 10s; }
364
-
365
- @keyframes float-qr {
366
- 0% { transform: translateY(0) rotate(0deg); opacity: 0; }
367
- 50% { opacity: 1; }
368
- 100% { transform: translateY(-100vh) rotate(360deg); opacity: 0; }
369
- }
370
-
371
- /* Mobile optimizations */
372
- @media (max-width: 480px) {
373
- .header {
374
- padding: 30px 15px;
375
- }
376
-
377
- .card {
378
- padding: 20px;
379
- border-radius: 15px;
380
- }
381
-
382
- .main-container {
383
- padding: 0 15px;
384
- }
385
- }
386
- </style>
387
  </head>
388
  <body>
389
- <div class="floating-elements">
390
- <div class="floating-qr"></div>
391
- <div class="floating-qr"></div>
392
- <div class="floating-qr"></div>
393
- </div>
394
 
395
- <header class="header">
396
- <h1 class="main-title">ابزار مدرن <span>QR Code</span></h1>
397
- <p class="subtitle">ساخت و خواندن کیوآر کد به سادگی</p>
398
- </header>
399
-
400
- <div class="main-container">
401
- <!-- کارت ساخت QR Code -->
402
- <div class="card">
403
- <div class="card-header">
404
- <div class="card-icon">
405
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
406
- <rect x="3" y="3" width="7" height="7"/>
407
- <rect x="14" y="3" width="7" height="7"/>
408
- <rect x="14" y="14" width="7" height="7"/>
409
- <rect x="3" y="14" width="7" height="7"/>
410
- <rect x="5" y="5" width="3" height="3"/>
411
- <rect x="16" y="5" width="3" height="3"/>
412
- <rect x="16" y="16" width="3" height="3"/>
413
- <rect x="5" y="16" width="3" height="3"/>
414
- </svg>
415
- </div>
416
- <h2 class="card-title">ساخت QR Code</h2>
417
- </div>
418
-
419
- <div class="form-group">
420
- <label for="text-input" class="form-label">متن یا لینک خود را وارد کنید:</label>
421
- <textarea id="text-input" class="form-input" rows="4" placeholder="مثال: https://google.com یا متن دلخواه">سلام دنیا</textarea>
422
- </div>
423
-
424
- <button id="generate-btn" class="btn">
425
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
426
- <polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>
427
- </svg>
428
- <span>ساخت QR Code</span>
429
- </button>
430
-
431
- <div class="result-container">
432
- <div id="generate-loader" class="loader" style="display: none;"></div>
433
- <div id="qr-code-display"></div>
434
- </div>
435
- </div>
436
-
437
- <!-- کارت خواندن QR Code -->
438
- <div class="card">
439
- <div class="card-header">
440
- <div class="card-icon">
441
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
442
- <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
443
- <circle cx="12" cy="12" r="3"/>
444
- </svg>
445
- </div>
446
- <h2 class="card-title">خواندن QR Code</h2>
447
- </div>
448
-
449
- <div class="form-group">
450
- <label for="file-input" class="form-label">تصویر QR Code را انتخاب کنید:</label>
451
- <div class="file-input-wrapper">
452
- <input type="file" id="file-input" class="file-input" accept="image/*">
453
- <label for="file-input" class="file-input-label">
454
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
455
- <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
456
- <polyline points="17 8 12 3 7 8"/>
457
- <line x1="12" y1="3" x2="12" y2="15"/>
458
- </svg>
459
- <span>انتخاب فایل تصویر</span>
460
- </label>
461
- </div>
462
- <div id="file-name" class="file-name"></div>
463
- </div>
464
-
465
- <button id="read-btn" class="btn btn-secondary">
466
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
467
- <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
468
- <polyline points="14,2 14,8 20,8"/>
469
- <line x1="16" y1="13" x2="8" y2="13"/>
470
- <line x1="16" y1="17" x2="8" y2="17"/>
471
- <polyline points="10,9 9,9 8,9"/>
472
- </svg>
473
- <span>خواندن QR Code</span>
474
- </button>
475
-
476
- <div class="result-container">
477
- <div id="read-loader" class="loader" style="display: none;"></div>
478
- <div id="decoded-text" class="result-text">نتیجه در اینجا نمایش داده می‌شود</div>
479
- </div>
480
- </div>
481
  </div>
482
 
483
- <script>
484
- const SPACE_URL = "https://cultrix-qrcode-read-generate.hf.space";
485
-
486
- // دریافت عناصر DOM
487
- const generateBtn = document.getElementById('generate-btn');
488
- const readBtn = document.getElementById('read-btn');
489
- const textInput = document.getElementById('text-input');
490
- const fileInput = document.getElementById('file-input');
491
- const qrCodeDisplay = document.getElementById('qr-code-display');
492
- const decodedText = document.getElementById('decoded-text');
493
- const generateLoader = document.getElementById('generate-loader');
494
- const readLoader = document.getElementById('read-loader');
495
- const fileNameDisplay = document.getElementById('file-name');
496
-
497
- // نمایش نام فایل انتخاب شده
498
- fileInput.addEventListener('change', () => {
499
- if (fileInput.files.length > 0) {
500
- fileNameDisplay.textContent = `✓ ${fileInput.files[0].name}`;
501
- fileNameDisplay.style.color = '#4facfe';
502
- } else {
503
- fileNameDisplay.textContent = '';
504
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
505
  });
506
 
507
- // توابع کمکی
508
- const generateSessionHash = () => Math.random().toString(36).substring(2, 15);
509
-
510
- const listenForData = (sessionHash, onResult, onError) => {
511
- const eventSource = new EventSource(`${SPACE_URL}/gradio_api/queue/data?session_hash=${sessionHash}`);
512
-
513
- eventSource.onmessage = (event) => {
514
- const data = JSON.parse(event.data);
515
- if (data.msg === "process_completed") {
516
- eventSource.close();
517
- if (data.output.error) {
518
- onError(data.output.error);
519
- } else {
520
- onResult(data.output.data);
521
- }
522
- } else if (data.msg === "process_starts") {
523
- console.log("پردازش شروع شد...");
524
- } else if (data.msg === "process_failed") {
525
- eventSource.close();
526
- onError("پردازش با خطا مواجه شد.");
527
- }
528
- };
529
-
530
- eventSource.onerror = (err) => {
531
- console.error("EventSource failed:", err);
532
- eventSource.close();
533
- onError("خطا در ارتباط با سرور.");
534
- };
535
- };
536
 
537
- // منطق ساخت QR Code
538
- generateBtn.addEventListener('click', async () => {
539
- const text = textInput.value.trim();
540
- if (!text) {
541
- textInput.focus();
542
- textInput.style.borderColor = '#f5576c';
543
- setTimeout(() => textInput.style.borderColor = 'transparent', 2000);
544
- return;
545
- }
546
-
547
- generateLoader.style.display = 'block';
548
- qrCodeDisplay.innerHTML = '';
549
- generateBtn.disabled = true;
550
-
551
- const sessionHash = generateSessionHash();
552
-
553
- try {
554
- const joinResponse = await fetch(`${SPACE_URL}/gradio_api/queue/join`, {
555
- method: 'POST',
556
- headers: { 'Content-Type': 'application/json' },
557
- body: JSON.stringify({ fn_index: 0, data: [text], session_hash: sessionHash })
558
- });
559
-
560
- if (!joinResponse.ok) throw new Error(`خطا در ارسال درخواست: ${joinResponse.statusText}`);
561
-
562
- listenForData(
563
- sessionHash,
564
- (result) => {
565
- if (result && result[0]) {
566
- qrCodeDisplay.innerHTML = result[0];
567
- } else {
568
- qrCodeDisplay.innerHTML = '<p class="result-text">خطا: خروجی معتبر دریافت نشد.</p>';
569
- }
570
- },
571
- (error) => {
572
- qrCodeDisplay.innerHTML = `<p class="result-text">خطا در ساخت: ${error}</p>`;
573
- }
574
- );
575
- } catch (error) {
576
- qrCodeDisplay.innerHTML = `<p class="result-text">خطا: ${error.message}</p>`;
577
- } finally {
578
- generateLoader.style.display = 'none';
579
- generateBtn.disabled = false;
580
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
581
  });
 
582
 
583
- // منطق خواندن QR Code
584
- readBtn.addEventListener('click', async () => {
585
- const file = fileInput.files[0];
586
- if (!file) {
587
- fileNameDisplay.textContent = '⚠️ لطفاً یک فایل انتخاب کنید';
588
- fileNameDisplay.style.color = '#f5576c';
589
- return;
590
- }
591
 
592
- readLoader.style.display = 'block';
593
- decodedText.textContent = '';
594
- readBtn.disabled = true;
595
-
596
- try {
597
- const formData = new FormData();
598
- formData.append('files', file);
599
-
600
- const uploadResponse = await fetch(`${SPACE_URL}/gradio_api/upload`, {
601
- method: 'POST',
602
- body: formData
603
- });
604
- if (!uploadResponse.ok) throw new Error(`خطا در آپلود فایل: ${uploadResponse.statusText}`);
605
-
606
- const uploadResult = await uploadResponse.json();
607
- const serverFilePath = uploadResult[0];
608
-
609
- const fileDataObject = {
610
- path: serverFilePath,
611
- url: `${SPACE_URL}/gradio_api/file=${serverFilePath}`,
612
- orig_name: file.name,
613
- };
614
-
615
- const sessionHash = generateSessionHash();
616
- const joinResponse = await fetch(`${SPACE_URL}/gradio_api/queue/join`, {
617
- method: 'POST',
618
- headers: { 'Content-Type': 'application/json' },
619
- body: JSON.stringify({ fn_index: 1, data: [fileDataObject], session_hash: sessionHash })
620
- });
621
- if (!joinResponse.ok) throw new Error(`خطا در ارسال به صف: ${joinResponse.statusText}`);
622
-
623
- listenForData(
624
- sessionHash,
625
- (result) => {
626
- decodedText.textContent = `📄 متن خوانده شده:\n\n${result[0]}`;
627
- },
628
- (error) => {
629
- decodedText.textContent = `❌ خطا در خواندن: ${error}`;
630
- }
631
- );
632
- } catch (error) {
633
- decodedText.textContent = `❌ خطا: ${error.message}`;
634
- } finally {
635
- readLoader.style.display = 'none';
636
- readBtn.disabled = false;
637
- }
638
  });
639
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
640
  </body>
641
  </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" />
6
+ <title>استودیوی QR | ابزار مدرن QR Code</title>
7
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@v33.003/Vazirmatn-font-face.css">
8
+ <style>
9
+ :root{
10
+ --accent-1:#7f5af0;
11
+ --accent-2:#2cb6f7;
12
+ --bg-1:#0b1220;
13
+ --bg-2:#0e1729;
14
+ --text:#e9eef6;
15
+ --muted:#9aa5b1;
16
+ --glass:rgba(255,255,255,.06);
17
+ --border:rgba(255,255,255,.14);
18
+ --input:rgba(255,255,255,.08);
19
+ --shadow:0 10px 40px rgba(0,0,0,.35);
20
+ }
21
+
22
+ *{box-sizing:border-box}
23
+ html,body{margin:0;padding:0}
24
+ body{
25
+ font-family:'Vazirmatn',system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;
26
+ color:var(--text);
27
+ background:
28
+ radial-gradient(900px 900px at 8% 12%, #1a2d4a 0%, transparent 50%),
29
+ radial-gradient(700px 700px at 90% 85%, #2a1550 0%, transparent 50%),
30
+ linear-gradient(135deg,var(--bg-1),var(--bg-2));
31
+ min-height:100dvh;
32
+ display:flex;
33
+ flex-direction:column;
34
+ overflow:hidden;
35
+ }
36
+ /* الگوی گرید الهام‌گرفته از QR */
37
+ body::after{
38
+ content:"";
39
+ position:fixed; inset:0;
40
+ background-image:
41
+ linear-gradient(to right, rgba(255,255,255,.045) 1px, transparent 1px),
42
+ linear-gradient(to bottom, rgba(255,255,255,.045) 1px, transparent 1px);
43
+ background-size:24px 24px;
44
+ mix-blend-mode:overlay;
45
+ pointer-events:none;
46
+ opacity:.18;
47
+ }
48
+
49
+ header{
50
+ width:100%;
51
+ padding:18px 16px;
52
+ display:flex;
53
+ align-items:center;
54
+ justify-content:center;
55
+ }
56
+ .brand{
57
+ display:flex; align-items:center; gap:12px;
58
+ padding:10px 14px;
59
+ border-radius:14px;
60
+ background:linear-gradient(180deg,rgba(255,255,255,.06),rgba(255,255,255,.03)) padding-box,
61
+ linear-gradient(135deg, rgba(127,90,240,.7), rgba(44,182,247,.7)) border-box;
62
+ border:1px solid transparent;
63
+ box-shadow:var(--shadow);
64
+ backdrop-filter: blur(10px);
65
+ -webkit-backdrop-filter: blur(10px);
66
+ }
67
+ .logo{
68
+ width:28px;height:28px; position:relative;
69
+ background:conic-gradient(from 0deg, var(--accent-1), var(--accent-2));
70
+ border-radius:6px;
71
+ }
72
+ .logo::before,.logo::after{
73
+ content:""; position:absolute; background:#0b1220; border-radius:2px;
74
+ }
75
+ .logo::before{ inset:5px 5px 13px 5px; }
76
+ .logo::after{ width:8px; height:8px; right:6px; bottom:6px; }
77
+ .title{ font-weight:800; letter-spacing:.2px; }
78
+ .subtitle{ font-weight:400; color:var(--muted); font-size:.9rem }
79
+
80
+ main{
81
+ flex:1;
82
+ display:grid;
83
+ grid-template-columns: 1fr 16px 1fr; /* همواره دو ستون؛ حتی در موبایل */
84
+ align-items:stretch;
85
+ gap:0;
86
+ width:min(1200px, 100%);
87
+ margin:0 auto;
88
+ padding:12px;
89
+ height:calc(100dvh - 86px);
90
+ }
91
+
92
+ .divider{
93
+ position:relative;
94
+ border-radius:12px;
95
+ background: linear-gradient(180deg, var(--accent-1), var(--accent-2));
96
+ box-shadow: 0 0 30px rgba(127,90,240,.35), 0 0 40px rgba(44,182,247,.25) inset;
97
+ opacity:.9;
98
+ }
99
+ .divider::after{
100
+ content:"";
101
+ position:absolute; inset:3px;
102
+ border-radius:10px;
103
+ background:linear-gradient(180deg, rgba(255,255,255,.15), rgba(255,255,255,.06));
104
+ }
105
+ .divider .handle{
106
+ position:absolute; top:50%; left:50%;
107
+ transform:translate(-50%,-50%);
108
+ width:6px; height:40px; border-radius:999px;
109
+ background:rgba(0,0,0,.25);
110
+ box-shadow: inset 0 0 0 1px rgba(255,255,255,.25);
111
+ }
112
+
113
+ .container{
114
+ height:100%;
115
+ background:
116
+ linear-gradient(180deg, rgba(255,255,255,.06), rgba(255,255,255,.03)) padding-box,
117
+ linear-gradient(135deg, rgba(127,90,240,.6), rgba(44,182,247,.6)) border-box;
118
+ border:1px solid transparent;
119
+ border-radius:18px;
120
+ backdrop-filter: blur(10px);
121
+ -webkit-backdrop-filter: blur(10px);
122
+ box-shadow: var(--shadow);
123
+ padding:22px;
124
+ display:flex; flex-direction:column; gap:18px;
125
+ overflow:auto;
126
+ position:relative;
127
+ }
128
+ .container:hover{
129
+ box-shadow: 0 14px 50px rgba(0,0,0,.45);
130
+ }
131
+
132
+ .section-head{
133
+ display:flex; align-items:center; justify-content:space-between; gap:12px;
134
+ margin-bottom:6px;
135
+ padding-bottom:12px;
136
+ border-bottom:1px dashed rgba(255,255,255,.15);
137
+ }
138
+ h2{
139
+ margin:0; font-weight:800; letter-spacing:.2px; color:#fff;
140
+ display:flex; align-items:center; gap:10px;
141
+ }
142
+ .hint{
143
+ color:var(--muted); font-size:.9rem;
144
+ }
145
+
146
+ label{ font-size:.92rem; color:#cbd5e1; }
147
+
148
+ textarea, .file-label{
149
+ width:100%;
150
+ padding:12px 14px;
151
+ border-radius:12px;
152
+ border:1px solid rgba(255,255,255,.16);
153
+ background: var(--input);
154
+ color:var(--text);
155
+ font-size:1rem;
156
+ transition: border-color .25s ease, box-shadow .25s ease, transform .05s ease;
157
+ }
158
+ textarea{ resize:vertical; min-height:112px; }
159
+ textarea::placeholder{ color:#94a3b8; }
160
+ textarea:focus{ outline:none; border-color: var(--accent-2); box-shadow: 0 0 0 6px rgba(44,182,247,.12); }
161
+
162
+ .file-wrapper{ position:relative; cursor:pointer; }
163
+ input[type="file"]{ display:none; }
164
+ .file-label{
165
+ display:flex; align-items:center; justify-content:center; gap:10px;
166
+ cursor:pointer;
167
+ }
168
+ .file-label:hover{ border-color:var(--accent-2); box-shadow: 0 0 0 6px rgba(44,182,247,.10); }
169
+ #file-name{ margin:2px 2px 6px; font-size:.85rem; color:#cbd5e1; min-height:1.2em; text-align:center }
170
+
171
+ button{
172
+ padding:14px 18px;
173
+ border:0; border-radius:12px; cursor:pointer;
174
+ color:white; font-weight:800; letter-spacing:.3px; font-size:1rem;
175
+ background: linear-gradient(135deg, var(--accent-1), var(--accent-2));
176
+ box-shadow: 0 8px 26px rgba(127,90,240,.35), 0 10px 30px rgba(44,182,247,.25);
177
+ display:flex; align-items:center; justify-content:center; gap:10px;
178
+ transition: transform .15s ease, box-shadow .2s ease, filter .2s ease;
179
+ }
180
+ button:hover:not(:disabled){ transform: translateY(-2px); filter:brightness(1.05) }
181
+ button:active:not(:disabled){ transform: translateY(0) scale(.99) }
182
+ button:disabled{ opacity:.65; cursor:not-allowed; filter:grayscale(.3) }
183
+
184
+ .result-box{
185
+ margin-top:2px;
186
+ padding:16px;
187
+ border-radius:14px;
188
+ border:1px dashed rgba(255,255,255,.18);
189
+ background: linear-gradient(180deg, rgba(255,255,255,.05), rgba(255,255,255,.02));
190
+ min-height:120px;
191
+ display:flex; align-items:center; justify-content:center;
192
+ text-align:center; color:#e6edf5;
193
+ position:relative; overflow:hidden;
194
+ }
195
+ .result-box::before{
196
+ content:"";
197
+ position:absolute; inset:-1px;
198
+ border-radius:14px;
199
+ background: radial-gradient(600px 600px at 100% 0%, rgba(127,90,240,.08), transparent 60%),
200
+ radial-gradient(600px 600px at 0% 100%, rgba(44,182,247,.08), transparent 60%);
201
+ pointer-events:none;
202
+ }
203
+
204
+ #qr-code-display img{
205
+ max-width:100%; height:auto;
206
+ border-radius:12px;
207
+ border:2px solid rgba(255,255,255,.9);
208
+ box-shadow: 0 14px 40px rgba(0,0,0,.45);
209
+ animation: fadeIn .45s ease-in-out;
210
+ }
211
+
212
+ .loader{
213
+ width:36px; height:36px;
214
+ border:3px solid rgba(255,255,255,.22);
215
+ border-top-color: var(--accent-2);
216
+ border-right-color: var(--accent-1);
217
+ border-radius:50%;
218
+ animation: spin .8s linear infinite;
219
+ display:none;
220
+ }
221
+
222
+ @keyframes spin{ to{ transform:rotate(360deg) } }
223
+ @keyframes fadeIn{ from{ opacity:0; transform:scale(.95) } to{ opacity:1; transform:scale(1) } }
224
+
225
+ /* بهینه‌سازی برای ارتفاع‌های خیلی کم */
226
+ @media (max-height:650px){
227
+ main{ height:auto; }
228
+ .container{ padding:18px }
229
+ textarea{ min-height:96px }
230
+ }
231
+ </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
  </head>
233
  <body>
 
 
 
 
 
234
 
235
+ <header>
236
+ <div class="brand">
237
+ <div class="logo"></div>
238
+ <div>
239
+ <div class="title">ابزار مدرن QR Code</div>
240
+ <div class="subtitle">ساخت و خواندن فوری با چیدمان دو ستونه</div>
241
+ </div>
242
+ </div>
243
+ </header>
244
+
245
+ <main>
246
+ <!-- پنل ساخت -->
247
+ <section class="container" aria-label="ساخت QR">
248
+ <div class="section-head">
249
+ <h2>
250
+ <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3h8v8H3zM13 3h8v8h-8zM3 13h8v8H3zM17 17h4"/></svg>
251
+ ساخت QR Code
252
+ </h2>
253
+ <span class="hint">متن یا لینک را وارد کنید</span>
254
+ </div>
255
+
256
+ <label for="text-input">متن یا لینک:</label>
257
+ <textarea id="text-input" rows="4" placeholder="مثلا: https://yourdomain.com/landing">سلام دنیا</textarea>
258
+
259
+ <button id="generate-btn">
260
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2v0z"/><path d="M12 8l4 4-4 4"/><path d="M8 12h7"/></svg>
261
+ ساخت QR Code
262
+ </button>
263
+
264
+ <div class="result-box">
265
+ <div id="generate-loader" class="loader"></div>
266
+ <div id="qr-code-display"></div>
267
+ </div>
268
+ </section>
269
+
270
+ <!-- جداکننده -->
271
+ <div class="divider" aria-hidden="true">
272
+ <div class="handle"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  </div>
274
 
275
+ <!-- پنل خواندن -->
276
+ <section class="container" aria-label="خواندن QR">
277
+ <div class="section-head">
278
+ <h2>
279
+ <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
280
+ خواندن QR Code
281
+ </h2>
282
+ <span class="hint">تصویر QR خود را آپلود کنید</span>
283
+ </div>
284
+
285
+ <label for="file-input">انتخاب فایل تصویر:</label>
286
+ <div class="file-wrapper">
287
+ <input type="file" id="file-input" accept="image/*">
288
+ <label for="file-input" class="file-label">
289
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
290
+ انتخاب تصویر QR
291
+ </label>
292
+ </div>
293
+ <p id="file-name"></p>
294
+
295
+ <button id="read-btn">
296
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
297
+ خواندن QR Code
298
+ </button>
299
+
300
+ <div class="result-box">
301
+ <div id="read-loader" class="loader"></div>
302
+ <p id="decoded-text">نتیجه در اینجا نمایش داده می‌شود.</p>
303
+ </div>
304
+ </section>
305
+ </main>
306
+
307
+ <script>
308
+ // همان منطق قبلی – فقط استایل و چیدمان تغییر کرده است
309
+ const SPACE_URL = "https://cultrix-qrcode-read-generate.hf.space";
310
+
311
+ const generateBtn = document.getElementById('generate-btn');
312
+ const readBtn = document.getElementById('read-btn');
313
+ const textInput = document.getElementById('text-input');
314
+ const fileInput = document.getElementById('file-input');
315
+ const qrCodeDisplay = document.getElementById('qr-code-display');
316
+ const decodedText = document.getElementById('decoded-text');
317
+ const generateLoader = document.getElementById('generate-loader');
318
+ const readLoader = document.getElementById('read-loader');
319
+ const fileNameDisplay = document.getElementById('file-name');
320
+
321
+ fileInput.addEventListener('change', () => {
322
+ if (fileInput.files.length > 0) {
323
+ fileNameDisplay.textContent = `فایل انتخاب شده: ${fileInput.files[0].name}`;
324
+ } else {
325
+ fileNameDisplay.textContent = '';
326
+ }
327
+ });
328
+
329
+ const generateSessionHash = () => Math.random().toString(36).substring(2, 15);
330
+
331
+ const listenForData = (sessionHash, onResult, onError) => {
332
+ const eventSource = new EventSource(`${SPACE_URL}/gradio_api/queue/data?session_hash=${sessionHash}`);
333
+
334
+ eventSource.onmessage = (event) => {
335
+ const data = JSON.parse(event.data);
336
+ if (data.msg === "process_completed") {
337
+ eventSource.close();
338
+ if (data.output.error) {
339
+ onError(data.output.error);
340
+ } else {
341
+ onResult(data.output.data);
342
+ }
343
+ } else if (data.msg === "process_starts") {
344
+ console.log("پردازش شروع شد...");
345
+ } else if (data.msg === "process_failed") {
346
+ eventSource.close();
347
+ onError("پردازش با خطا مواجه شد.");
348
+ }
349
+ };
350
+
351
+ eventSource.onerror = (err) => {
352
+ console.error("EventSource failed:", err);
353
+ eventSource.close();
354
+ onError("خطا در ارتباط با سرور.");
355
+ };
356
+ };
357
+
358
+ generateBtn.addEventListener('click', async () => {
359
+ const text = textInput.value.trim();
360
+ if (!text) return alert("لطفاً متنی را وارد کنید.");
361
+
362
+ generateLoader.style.display = 'block';
363
+ qrCodeDisplay.innerHTML = '';
364
+ generateBtn.disabled = true;
365
+
366
+ const sessionHash = generateSessionHash();
367
+
368
+ try {
369
+ const joinResponse = await fetch(`${SPACE_URL}/gradio_api/queue/join`, {
370
+ method: 'POST',
371
+ headers: { 'Content-Type': 'application/json' },
372
+ body: JSON.stringify({ fn_index: 0, data: [text], session_hash: sessionHash })
373
  });
374
 
375
+ if (!joinResponse.ok) throw new Error(`خطا در ارسال درخواست: ${joinResponse.statusText}`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
376
 
377
+ listenForData(
378
+ sessionHash,
379
+ (result) => {
380
+ if (result && result[0]) {
381
+ qrCodeDisplay.innerHTML = result[0];
382
+ } else {
383
+ qrCodeDisplay.innerText = "خطا: خروجی معتبر دریافت نشد.";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
384
  }
385
+ },
386
+ (error) => {
387
+ alert(`خطا در پردازش: ${error}`);
388
+ qrCodeDisplay.innerText = "خطا در ساخت تصویر.";
389
+ }
390
+ );
391
+ } catch (error) {
392
+ alert(`یک خطا رخ داد: ${error.message}`);
393
+ } finally {
394
+ generateLoader.style.display = 'none';
395
+ generateBtn.disabled = false;
396
+ }
397
+ });
398
+
399
+ readBtn.addEventListener('click', async () => {
400
+ const file = fileInput.files[0];
401
+ if (!file) return alert("لطفاً یک فایل تصویر انتخاب کنید.");
402
+
403
+ readLoader.style.display = 'block';
404
+ decodedText.innerText = '';
405
+ readBtn.disabled = true;
406
+
407
+ try {
408
+ const formData = new FormData();
409
+ formData.append('files', file);
410
+
411
+ const uploadResponse = await fetch(`${SPACE_URL}/gradio_api/upload`, {
412
+ method: 'POST',
413
+ body: formData
414
  });
415
+ if (!uploadResponse.ok) throw new Error(`خطا در آپلود فایل: ${uploadResponse.statusText}`);
416
 
417
+ const uploadResult = await uploadResponse.json();
418
+ const serverFilePath = uploadResult[0];
 
 
 
 
 
 
419
 
420
+ const fileDataObject = {
421
+ path: serverFilePath,
422
+ url: `${SPACE_URL}/gradio_api/file=${serverFilePath}`,
423
+ orig_name: file.name,
424
+ };
425
+
426
+ const sessionHash = generateSessionHash();
427
+ const joinResponse = await fetch(`${SPACE_URL}/gradio_api/queue/join`, {
428
+ method: 'POST',
429
+ headers: { 'Content-Type': 'application/json' },
430
+ body: JSON.stringify({ fn_index: 1, data: [fileDataObject], session_hash: sessionHash })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
431
  });
432
+ if (!joinResponse.ok) throw new Error(`خطا در ارسال به صف: ${joinResponse.statusText}`);
433
+
434
+ listenForData(
435
+ sessionHash,
436
+ (result) => {
437
+ decodedText.innerText = `متن خوانده شده:\n${result[0]}`;
438
+ },
439
+ (error) => {
440
+ alert(`خطا در پردازش: ${error}`);
441
+ decodedText.innerText = "خطا در خواندن کد.";
442
+ }
443
+ );
444
+ } catch (error) {
445
+ alert(`یک خطا رخ داد: ${error.message}`);
446
+ decodedText.innerText = "نتیجه در اینجا نمایش داده می‌شود.";
447
+ } finally {
448
+ readLoader.style.display = 'none';
449
+ readBtn.disabled = false;
450
+ }
451
+ });
452
+ </script>
453
  </body>
454
  </html>