Elias207 commited on
Commit
fabd896
·
verified ·
1 Parent(s): 942c2eb

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +705 -547
index.html CHANGED
@@ -1,569 +1,727 @@
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
-
8
- <!-- فونت فارسی -->
9
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@v33.003/Vazirmatn-font-face.css" />
10
-
11
- <style>
12
- :root{
13
- --bg-1:#0b0f17;
14
- --bg-2:#0a0d14;
15
- --grid:#ffffff12;
16
- --text:#eaf2ff;
17
- --muted:#9aa7b7;
18
- --accent:#06b6d4; /* فیروزه‌ای */
19
- --accent-2:#7c3aed; /* بنفش */
20
- --card-bg:rgba(255,255,255,0.06);
21
- --card-stroke:rgba(255,255,255,0.12);
22
- --input-bg:rgba(255,255,255,0.08);
23
- --shadow:0 20px 70px rgba(0,0,0,.45);
24
- }
25
-
26
- *{box-sizing:border-box}
27
- html,body{height:100%}
28
- body{
29
- margin:0;
30
- font-family:"Vazirmatn", system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
31
- color:var(--text);
32
- background:
33
- radial-gradient(1000px 600px at 90% -10%, #3b82f633 0%, transparent 50%),
34
- radial-gradient(900px 500px at -10% 110%, #a855f733 0%, transparent 55%),
35
- linear-gradient(180deg, var(--bg-1), var(--bg-2));
36
- position:relative;
37
- -webkit-font-smoothing:antialiased;
38
- -moz-osx-font-smoothing:grayscale;
39
- }
40
-
41
- /* الگوی ظریف شبکه‌ای شبیه ماتریس QR */
42
- body::before{
43
- content:"";
44
- position:fixed;
45
- inset:0;
46
- pointer-events:none;
47
- background:
48
- repeating-linear-gradient(0deg, var(--grid) 0 1px, transparent 1px 22px),
49
- repeating-linear-gradient(90deg, var(--grid) 0 1px, transparent 1px 22px);
50
- mask-image: radial-gradient(80% 80% at 50% 50%, black 55%, transparent 100%);
51
- opacity:.5;
52
- }
53
-
54
- .shell{
55
- max-width:1200px;
56
- margin-inline:auto;
57
- padding:28px 18px 40px;
58
- }
59
-
60
- .hero{
61
- text-align:center;
62
- margin-bottom:16px;
63
- }
64
- .title{
65
- margin:0 0 8px 0;
66
- font-weight:800;
67
- font-size:clamp(22px, 5vw, 34px);
68
- letter-spacing:-.3px;
69
- background:linear-gradient(90deg, var(--accent), var(--accent-2));
70
- -webkit-background-clip:text;
71
- background-clip:text;
72
- color:transparent;
73
- filter:drop-shadow(0 6px 24px rgba(124,58,237,.25));
74
- }
75
- .subtitle{
76
- margin:0 auto;
77
- max-width:680px;
78
- color:var(--muted);
79
- font-weight:400;
80
- line-height:1.8;
81
- }
82
-
83
- /* ناوبری بین دو پنل (مخصوص موبایل) */
84
- .nav-pills{
85
- display:flex;
86
- justify-content:center;
87
- gap:10px;
88
- margin:18px auto 10px;
89
- padding:6px;
90
- width:fit-content;
91
- background:linear-gradient(180deg, rgba(255,255,255,0.08), rgba(255,255,255,0.05));
92
- border:1px solid var(--card-stroke);
93
- border-radius:999px;
94
- backdrop-filter:blur(10px) saturate(140%);
95
- -webkit-backdrop-filter:blur(10px) saturate(140%);
96
- box-shadow:0 10px 30px rgba(0,0,0,.25);
97
- }
98
- .pill{
99
- border:0;
100
- color:var(--text);
101
- background:transparent;
102
- padding:10px 16px;
103
- border-radius:999px;
104
- font-weight:700;
105
- cursor:pointer;
106
- transition:all .25s ease;
107
- font-size:.98rem;
108
- }
109
- .pill:hover{color:#fff; transform:translateY(-1px)}
110
- .pill.active{
111
- background:linear-gradient(90deg, var(--accent), var(--accent-2));
112
- box-shadow:0 6px 18px rgba(6,182,212,.35);
113
- }
114
-
115
- /* محفظه‌ی اسکرول افقی (موبایل) */
116
- .carousel{
117
- display:flex;
118
- overflow-x:auto;
119
- gap:16px;
120
- scroll-snap-type:x mandatory;
121
- scroll-behavior:smooth;
122
- padding:8px 10px;
123
- -webkit-overflow-scrolling:touch;
124
- border-radius:16px;
125
- }
126
- /* جهت اسکرول LTR تا رفتار اسکرول اینترویویو دقیق باشه */
127
- .carousel[dir="ltr"]{direction:ltr}
128
-
129
- /* کارت/پنل‌ها */
130
- .panel{
131
- position:relative;
132
- scroll-snap-align:start;
133
- min-width:calc(100vw - 40px); /* هر بار یک پنل کامل در موبایل */
134
- background:linear-gradient(180deg, rgba(255,255,255,0.10), rgba(255,255,255,0.06));
135
- border:1px solid var(--card-stroke);
136
- border-radius:18px;
137
- padding:22px 18px;
138
- box-shadow:var(--shadow);
139
- backdrop-filter:blur(12px) saturate(150%);
140
- -webkit-backdrop-filter:blur(12px) saturate(150%);
141
- }
142
-
143
- /* تزئین گوشه‌ها شبیه فایندر QR */
144
- .panel .qr-corner{
145
- position:absolute;
146
- width:34px; height:34px; border-radius:8px;
147
- border:3px solid var(--accent);
148
- opacity:.7; pointer-events:none;
149
- box-shadow:0 0 0 3px rgba(6,182,212,.12) inset, 0 0 24px rgba(6,182,212,.15);
150
- }
151
- .panel .qr-corner::after{
152
- content:""; position:absolute; inset:6px;
153
- border-radius:4px; border:3px solid var(--accent-2); opacity:.9;
154
- }
155
- .panel .qr-corner.tl{top:12px; right:12px}
156
- .panel .qr-corner.tr{top:12px; left:12px}
157
- .panel .qr-corner.bl{bottom:12px; right:12px}
158
- .panel .qr-corner.br{bottom:12px; left:12px}
159
-
160
- .panel h2{
161
- margin:4px 0 14px 0;
162
- font-weight:800;
163
- font-size:1.25rem;
164
- display:flex; align-items:center; gap:10px;
165
- color:#fff;
166
- }
167
- .hint{
168
- color:var(--muted);
169
- font-size:.88rem;
170
- margin:-4px 0 16px 0;
171
- }
172
-
173
- .group{display:flex; flex-direction:column; gap:8px; margin-bottom:14px}
174
- label{color:var(--muted); font-size:.92rem}
175
-
176
- textarea{
177
- width:100%;
178
- min-height:110px;
179
- resize:vertical;
180
- padding:12px 14px;
181
- color:var(--text);
182
- background:var(--input-bg);
183
- border:1px solid var(--card-stroke);
184
- border-radius:12px;
185
- outline:none;
186
- transition:border-color .25s, box-shadow .25s;
187
- }
188
- textarea::placeholder{color:#98a3b3}
189
- textarea:focus{
190
- border-color: color-mix(in oklab, var(--accent) 65%, #fff 15%);
191
- box-shadow:0 0 0 6px rgba(6,182,212,.15);
192
- }
193
-
194
- /* ورودی فایل شیک */
195
- .file-wrap{position:relative}
196
- input[type="file"]{display:none}
197
- .file-label{
198
- display:flex; align-items:center; justify-content:center; gap:10px;
199
- width:100%;
200
- padding:14px 16px;
201
- background:var(--input-bg);
202
- border:1px dashed color-mix(in oklab, var(--accent) 60%, #000 15%);
203
- color:var(--text);
204
- border-radius:12px;
205
- cursor:pointer;
206
- transition:all .25s ease;
207
- }
208
- .file-label:hover{
209
- border-color:var(--accent);
210
- box-shadow:0 0 0 6px rgba(124,58,237,.14);
211
- transform:translateY(-1px);
212
- }
213
- #file-name{
214
- min-height:1.2em; margin:8px 6px 0 0; font-size:.86rem; color:var(--muted);
215
- }
216
-
217
- .actions{
218
- display:flex; gap:10px; flex-wrap:wrap;
219
- }
220
- button{
221
- padding:12px 16px;
222
- border-radius:12px;
223
- border:0; cursor:pointer;
224
- color:#fff; font-weight:800; font-size:1rem;
225
- background:linear-gradient(90deg, var(--accent), var(--accent-2));
226
- box-shadow:0 10px 26px rgba(6,182,212,.25);
227
- transition:transform .2s ease, box-shadow .25s ease, opacity .25s ease;
228
- display:flex; align-items:center; gap:10px;
229
- }
230
- button:hover:not(:disabled){
231
- transform:translateY(-2px);
232
- box-shadow:0 14px 34px rgba(124,58,237,.28);
233
- }
234
- button:disabled{opacity:.6; cursor:not-allowed}
235
-
236
- .result{
237
- margin-top:12px;
238
- padding:18px;
239
- background:linear-gradient(180deg, rgba(20,20,28,.35), rgba(20,20,28,.15));
240
- border:1px solid var(--card-stroke);
241
- border-radius:14px;
242
- min-height:120px;
243
- display:grid;
244
- place-items:center;
245
- text-align:center;
246
- color:#eaf2ff;
247
- }
248
- #qr-code-display img{
249
- max-width:100%; height:auto; border-radius:10px;
250
- border:2px solid #fff; box-shadow:0 8px 30px rgba(0,0,0,.4);
251
- animation:fade .5s ease;
252
- }
253
- @keyframes fade{from{opacity:0; transform:scale(.96)} to{opacity:1; transform:scale(1)}}
254
-
255
- .loader{
256
- width:32px; height:32px; border-radius:50%;
257
- border:3px solid rgba(255,255,255,.15);
258
- border-top-color: color-mix(in oklab, var(--accent) 70%, #fff 10%);
259
- animation:spin 1s linear infinite; display:none;
260
- }
261
- @keyframes spin{to{transform:rotate(360deg)}}
262
-
263
- .muted{color:var(--muted)}
264
- .divider{
265
- height:1px; background:var(--card-stroke);
266
- margin:4px 0 12px 0; opacity:.7;
267
- }
268
-
269
- /* دسکتاپ: نمایش دو ستونه و بدون اسکرول افقی */
270
- @media (min-width: 980px){
271
- .carousel{
272
- overflow:visible;
273
- display:grid;
274
- grid-template-columns:1fr 1fr;
275
- gap:22px;
276
- padding-inline:0;
277
- }
278
- .panel{min-width:auto}
279
- .nav-pills{display:none}
280
- .hero{margin-bottom:18px}
281
- }
282
- </style>
283
- </head>
284
- <body>
285
- <div class="shell">
286
- <header class="hero">
287
- <h1 class="title">استودیوی QR</h1>
288
- <p class="subtitle">ساخت سریع QR Code از متن یا لینک، و خواندن متن از تصویر QR — طراحی مدرن، سبک و مینیمال با تمرکز روی تجربه موبایل</p>
289
- </header>
290
-
291
- <!-- ناوبری سریع بین پنل‌ها (روی موبایل) -->
292
- <div class="nav-pills" role="tablist" aria-label="جابه‌جایی بین پنل‌ها">
293
- <button class="pill active" data-target="panel-generate">ساخت</button>
294
- <button class="pill" data-target="panel-read">خواندن</button>
295
- </div>
296
 
297
- <!-- پنل‌ها: اسکرول افقی روی موبایل، دو ستونه روی دسکتاپ -->
298
- <div class="carousel" id="carousel" dir="ltr">
299
- <!-- پنل ساخت -->
300
- <section class="panel" id="panel-generate" dir="rtl">
301
- <span class="qr-corner tl"></span>
302
- <span class="qr-corner tr"></span>
303
- <span class="qr-corner bl"></span>
304
- <span class="qr-corner br"></span>
305
-
306
- <h2>
307
- <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor"
308
- stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
309
- <rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect>
310
- <rect x="14" y="14" width="7" height="7"></rect><path d="M3 14h7v7H3z"></path>
311
- </svg>
312
- ساخت QR Code
313
- </h2>
314
- <p class="hint">متن یا لینک خود را وارد کنید. برای مثال: https://example.com</p>
315
-
316
- <div class="group">
317
- <label for="text-input">متن یا لینک</label>
318
- <textarea id="text-input" rows="4" placeholder="مثلاً: https://google.com">سلام دنیا</textarea>
319
- </div>
320
 
321
- <div class="actions">
322
- <button id="generate-btn" type="button" title="ساخت QR Code">
323
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor"
324
- stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
325
- <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"/>
326
- </svg>
327
- ساخت QR
328
- </button>
329
- </div>
 
 
 
330
 
331
- <div class="result">
332
- <div id="generate-loader" class="loader"></div>
333
- <div id="qr-code-display"></div>
334
- </div>
335
- </section>
336
-
337
- <!-- پنل خواندن -->
338
- <section class="panel" id="panel-read" dir="rtl">
339
- <span class="qr-corner tl"></span>
340
- <span class="qr-corner tr"></span>
341
- <span class="qr-corner bl"></span>
342
- <span class="qr-corner br"></span>
343
-
344
- <h2>
345
- <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor"
346
- stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
347
- <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"/>
348
- </svg>
349
- خواندن QR Code
350
- </h2>
351
- <p class="hint">تصویر QR Code را انتخاب کنید تا متن داخل آن خوانده شود.</p>
352
-
353
- <div class="group">
354
- <label for="file-input">انتخاب تصویر QR</label>
355
- <div class="file-wrap">
356
- <input type="file" id="file-input" accept="image/*" />
357
- <label class="file-label" for="file-input">
358
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor"
359
- stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
360
- <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"/>
361
- </svg>
362
- لمس برای انتخاب فایل تصویر
363
- </label>
364
- </div>
365
- <p id="file-name"></p>
366
- </div>
367
 
368
- <div class="actions">
369
- <button id="read-btn" type="button" title="خواندن QR Code">
370
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor"
371
- stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
372
- <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"/>
373
- </svg>
374
- خواندن
375
- </button>
376
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
377
 
378
- <div class="result">
379
- <div id="read-loader" class="loader"></div>
380
- <p id="decoded-text" class="muted">نتیجه در اینجا نمایش داده می‌شود.</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
  </div>
382
- </section>
383
  </div>
384
- </div>
385
-
386
- <script>
387
- // Endpoint سرویس
388
- const SPACE_URL = "https://cultrix-qrcode-read-generate.hf.space";
389
-
390
- // المان‌ها
391
- const generateBtn = document.getElementById('generate-btn');
392
- const readBtn = document.getElementById('read-btn');
393
- const textInput = document.getElementById('text-input');
394
- const fileInput = document.getElementById('file-input');
395
- const qrCodeDisplay = document.getElementById('qr-code-display');
396
- const decodedText = document.getElementById('decoded-text');
397
- const generateLoader = document.getElementById('generate-loader');
398
- const readLoader = document.getElementById('read-loader');
399
- const fileNameDisplay = document.getElementById('file-name');
400
-
401
- // نمایش نام فایل انتخاب‌شده
402
- fileInput.addEventListener('change', () => {
403
- if (fileInput.files.length > 0) {
404
- fileNameDisplay.textContent = `فایل انتخاب شده: ${fileInput.files[0].name}`;
405
- } else {
406
- fileNameDisplay.textContent = '';
407
- }
408
- });
409
-
410
- // توابع کمکی Gradio Queue
411
- const generateSessionHash = () => Math.random().toString(36).substring(2, 15);
412
-
413
- const listenForData = (sessionHash, onResult, onError) => {
414
- const eventSource = new EventSource(`${SPACE_URL}/gradio_api/queue/data?session_hash=${sessionHash}`);
415
-
416
- eventSource.onmessage = (event) => {
417
- const data = JSON.parse(event.data);
418
- if (data.msg === "process_completed") {
419
- eventSource.close();
420
- if (data.output.error) {
421
- onError(data.output.error);
422
- } else {
423
- onResult(data.output.data);
424
- }
425
- } else if (data.msg === "process_starts") {
426
- // شروع پردازش
427
- console.log("پردازش شروع شد...");
428
- } else if (data.msg === "process_failed") {
429
- eventSource.close();
430
- onError("پردازش با خطا مواجه شد.");
431
- }
432
- };
433
-
434
- eventSource.onerror = (err) => {
435
- console.error("EventSource failed:", err);
436
- eventSource.close();
437
- onError("خطا در ارتباط با سرور.");
438
- };
439
- };
440
-
441
- // ساخت QR
442
- generateBtn.addEventListener('click', async () => {
443
- const text = textInput.value.trim();
444
- if (!text) return alert("لطفاً متنی را وارد کنید.");
445
-
446
- generateLoader.style.display = 'block';
447
- qrCodeDisplay.innerHTML = '';
448
- generateBtn.disabled = true;
449
-
450
- const sessionHash = generateSessionHash();
451
-
452
- try {
453
- const joinResponse = await fetch(`${SPACE_URL}/gradio_api/queue/join`, {
454
- method: 'POST',
455
- headers: { 'Content-Type': 'application/json' },
456
- body: JSON.stringify({ fn_index: 0, data: [text], session_hash: sessionHash })
457
  });
458
 
459
- if (!joinResponse.ok) throw new Error(`خطا در ارسال درخواست: ${joinResponse.statusText}`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
 
461
- listenForData(
462
- sessionHash,
463
- (result) => {
464
- if (result && result[0]) {
465
- qrCodeDisplay.innerHTML = result[0];
466
- } else {
467
- qrCodeDisplay.innerText = "خطا: خروجی معتبر دریافت نشد.";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
468
  }
469
- },
470
- (error) => {
471
- alert(`خطا در پردازش: ${error}`);
472
- qrCodeDisplay.innerText = "خطا در ساخت تصویر.";
473
- }
474
- );
475
- } catch (error) {
476
- alert(`یک خطا رخ داد: ${error.message}`);
477
- } finally {
478
- generateLoader.style.display = 'none';
479
- generateBtn.disabled = false;
480
- }
481
- });
482
-
483
- // خواندن QR
484
- readBtn.addEventListener('click', async () => {
485
- const file = fileInput.files[0];
486
- if (!file) return alert("لطفاً یک فایل تصویر انتخاب کنید.");
487
-
488
- readLoader.style.display = 'block';
489
- decodedText.innerText = '';
490
- readBtn.disabled = true;
491
-
492
- try {
493
- const formData = new FormData();
494
- formData.append('files', file);
495
-
496
- const uploadResponse = await fetch(`${SPACE_URL}/gradio_api/upload`, {
497
- method: 'POST',
498
- body: formData
499
  });
500
- if (!uploadResponse.ok) throw new Error(`خطا در آپلود فایل: ${uploadResponse.statusText}`);
501
 
502
- const uploadResult = await uploadResponse.json();
503
- const serverFilePath = uploadResult[0];
 
 
 
 
 
504
 
505
- const fileDataObject = {
506
- path: serverFilePath,
507
- url: `${SPACE_URL}/gradio_api/file=${serverFilePath}`,
508
- orig_name: file.name,
509
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
510
 
511
- const sessionHash = generateSessionHash();
512
- const joinResponse = await fetch(`${SPACE_URL}/gradio_api/queue/join`, {
513
- method: 'POST',
514
- headers: { 'Content-Type': 'application/json' },
515
- body: JSON.stringify({ fn_index: 1, data: [fileDataObject], session_hash: sessionHash })
516
  });
517
- if (!joinResponse.ok) throw new Error(`خطا در ارسال به صف: ${joinResponse.statusText}`);
518
-
519
- listenForData(
520
- sessionHash,
521
- (result) => {
522
- decodedText.innerText = `متن خوانده شده:\n${result[0]}`;
523
- },
524
- (error) => {
525
- alert(`خطا در پردازش: ${error}`);
526
- decodedText.innerText = "خطا در خواندن کد.";
527
- }
528
- );
529
- } catch (error) {
530
- alert(`یک خطا رخ داد: ${error.message}`);
531
- decodedText.innerText = "نتیجه در اینجا نمایش داده می‌شود.";
532
- } finally {
533
- readLoader.style.display = 'none';
534
- readBtn.disabled = false;
535
- }
536
- });
537
-
538
- // ناوبری بین پنل‌ها (موبایل): کلیک روی قرص‌ها => اسکرول به پنل هدف
539
- const carousel = document.getElementById('carousel');
540
- const pills = document.querySelectorAll('.pill');
541
- const panels = Array.from(carousel.querySelectorAll('.panel'));
542
-
543
- pills.forEach(btn=>{
544
- btn.addEventListener('click', ()=>{
545
- const el = document.getElementById(btn.dataset.target);
546
- if (el) el.scrollIntoView({behavior:'smooth', inline:'start', block:'nearest'});
547
- });
548
- });
549
-
550
- // به‌روزرسانی حالت Active بر اساس اسکرول
551
- const setActiveByScroll = ()=>{
552
- const bounds = panels.map(el => Math.abs(el.getBoundingClientRect().left - carousel.getBoundingClientRect().left));
553
- const nearestIndex = bounds.indexOf(Math.min(...bounds));
554
- pills.forEach((p,i)=>p.classList.toggle('active', i===nearestIndex));
555
- };
556
-
557
- let ticking = false;
558
- carousel.addEventListener('scroll', ()=>{
559
- if(!ticking){
560
- window.requestAnimationFrame(()=>{
561
- setActiveByScroll();
562
- ticking = false;
563
  });
564
- ticking = true;
565
- }
566
- });
567
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
568
  </body>
569
  </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>QR Code Studio | استودیو کیوآر کد</title>
7
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@v33.003/Vazirmatn-font-face.css">
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ :root {
11
+ --primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
12
+ --secondary-gradient: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
13
+ --success-gradient: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
14
+ --warning-gradient: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
15
+ --dark-bg: #0f0f23;
16
+ --card-bg: rgba(255, 255, 255, 0.05);
17
+ --glass-bg: rgba(255, 255, 255, 0.1);
18
+ --text-primary: #ffffff;
19
+ --text-secondary: #b8c5d6;
20
+ --accent-color: #64ffda;
21
+ --border-color: rgba(255, 255, 255, 0.15);
22
+ --shadow-light: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
23
+ --shadow-hover: 0 15px 35px rgba(31, 38, 135, 0.5);
24
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
+ * {
27
+ margin: 0;
28
+ padding: 0;
29
+ box-sizing: border-box;
30
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
+ body {
33
+ font-family: 'Vazirmatn', sans-serif;
34
+ background: var(--dark-bg);
35
+ background-image:
36
+ radial-gradient(circle at 20% 50%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
37
+ radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.3) 0%, transparent 50%),
38
+ radial-gradient(circle at 40% 80%, rgba(120, 219, 255, 0.3) 0%, transparent 50%);
39
+ color: var(--text-primary);
40
+ min-height: 100vh;
41
+ overflow-x: hidden;
42
+ position: relative;
43
+ }
44
 
45
+ /* خطوط پس‌زمینه برای جلوه QR */
46
+ body::before {
47
+ content: '';
48
+ position: fixed;
49
+ top: 0;
50
+ left: 0;
51
+ width: 100%;
52
+ height: 100%;
53
+ background-image:
54
+ linear-gradient(90deg, rgba(255,255,255,0.02) 1px, transparent 1px),
55
+ linear-gradient(rgba(255,255,255,0.02) 1px, transparent 1px);
56
+ background-size: 50px 50px;
57
+ pointer-events: none;
58
+ z-index: -1;
59
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
+ .container {
62
+ max-width: 1200px;
63
+ margin: 0 auto;
64
+ padding: 20px;
65
+ min-height: 100vh;
66
+ display: flex;
67
+ flex-direction: column;
68
+ }
69
+
70
+ /* هدر */
71
+ .header {
72
+ text-align: center;
73
+ margin-bottom: 3rem;
74
+ position: relative;
75
+ }
76
+
77
+ .main-title {
78
+ font-size: clamp(2rem, 5vw, 3.5rem);
79
+ font-weight: 700;
80
+ background: var(--primary-gradient);
81
+ background-clip: text;
82
+ -webkit-background-clip: text;
83
+ -webkit-text-fill-color: transparent;
84
+ margin-bottom: 0.5rem;
85
+ position: relative;
86
+ }
87
+
88
+ .subtitle {
89
+ font-size: 1.1rem;
90
+ color: var(--text-secondary);
91
+ font-weight: 300;
92
+ }
93
+
94
+ /* منطقه اصلی */
95
+ .main-content {
96
+ display: grid;
97
+ grid-template-columns: 1fr;
98
+ gap: 2rem;
99
+ flex: 1;
100
+ }
101
+
102
+ @media (min-width: 768px) {
103
+ .main-content {
104
+ grid-template-columns: 1fr 1fr;
105
+ gap: 3rem;
106
+ }
107
+ }
108
+
109
+ /* کارت‌ها */
110
+ .card {
111
+ background: var(--card-bg);
112
+ backdrop-filter: blur(20px);
113
+ border: 1px solid var(--border-color);
114
+ border-radius: 24px;
115
+ padding: 2rem;
116
+ position: relative;
117
+ overflow: hidden;
118
+ transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
119
+ box-shadow: var(--shadow-light);
120
+ }
121
+
122
+ .card::before {
123
+ content: '';
124
+ position: absolute;
125
+ top: 0;
126
+ left: 0;
127
+ right: 0;
128
+ height: 4px;
129
+ background: var(--primary-gradient);
130
+ transform: scaleX(0);
131
+ transition: transform 0.3s ease;
132
+ }
133
+
134
+ .card:hover {
135
+ transform: translateY(-8px);
136
+ box-shadow: var(--shadow-hover);
137
+ }
138
+
139
+ .card:hover::before {
140
+ transform: scaleX(1);
141
+ }
142
+
143
+ .card-header {
144
+ display: flex;
145
+ align-items: center;
146
+ gap: 1rem;
147
+ margin-bottom: 2rem;
148
+ }
149
+
150
+ .card-icon {
151
+ width: 50px;
152
+ height: 50px;
153
+ border-radius: 12px;
154
+ display: flex;
155
+ align-items: center;
156
+ justify-content: center;
157
+ font-size: 1.5rem;
158
+ color: white;
159
+ position: relative;
160
+ overflow: hidden;
161
+ }
162
+
163
+ .generate-card .card-icon {
164
+ background: var(--primary-gradient);
165
+ }
166
+
167
+ .read-card .card-icon {
168
+ background: var(--secondary-gradient);
169
+ }
170
+
171
+ .card-title {
172
+ font-size: 1.4rem;
173
+ font-weight: 600;
174
+ color: var(--text-primary);
175
+ }
176
+
177
+ /* فرم عناصر */
178
+ .form-group {
179
+ margin-bottom: 1.5rem;
180
+ }
181
+
182
+ .form-label {
183
+ display: block;
184
+ margin-bottom: 0.5rem;
185
+ color: var(--text-secondary);
186
+ font-size: 0.9rem;
187
+ font-weight: 500;
188
+ }
189
+
190
+ .form-input {
191
+ width: 100%;
192
+ padding: 1rem;
193
+ border: 2px solid var(--border-color);
194
+ border-radius: 12px;
195
+ background: var(--glass-bg);
196
+ color: var(--text-primary);
197
+ font-size: 1rem;
198
+ transition: all 0.3s ease;
199
+ backdrop-filter: blur(10px);
200
+ resize: vertical;
201
+ min-height: 120px;
202
+ }
203
+
204
+ .form-input::placeholder {
205
+ color: var(--text-secondary);
206
+ }
207
+
208
+ .form-input:focus {
209
+ outline: none;
210
+ border-color: var(--accent-color);
211
+ box-shadow: 0 0 0 3px rgba(100, 255, 218, 0.1);
212
+ transform: translateY(-2px);
213
+ }
214
+
215
+ /* فایل آپلود */
216
+ .file-upload {
217
+ position: relative;
218
+ overflow: hidden;
219
+ border: 2px dashed var(--border-color);
220
+ border-radius: 12px;
221
+ padding: 2rem;
222
+ text-align: center;
223
+ cursor: pointer;
224
+ transition: all 0.3s ease;
225
+ background: var(--glass-bg);
226
+ }
227
+
228
+ .file-upload:hover {
229
+ border-color: var(--accent-color);
230
+ background: rgba(100, 255, 218, 0.05);
231
+ }
232
+
233
+ .file-upload input {
234
+ position: absolute;
235
+ left: -9999px;
236
+ }
237
+
238
+ .file-upload-icon {
239
+ font-size: 2rem;
240
+ color: var(--accent-color);
241
+ margin-bottom: 1rem;
242
+ }
243
+
244
+ .file-upload-text {
245
+ color: var(--text-secondary);
246
+ font-size: 0.9rem;
247
+ }
248
+
249
+ .file-name {
250
+ margin-top: 1rem;
251
+ padding: 0.5rem 1rem;
252
+ background: rgba(100, 255, 218, 0.1);
253
+ border-radius: 8px;
254
+ color: var(--accent-color);
255
+ font-size: 0.85rem;
256
+ display: none;
257
+ }
258
+
259
+ /* دکمه‌ها */
260
+ .btn {
261
+ width: 100%;
262
+ padding: 1rem 2rem;
263
+ border: none;
264
+ border-radius: 12px;
265
+ font-size: 1rem;
266
+ font-weight: 600;
267
+ cursor: pointer;
268
+ position: relative;
269
+ overflow: hidden;
270
+ transition: all 0.3s ease;
271
+ display: flex;
272
+ align-items: center;
273
+ justify-content: center;
274
+ gap: 0.5rem;
275
+ color: white;
276
+ text-transform: none;
277
+ }
278
+
279
+ .btn-primary {
280
+ background: var(--primary-gradient);
281
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
282
+ }
283
+
284
+ .btn-secondary {
285
+ background: var(--secondary-gradient);
286
+ box-shadow: 0 4px 15px rgba(245, 87, 108, 0.4);
287
+ }
288
+
289
+ .btn:hover:not(:disabled) {
290
+ transform: translateY(-3px);
291
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
292
+ }
293
+
294
+ .btn:disabled {
295
+ opacity: 0.6;
296
+ cursor: not-allowed;
297
+ transform: none;
298
+ }
299
+
300
+ .btn::before {
301
+ content: '';
302
+ position: absolute;
303
+ top: 0;
304
+ left: -100%;
305
+ width: 100%;
306
+ height: 100%;
307
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
308
+ transition: left 0.5s;
309
+ }
310
+
311
+ .btn:hover::before {
312
+ left: 100%;
313
+ }
314
+
315
+ /* نتایج */
316
+ .result-box {
317
+ margin-top: 1.5rem;
318
+ padding: 2rem;
319
+ background: var(--glass-bg);
320
+ border: 1px solid var(--border-color);
321
+ border-radius: 16px;
322
+ min-height: 200px;
323
+ display: flex;
324
+ align-items: center;
325
+ justify-content: center;
326
+ text-align: center;
327
+ backdrop-filter: blur(10px);
328
+ position: relative;
329
+ overflow: hidden;
330
+ }
331
+
332
+ .result-empty {
333
+ color: var(--text-secondary);
334
+ font-style: italic;
335
+ }
336
+
337
+ #qr-code-display img {
338
+ max-width: 100%;
339
+ height: auto;
340
+ border-radius: 12px;
341
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
342
+ animation: zoomIn 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
343
+ }
344
+
345
+ .decoded-result {
346
+ background: rgba(100, 255, 218, 0.1);
347
+ border: 1px solid var(--accent-color);
348
+ border-radius: 12px;
349
+ padding: 1.5rem;
350
+ color: var(--text-primary);
351
+ word-break: break-all;
352
+ line-height: 1.6;
353
+ }
354
+
355
+ /* لودر */
356
+ .loader {
357
+ width: 50px;
358
+ height: 50px;
359
+ border: 3px solid var(--border-color);
360
+ border-top: 3px solid var(--accent-color);
361
+ border-radius: 50%;
362
+ animation: spin 1s linear infinite;
363
+ }
364
+
365
+ /* انیمیشن‌ها */
366
+ @keyframes spin {
367
+ 0% { transform: rotate(0deg); }
368
+ 100% { transform: rotate(360deg); }
369
+ }
370
+
371
+ @keyframes zoomIn {
372
+ from {
373
+ opacity: 0;
374
+ transform: scale(0.5);
375
+ }
376
+ to {
377
+ opacity: 1;
378
+ transform: scale(1);
379
+ }
380
+ }
381
+
382
+ @keyframes slideInUp {
383
+ from {
384
+ opacity: 0;
385
+ transform: translateY(30px);
386
+ }
387
+ to {
388
+ opacity: 1;
389
+ transform: translateY(0);
390
+ }
391
+ }
392
 
393
+ .card {
394
+ animation: slideInUp 0.6s ease-out forwards;
395
+ }
396
+
397
+ .generate-card {
398
+ animation-delay: 0.1s;
399
+ }
400
+
401
+ .read-card {
402
+ animation-delay: 0.2s;
403
+ }
404
+
405
+ /* ریسپانسیو */
406
+ @media (max-width: 767px) {
407
+ .container {
408
+ padding: 1rem;
409
+ }
410
+
411
+ .card {
412
+ padding: 1.5rem;
413
+ }
414
+
415
+ .main-content {
416
+ gap: 1.5rem;
417
+ }
418
+ }
419
+
420
+ /* اسکرول بار سفارشی */
421
+ ::-webkit-scrollbar {
422
+ width: 8px;
423
+ }
424
+
425
+ ::-webkit-scrollbar-track {
426
+ background: rgba(255, 255, 255, 0.1);
427
+ border-radius: 4px;
428
+ }
429
+
430
+ ::-webkit-scrollbar-thumb {
431
+ background: var(--accent-color);
432
+ border-radius: 4px;
433
+ }
434
+
435
+ ::-webkit-scrollbar-thumb:hover {
436
+ background: rgba(100, 255, 218, 0.8);
437
+ }
438
+ </style>
439
+ </head>
440
+ <body>
441
+ <div class="container">
442
+ <!-- هدر -->
443
+ <header class="header">
444
+ <h1 class="main-title">
445
+ <i class="fas fa-qrcode"></i>
446
+ QR Code Studio
447
+ </h1>
448
+ <p class="subtitle">ابزار کامل ساخت و خواندن کد QR</p>
449
+ </header>
450
+
451
+ <!-- محتوای اصلی -->
452
+ <div class="main-content">
453
+ <!-- بخش ساخت QR -->
454
+ <div class="card generate-card">
455
+ <div class="card-header">
456
+ <div class="card-icon">
457
+ <i class="fas fa-plus-circle"></i>
458
+ </div>
459
+ <h2 class="card-title">ساخت QR Code</h2>
460
+ </div>
461
+
462
+ <div class="form-group">
463
+ <label class="form-label" for="text-input">
464
+ <i class="fas fa-edit"></i>
465
+ متن یا لینک خود را وارد کنید
466
+ </label>
467
+ <textarea
468
+ id="text-input"
469
+ class="form-input"
470
+ placeholder="مثال: https://google.com یا هر متن دلخواه">سلام دنیا</textarea>
471
+ </div>
472
+
473
+ <button id="generate-btn" class="btn btn-primary">
474
+ <i class="fas fa-magic"></i>
475
+ <span>ساخت QR Code</span>
476
+ </button>
477
+
478
+ <div class="result-box">
479
+ <div id="generate-loader" class="loader" style="display: none;"></div>
480
+ <div id="qr-code-display">
481
+ <span class="result-empty">QR Code شما اینجا نمایش داده می‌شود</span>
482
+ </div>
483
+ </div>
484
+ </div>
485
+
486
+ <!-- بخش خواندن QR -->
487
+ <div class="card read-card">
488
+ <div class="card-header">
489
+ <div class="card-icon">
490
+ <i class="fas fa-search"></i>
491
+ </div>
492
+ <h2 class="card-title">خواندن QR Code</h2>
493
+ </div>
494
+
495
+ <div class="form-group">
496
+ <label class="form-label">
497
+ <i class="fas fa-image"></i>
498
+ تصویر QR Code را انتخاب کنید
499
+ </label>
500
+ <div class="file-upload" onclick="document.getElementById('file-input').click()">
501
+ <input type="file" id="file-input" accept="image/*">
502
+ <div class="file-upload-icon">
503
+ <i class="fas fa-cloud-upload-alt"></i>
504
+ </div>
505
+ <div class="file-upload-text">
506
+ کلیک کنید یا فایل را اینجا بکشید
507
+ </div>
508
+ <div id="file-name" class="file-name"></div>
509
+ </div>
510
+ </div>
511
+
512
+ <button id="read-btn" class="btn btn-secondary">
513
+ <i class="fas fa-eye"></i>
514
+ <span>خواندن QR Code</span>
515
+ </button>
516
+
517
+ <div class="result-box">
518
+ <div id="read-loader" class="loader" style="display: none;"></div>
519
+ <div id="decoded-text" class="result-empty">نتیجه خواندن اینجا نمایش داده می‌شود</div>
520
+ </div>
521
+ </div>
522
  </div>
 
523
  </div>
524
+
525
+ <script>
526
+ const SPACE_URL = "https://cultrix-qrcode-read-generate.hf.space";
527
+
528
+ // دریافت عناصر DOM
529
+ const generateBtn = document.getElementById('generate-btn');
530
+ const readBtn = document.getElementById('read-btn');
531
+ const textInput = document.getElementById('text-input');
532
+ const fileInput = document.getElementById('file-input');
533
+ const qrCodeDisplay = document.getElementById('qr-code-display');
534
+ const decodedText = document.getElementById('decoded-text');
535
+ const generateLoader = document.getElementById('generate-loader');
536
+ const readLoader = document.getElementById('read-loader');
537
+ const fileNameDisplay = document.getElementById('file-name');
538
+
539
+ // نمایش نام فایل انتخاب شده
540
+ fileInput.addEventListener('change', () => {
541
+ if (fileInput.files.length > 0) {
542
+ fileNameDisplay.textContent = `فایل انتخاب شده: ${fileInput.files[0].name}`;
543
+ fileNameDisplay.style.display = 'block';
544
+ } else {
545
+ fileNameDisplay.style.display = 'none';
546
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
547
  });
548
 
549
+ // توابع کمکی
550
+ const generateSessionHash = () => Math.random().toString(36).substring(2, 15);
551
+
552
+ const listenForData = (sessionHash, onResult, onError) => {
553
+ const eventSource = new EventSource(`${SPACE_URL}/gradio_api/queue/data?session_hash=${sessionHash}`);
554
+
555
+ eventSource.onmessage = (event) => {
556
+ const data = JSON.parse(event.data);
557
+ if (data.msg === "process_completed") {
558
+ eventSource.close();
559
+ if (data.output.error) {
560
+ onError(data.output.error);
561
+ } else {
562
+ onResult(data.output.data);
563
+ }
564
+ } else if (data.msg === "process_starts") {
565
+ console.log("پردازش شروع شد...");
566
+ } else if (data.msg === "process_failed") {
567
+ eventSource.close();
568
+ onError("پردازش با خطا مواجه شد.");
569
+ }
570
+ };
571
+
572
+ eventSource.onerror = (err) => {
573
+ console.error("EventSource failed:", err);
574
+ eventSource.close();
575
+ onError("خطا در ارتباط با سرور.");
576
+ };
577
+ };
578
 
579
+ // منطق ساخت QR Code
580
+ generateBtn.addEventListener('click', async () => {
581
+ const text = textInput.value.trim();
582
+ if (!text) {
583
+ alert("لطفاً متنی را وارد کنید.");
584
+ textInput.focus();
585
+ return;
586
+ }
587
+
588
+ generateLoader.style.display = 'block';
589
+ qrCodeDisplay.innerHTML = '';
590
+ generateBtn.disabled = true;
591
+
592
+ const sessionHash = generateSessionHash();
593
+
594
+ try {
595
+ const joinResponse = await fetch(`${SPACE_URL}/gradio_api/queue/join`, {
596
+ method: 'POST',
597
+ headers: { 'Content-Type': 'application/json' },
598
+ body: JSON.stringify({ fn_index: 0, data: [text], session_hash: sessionHash })
599
+ });
600
+
601
+ if (!joinResponse.ok) throw new Error(`خطا در ارسال درخواست: ${joinResponse.statusText}`);
602
+
603
+ listenForData(
604
+ sessionHash,
605
+ (result) => {
606
+ if (result && result[0]) {
607
+ qrCodeDisplay.innerHTML = result[0];
608
+ } else {
609
+ qrCodeDisplay.innerHTML = '<span class="result-empty">خطا: خروجی معتبر دریافت نشد.</span>';
610
+ }
611
+ },
612
+ (error) => {
613
+ alert(`خطا در پردازش: ${error}`);
614
+ qrCodeDisplay.innerHTML = '<span class="result-empty">خطا در ساخت تصویر.</span>';
615
+ }
616
+ );
617
+ } catch (error) {
618
+ alert(`یک خطا رخ داد: ${error.message}`);
619
+ qrCodeDisplay.innerHTML = '<span class="result-empty">خطا در ساخت QR Code</span>';
620
+ } finally {
621
+ generateLoader.style.display = 'none';
622
+ generateBtn.disabled = false;
623
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
624
  });
 
625
 
626
+ // منطق خواندن QR Code
627
+ readBtn.addEventListener('click', async () => {
628
+ const file = fileInput.files[0];
629
+ if (!file) {
630
+ alert("لطفاً یک فایل تصویر انتخاب کنید.");
631
+ return;
632
+ }
633
 
634
+ readLoader.style.display = 'block';
635
+ decodedText.innerHTML = '';
636
+ readBtn.disabled = true;
637
+
638
+ try {
639
+ const formData = new FormData();
640
+ formData.append('files', file);
641
+
642
+ const uploadResponse = await fetch(`${SPACE_URL}/gradio_api/upload`, {
643
+ method: 'POST',
644
+ body: formData
645
+ });
646
+ if (!uploadResponse.ok) throw new Error(`خطا در آپلود فایل: ${uploadResponse.statusText}`);
647
+
648
+ const uploadResult = await uploadResponse.json();
649
+ const serverFilePath = uploadResult[0];
650
+
651
+ const fileDataObject = {
652
+ path: serverFilePath,
653
+ url: `${SPACE_URL}/gradio_api/file=${serverFilePath}`,
654
+ orig_name: file.name,
655
+ };
656
+
657
+ const sessionHash = generateSessionHash();
658
+ const joinResponse = await fetch(`${SPACE_URL}/gradio_api/queue/join`, {
659
+ method: 'POST',
660
+ headers: { 'Content-Type': 'application/json' },
661
+ body: JSON.stringify({ fn_index: 1, data: [fileDataObject], session_hash: sessionHash })
662
+ });
663
+ if (!joinResponse.ok) throw new Error(`خطا در ارسال به صف: ${joinResponse.statusText}`);
664
+
665
+ listenForData(
666
+ sessionHash,
667
+ (result) => {
668
+ decodedText.innerHTML = `<div class="decoded-result"><strong>متن خوانده شده:</strong><br>${result[0]}</div>`;
669
+ },
670
+ (error) => {
671
+ alert(`خطا در پردازش: ${error}`);
672
+ decodedText.innerHTML = '<span class="result-empty">خطا در خواندن کد.</span>';
673
+ }
674
+ );
675
+ } catch (error) {
676
+ alert(`یک خطا رخ داد: ${error.message}`);
677
+ decodedText.innerHTML = '<span class="result-empty">خطا در پردازش فایل</span>';
678
+ } finally {
679
+ readLoader.style.display = 'none';
680
+ readBtn.disabled = false;
681
+ }
682
+ });
683
 
684
+ // افکت drag & drop برای فایل آپلود
685
+ const fileUpload = document.querySelector('.file-upload');
686
+
687
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
688
+ fileUpload.addEventListener(eventName, preventDefaults, false);
689
  });
690
+
691
+ function preventDefaults(e) {
692
+ e.preventDefault();
693
+ e.stopPropagation();
694
+ }
695
+
696
+ ['dragenter', 'dragover'].forEach(eventName => {
697
+ fileUpload.addEventListener(eventName, highlight, false);
698
+ });
699
+
700
+ ['dragleave', 'drop'].forEach(eventName => {
701
+ fileUpload.addEventListener(eventName, unhighlight, false);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
702
  });
703
+
704
+ function highlight(e) {
705
+ fileUpload.style.borderColor = 'var(--accent-color)';
706
+ fileUpload.style.background = 'rgba(100, 255, 218, 0.1)';
707
+ }
708
+
709
+ function unhighlight(e) {
710
+ fileUpload.style.borderColor = 'var(--border-color)';
711
+ fileUpload.style.background = 'var(--glass-bg)';
712
+ }
713
+
714
+ fileUpload.addEventListener('drop', handleDrop, false);
715
+
716
+ function handleDrop(e) {
717
+ const dt = e.dataTransfer;
718
+ const files = dt.files;
719
+
720
+ if (files.length > 0) {
721
+ fileInput.files = files;
722
+ fileInput.dispatchEvent(new Event('change'));
723
+ }
724
+ }
725
+ </script>
726
  </body>
727
  </html>