QRcode / index.html
Elias207's picture
Update index.html
0fd2356 verified
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>آلفا QR کد | ساخت و خواندن</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@v33.003/Vazirmatn-font-face.css">
<style>
/* ===== THEME VARIABLES FROM TTS APP ===== */
:root {
--app-font: 'Vazirmatn', sans-serif;
--app-bg: #F8F9FC;
--panel-bg: #FFFFFF;
--panel-border: #EAEFF7;
--input-bg: #F6F8FB;
--input-border: #E1E7EF;
--text-primary: #1A202C;
--text-secondary: #626F86;
--text-tertiary: #8A94A6;
--accent-primary: #4A6CFA;
--accent-primary-hover: #3553D6;
--accent-primary-glow: rgba(74, 108, 250, 0.25);
--accent-secondary: #0FD4A8;
--accent-secondary-hover: #0DA986;
--accent-secondary-glow: rgba(15, 212, 168, 0.2);
--shadow-sm: 0 1px 2px 0 rgba(26, 32, 44, 0.03);
--shadow-md: 0 4px 6px -1px rgba(26, 32, 44, 0.05), 0 2px 4px -2px rgba(26, 32, 44, 0.04);
--shadow-lg: 0 10px 15px -3px rgba(26, 32, 44, 0.06), 0 4px 6px -4px rgba(26, 32, 44, 0.05);
--shadow-xl: 0 20px 25px -5px rgba(26, 32, 44, 0.07), 0 8px 10px -6px rgba(26, 32, 44, 0.05);
--radius-card: 24px;
--radius-btn: 14px;
--radius-input: 12px;
--transition-smooth: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
}
/* ===== BASE STYLES ===== */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--app-font);
background-color: var(--app-bg);
color: var(--text-primary);
min-height: 100vh;
overflow-x: hidden;
display: flex;
justify-content: center;
align-items: flex-start;
padding: 2.5rem 1rem;
background-image: radial-gradient(var(--text-tertiary) 0.5px, transparent 0.5px);
background-size: 20px 20px;
background-position: -10px -10px;
}
.container {
max-width: 480px;
width: 100%;
margin: 0 auto;
display: flex;
flex-direction: column;
}
/* ===== HEADER ===== */
.header {
text-align: center;
padding: 1rem 0 2rem;
}
.logo {
font-size: 3rem;
margin-bottom: 10px;
filter: drop-shadow(0 4px 8px rgba(0,0,0,0.1));
}
.title {
font-size: 2.2rem;
font-weight: 900;
margin-bottom: 0.8rem;
background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
letter-spacing: -1px;
}
.subtitle {
font-size: 1rem;
color: var(--text-secondary);
opacity: 0.9;
}
/* ===== TABS ===== */
.tabs {
display: flex;
background: var(--input-bg);
border-radius: var(--radius-btn);
padding: 6px;
margin-bottom: 20px;
border: 1px solid var(--panel-border);
}
.tab {
flex: 1;
padding: 12px;
text-align: center;
border-radius: 10px;
cursor: pointer;
transition: var(--transition-smooth);
font-weight: 600;
font-size: 0.9rem;
color: var(--text-secondary);
}
.tab.active {
background: var(--panel-bg);
color: var(--text-primary);
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
}
/* ===== CARD ===== */
.card {
background: var(--panel-bg);
border-radius: var(--radius-card);
padding: 30px;
border: 1px solid var(--panel-border);
box-shadow: var(--shadow-xl);
flex: 1;
display: none;
flex-direction: column;
}
.tab-content.active {
display: flex;
}
.form-group {
margin-bottom: 25px;
}
.label {
display: block;
margin-bottom: 12px;
font-size: 1rem;
font-weight: 700;
color: var(--text-primary);
}
/* ===== INPUTS & UPLOAD AREA ===== */
.input {
width: 100%;
padding: 15px;
border: 1px solid var(--input-border);
border-radius: var(--radius-input);
background: var(--input-bg);
color: var(--text-primary);
font-size: 1rem;
font-family: var(--app-font);
min-height: 120px;
resize: vertical;
outline: none;
transition: var(--transition-smooth);
box-shadow: var(--shadow-sm) inset;
}
.input:focus {
background: var(--panel-bg);
border-color: var(--accent-primary);
box-shadow: 0 0 0 3px var(--accent-primary-glow), var(--shadow-sm) inset;
}
.file-upload {
position: relative;
background: var(--input-bg);
border: 2px dashed var(--input-border);
border-radius: var(--radius-input);
padding: 30px 20px;
text-align: center;
cursor: pointer;
transition: var(--transition-smooth);
min-height: 140px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.file-upload.has-preview {
padding: 10px;
border-style: solid;
border-color: var(--accent-primary);
}
.file-upload:hover, .file-upload.drag-over {
background: white;
border-color: var(--accent-primary);
box-shadow: 0 0 15px var(--accent-primary-glow);
transform: translateY(-2px);
}
.file-upload input {
position: absolute;
left: -9999px;
}
.file-upload img {
max-width: 100%;
max-height: 120px;
height: auto;
border-radius: 8px;
object-fit: contain;
}
.upload-icon {
font-size: 2.5rem;
margin-bottom: 10px;
color: var(--accent-primary);
}
.upload-text {
color: var(--text-secondary);
font-size: 0.9rem;
font-weight: 500;
}
/* ===== BUTTONS ===== */
.btn {
width: 100%;
padding: 16px;
border: none;
border-radius: var(--radius-btn);
font-size: 1.1rem;
font-weight: 700;
cursor: pointer;
margin-top: auto;
transition: var(--transition-smooth);
background: linear-gradient(95deg, var(--accent-secondary) 0%, var(--accent-primary) 100%);
color: white;
box-shadow: 0 6px 12px -3px var(--accent-primary-glow), 0 6px 12px -3px var(--accent-secondary-glow);
}
.btn:hover:not(:disabled) {
transform: translateY(-5px) scale(1.02);
box-shadow: 0 8px 20px -4px var(--accent-primary-glow), 0 8px 20px -4px var(--accent-secondary-glow);
}
.btn:disabled {
background: var(--text-tertiary);
color: var(--text-secondary);
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.btn:active:not(:disabled) {
transform: translateY(0);
}
/* ===== RESULT AREA ===== */
.result {
flex: 1;
background: var(--input-bg);
border: 2px dashed var(--input-border);
border-radius: var(--radius-card);
padding: 20px;
min-height: 150px;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
margin-bottom: 25px;
box-shadow: var(--shadow-sm) inset;
}
.result-empty {
color: var(--text-secondary);
font-style: italic;
}
.result img {
max-width: 100%;
height: auto;
border-radius: var(--radius-input);
background: white;
padding: 10px;
box-shadow: var(--shadow-lg);
animation: zoomIn 0.4s ease;
border: 1px solid var(--panel-border);
}
.result-text-wrapper {
width: 100%;
background: var(--panel-bg);
border: 1px solid var(--panel-border);
border-radius: var(--radius-input);
padding: 12px 18px;
display: flex;
align-items: center;
justify-content: space-between;
animation: slideUp 0.4s ease;
box-shadow: var(--shadow-md);
}
.result-text {
flex-grow: 1;
margin-left: 10px;
word-break: break-all;
line-height: 1.6;
text-align: right;
color: var(--text-primary);
font-weight: 500;
}
.copy-btn {
background: var(--accent-primary);
color: white;
border: none;
border-radius: 8px;
padding: 8px 14px;
font-family: var(--app-font);
font-weight: 600;
cursor: pointer;
transition: var(--transition-smooth);
flex-shrink: 0;
font-size: 0.9rem;
box-shadow: var(--shadow-sm);
}
.copy-btn:hover {
background: var(--accent-primary-hover);
transform: scale(1.05);
}
.copy-btn.copied {
background-color: var(--accent-secondary);
transform: scale(1.05);
}
/* ===== LOADER & ANIMATIONS ===== */
.loader {
width: 40px;
height: 40px;
border: 4px solid var(--input-border);
border-top: 4px solid var(--accent-primary);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
@keyframes zoomIn { from { opacity: 0; transform: scale(0.8); } to { opacity: 1; transform: scale(1); } }
@keyframes slideUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
.icon {
display: inline-block;
margin-left: 8px;
vertical-align: middle;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="logo">📱</div>
<h1 class="title">ساخت Qr Code آلفا</h1>
<p class="subtitle">ساخت و خواندن آسان کیوآر کد</p>
</div>
<div class="tabs">
<div class="tab active" data-tab="generate"><span class="icon"></span>ساخت</div>
<div class="tab" data-tab="read"><span class="icon">👁️</span>خواندن</div>
</div>
<div class="card tab-content active" id="generate-content">
<div class="form-group">
<label class="label">متن یا لینک خود را بنویسید:</label>
<textarea id="text-input" class="input" placeholder="مثال: https://google.com یا سلام دنیا"></textarea>
</div>
<div class="result" id="qr-result">
<div id="generate-loader" class="loader" style="display: none;"></div>
<div id="qr-display"><span class="result-empty">QR کد شما اینجا ظاهر می‌شود</span></div>
</div>
<button id="generate-btn" class="btn"><span class="icon"></span>ساخت QR کد</button>
</div>
<div class="card tab-content" id="read-content">
<div class="form-group">
<label class="label">تصویر QR کد را انتخاب کنید:</label>
<div class="file-upload" onclick="document.getElementById('file-input').click()">
<input type="file" id="file-input" accept="image/*">
<div class="upload-content">
<div class="upload-icon">📷</div>
<div class="upload-text">انتخاب تصویر</div>
</div>
</div>
</div>
<div class="result" id="read-result">
<div id="read-loader" class="loader" style="display: none;"></div>
<div id="decoded-text"><span class="result-empty">نتیجه اینجا نمایش داده می‌شود</span></div>
</div>
<button id="read-btn" class="btn"><span class="icon">🔍</span>خواندن QR کد</button>
</div>
</div>
<script>
// JAVASCRIPT IS UNCHANGED
const SPACE_URL = "https://cultrix-qrcode-read-generate.hf.space";
const tabs = document.querySelectorAll('.tab');
const tabContents = document.querySelectorAll('.tab-content');
const generateBtn = document.getElementById('generate-btn');
const readBtn = document.getElementById('read-btn');
const textInput = document.getElementById('text-input');
const fileInput = document.getElementById('file-input');
const qrDisplay = document.getElementById('qr-display');
const decodedText = document.getElementById('decoded-text');
const generateLoader = document.getElementById('generate-loader');
const readLoader = document.getElementById('read-loader');
const fileUpload = document.querySelector('.file-upload');
const uploadContent = document.querySelector('.upload-content');
function copyToClipboard(button, text) {
if (button.classList.contains('copied')) return;
navigator.clipboard.writeText(text).then(() => {
const originalText = button.innerHTML;
button.innerHTML = '✓ کپی شد';
button.classList.add('copied');
button.disabled = true;
setTimeout(() => {
button.innerHTML = originalText;
button.classList.remove('copied');
button.disabled = false;
}, 2000);
}).catch(err => {
console.error('Failed to copy text: ', err);
alert('کپی کردن با خطا مواجه شد.');
});
}
tabs.forEach(tab => {
tab.addEventListener('click', () => {
tabs.forEach(t => t.classList.remove('active'));
tabContents.forEach(tc => tc.classList.remove('active'));
tab.classList.add('active');
document.getElementById(tab.getAttribute('data-tab') + '-content').classList.add('active');
});
});
fileInput.addEventListener('change', () => {
const file = fileInput.files[0];
if (file) {
if (file.type.startsWith('image/')) {
const reader = new FileReader();
reader.onload = function(e) {
const previewImage = document.createElement('img');
previewImage.src = e.target.result;
uploadContent.style.display = 'none';
if(fileUpload.querySelector('img')) fileUpload.querySelector('img').remove();
fileUpload.appendChild(previewImage);
fileUpload.classList.add('has-preview');
}
reader.readAsDataURL(file);
} else {
alert('لطفا یک فایل تصویری انتخاب کنید.');
fileInput.value = ''; // Reset input
}
} else {
if(fileUpload.querySelector('img')) fileUpload.querySelector('img').remove();
uploadContent.style.display = 'block';
uploadContent.querySelector('.upload-icon').textContent = '📷';
uploadContent.querySelector('.upload-text').textContent = 'انتخاب تصویر';
fileUpload.classList.remove('has-preview');
}
});
const generateSessionHash = () => Math.random().toString(36).substring(2, 15);
const listenForData = (sessionHash, onResult, onError, onFinally) => {
const eventSource = new EventSource(`${SPACE_URL}/gradio_api/queue/data?session_hash=${sessionHash}`);
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.msg === "process_completed") {
eventSource.close();
if (data.output.error) onError(data.output.error);
else onResult(data.output.data);
onFinally();
} else if (data.msg === "process_failed") {
eventSource.close();
onError("پردازش با خطا مواجه شد.");
onFinally();
}
};
eventSource.onerror = (err) => {
eventSource.close();
onError("خطا در ارتباط با سرور.");
onFinally();
};
};
generateBtn.addEventListener('click', async () => {
const text = textInput.value.trim();
if (!text) return alert("لطفاً متنی را وارد کنید.");
generateLoader.style.display = 'block';
qrDisplay.innerHTML = '';
generateBtn.disabled = true;
try {
const sessionHash = generateSessionHash();
const res = await fetch(`${SPACE_URL}/gradio_api/queue/join`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ fn_index: 0, data: [text], session_hash: sessionHash })
});
if (!res.ok) throw new Error('خطا در ارسال درخواست');
listenForData(sessionHash,
(result) => qrDisplay.innerHTML = (result && result[0]) ? result[0] : '<span class="result-empty">خطا در ساخت</span>',
(error) => { alert(`خطا: ${error}`); qrDisplay.innerHTML = '<span class="result-empty">خطا</span>'; },
() => { generateLoader.style.display = 'none'; generateBtn.disabled = false; }
);
} catch (error) {
alert(`خطا: ${error.message}`);
qrDisplay.innerHTML = '<span class="result-empty">خطا در ساخت</span>';
generateLoader.style.display = 'none';
generateBtn.disabled = false;
}
});
readBtn.addEventListener('click', async () => {
const file = fileInput.files[0];
if (!file) return alert("لطفاً یک تصویر انتخاب کنید.");
if (!file.type.startsWith('image/')) return alert("لطفاً یک فایل تصویری انتخاب کنید.");
readLoader.style.display = 'block';
decodedText.innerHTML = '';
readBtn.disabled = true;
try {
const formData = new FormData();
formData.append('files', file);
const uploadRes = await fetch(`${SPACE_URL}/gradio_api/upload`, { method: 'POST', body: formData });
if (!uploadRes.ok) throw new Error('خطا در آپلود فایل');
const [serverFilePath] = await uploadRes.json();
const fileData = { path: serverFilePath, url: `${SPACE_URL}/gradio_api/file=${serverFilePath}`, orig_name: file.name };
const sessionHash = generateSessionHash();
const joinRes = await fetch(`${SPACE_URL}/gradio_api/queue/join`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ fn_index: 1, data: [fileData], session_hash: sessionHash })
});
if (!joinRes.ok) throw new Error('خطا در پردازش');
listenForData(sessionHash,
(result) => {
const textToCopy = result[0];
const escapedText = textToCopy.replace(/'/g, "\\'").replace(/"/g, '&quot;');
decodedText.innerHTML = `
<div class="result-text-wrapper">
<span class="result-text">📝 ${textToCopy}</span>
<button class="copy-btn" onclick="copyToClipboard(this, '${escapedText}')">کپی</button>
</div>`;
},
(error) => { alert(`خطا: ${error}`); decodedText.innerHTML = '<span class="result-empty">خطا در خواندن</span>'; },
() => { readLoader.style.display = 'none'; readBtn.disabled = false; }
);
} catch (error) {
alert(`خطا: ${error.message}`);
decodedText.innerHTML = '<span class="result-empty">خطا در پردازش</span>';
readLoader.style.display = 'none';
readBtn.disabled = false;
}
});
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
fileUpload.addEventListener(eventName, e => { e.preventDefault(); e.stopPropagation(); });
});
['dragenter', 'dragover'].forEach(eventName => {
fileUpload.addEventListener(eventName, () => fileUpload.classList.add('drag-over'));
});
['dragleave', 'drop'].forEach(eventName => {
fileUpload.addEventListener(eventName, () => fileUpload.classList.remove('drag-over'));
});
fileUpload.addEventListener('drop', e => {
if (e.dataTransfer.files.length > 0) {
fileInput.files = e.dataTransfer.files;
fileInput.dispatchEvent(new Event('change'));
}
});
</script>
</body>
</html>