480 lines
21 KiB
HTML
Executable file
480 lines
21 KiB
HTML
Executable file
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Asset Browser — deRenzy BT</title>
|
||
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon">
|
||
<link rel="icon" href="/assets/favicon-192.png" type="image/png" sizes="192x192">
|
||
<link rel="apple-touch-icon" href="/assets/apple-touch-icon.png">
|
||
<link rel="stylesheet" href="/styles/variables.css">
|
||
<link rel="stylesheet" href="/styles/main.css">
|
||
<link rel="stylesheet" href="/styles/card.css">
|
||
<link rel="stylesheet" href="/styles/sidebar.css">
|
||
<link rel="stylesheet" href="/styles/label.css">
|
||
<link rel="stylesheet" href="/styles/labelCenter.css">
|
||
<!-- JsBarcode vendored for offline use -->
|
||
<script src="/vendor/JsBarcode.all.min.js"></script>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- ── Header ── -->
|
||
<header id="app-header">
|
||
|
||
<div class="header-left">
|
||
<a href="https://derenzy.com" target="_blank" rel="noopener noreferrer" class="header-logo-link">
|
||
<img src="/assets/logo-swirl.png" alt="deRenzy BT" class="header-logo">
|
||
</a>
|
||
<div class="header-title">
|
||
<button type="button" class="title-main" id="btn-home">Asset Browser</button>
|
||
<a href="https://derenzy.com" target="_blank" rel="noopener noreferrer" class="title-sub">deRenzy Business Technologies</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="header-center">
|
||
<div class="scan-input-wrap">
|
||
<span class="scan-input-icon">
|
||
<!-- Barcode icon — shown for staff roles -->
|
||
<svg class="icon-barcode" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<path d="M3 5v14M7 5v14M11 5v14M17 5v14M21 5v14"/>
|
||
<path d="M3 5h2M3 19h2M19 5h2M19 19h2"/>
|
||
</svg>
|
||
<!-- Magnifying glass — shown for client role -->
|
||
<svg class="icon-search" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
||
</svg>
|
||
</span>
|
||
<input
|
||
type="text"
|
||
id="scan-input"
|
||
placeholder="Search by name, serial, user… or scan a barcode"
|
||
autocomplete="off"
|
||
autocorrect="off"
|
||
spellcheck="false"
|
||
inputmode="numeric"
|
||
data-lpignore="true"
|
||
data-1p-ignore
|
||
data-bwignore
|
||
data-form-type="other"
|
||
>
|
||
<button id="scan-clear" class="scan-clear-btn" hidden aria-label="Clear">✕</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="header-right">
|
||
|
||
<nav class="mode-nav" aria-label="App mode">
|
||
<button class="mode-btn" data-mode="scan" id="btn-scan-mode">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/>
|
||
<rect x="3" y="14" width="7" height="7" rx="1"/><path d="M14 14h7v7h-7z" fill="currentColor" opacity=".3"/>
|
||
<path d="M14 14h7v7h-7z"/>
|
||
</svg>
|
||
Scan Mode
|
||
</button>
|
||
<button class="mode-btn" id="btn-quick-view">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<rect x="3" y="3" width="7" height="8" rx="1"/><rect x="14" y="3" width="7" height="8" rx="1"/>
|
||
<rect x="3" y="13" width="7" height="8" rx="1"/><rect x="14" y="13" width="7" height="8" rx="1"/>
|
||
</svg>
|
||
Quick View
|
||
</button>
|
||
</nav>
|
||
|
||
<span class="header-btn-divider" aria-hidden="true"></span>
|
||
|
||
<!-- App menu -->
|
||
<div class="app-menu-wrap">
|
||
<button class="mode-btn" id="btn-menu-toggle" aria-haspopup="true" aria-expanded="false">
|
||
Menu
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||
<line x1="3" y1="6" x2="21" y2="6"/>
|
||
<line x1="3" y1="12" x2="21" y2="12"/>
|
||
<line x1="3" y1="18" x2="21" y2="18"/>
|
||
</svg>
|
||
<span class="lc-menu-badge" id="lc-badge" hidden>0</span>
|
||
</button>
|
||
|
||
<div id="app-menu" class="app-menu" hidden>
|
||
|
||
<!-- User info -->
|
||
<div class="app-menu-user">
|
||
<span class="user-name" id="user-name">…</span>
|
||
<span class="user-role-badge" id="user-role"></span>
|
||
</div>
|
||
|
||
<!-- Admin portal (shown only for admin+) -->
|
||
<a href="/admin" class="app-menu-item" id="menu-admin-link" hidden>
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
||
</svg>
|
||
Admin Portal
|
||
</a>
|
||
|
||
<div class="app-menu-divider"></div>
|
||
|
||
<!-- Label Center — batch sheet printing -->
|
||
<button class="app-menu-item" id="btn-label-center">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<rect x="2" y="3" width="20" height="14" rx="2"/>
|
||
<line x1="8" y1="21" x2="16" y2="21"/>
|
||
<line x1="12" y1="17" x2="12" y2="21"/>
|
||
</svg>
|
||
Label Center
|
||
</button>
|
||
|
||
<!-- Timer toggle -->
|
||
<button class="app-menu-item" id="btn-timer-toggle">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<circle cx="12" cy="12" r="10"/>
|
||
<polyline points="12 6 12 12 16 14"/>
|
||
</svg>
|
||
<span id="timer-toggle-label">Timer: ON</span>
|
||
</button>
|
||
|
||
<!-- Scan mode toggle (USB keyboard wedge ↔ camera) -->
|
||
<button class="app-menu-item" id="btn-scan-mode-toggle">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<path d="M23 19a2 2 0 01-2 2H3a2 2 0 01-2-2V8a2 2 0 012-2h4l2-3h6l2 3h4a2 2 0 012 2z"/>
|
||
<circle cx="12" cy="13" r="4"/>
|
||
</svg>
|
||
Camera Scan
|
||
</button>
|
||
|
||
<div class="app-menu-divider"></div>
|
||
|
||
<!-- Sign out -->
|
||
<button class="app-menu-item app-menu-item-danger" id="btn-logout">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4"/>
|
||
<polyline points="16 17 21 12 16 7"/>
|
||
<line x1="21" y1="12" x2="9" y2="12"/>
|
||
</svg>
|
||
Sign Out
|
||
</button>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</header>
|
||
|
||
<!-- ── App body (sidebar + main) ── -->
|
||
<div id="app-body">
|
||
|
||
<!-- ── Asset Browser Sidebar ── -->
|
||
<aside id="asset-sidebar" class="sidebar" data-no-refocus>
|
||
|
||
<div class="sidebar-header">
|
||
<span class="sidebar-title">Assets</span>
|
||
<button id="sidebar-refresh" class="sidebar-refresh-btn" aria-label="Refresh assets" title="Refresh asset list">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||
<polyline points="23 4 23 10 17 10"/>
|
||
<path d="M20.49 15a9 9 0 11-2.12-9.36L23 10"/>
|
||
</svg>
|
||
</button>
|
||
<button id="sidebar-filter-btn" class="sidebar-filter-btn" aria-label="Filter assets" title="Filter assets">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/>
|
||
</svg>
|
||
<span class="sidebar-filter-badge" id="sidebar-filter-badge" hidden>0</span>
|
||
</button>
|
||
<button id="sidebar-menu-btn" class="sidebar-menu-btn" aria-label="Display options" title="Display options">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||
<line x1="4" y1="6" x2="20" y2="6"/>
|
||
<line x1="4" y1="12" x2="20" y2="12"/>
|
||
<line x1="4" y1="18" x2="20" y2="18"/>
|
||
<circle cx="9" cy="6" r="2.5" fill="currentColor" stroke="none"/>
|
||
<circle cx="15" cy="12" r="2.5" fill="currentColor" stroke="none"/>
|
||
<circle cx="9" cy="18" r="2.5" fill="currentColor" stroke="none"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="sidebar-search">
|
||
<input type="text" id="sidebar-search" placeholder="Search name, serial, user…" autocomplete="off">
|
||
<button id="sidebar-search-clear" class="sidebar-search-clear" hidden aria-label="Clear search">✕</button>
|
||
</div>
|
||
|
||
<!-- Filter panel — shown when filter button is active -->
|
||
<div id="sidebar-filter-panel" class="sidebar-filter-panel">
|
||
|
||
<div class="sf-section" data-filter-type="lifecycle">
|
||
<div class="sf-label">Lifecycle</div>
|
||
<div class="sf-chips">
|
||
<button class="sf-chip" data-value="Active">Active</button>
|
||
<button class="sf-chip" data-value="Inventory">Inventory</button>
|
||
<button class="sf-chip" data-value="Pre-Deployment">Pre-Deploy</button>
|
||
<button class="sf-chip" data-value="For Repair">Repair</button>
|
||
<button class="sf-chip" data-value="For Upgrade">Upgrade</button>
|
||
<button class="sf-chip" data-value="For Parts">Parts</button>
|
||
<button class="sf-chip" data-value="Decommissioned">Decomm.</button>
|
||
<button class="sf-chip" data-value="Disposed of">Disposed</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="sf-section" data-filter-type="possession">
|
||
<div class="sf-label">Possession</div>
|
||
<div class="sf-chips">
|
||
<button class="sf-chip active" data-value="">All</button>
|
||
<button class="sf-chip" data-value="IT">IT Possession</button>
|
||
<button class="sf-chip" data-value="Deployed">Deployed</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="sf-section" data-filter-type="infra">
|
||
<div class="sf-label">Type</div>
|
||
<div class="sf-chips">
|
||
<button class="sf-chip active" data-value="">All</button>
|
||
<button class="sf-chip" data-value="false">Devices</button>
|
||
<button class="sf-chip" data-value="true">Infrastructure</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="sf-footer">
|
||
<label class="sf-remember-label">
|
||
<input type="checkbox" id="filter-remember">
|
||
<span>Remember</span>
|
||
</label>
|
||
<button id="sf-clear-btn" class="sf-clear-btn">Clear Filters</button>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- Display/sort options menu — shown when menu button is active -->
|
||
<div id="sidebar-menu-panel" class="sidebar-menu-panel">
|
||
|
||
<!-- ▼ Badges -->
|
||
<div class="sidebar-menu-subsection open">
|
||
<button class="sidebar-menu-subsection-header" type="button">
|
||
<svg class="subsection-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="6 9 12 15 18 9"/></svg>
|
||
<span>Badges</span>
|
||
</button>
|
||
<div class="sidebar-menu-subsection-body">
|
||
<div class="sidebar-menu-checkbox-row">
|
||
<label class="sidebar-menu-item"><input type="checkbox" id="badge-vis-possession"><span>Possession</span></label>
|
||
<label class="sidebar-menu-item"><input type="checkbox" id="badge-vis-user"><span>User</span></label>
|
||
<label class="sidebar-menu-item"><input type="checkbox" id="badge-vis-lifecycle"><span>Lifecycle</span></label>
|
||
<label class="sidebar-menu-item"><input type="checkbox" id="badge-vis-infra"><span>Infra</span></label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ▼ Clients -->
|
||
<div class="sidebar-menu-subsection open">
|
||
<button class="sidebar-menu-subsection-header" type="button">
|
||
<svg class="subsection-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="6 9 12 15 18 9"/></svg>
|
||
<span>Clients</span>
|
||
</button>
|
||
<div class="sidebar-menu-subsection-body">
|
||
<div class="sidebar-menu-checkbox-row">
|
||
<label class="sidebar-menu-item"><input type="checkbox" id="disp-show-count"><span>Show count</span></label>
|
||
<label class="sidebar-menu-item"><input type="checkbox" id="disp-show-billable"><span>Show billable</span></label>
|
||
</div>
|
||
<div class="sidebar-menu-row">
|
||
<span class="sidebar-menu-row-label">Hide empty:</span>
|
||
<select id="disp-hide-empty" class="sidebar-menu-select">
|
||
<option value="none">Show all</option>
|
||
<option value="zero-assets">No assets</option>
|
||
<option value="zero-billable">No billable</option>
|
||
</select>
|
||
</div>
|
||
<div class="sidebar-menu-row"><span class="sidebar-menu-row-label">Sort:</span></div>
|
||
<div class="sidebar-sort-chips">
|
||
<button class="sidebar-sort-chip" type="button" data-menu-sort="clients" data-value="alpha">A–Z</button>
|
||
<button class="sidebar-sort-chip" type="button" data-menu-sort="clients" data-value="most-assets">Most Assets</button>
|
||
<button class="sidebar-sort-chip" type="button" data-menu-sort="clients" data-value="most-billable">Billable</button>
|
||
<button class="sidebar-sort-chip" type="button" data-menu-sort="clients" data-value="most-users">Users</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ▼ Assets -->
|
||
<div class="sidebar-menu-subsection open">
|
||
<button class="sidebar-menu-subsection-header" type="button">
|
||
<svg class="subsection-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="6 9 12 15 18 9"/></svg>
|
||
<span>Assets</span>
|
||
</button>
|
||
<div class="sidebar-menu-subsection-body">
|
||
<div class="sidebar-menu-row"><span class="sidebar-menu-row-label">Sort:</span></div>
|
||
<div class="sidebar-sort-chips">
|
||
<button class="sidebar-sort-chip" type="button" data-menu-sort="assets" data-value="default">Default</button>
|
||
<button class="sidebar-sort-chip" type="button" data-menu-sort="assets" data-value="alpha">A–Z</button>
|
||
<button class="sidebar-sort-chip" type="button" data-menu-sort="assets" data-value="user">By User</button>
|
||
<button class="sidebar-sort-chip" type="button" data-menu-sort="assets" data-value="last-sync">Last Sync</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="sidebar-menu-footer">
|
||
<label class="sidebar-menu-item sidebar-menu-remember">
|
||
<input type="checkbox" id="menu-remember">
|
||
<span>Remember settings</span>
|
||
</label>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div id="sidebar-tree" class="sidebar-tree"></div>
|
||
|
||
</aside>
|
||
|
||
<!-- ── Sidebar resize handle ── -->
|
||
<div id="sidebar-resize-handle" class="sidebar-resize-handle" aria-hidden="true"></div>
|
||
|
||
<!-- ── Main ── -->
|
||
<main id="app-main">
|
||
|
||
<!-- Neutral / no mode selected -->
|
||
<section id="view-neutral" class="view active" aria-live="polite"></section>
|
||
|
||
<!-- Idle / Ready to Scan -->
|
||
<section id="view-idle" class="view" aria-live="polite">
|
||
<div class="idle-content">
|
||
<div class="scan-indicator">
|
||
<div class="scan-pulse"></div>
|
||
<svg class="scan-icon-large" viewBox="0 0 64 64" fill="none" stroke="currentColor" stroke-width="2.5">
|
||
<rect x="8" y="14" width="5" height="36" rx="1"/>
|
||
<rect x="16" y="14" width="3" height="36" rx="1"/>
|
||
<rect x="22" y="14" width="7" height="36" rx="1"/>
|
||
<rect x="32" y="14" width="3" height="36" rx="1"/>
|
||
<rect x="38" y="14" width="5" height="36" rx="1"/>
|
||
<rect x="46" y="14" width="3" height="36" rx="1"/>
|
||
<rect x="52" y="14" width="4" height="36" rx="1"/>
|
||
<line x1="4" y1="32" x2="60" y2="32" stroke="#C4622A" stroke-width="2" stroke-dasharray="4 2" opacity="0.8"/>
|
||
</svg>
|
||
</div>
|
||
<h2>Ready to Scan</h2>
|
||
<p>Aim your scanner at an asset label, or type an asset ID above</p>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Loading -->
|
||
<section id="view-loading" class="view" aria-live="polite">
|
||
<div class="loading-content">
|
||
<div class="spinner"></div>
|
||
<p>Looking up asset…</p>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Asset Card -->
|
||
<section id="view-asset" class="view" aria-live="polite">
|
||
<div id="asset-card-container"></div>
|
||
</section>
|
||
|
||
<!-- Search Results -->
|
||
<section id="view-search-results" class="view" aria-live="polite">
|
||
<div id="search-results-container"></div>
|
||
</section>
|
||
|
||
<!-- Label Generation -->
|
||
<section id="view-label" class="view" aria-live="polite">
|
||
<div id="label-gen-container"></div>
|
||
</section>
|
||
|
||
<!-- Quick View Dashboard (client role) -->
|
||
<section id="view-quick-view" class="view" aria-live="polite">
|
||
<div id="quick-view-container"></div>
|
||
</section>
|
||
|
||
<!-- Error -->
|
||
<section id="view-error" class="view" aria-live="assertive">
|
||
<div id="error-container"></div>
|
||
</section>
|
||
|
||
</main>
|
||
|
||
</div><!-- /#app-body -->
|
||
|
||
<!-- Toast container -->
|
||
<div id="toast-container" aria-live="polite"></div>
|
||
|
||
<!-- Label Center overlay -->
|
||
<div id="label-center-overlay" class="lc-overlay" hidden aria-modal="true" role="dialog" aria-label="Label Center">
|
||
|
||
<div class="lc-header">
|
||
<div class="lc-header-left">
|
||
<svg class="lc-header-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<polyline points="6 9 6 2 18 2 18 9"/>
|
||
<path d="M6 18H4a2 2 0 01-2-2v-5a2 2 0 012-2h16a2 2 0 012 2v5a2 2 0 01-2 2h-2"/>
|
||
<rect x="6" y="14" width="12" height="8"/>
|
||
</svg>
|
||
<span class="lc-header-title">Label Center</span>
|
||
<span class="lc-header-sub" id="lc-queue-count">0 items queued</span>
|
||
</div>
|
||
<div class="lc-header-actions">
|
||
<button class="btn btn-accent" id="lc-print-btn" disabled>Print All</button>
|
||
<button class="btn btn-ghost" id="lc-close-btn">Close</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="lc-body">
|
||
|
||
<!-- Left: queue list -->
|
||
<div class="lc-queue-panel">
|
||
<div class="lc-queue-panel-header">
|
||
<span class="lc-panel-title">Queued Labels</span>
|
||
<button class="btn btn-ghost" id="lc-clear-all-btn" style="font-size:.78rem;padding:4px 10px">Clear All</button>
|
||
</div>
|
||
<div class="lc-queue-list" id="lc-queue-list"></div>
|
||
</div>
|
||
|
||
<!-- Right: sheet pages -->
|
||
<div class="lc-sheet-panel">
|
||
<div class="lc-sheet-panel-header">
|
||
<select id="lc-sheet-type" class="lc-sheet-select">
|
||
<option value="OL875LP">OL875LP — 2.625" × 1" (30-up)</option>
|
||
<option value="OL25LP">OL25LP — 1.75" × 0.5" (80-up)</option>
|
||
</select>
|
||
<button class="btn btn-ghost" id="lc-autofill-btn" style="font-size:.78rem;padding:4px 10px">Auto-fill all</button>
|
||
<button class="btn btn-ghost btn-danger" id="lc-reset-all-btn" style="font-size:.78rem;padding:4px 10px">Reset all</button>
|
||
</div>
|
||
<div class="lc-sheet-scroll">
|
||
<div class="lc-pages-container" id="lc-pages-container"></div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- Assignment picker flyout (positioned by JS) -->
|
||
<div class="lc-picker" id="lc-picker" hidden>
|
||
<div class="lc-picker-title">Assign to Position <span id="lc-picker-pos-label"></span></div>
|
||
|
||
<!-- Asset search -->
|
||
<div class="lc-picker-search-wrap">
|
||
<svg class="lc-picker-search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
||
</svg>
|
||
<input type="text" id="lc-picker-search" class="lc-picker-search-input"
|
||
placeholder="Search assets…" autocomplete="off" spellcheck="false">
|
||
</div>
|
||
<div id="lc-picker-search-results" class="lc-picker-search-results" hidden></div>
|
||
|
||
<div class="lc-picker-section-label">Queued labels</div>
|
||
<div class="lc-picker-list" id="lc-picker-list"></div>
|
||
<button class="btn btn-ghost" id="lc-picker-cancel" style="width:100%;margin-top:8px;font-size:.8rem">Cancel</button>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- Camera scan overlay -->
|
||
<div id="camera-overlay" class="camera-overlay" hidden aria-modal="true" role="dialog" aria-label="Camera Scanner">
|
||
<div class="camera-overlay-inner">
|
||
<div class="camera-header">
|
||
<span class="camera-title">Camera Scanner</span>
|
||
<button class="camera-close-btn" id="btn-camera-close" aria-label="Close camera scanner">×</button>
|
||
</div>
|
||
<div class="camera-viewport">
|
||
<video id="camera-video" autoplay playsinline muted></video>
|
||
<div class="camera-reticle" aria-hidden="true">
|
||
<div class="camera-scan-line"></div>
|
||
</div>
|
||
</div>
|
||
<p class="camera-status" id="camera-status">Initializing camera…</p>
|
||
</div>
|
||
</div>
|
||
|
||
<script type="module" src="/app.js"></script>
|
||
</body>
|
||
</html>
|