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

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +623 -436
index.html CHANGED
@@ -1,454 +1,641 @@
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>
 
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>