@extends('layout') @section('title') <title>Scan QR</title> @endsection @section('content') <section class="container py-4"> <h1 class="h4 mb-2">Scan Checkpoint QR</h1> <p class="text-muted mb-3"> Point your camera at the QR on the sign. Voice guidance will play automatically after we update your location. </p> <div id="reader" style="max-width: 520px; aspect-ratio: 1 / 1; background:#f8f9fa; border-radius:12px;"></div> <div id="scan-status" class="small text-muted mt-2"></div> <div class="mt-3 d-flex gap-2"> <a href="{{ url()->previous() ?: route('map.show','floor1') }}" class="btn btn-outline-secondary">Back</a> </div> </section> {{-- Local copy: no CORS/CSP issues --}} <script src="{{ asset('vendor/html5-qrcode/html5-qrcode.min.js') }}"></script> <script> (function() { const box = document.getElementById('reader'); const status = document.getElementById('scan-status'); function setStatus(msg, isErr=false) { status.textContent = msg || ''; status.classList.toggle('text-danger', !!isErr); } // Secure-origin check const isSecure = location.protocol === 'https:' || location.hostname === 'localhost' || location.hostname === '127.0.0.1'; if (!isSecure) { setStatus('Camera requires HTTPS or http://localhost. Use http://localhost:8000 or HTTPS (Valet/ngrok).', true); return; } if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { setStatus('This browser does not support camera access (getUserMedia). Try a modern browser.', true); return; } function go(url) { sessionStorage.setItem('map_voice_unlocked', '1'); localStorage.setItem('map_voice_enabled', '1'); try { const u = new URL(url, window.location.origin); if (!u.searchParams.has('s')) u.searchParams.set('s','1'); window.location.href = u.toString(); } catch { window.location.href = url; } } function onScanSuccess(text) { try { const u = new URL(text); go(u.toString()); return; } catch {} let path = text.trim(); if (/^https?:/i.test(path)) { go(path); return; } // Compose /qr/{slug} (supports ?to=...) const [slugPart, queryPart] = path.split('?'); let url = new URL("{{ route('nav.qr', ['slug'=>'__SLUG__']) }}".replace('__SLUG__','')); url.pathname += encodeURIComponent(slugPart); if (queryPart) { for (const kv of queryPart.split('&')) { const [k,v] = kv.split('='); if (k) url.searchParams.set(k, v ?? ''); } } url.searchParams.set('s','1'); go(url.toString()); } function onScanFailure(err) { // per-frame decode errors are normal; keep silent } // Start the high-level scanner; prefers the back camera setStatus('Requesting camera permission…'); const scanner = new Html5QrcodeScanner('reader', { fps: 10, qrbox: 300, rememberLastUsedCamera: true }, false); scanner.render(onScanSuccess, onScanFailure); setTimeout(() => { if (!document.querySelector('#reader video')) { setStatus('If you don’t see the camera prompt, allow camera access in the browser/site settings and reload.', true); } else { setStatus(''); } }, 1500); })(); </script> @endsection