Update Saqel Platform: 2026-08-28 18:21:58
This commit is contained in:
@@ -32,7 +32,9 @@ class CurriculumStudio
|
||||
:root {
|
||||
--bg-base: #0B0F19;
|
||||
--bg-card: rgba(22, 27, 34, 0.85);
|
||||
--bg-hover: rgba(255, 255, 255, 0.05);
|
||||
--border: rgba(255, 255, 255, 0.08);
|
||||
--border-color: rgba(255, 255, 255, 0.12);
|
||||
--accent-cyan: #00F5D4;
|
||||
--accent-gold: #FFD166;
|
||||
--accent-purple: #7B2CBF;
|
||||
@@ -110,6 +112,12 @@ class CurriculumStudio
|
||||
font-size: 11.5px; font-family: monospace; color: var(--accent-cyan); margin-bottom: 14px;
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { transform: scale(1); opacity: 1; }
|
||||
50% { transform: scale(1.1); opacity: 0.7; }
|
||||
100% { transform: scale(1); opacity: 1; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -121,7 +129,10 @@ class CurriculumStudio
|
||||
<span style="font-weight: 800; font-size: 18px; color: #FFF;">صَقِل Enterprise</span>
|
||||
<span style="font-size: 11px; font-weight: 700; color: var(--accent-gold); background: rgba(255,209,102,0.12); padding: 2px 10px; border-radius: 980px;">استوديو تفريغ وفهرسة المناهج الحية 📚</span>
|
||||
</div>
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<div style="display: flex; gap: 10px; align-items: center;">
|
||||
<button type="button" onclick="openDirectLogViewer()" style="font-size: 12px; color: var(--accent-gold); background: rgba(255,209,102,0.1); border: 1px solid rgba(255,209,102,0.3); padding: 5px 14px; border-radius: 980px; cursor: pointer; font-weight: 700;">
|
||||
🔍 سجل العمليات والسيرفر (Logs)
|
||||
</button>
|
||||
<a href="/teacher" style="font-size: 12px; color: var(--accent-cyan); text-decoration: none; border: 1px solid rgba(0,245,212,0.3); padding: 5px 14px; border-radius: 980px;">استوديو المعلم 👨🏫</a>
|
||||
<a href="/student" style="font-size: 12px; color: var(--text-muted); text-decoration: none; border: 1px solid var(--border); padding: 5px 14px; border-radius: 980px;">بوابة الطالب 🎓</a>
|
||||
</div>
|
||||
@@ -134,7 +145,7 @@ class CurriculumStudio
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 16px;">
|
||||
<div>
|
||||
<h2 style="font-size: 20px; font-weight: 900; color: #FFF; margin-bottom: 4px;">رفع وتفريغ كتب المناهج الوزارية (PDF ⟵ Markdown Tree) 📑</h2>
|
||||
<p style="font-size: 12.5px; color: var(--text-secondary);" id="upload_status_desc">اختر ملف كتاب المنهاج (PDF) ليقوم الخادم بتفريغه وفهرسة وحداته ودروسه الحقيقية على القرص وتوليد ملفات المارك داون.</p>
|
||||
<p style="font-size: 12.5px; color: var(--text-secondary);" id="upload_status_desc">اختر ملف كتاب المنهاج (PDF) ليقوم الخادم والذكاء الاصطناعي بتفريغ الوحدات والدروس والمصادر كملفات Markdown حية.</p>
|
||||
</div>
|
||||
<div style="display: flex; gap: 10px; align-items: center;">
|
||||
<input type="file" id="curriculum_pdf_input" accept=".pdf" style="display: none;" onchange="handleRealPdfUpload(this)">
|
||||
@@ -192,14 +203,46 @@ class CurriculumStudio
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upload Progress & Live Terminal Modal -->
|
||||
<div id="upload_progress_overlay" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(10, 15, 26, 0.9); backdrop-filter: blur(10px); z-index: 9999; flex-direction: column; align-items: center; justify-content: center;">
|
||||
<div style="background: #16203D; border: 1px solid rgba(255,255,255,0.15); border-radius: 20px; width: 550px; max-width: 92%; padding: 28px; text-align: center; box-shadow: 0 20px 50px rgba(0,0,0,0.7);">
|
||||
<div id="ai_spinner" style="font-size: 44px; margin-bottom: 12px; animation: pulse 1.5s infinite;">🧠</div>
|
||||
<h3 style="color: #FFF; font-size: 18px; font-weight: 800; margin-bottom: 6px;">معالجة واستخراج الذكاء الاصطناعي</h3>
|
||||
<p id="upload_progress_text" style="color: var(--accent-cyan); font-size: 13px; font-weight: 700; margin-bottom: 14px;">جاري التهيئة...</p>
|
||||
|
||||
<div style="width: 100%; background: rgba(0,0,0,0.5); border-radius: 980px; height: 12px; margin-bottom: 8px; overflow: hidden; border: 1px solid rgba(255,255,255,0.1);">
|
||||
<div id="upload_progress_bar" style="height: 100%; width: 5%; background: linear-gradient(90deg, #00F5D4, #FFD166); transition: width 0.4s ease;"></div>
|
||||
</div>
|
||||
<span id="upload_progress_percent" style="color: var(--accent-gold); font-size: 12px; font-weight: 800; font-family: monospace;">5%</span>
|
||||
|
||||
<div style="margin-top: 20px; display: flex; justify-content: center; gap: 10px;">
|
||||
<button type="button" id="btn_toggle_logs" onclick="toggleUploadLogs()" style="background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.15); color: #FFF; padding: 6px 16px; border-radius: 980px; font-size: 12px; cursor: pointer; font-weight: 700;">
|
||||
🔍 تفاصيل السجل الحي (Terminal Logs)
|
||||
</button>
|
||||
<button type="button" id="btn_close_overlay" onclick="closeProgressOverlay()" style="display: none; background: rgba(239,68,68,0.15); border: 1px solid #EF4444; color: #F87171; padding: 6px 16px; border-radius: 980px; font-size: 12px; cursor: pointer; font-weight: 700;">
|
||||
إغلاق النافذة ✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Terminal Output Box -->
|
||||
<div id="upload_logs_container" style="display: none; margin-top: 18px; width: 100%; text-align: left;">
|
||||
<div style="background: #0D1117; border: 1px solid #30363D; border-radius: 10px; padding: 12px; font-family: monospace; font-size: 11.5px; color: #39D353; height: 160px; overflow-y: auto; direction: ltr;" id="upload_logs_textarea">
|
||||
[System] Initializing background task worker...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentFilePath = '';
|
||||
let currentUploadTaskId = null;
|
||||
let logsPollingInterval = null;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
renderCurriculumTree(window.CURRICULUM_TREE);
|
||||
});
|
||||
|
||||
function renderCurriculumTree(tree) {
|
||||
function renderCurriculumTree(tree) {
|
||||
const container = document.getElementById('curriculum_tree_container');
|
||||
const entries = Object.entries(tree || {});
|
||||
|
||||
@@ -240,20 +283,18 @@ class CurriculumStudio
|
||||
}
|
||||
}
|
||||
|
||||
// NEW: Render Resources (Minhaji Style Categories)
|
||||
// Minhaji Style Categories
|
||||
if (sem.resources && Object.keys(sem.resources).length > 0) {
|
||||
html += `<div style="margin-top: 15px; margin-bottom: 5px; font-weight: bold; color: var(--text-secondary); font-size: 11px; padding-right: 15px;">📂 مصادر المادة الإضافية:</div>`;
|
||||
html += `<div style="display: flex; flex-wrap: wrap; gap: 6px; padding-right: 15px; margin-bottom: 10px;">`;
|
||||
for (const [resKey, resourceGroup] of Object.entries(sem.resources)) {
|
||||
if (!resourceGroup.items || resourceGroup.items.length === 0) continue;
|
||||
|
||||
// Hexagon / Badge Style
|
||||
html += `<div style="flex: 1 1 45%; background: var(--bg-hover); border: 1px solid var(--border-color); border-radius: 6px; padding: 8px; text-align: center; cursor: pointer; transition: all 0.2s;" onmouseover="this.style.background='rgba(255,209,102,0.1)'" onmouseout="this.style.background='var(--bg-hover)'">
|
||||
<div style="font-size: 14px; margin-bottom: 4px;">📌</div>
|
||||
<div style="font-size: 10px; font-weight: bold; color: var(--text-primary);">${escapeHtml(resourceGroup.name)}</div>
|
||||
</div>`;
|
||||
|
||||
// Render actual files below the badge (hidden or just list them)
|
||||
for (const item of resourceGroup.items) {
|
||||
const activeClass = (item.file === currentFilePath) ? ' active' : '';
|
||||
html += `
|
||||
@@ -273,7 +314,6 @@ class CurriculumStudio
|
||||
|
||||
container.innerHTML = html;
|
||||
|
||||
// Auto-select first lesson if none selected
|
||||
if (!currentFilePath) {
|
||||
const firstGrade = Object.values(tree)[0];
|
||||
const firstSub = firstGrade?.subjects ? Object.values(firstGrade.subjects)[0] : null;
|
||||
@@ -285,6 +325,7 @@ class CurriculumStudio
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function selectLesson(filePath, title, breadcrumb, outcomes) {
|
||||
currentFilePath = filePath;
|
||||
document.getElementById('empty_selection_view').style.display = 'none';
|
||||
@@ -294,7 +335,6 @@ class CurriculumStudio
|
||||
document.getElementById('current_lesson_title_display').textContent = title;
|
||||
document.getElementById('server_file_path_display').textContent = `backend/storage/curriculum/${filePath}`;
|
||||
|
||||
// Render Outcomes
|
||||
const tags = document.getElementById('current_outcomes_tags');
|
||||
tags.innerHTML = (outcomes || []).map(o => `
|
||||
<span style="font-size: 11px; font-weight: 700; background: rgba(255,209,102,0.12); color: var(--accent-gold); border: 1px solid rgba(255,209,102,0.3); padding: 3px 10px; border-radius: 980px;">
|
||||
@@ -302,11 +342,11 @@ class CurriculumStudio
|
||||
</span>
|
||||
`).join('');
|
||||
|
||||
// Highlight Active in Sidebar
|
||||
document.querySelectorAll('.tree-lesson').forEach(el => el.classList.remove('active'));
|
||||
event?.currentTarget?.classList.add('active');
|
||||
if (window.event?.currentTarget) {
|
||||
window.event.currentTarget.classList.add('active');
|
||||
}
|
||||
|
||||
// Fetch Real Markdown from Server
|
||||
try {
|
||||
const res = await fetch(`/api/curriculum/lesson?file=${encodeURIComponent(filePath)}`);
|
||||
const data = await res.json();
|
||||
@@ -321,15 +361,24 @@ class CurriculumStudio
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================
|
||||
// Live PDF Upload & Real-Time AI Extraction Tracking
|
||||
// ==========================================================
|
||||
async function handleRealPdfUpload(input) {
|
||||
if (!input.files || !input.files[0]) return;
|
||||
const file = input.files[0];
|
||||
|
||||
const btn = document.getElementById('btn_upload_pdf');
|
||||
const desc = document.getElementById('upload_status_desc');
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span>⏳ جارٍ فك تشفير وتفريغ الكتاب...</span>';
|
||||
desc.textContent = `جارٍ تفريغ ملف [${file.name}] إلى شجرة ملفات Markdown حقيقية على السيرفر...`;
|
||||
|
||||
// Show Progress Modal Overlay
|
||||
const overlay = document.getElementById('upload_progress_overlay');
|
||||
overlay.style.display = 'flex';
|
||||
document.getElementById('btn_close_overlay').style.display = 'none';
|
||||
document.getElementById('upload_progress_bar').style.width = '8%';
|
||||
document.getElementById('upload_progress_text').textContent = `جاري رفع [${file.name}] إلى الخادم...`;
|
||||
document.getElementById('upload_progress_percent').textContent = '8%';
|
||||
document.getElementById('upload_logs_textarea').innerHTML = `[System] Uploading ${escapeHtml(file.name)} (${(file.size/1024/1024).toFixed(2)} MB)...<br>`;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('pdf_file', file);
|
||||
@@ -340,35 +389,128 @@ class CurriculumStudio
|
||||
body: formData
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.ok && data.status === 'success') {
|
||||
window.CURRICULUM_TREE = data.tree;
|
||||
currentFilePath = data.active_file || '';
|
||||
renderCurriculumTree(data.tree);
|
||||
|
||||
if (data.active_file) {
|
||||
document.getElementById('empty_selection_view').style.display = 'none';
|
||||
document.getElementById('active_lesson_view').style.display = 'block';
|
||||
document.getElementById('lesson_markdown_editor').value = data.active_md || '';
|
||||
document.getElementById('current_lesson_title_display').textContent = data.active_title || 'الدرس المستخرج';
|
||||
document.getElementById('current_breadcrumb').textContent = data.active_breadcrumb || 'المسار التعليمي';
|
||||
document.getElementById('server_file_path_display').textContent = `${data.server_storage_dir}/${data.active_file}`;
|
||||
}
|
||||
|
||||
alert(`✅ ${data.message}\n\nتم حفظ ملفات المنهاج على السيرفر في:\n${data.server_storage_dir}`);
|
||||
desc.textContent = `تم اعتماد المنهاج [${file.name}] بنجاح في قاعدة المعرفة الشجرية!`;
|
||||
if (res.ok && data.status === 'processing') {
|
||||
currentUploadTaskId = data.task_id;
|
||||
document.getElementById('upload_progress_bar').style.width = '15%';
|
||||
document.getElementById('upload_progress_text').textContent = data.message || 'تم استلام الملف، جاري بدء الذكاء الاصطناعي...';
|
||||
document.getElementById('upload_progress_percent').textContent = '15%';
|
||||
appendLog(`[Server] Task registered with ID: ${data.task_id}`);
|
||||
pollTaskStatus(data.task_id, file.name);
|
||||
} else if (res.ok && data.status === 'success') {
|
||||
overlay.style.display = 'none';
|
||||
resetUploadBtn();
|
||||
loadCurriculumTree();
|
||||
} else {
|
||||
alert('⚠️ ' + (data.message || 'فشلت معالجة الملف'));
|
||||
document.getElementById('upload_progress_text').textContent = '⚠️ ' + (data.message || 'فشلت معالجة الملف');
|
||||
document.getElementById('btn_close_overlay').style.display = 'inline-block';
|
||||
resetUploadBtn();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('PDF Upload error:', err);
|
||||
alert('حدث خطأ في الاتصال بالخادم أثناء رفع الملف.');
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<span>📤 رفع كتاب المنهاج PDF الحقيقي</span>';
|
||||
input.value = '';
|
||||
document.getElementById('upload_progress_text').textContent = 'حدث خطأ في الاتصال بالخادم أثناء رفع الملف.';
|
||||
document.getElementById('btn_close_overlay').style.display = 'inline-block';
|
||||
resetUploadBtn();
|
||||
}
|
||||
}
|
||||
|
||||
async function pollTaskStatus(taskId, fileName) {
|
||||
try {
|
||||
const res = await fetch(`/api/curriculum/upload-status?task_id=${taskId}`);
|
||||
if (!res.ok) throw new Error("Network response was not ok");
|
||||
const state = await res.json();
|
||||
|
||||
// Live Update Modal
|
||||
document.getElementById('upload_progress_bar').style.width = (state.progress || 20) + '%';
|
||||
document.getElementById('upload_progress_percent').textContent = (state.progress || 20) + '%';
|
||||
document.getElementById('upload_progress_text').textContent = state.message || "جاري المعالجة بالذكاء الاصطناعي...";
|
||||
|
||||
fetchUploadLogs();
|
||||
|
||||
if (state.status === 'completed') {
|
||||
document.getElementById('upload_progress_bar').style.width = '100%';
|
||||
document.getElementById('upload_progress_percent').textContent = '100%';
|
||||
document.getElementById('upload_progress_text').textContent = `✅ اكتملت قراءة وتفريغ المنهاج بنجاح!`;
|
||||
|
||||
setTimeout(() => {
|
||||
document.getElementById('upload_progress_overlay').style.display = 'none';
|
||||
resetUploadBtn();
|
||||
if (state.tree) {
|
||||
window.CURRICULUM_TREE = state.tree;
|
||||
renderCurriculumTree(state.tree);
|
||||
if (state.active_file) {
|
||||
selectLesson(state.active_file, state.extracted_data?.subject_name || 'الدرس المستخرج', 'المنهاج المستخرج', []);
|
||||
}
|
||||
} else {
|
||||
location.reload();
|
||||
}
|
||||
}, 2000);
|
||||
} else if (state.status === 'error') {
|
||||
document.getElementById('upload_progress_text').textContent = '⚠️ فشل: ' + state.message;
|
||||
document.getElementById('btn_close_overlay').style.display = 'inline-block';
|
||||
resetUploadBtn();
|
||||
} else {
|
||||
// Poll again in 2.5 seconds
|
||||
setTimeout(() => pollTaskStatus(taskId, fileName), 2500);
|
||||
}
|
||||
} catch(e) {
|
||||
setTimeout(() => pollTaskStatus(taskId, fileName), 3000);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchUploadLogs() {
|
||||
if (!currentUploadTaskId) return;
|
||||
try {
|
||||
const res = await fetch(`/api/curriculum/upload-log?task_id=${currentUploadTaskId}`);
|
||||
const data = await res.json();
|
||||
const container = document.getElementById('upload_logs_textarea');
|
||||
if (data.status === 'success' && data.log) {
|
||||
container.innerHTML = data.log.replace(/\n/g, '<br>');
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Fetch logs error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function appendLog(msg) {
|
||||
const container = document.getElementById('upload_logs_textarea');
|
||||
container.innerHTML += `${escapeHtml(msg)}<br>`;
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
|
||||
function toggleUploadLogs() {
|
||||
const container = document.getElementById('upload_logs_container');
|
||||
container.style.display = (container.style.display === 'none') ? 'block' : 'none';
|
||||
if (container.style.display === 'block') {
|
||||
fetchUploadLogs();
|
||||
}
|
||||
}
|
||||
|
||||
function openDirectLogViewer() {
|
||||
const overlay = document.getElementById('upload_progress_overlay');
|
||||
overlay.style.display = 'flex';
|
||||
document.getElementById('btn_close_overlay').style.display = 'inline-block';
|
||||
document.getElementById('upload_logs_container').style.display = 'block';
|
||||
if (currentUploadTaskId) {
|
||||
fetchUploadLogs();
|
||||
} else {
|
||||
document.getElementById('upload_logs_textarea').innerHTML = '[System] No active task ID yet. Upload a textbook PDF to stream live AI logs.<br>';
|
||||
}
|
||||
}
|
||||
|
||||
function closeProgressOverlay() {
|
||||
document.getElementById('upload_progress_overlay').style.display = 'none';
|
||||
resetUploadBtn();
|
||||
}
|
||||
|
||||
function resetUploadBtn() {
|
||||
const btn = document.getElementById('btn_upload_pdf');
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<span>📤 رفع كتاب المنهاج PDF الحقيقي</span>';
|
||||
document.getElementById('curriculum_pdf_input').value = '';
|
||||
}
|
||||
|
||||
async function saveActiveMarkdown() {
|
||||
if (!currentFilePath) return;
|
||||
const content = document.getElementById('lesson_markdown_editor').value;
|
||||
@@ -439,62 +581,6 @@ class CurriculumStudio
|
||||
return d.innerHTML;
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Upload Progress Modal -->
|
||||
<div id="upload_progress_overlay" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(10, 15, 26, 0.85); backdrop-filter: blur(8px); z-index: 9999; flex-direction: column; align-items: center; justify-content: center;">
|
||||
<div style="background: var(--bg-card); padding: 30px; border-radius: 16px; border: 1px solid var(--border-color); width: 400px; max-width: 90%; text-align: center; box-shadow: 0 10px 40px rgba(0,0,0,0.5);">
|
||||
<div id="ai_spinner" style="font-size: 40px; margin-bottom: 15px; animation: pulse 1.5s infinite;">🧠</div>
|
||||
<h3 style="color: var(--text-primary); margin-bottom: 10px; font-family: 'SF Arabic', sans-serif;">معالجة الذكاء الاصطناعي</h3>
|
||||
<div style="width: 100%; background: var(--bg-hover); border-radius: 10px; height: 10px; margin-bottom: 15px; overflow: hidden; position: relative;">
|
||||
<div id="upload_progress_bar" style="height: 100%; width: 0%; background: linear-gradient(90deg, var(--accent-gold), #ffed4a); transition: width 0.4s ease;"></div>
|
||||
</div>
|
||||
<p id="upload_progress_text" style="color: var(--text-secondary); font-size: 13px; font-weight: bold;">جاري التهيئة...</p>
|
||||
<p id="upload_progress_percent" style="color: var(--accent-gold); font-size: 12px; margin-top: 5px;">0%</p>
|
||||
|
||||
<button id="btn_view_logs" type="button" style="margin-top: 15px; background: transparent; border: 1px solid var(--border-color); color: var(--text-muted); padding: 5px 12px; border-radius: 6px; font-size: 11px; cursor: pointer; transition: all 0.2s;" onmouseover="this.style.color='#fff'" onmouseout="this.style.color='var(--text-muted)'" onclick="toggleUploadLogs()">🔍 عرض سجل السيرفر (Logs)</button>
|
||||
<div id="upload_logs_container" style="display: none; margin-top: 15px; width: 100%; text-align: left;">
|
||||
<textarea id="upload_logs_textarea" readonly style="width: 100%; height: 120px; background: #000; color: #0f0; font-family: monospace; font-size: 11px; border: 1px solid #333; border-radius: 4px; padding: 8px; resize: none; direction: ltr;"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentUploadTaskId = null;
|
||||
|
||||
function toggleUploadLogs() {
|
||||
const container = document.getElementById('upload_logs_container');
|
||||
if (container.style.display === 'none') {
|
||||
container.style.display = 'block';
|
||||
fetchUploadLogs();
|
||||
} else {
|
||||
container.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchUploadLogs() {
|
||||
if (!currentUploadTaskId) return;
|
||||
try {
|
||||
const res = await fetch(`/api/curriculum/upload-log?task_id=${currentUploadTaskId}`);
|
||||
const data = await res.json();
|
||||
const textarea = document.getElementById('upload_logs_textarea');
|
||||
if (data.status === 'success') {
|
||||
textarea.value = data.log || 'No logs generated yet...';
|
||||
textarea.scrollTop = textarea.scrollHeight;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@keyframes pulse {
|
||||
0% { transform: scale(1); opacity: 1; }
|
||||
50% { transform: scale(1.1); opacity: 0.7; }
|
||||
100% { transform: scale(1); opacity: 1; }
|
||||
}
|
||||
</style>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
|
||||
Reference in New Issue
Block a user