449 lines
22 KiB
HTML
449 lines
22 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>Presenton – Setup required</title>
|
||
<style>
|
||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||
|
||
:root {
|
||
--bg: #0d0d14;
|
||
--surface: #16162a;
|
||
--surface2: #1e1e38;
|
||
--border: rgba(145, 52, 234, 0.25);
|
||
--grad-a: #9034EA;
|
||
--grad-b: #5146E5;
|
||
--text: #f0eeff;
|
||
--text-muted: #9b96c4;
|
||
--text-dim: #5e5a88;
|
||
--track: #1e1e38;
|
||
--log-bg: #0a0a12;
|
||
--log-border: rgba(145,52,234,0.15);
|
||
--radius: 14px;
|
||
--radius-sm: 8px;
|
||
}
|
||
|
||
html, body {
|
||
width: 100%; height: 100%;
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
-webkit-font-smoothing: antialiased;
|
||
overflow: hidden;
|
||
user-select: none;
|
||
}
|
||
|
||
.shell { display: flex; flex-direction: column; height: 100vh; padding: 24px 32px 0; }
|
||
|
||
.logo-wrap { text-align: center; margin-bottom: 12px; flex-shrink: 0; }
|
||
.logo-wrap img { height: 36px; display: inline-block; }
|
||
.logo-fallback { font-size: 20px; font-weight: 700; background: linear-gradient(135deg, var(--grad-a), var(--grad-b)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }
|
||
|
||
.step-badge {
|
||
font-size: 11px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase;
|
||
color: var(--text-dim); margin-bottom: 16px; text-align: center;
|
||
}
|
||
|
||
.card {
|
||
width: 100%; flex-shrink: 0; background: var(--surface); border: 1px solid var(--border);
|
||
border-radius: var(--radius); padding: 28px 28px 24px; display: flex; flex-direction: column;
|
||
align-items: center; gap: 16px; position: relative; overflow: hidden;
|
||
}
|
||
.card::before {
|
||
content: ''; position: absolute; inset: 0; border-radius: var(--radius);
|
||
background: radial-gradient(ellipse 60% 40% at 50% -10%, rgba(145,52,234,0.18) 0%, transparent 70%);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.state { display: none; flex-direction: column; align-items: center; gap: 14px; width: 100%; }
|
||
.state.active { display: flex; }
|
||
|
||
.icon-wrap {
|
||
width: 52px; height: 52px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
|
||
font-size: 24px;
|
||
}
|
||
.icon-wrap.purple { background: linear-gradient(135deg, rgba(145,52,234,0.2), rgba(81,70,229,0.2)); border: 1px solid rgba(145,52,234,0.35); }
|
||
.icon-wrap.green { background: rgba(34,197,94,0.12); border: 1px solid rgba(34,197,94,0.3); }
|
||
.icon-wrap.red { background: rgba(239,68,68,0.12); border: 1px solid rgba(239,68,68,0.3); }
|
||
|
||
.heading { font-size: 16px; font-weight: 600; color: var(--text); text-align: center; }
|
||
.sub { font-size: 13px; color: var(--text-muted); text-align: center; max-width: 340px; line-height: 1.6; }
|
||
.sub strong { color: var(--text); font-weight: 500; }
|
||
|
||
.btn-row { display: flex; gap: 12px; width: 100%; justify-content: center; margin-top: 2px; }
|
||
button {
|
||
cursor: pointer; border: none; border-radius: var(--radius-sm); font-family: inherit;
|
||
font-size: 13px; font-weight: 500; padding: 8px 20px; transition: opacity 0.15s, transform 0.1s; outline: none;
|
||
}
|
||
button:active { transform: scale(0.97); }
|
||
button:disabled { opacity: 0.45; cursor: not-allowed; transform: none; }
|
||
.btn-primary { background: linear-gradient(135deg, var(--grad-a), var(--grad-b)); color: #fff; min-width: 150px; box-shadow: 0 4px 20px rgba(145,52,234,0.35); }
|
||
.btn-primary:hover:not(:disabled) { opacity: 0.9; }
|
||
.btn-ghost { background: transparent; color: var(--text-muted); border: 1px solid rgba(155,150,196,0.25); }
|
||
.btn-ghost:hover:not(:disabled) { color: var(--text); border-color: rgba(155,150,196,0.5); }
|
||
|
||
.progress-wrap { width: 100%; display: flex; flex-direction: column; gap: 7px; }
|
||
.progress-meta { display: flex; justify-content: space-between; font-size: 12px; color: var(--text-muted); }
|
||
.progress-track { width: 100%; height: 6px; background: var(--track); border-radius: 99px; overflow: hidden; }
|
||
.progress-fill { height: 100%; border-radius: 99px; background: linear-gradient(90deg, var(--grad-a), var(--grad-b)); width: 0%; transition: width 0.3s ease; }
|
||
.progress-fill.indeterminate { width: 40% !important; animation: shimmer 1.4s ease-in-out infinite; }
|
||
@keyframes shimmer { 0% { transform: translateX(-120%); } 100% { transform: translateX(310%); } }
|
||
|
||
.spinner {
|
||
width: 28px; height: 28px; border: 3px solid rgba(145,52,234,0.2);
|
||
border-top-color: var(--grad-a); border-radius: 50%; animation: spin 0.8s linear infinite;
|
||
}
|
||
@keyframes spin { to { transform: rotate(360deg); } }
|
||
.phase-label { font-size: 12px; color: var(--text-dim); text-align: center; }
|
||
|
||
.dots { position: absolute; bottom: -20px; right: -20px; width: 100px; height: 100px; background-image: radial-gradient(circle, rgba(145,52,234,0.18) 1px, transparent 1px); background-size: 14px 14px; pointer-events: none; opacity: 0.6; }
|
||
|
||
.log-section { width: 100%; flex: 1; display: flex; flex-direction: column; min-height: 0; margin-top: 10px; padding-bottom: 12px; }
|
||
.log-toggle { display: flex; align-items: center; gap: 6px; cursor: pointer; padding: 4px 0; color: var(--text-dim); font-size: 12px; font-weight: 500; background: none; border: none; font-family: inherit; text-align: left; width: 100%; }
|
||
.log-toggle:hover { color: var(--text-muted); }
|
||
.log-toggle-chevron { display: inline-block; width: 14px; height: 14px; position: relative; flex-shrink: 0; }
|
||
.log-toggle-chevron::before, .log-toggle-chevron::after { content: ''; position: absolute; top: 50%; left: 50%; width: 6px; height: 1.5px; background: currentColor; border-radius: 1px; transition: transform 0.2s; }
|
||
.log-toggle-chevron::before { transform: translate(-50%, -50%) rotate(-40deg) translateX(-2px); }
|
||
.log-toggle-chevron::after { transform: translate(-50%, -50%) rotate(40deg) translateX(2px); }
|
||
.log-toggle.open .log-toggle-chevron::before { transform: translate(-50%, -50%) rotate(40deg) translateX(-2px); }
|
||
.log-toggle.open .log-toggle-chevron::after { transform: translate(-50%, -50%) rotate(-40deg) translateX(2px); }
|
||
.log-count { margin-left: auto; font-size: 11px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
|
||
.log-panel { display: none; flex-direction: column; flex: 1; min-height: 0; margin-top: 6px; background: var(--log-bg); border: 1px solid var(--log-border); border-radius: var(--radius-sm); overflow: hidden; }
|
||
.log-panel.open { display: flex; }
|
||
.log-inner { flex: 1; overflow-y: auto; padding: 10px 12px; font-family: "SF Mono", "Cascadia Code", monospace; font-size: 11px; line-height: 1.7; color: var(--text-muted); }
|
||
.log-line { display: flex; gap: 8px; padding: 1px 0; }
|
||
.log-time { color: var(--text-dim); flex-shrink: 0; font-size: 10px; }
|
||
.log-text { white-space: pre-wrap; word-break: break-all; flex: 1; }
|
||
.log-line.info .log-text { color: var(--text-muted); }
|
||
.log-line.error .log-text { color: #e05a5a; }
|
||
.log-line.ok .log-text { color: #5ab870; }
|
||
.log-line.cmd .log-text { color: #7c8fd8; }
|
||
.log-empty { color: var(--text-dim); font-style: italic; font-size: 11px; text-align: center; padding: 12px 0; }
|
||
.log-panel-header { display: flex; align-items: center; justify-content: space-between; padding: 5px 10px 5px 12px; border-bottom: 1px solid var(--log-border); }
|
||
.log-panel-title { font-size: 10px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-dim); }
|
||
.log-clear-btn { font-size: 10px; padding: 2px 8px; color: var(--text-dim); background: transparent; border: 1px solid rgba(155,150,196,0.15); border-radius: 4px; cursor: pointer; font-family: inherit; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="shell">
|
||
<div class="logo-wrap">
|
||
<img src="../assets/images/presenton_logo.png" alt="Presenton" onerror="this.style.display='none'; document.getElementById('logo-fb').style.display='block';" />
|
||
<span id="logo-fb" class="logo-fallback" style="display:none;">Presenton</span>
|
||
</div>
|
||
<div class="step-badge" id="step-badge">Setup</div>
|
||
|
||
<div class="card">
|
||
<div class="dots"></div>
|
||
|
||
<div id="state-prompt" class="state active">
|
||
<div class="icon-wrap purple">📦</div>
|
||
<p class="heading" id="prompt-heading">Dependencies required</p>
|
||
<p class="sub" id="prompt-sub">Presenton needs LibreOffice, Chrome, and ImageMagick to create and export presentations reliably. Install them now so everything works.</p>
|
||
<div class="btn-row">
|
||
<button class="btn-primary" id="btn-install">Install</button>
|
||
<button class="btn-ghost" id="btn-skip">Skip for now</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="state-downloading" class="state">
|
||
<div class="spinner"></div>
|
||
<p class="heading" id="dl-heading">Downloading</p>
|
||
<p class="sub" id="dl-filename">Preparing…</p>
|
||
<div class="progress-wrap">
|
||
<div class="progress-meta">
|
||
<span id="dl-label">0%</span>
|
||
<span id="dl-size"></span>
|
||
</div>
|
||
<div class="progress-track"><div class="progress-fill" id="dl-bar"></div></div>
|
||
</div>
|
||
<p class="phase-label" id="dl-phase">This may take a few minutes</p>
|
||
</div>
|
||
|
||
<div id="state-installing" class="state">
|
||
<div class="spinner"></div>
|
||
<p class="heading" id="install-heading">Installing</p>
|
||
<p class="sub">Please wait…</p>
|
||
<div class="progress-wrap">
|
||
<div class="progress-track"><div class="progress-fill indeterminate" id="install-bar"></div></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="state-success" class="state">
|
||
<div class="icon-wrap green">✓</div>
|
||
<p class="heading" id="success-heading">Installed</p>
|
||
<p class="sub" id="success-sub">Continuing in a moment…</p>
|
||
<div class="progress-wrap">
|
||
<div class="progress-track"><div class="progress-fill" id="success-bar" style="width:0%; transition: width 2s linear;"></div></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="state-error" class="state">
|
||
<div class="icon-wrap red">⚠</div>
|
||
<p class="heading">Installation failed</p>
|
||
<p class="sub" id="err-msg">Something went wrong. You can try again or skip.</p>
|
||
<div class="btn-row">
|
||
<button class="btn-primary" id="btn-retry">Try again</button>
|
||
<button class="btn-ghost" id="btn-skip-error">Skip</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="log-section" id="log-section" style="display:none;">
|
||
<button class="log-toggle" id="log-toggle" onclick="toggleLog()">
|
||
<span class="log-toggle-chevron"></span>
|
||
<span id="log-toggle-label">Show details</span>
|
||
<span class="log-count" id="log-count"></span>
|
||
</button>
|
||
<div class="log-panel" id="log-panel">
|
||
<div class="log-panel-header">
|
||
<span class="log-panel-title">Log</span>
|
||
<button class="log-clear-btn" onclick="clearLog()">Clear</button>
|
||
</div>
|
||
<div class="log-inner" id="log-inner">
|
||
<div class="log-empty" id="log-empty">No output yet.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const STATES = ['prompt','downloading','installing','success','error'];
|
||
const setupInstallerApi = window.setupInstaller;
|
||
let logLines = 0;
|
||
let currentStep = null; // 'libreoffice' | 'chrome' | 'imagemagick'
|
||
let status = { needsLibreOffice: false, needsChrome: false, needsImageMagick: false };
|
||
let steps = [];
|
||
let logOpen = false;
|
||
|
||
function showState(name) {
|
||
STATES.forEach(s => {
|
||
const el = document.getElementById('state-' + s);
|
||
if (el) el.classList.toggle('active', s === name);
|
||
});
|
||
const logSection = document.getElementById('log-section');
|
||
if (logSection) logSection.style.display = (name === 'downloading' || name === 'installing' || name === 'error') ? 'flex' : 'none';
|
||
}
|
||
|
||
function setStepBadge(stepNum, total, label) {
|
||
const el = document.getElementById('step-badge');
|
||
if (el) el.textContent = total > 1 ? `Step ${stepNum} of ${total}: ${label}` : label;
|
||
}
|
||
|
||
function escHtml(str) {
|
||
return String(str).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||
}
|
||
function appendLog(level, text) {
|
||
const inner = document.getElementById('log-inner');
|
||
if (!inner) return;
|
||
const placeholder = document.getElementById('log-empty');
|
||
if (placeholder) placeholder.remove();
|
||
const now = new Date();
|
||
const ts = now.toTimeString().slice(0,8);
|
||
const line = document.createElement('div');
|
||
line.className = `log-line ${level}`;
|
||
line.innerHTML = `<span class="log-time">${ts}</span><span class="log-text">${escHtml(text)}</span>`;
|
||
inner.appendChild(line);
|
||
logLines++;
|
||
const countEl = document.getElementById('log-count');
|
||
if (countEl) countEl.textContent = logLines > 0 ? `${logLines} lines` : '';
|
||
const nearBottom = inner.scrollHeight - inner.scrollTop - inner.clientHeight < 80;
|
||
if (nearBottom) line.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
||
}
|
||
function clearLog() {
|
||
const inner = document.getElementById('log-inner');
|
||
if (!inner) return;
|
||
inner.innerHTML = '<div class="log-empty" id="log-empty">Log cleared.</div>';
|
||
logLines = 0;
|
||
document.getElementById('log-count').textContent = '';
|
||
}
|
||
function toggleLog() {
|
||
logOpen = !logOpen;
|
||
document.getElementById('log-toggle').classList.toggle('open', logOpen);
|
||
document.getElementById('log-panel').classList.toggle('open', logOpen);
|
||
document.getElementById('log-toggle-label').textContent = logOpen ? 'Hide details' : 'Show details';
|
||
}
|
||
|
||
function getStepsFromStatus() {
|
||
const queue = [];
|
||
if (status.needsLibreOffice) queue.push('libreoffice');
|
||
if (status.needsChrome) queue.push('chrome');
|
||
if (status.needsImageMagick) queue.push('imagemagick');
|
||
return queue;
|
||
}
|
||
|
||
function showPromptForStep(step) {
|
||
currentStep = step;
|
||
const total = steps.length || 1;
|
||
const stepNum = Math.max(1, steps.indexOf(step) + 1);
|
||
const stepLabel = step === 'libreoffice' ? 'LibreOffice' : step === 'chrome' ? 'Chromium' : 'ImageMagick';
|
||
setStepBadge(stepNum, total, stepLabel);
|
||
document.getElementById('prompt-heading').textContent =
|
||
step === 'libreoffice' ? 'LibreOffice required' :
|
||
step === 'chrome' ? 'Chromium required' :
|
||
'ImageMagick required';
|
||
document.getElementById('prompt-sub').innerHTML =
|
||
step === 'libreoffice'
|
||
? '<strong>Presenton</strong> uses LibreOffice to generate custom templates from PPTX files.'
|
||
: step === 'chrome'
|
||
? '<strong>Presenton</strong> uses Chromium for export and slide rendering. Download it now (~150 MB).'
|
||
: '<strong>Presenton</strong> uses ImageMagick for OCR/document conversion support. Linux uses apt, macOS installs Homebrew first (if needed) and then runs brew install imagemagick, and Windows downloads and installs it directly into the Presenton runtime.';
|
||
document.getElementById('btn-install').onclick = () => startInstall(step);
|
||
document.getElementById('btn-skip').onclick = () => handleSkip();
|
||
showState('prompt');
|
||
}
|
||
|
||
function startInstall(step) {
|
||
if (!setupInstallerApi) {
|
||
showState('error');
|
||
document.getElementById('err-msg').textContent = 'Installer bridge is unavailable. Restart Presenton and try again.';
|
||
return;
|
||
}
|
||
|
||
currentStep = step;
|
||
showState('downloading');
|
||
if (!logOpen) toggleLog();
|
||
if (step === 'libreoffice') {
|
||
document.getElementById('dl-heading').textContent = 'Downloading LibreOffice';
|
||
document.getElementById('dl-phase').textContent = 'This may take a few minutes (~300 MB)';
|
||
setupInstallerApi.installLibreOffice();
|
||
} else if (step === 'chrome') {
|
||
document.getElementById('dl-heading').textContent = 'Downloading Chromium';
|
||
document.getElementById('dl-phase').textContent = 'This may take a few minutes (~150 MB)';
|
||
setupInstallerApi.installChrome().then(res => {
|
||
if (!res.ok && currentStep === 'chrome') {
|
||
document.getElementById('err-msg').textContent = res.error || 'Chrome download failed.';
|
||
showState('error');
|
||
document.getElementById('btn-retry').onclick = () => startInstall('chrome');
|
||
document.getElementById('btn-skip-error').onclick = () => nextOrDone();
|
||
}
|
||
});
|
||
} else {
|
||
document.getElementById('dl-heading').textContent = 'Installing ImageMagick';
|
||
document.getElementById('dl-phase').textContent = 'Linux: apt-get | macOS: Homebrew + brew install | Windows: direct installer (Presenton runtime)';
|
||
setupInstallerApi.installImageMagick().then((installResult) => {
|
||
if (!installResult || !installResult.ok) {
|
||
if (currentStep !== 'imagemagick') return;
|
||
document.getElementById('err-msg').textContent = installResult?.error || 'ImageMagick installation needs manual completion. Follow the shown commands and then click Retry.';
|
||
showState('error');
|
||
document.getElementById('btn-retry').onclick = () => startInstall('imagemagick');
|
||
document.getElementById('btn-skip-error').onclick = () => nextOrDone();
|
||
return;
|
||
}
|
||
|
||
setupInstallerApi.checkImageMagick().then(res => {
|
||
if (!res.ok && currentStep === 'imagemagick') {
|
||
document.getElementById('err-msg').textContent = res.error || 'ImageMagick is not installed yet.';
|
||
showState('error');
|
||
document.getElementById('btn-retry').onclick = () => startInstall('imagemagick');
|
||
document.getElementById('btn-skip-error').onclick = () => nextOrDone();
|
||
}
|
||
});
|
||
});
|
||
}
|
||
}
|
||
|
||
function nextOrDone() {
|
||
if (!setupInstallerApi) {
|
||
return;
|
||
}
|
||
|
||
const idx = steps.indexOf(currentStep);
|
||
const nextStep = idx >= 0 ? steps[idx + 1] : null;
|
||
if (nextStep) {
|
||
showPromptForStep(nextStep);
|
||
} else {
|
||
setupInstallerApi.done();
|
||
}
|
||
}
|
||
|
||
function handleSkip() {
|
||
nextOrDone();
|
||
}
|
||
|
||
function onProgress(which, data) {
|
||
const { phase, percent, message } = data;
|
||
if (which !== currentStep) return;
|
||
|
||
if (phase === 'downloading') {
|
||
showState('downloading');
|
||
const bar = document.getElementById('dl-bar');
|
||
const label = document.getElementById('dl-label');
|
||
const size = document.getElementById('dl-size');
|
||
const fname = document.getElementById('dl-filename');
|
||
if (bar) bar.style.width = (percent || 0) + '%';
|
||
if (label) label.textContent = (percent || 0) + '%';
|
||
if (message) {
|
||
const parts = String(message).split('|');
|
||
if (fname && parts[0]) fname.textContent = parts[0];
|
||
if (size && parts[1]) size.textContent = parts[1];
|
||
}
|
||
return;
|
||
}
|
||
if (phase === 'installing' || phase === 'extracting') {
|
||
showState('installing');
|
||
document.getElementById('install-heading').textContent = phase === 'extracting' ? 'Extracting…' : 'Installing…';
|
||
return;
|
||
}
|
||
if (phase === 'done') {
|
||
showState('success');
|
||
document.getElementById('success-heading').textContent =
|
||
currentStep === 'libreoffice' ? 'LibreOffice installed' :
|
||
currentStep === 'chrome' ? 'Chromium installed' :
|
||
'ImageMagick ready';
|
||
const idx = steps.indexOf(currentStep);
|
||
const nextStep = idx >= 0 ? steps[idx + 1] : null;
|
||
document.getElementById('success-sub').textContent = nextStep ? 'Continuing with next step…' : 'Continuing in a moment…';
|
||
const bar = document.getElementById('success-bar');
|
||
if (bar) bar.style.width = '100%';
|
||
setTimeout(() => {
|
||
nextOrDone();
|
||
}, 2200);
|
||
return;
|
||
}
|
||
if (phase === 'error') {
|
||
showState('error');
|
||
document.getElementById('err-msg').textContent = message || 'Installation failed.';
|
||
document.getElementById('btn-retry').onclick = () => startInstall(currentStep);
|
||
document.getElementById('btn-skip-error').onclick = () => nextOrDone();
|
||
if (!logOpen) toggleLog();
|
||
}
|
||
}
|
||
|
||
function onLog(which, data) {
|
||
if (which !== currentStep) return;
|
||
appendLog(data.level || 'info', data.text || '');
|
||
}
|
||
|
||
document.getElementById('btn-retry').onclick = () => startInstall(currentStep);
|
||
document.getElementById('btn-skip-error').onclick = () => nextOrDone();
|
||
|
||
if (!setupInstallerApi) {
|
||
showState('error');
|
||
document.getElementById('err-msg').textContent = 'Installer bridge failed to initialize. Please restart Presenton and try again.';
|
||
document.getElementById('btn-retry').onclick = () => location.reload();
|
||
document.getElementById('btn-skip-error').onclick = () => window.close();
|
||
appendLog('error', 'setupInstaller API is unavailable in this renderer.');
|
||
} else {
|
||
setupInstallerApi.onLibreOfficeProgress((data) => onProgress('libreoffice', data));
|
||
setupInstallerApi.onLibreOfficeLog((data) => onLog('libreoffice', data));
|
||
setupInstallerApi.onChromeProgress((data) => onProgress('chrome', data));
|
||
setupInstallerApi.onChromeLog((data) => onLog('chrome', data));
|
||
setupInstallerApi.onImageMagickProgress((data) => onProgress('imagemagick', data));
|
||
setupInstallerApi.onImageMagickLog((data) => onLog('imagemagick', data));
|
||
|
||
setupInstallerApi.getStatus().then(s => {
|
||
status = s;
|
||
steps = getStepsFromStatus();
|
||
if (steps.length === 0) {
|
||
setupInstallerApi.done();
|
||
return;
|
||
}
|
||
showPromptForStep(steps[0]);
|
||
});
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|