feat(scanner): rebuild as mobile-first three-phase flow
Replace the desktop-first, everything-at-once scanner layout with a
phased mobile-optimized experience: Setup → Scanning → Review.
Phase 1 (Setup): destination picker, game filter, deck mode toggle,
scan history (last 5 sessions).
Phase 2 (Scanning): full-screen camera with auto-start, haptic + sound
feedback on card detection, torch/flash toggle, count pill, bottom-sheet
disambiguation (replaces full-screen modal).
Phase 3 (Review): card list with inline condition/foil/qty edits,
batch confirm, 30-second undo, deck progress indicator.
New features:
- Deck mode (progress toward 40/60/99 card target)
- Scan history (persisted to localStorage)
- Sound feedback (Web Audio oscillator, configurable)
- Offline queue (localStorage persistence + auto-retry on reconnect)
- Camera flash/torch toggle
- Batch ownership API (replaces N+1 per-card fetches)
- Visibility pause (detection loop stops when tab is backgrounded)
Convoy: scanner-rebuild
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 09:42:58 -04:00
|
|
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
2026-08-14 21:20:43 -04:00
|
|
|
import GlassSurface from '../ui/GlassSurface.js';
|
feat(scanner): rebuild as mobile-first three-phase flow
Replace the desktop-first, everything-at-once scanner layout with a
phased mobile-optimized experience: Setup → Scanning → Review.
Phase 1 (Setup): destination picker, game filter, deck mode toggle,
scan history (last 5 sessions).
Phase 2 (Scanning): full-screen camera with auto-start, haptic + sound
feedback on card detection, torch/flash toggle, count pill, bottom-sheet
disambiguation (replaces full-screen modal).
Phase 3 (Review): card list with inline condition/foil/qty edits,
batch confirm, 30-second undo, deck progress indicator.
New features:
- Deck mode (progress toward 40/60/99 card target)
- Scan history (persisted to localStorage)
- Sound feedback (Web Audio oscillator, configurable)
- Offline queue (localStorage persistence + auto-retry on reconnect)
- Camera flash/torch toggle
- Batch ownership API (replaces N+1 per-card fetches)
- Visibility pause (detection loop stops when tab is backgrounded)
Convoy: scanner-rebuild
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 09:42:58 -04:00
|
|
|
import ScannerToast from './ScannerToast.js';
|
|
|
|
|
import ScannerCountPill from './ScannerCountPill.js';
|
2026-08-14 21:20:43 -04:00
|
|
|
import ScannerScanPeek from './ScannerScanPeek.js';
|
feat(scanner): rebuild as mobile-first three-phase flow
Replace the desktop-first, everything-at-once scanner layout with a
phased mobile-optimized experience: Setup → Scanning → Review.
Phase 1 (Setup): destination picker, game filter, deck mode toggle,
scan history (last 5 sessions).
Phase 2 (Scanning): full-screen camera with auto-start, haptic + sound
feedback on card detection, torch/flash toggle, count pill, bottom-sheet
disambiguation (replaces full-screen modal).
Phase 3 (Review): card list with inline condition/foil/qty edits,
batch confirm, 30-second undo, deck progress indicator.
New features:
- Deck mode (progress toward 40/60/99 card target)
- Scan history (persisted to localStorage)
- Sound feedback (Web Audio oscillator, configurable)
- Offline queue (localStorage persistence + auto-retry on reconnect)
- Camera flash/torch toggle
- Batch ownership API (replaces N+1 per-card fetches)
- Visibility pause (detection loop stops when tab is backgrounded)
Convoy: scanner-rebuild
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 09:42:58 -04:00
|
|
|
import ScannerDisambiguation from './ScannerDisambiguation.js';
|
|
|
|
|
import { useScannerSound } from '../../lib/use-scanner-sound.js';
|
|
|
|
|
import { useScannerFlash } from '../../lib/use-scanner-flash.js';
|
|
|
|
|
|
|
|
|
|
const TOAST_DURATION_MS = 2500;
|
|
|
|
|
|
2026-08-14 21:20:43 -04:00
|
|
|
function DetectionFrame({ card, videoMetrics }) {
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
className="absolute pointer-events-none scan-frame"
|
|
|
|
|
style={{
|
|
|
|
|
left: `${(card.bounds.x / videoMetrics.width) * 100}%`,
|
|
|
|
|
top: `${(card.bounds.y / videoMetrics.height) * 100}%`,
|
|
|
|
|
width: `${(card.bounds.width / videoMetrics.width) * 100}%`,
|
|
|
|
|
height: `${(card.bounds.height / videoMetrics.height) * 100}%`,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<span className="scan-bracket scan-bracket-tl" aria-hidden="true" />
|
|
|
|
|
<span className="scan-bracket scan-bracket-tr" aria-hidden="true" />
|
|
|
|
|
<span className="scan-bracket scan-bracket-bl" aria-hidden="true" />
|
|
|
|
|
<span className="scan-bracket scan-bracket-br" aria-hidden="true" />
|
|
|
|
|
<span className="scan-line" aria-hidden="true" />
|
|
|
|
|
<style jsx>{`
|
|
|
|
|
.scan-bracket {
|
|
|
|
|
position: absolute;
|
|
|
|
|
width: 22px;
|
|
|
|
|
height: 22px;
|
|
|
|
|
border-color: var(--accent-ember);
|
|
|
|
|
border-style: solid;
|
|
|
|
|
filter: drop-shadow(0 0 6px color-mix(in srgb, var(--accent-flame) 55%, transparent));
|
|
|
|
|
}
|
|
|
|
|
.scan-bracket-tl { top: 0; left: 0; border-width: 3px 0 0 3px; }
|
|
|
|
|
.scan-bracket-tr { top: 0; right: 0; border-width: 3px 3px 0 0; }
|
|
|
|
|
.scan-bracket-bl { bottom: 0; left: 0; border-width: 0 0 3px 3px; }
|
|
|
|
|
.scan-bracket-br { bottom: 0; right: 0; border-width: 0 3px 3px 0; }
|
|
|
|
|
.scan-line {
|
|
|
|
|
position: absolute;
|
|
|
|
|
left: 8%;
|
|
|
|
|
right: 8%;
|
|
|
|
|
height: 2px;
|
|
|
|
|
top: 0;
|
|
|
|
|
background: linear-gradient(
|
|
|
|
|
90deg,
|
|
|
|
|
transparent,
|
|
|
|
|
var(--accent-ember),
|
|
|
|
|
var(--accent-flame),
|
|
|
|
|
var(--accent-ember),
|
|
|
|
|
transparent
|
|
|
|
|
);
|
|
|
|
|
box-shadow: 0 0 10px color-mix(in srgb, var(--accent-flame) 70%, transparent);
|
|
|
|
|
animation: scan-sweep 1.6s var(--motion-ease-linear, linear) infinite;
|
|
|
|
|
}
|
|
|
|
|
@keyframes scan-sweep {
|
|
|
|
|
0% { top: 8%; opacity: 0.35; }
|
|
|
|
|
50% { top: 88%; opacity: 1; }
|
|
|
|
|
100% { top: 8%; opacity: 0.35; }
|
|
|
|
|
}
|
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
|
|
|
.scan-line {
|
|
|
|
|
animation: none;
|
|
|
|
|
top: 50%;
|
|
|
|
|
opacity: 0.85;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`}</style>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
feat(scanner): rebuild as mobile-first three-phase flow
Replace the desktop-first, everything-at-once scanner layout with a
phased mobile-optimized experience: Setup → Scanning → Review.
Phase 1 (Setup): destination picker, game filter, deck mode toggle,
scan history (last 5 sessions).
Phase 2 (Scanning): full-screen camera with auto-start, haptic + sound
feedback on card detection, torch/flash toggle, count pill, bottom-sheet
disambiguation (replaces full-screen modal).
Phase 3 (Review): card list with inline condition/foil/qty edits,
batch confirm, 30-second undo, deck progress indicator.
New features:
- Deck mode (progress toward 40/60/99 card target)
- Scan history (persisted to localStorage)
- Sound feedback (Web Audio oscillator, configurable)
- Offline queue (localStorage persistence + auto-retry on reconnect)
- Camera flash/torch toggle
- Batch ownership API (replaces N+1 per-card fetches)
- Visibility pause (detection loop stops when tab is backgrounded)
Convoy: scanner-rebuild
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 09:42:58 -04:00
|
|
|
}
|
|
|
|
|
|
2026-08-14 21:20:43 -04:00
|
|
|
function ChromeIconButton({
|
|
|
|
|
onClick,
|
|
|
|
|
label,
|
|
|
|
|
pressed,
|
|
|
|
|
busy,
|
|
|
|
|
children,
|
|
|
|
|
className = '',
|
|
|
|
|
}) {
|
|
|
|
|
return (
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={onClick}
|
|
|
|
|
className={`w-11 h-11 rounded-full flex items-center justify-center transition-transform active:scale-95 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 ${className}`}
|
|
|
|
|
style={{
|
|
|
|
|
minWidth: 44,
|
|
|
|
|
minHeight: 44,
|
|
|
|
|
'--tw-ring-color': 'var(--accent-ember)',
|
|
|
|
|
'--tw-ring-offset-color': 'transparent',
|
|
|
|
|
}}
|
|
|
|
|
aria-label={label}
|
|
|
|
|
aria-pressed={pressed}
|
|
|
|
|
aria-busy={busy || undefined}
|
|
|
|
|
disabled={busy}
|
|
|
|
|
>
|
|
|
|
|
<GlassSurface
|
|
|
|
|
as="span"
|
|
|
|
|
tint="mid"
|
|
|
|
|
rim="subtle"
|
|
|
|
|
blur="mid"
|
|
|
|
|
className="w-full h-full rounded-full flex items-center justify-center"
|
|
|
|
|
style={{ color: 'var(--text-primary)' }}
|
|
|
|
|
>
|
|
|
|
|
{children}
|
|
|
|
|
</GlassSurface>
|
|
|
|
|
</button>
|
|
|
|
|
);
|
feat(scanner): rebuild as mobile-first three-phase flow
Replace the desktop-first, everything-at-once scanner layout with a
phased mobile-optimized experience: Setup → Scanning → Review.
Phase 1 (Setup): destination picker, game filter, deck mode toggle,
scan history (last 5 sessions).
Phase 2 (Scanning): full-screen camera with auto-start, haptic + sound
feedback on card detection, torch/flash toggle, count pill, bottom-sheet
disambiguation (replaces full-screen modal).
Phase 3 (Review): card list with inline condition/foil/qty edits,
batch confirm, 30-second undo, deck progress indicator.
New features:
- Deck mode (progress toward 40/60/99 card target)
- Scan history (persisted to localStorage)
- Sound feedback (Web Audio oscillator, configurable)
- Offline queue (localStorage persistence + auto-retry on reconnect)
- Camera flash/torch toggle
- Batch ownership API (replaces N+1 per-card fetches)
- Visibility pause (detection loop stops when tab is backgrounded)
Convoy: scanner-rebuild
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 09:42:58 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default function ScannerCamera({
|
|
|
|
|
queue,
|
|
|
|
|
camera,
|
|
|
|
|
identification,
|
2026-08-14 21:20:43 -04:00
|
|
|
onBack = () => {},
|
|
|
|
|
onOpenCheckout = () => {},
|
|
|
|
|
onGalleryIdentify,
|
|
|
|
|
latestPeekCard = null,
|
|
|
|
|
cartCount,
|
|
|
|
|
isCheckoutOpen = false,
|
|
|
|
|
verificationPausedRef,
|
feat(scanner): rebuild as mobile-first three-phase flow
Replace the desktop-first, everything-at-once scanner layout with a
phased mobile-optimized experience: Setup → Scanning → Review.
Phase 1 (Setup): destination picker, game filter, deck mode toggle,
scan history (last 5 sessions).
Phase 2 (Scanning): full-screen camera with auto-start, haptic + sound
feedback on card detection, torch/flash toggle, count pill, bottom-sheet
disambiguation (replaces full-screen modal).
Phase 3 (Review): card list with inline condition/foil/qty edits,
batch confirm, 30-second undo, deck progress indicator.
New features:
- Deck mode (progress toward 40/60/99 card target)
- Scan history (persisted to localStorage)
- Sound feedback (Web Audio oscillator, configurable)
- Offline queue (localStorage persistence + auto-retry on reconnect)
- Camera flash/torch toggle
- Batch ownership API (replaces N+1 per-card fetches)
- Visibility pause (detection loop stops when tab is backgrounded)
Convoy: scanner-rebuild
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 09:42:58 -04:00
|
|
|
}) {
|
|
|
|
|
const [toast, setToast] = useState({ message: '', visible: false, type: 'success' });
|
2026-08-14 21:20:43 -04:00
|
|
|
const [galleryBusy, setGalleryBusy] = useState(false);
|
feat(scanner): rebuild as mobile-first three-phase flow
Replace the desktop-first, everything-at-once scanner layout with a
phased mobile-optimized experience: Setup → Scanning → Review.
Phase 1 (Setup): destination picker, game filter, deck mode toggle,
scan history (last 5 sessions).
Phase 2 (Scanning): full-screen camera with auto-start, haptic + sound
feedback on card detection, torch/flash toggle, count pill, bottom-sheet
disambiguation (replaces full-screen modal).
Phase 3 (Review): card list with inline condition/foil/qty edits,
batch confirm, 30-second undo, deck progress indicator.
New features:
- Deck mode (progress toward 40/60/99 card target)
- Scan history (persisted to localStorage)
- Sound feedback (Web Audio oscillator, configurable)
- Offline queue (localStorage persistence + auto-retry on reconnect)
- Camera flash/torch toggle
- Batch ownership API (replaces N+1 per-card fetches)
- Visibility pause (detection loop stops when tab is backgrounded)
Convoy: scanner-rebuild
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 09:42:58 -04:00
|
|
|
const toastTimerRef = useRef(null);
|
|
|
|
|
const prevCountRef = useRef(queue.scannedCards?.length ?? 0);
|
2026-08-14 21:20:43 -04:00
|
|
|
const galleryInputRef = useRef(null);
|
feat(scanner): rebuild as mobile-first three-phase flow
Replace the desktop-first, everything-at-once scanner layout with a
phased mobile-optimized experience: Setup → Scanning → Review.
Phase 1 (Setup): destination picker, game filter, deck mode toggle,
scan history (last 5 sessions).
Phase 2 (Scanning): full-screen camera with auto-start, haptic + sound
feedback on card detection, torch/flash toggle, count pill, bottom-sheet
disambiguation (replaces full-screen modal).
Phase 3 (Review): card list with inline condition/foil/qty edits,
batch confirm, 30-second undo, deck progress indicator.
New features:
- Deck mode (progress toward 40/60/99 card target)
- Scan history (persisted to localStorage)
- Sound feedback (Web Audio oscillator, configurable)
- Offline queue (localStorage persistence + auto-retry on reconnect)
- Camera flash/torch toggle
- Batch ownership API (replaces N+1 per-card fetches)
- Visibility pause (detection loop stops when tab is backgrounded)
Convoy: scanner-rebuild
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 09:42:58 -04:00
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
videoRef,
|
|
|
|
|
canvasRef,
|
|
|
|
|
detectionCanvasRef,
|
|
|
|
|
isStreaming,
|
|
|
|
|
trackedCards,
|
|
|
|
|
videoMetrics,
|
|
|
|
|
startCamera,
|
|
|
|
|
streamRef,
|
2026-08-14 21:20:43 -04:00
|
|
|
facingMode,
|
|
|
|
|
switchFacingMode,
|
feat(scanner): rebuild as mobile-first three-phase flow
Replace the desktop-first, everything-at-once scanner layout with a
phased mobile-optimized experience: Setup → Scanning → Review.
Phase 1 (Setup): destination picker, game filter, deck mode toggle,
scan history (last 5 sessions).
Phase 2 (Scanning): full-screen camera with auto-start, haptic + sound
feedback on card detection, torch/flash toggle, count pill, bottom-sheet
disambiguation (replaces full-screen modal).
Phase 3 (Review): card list with inline condition/foil/qty edits,
batch confirm, 30-second undo, deck progress indicator.
New features:
- Deck mode (progress toward 40/60/99 card target)
- Scan history (persisted to localStorage)
- Sound feedback (Web Audio oscillator, configurable)
- Offline queue (localStorage persistence + auto-retry on reconnect)
- Camera flash/torch toggle
- Batch ownership API (replaces N+1 per-card fetches)
- Visibility pause (detection loop stops when tab is backgrounded)
Convoy: scanner-rebuild
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 09:42:58 -04:00
|
|
|
} = camera;
|
|
|
|
|
|
|
|
|
|
const sound = useScannerSound();
|
|
|
|
|
const flash = useScannerFlash(streamRef);
|
|
|
|
|
|
2026-08-14 21:20:43 -04:00
|
|
|
const resolvedCartCount = cartCount ?? queue.scannedCards?.length ?? 0;
|
|
|
|
|
|
feat(scanner): rebuild as mobile-first three-phase flow
Replace the desktop-first, everything-at-once scanner layout with a
phased mobile-optimized experience: Setup → Scanning → Review.
Phase 1 (Setup): destination picker, game filter, deck mode toggle,
scan history (last 5 sessions).
Phase 2 (Scanning): full-screen camera with auto-start, haptic + sound
feedback on card detection, torch/flash toggle, count pill, bottom-sheet
disambiguation (replaces full-screen modal).
Phase 3 (Review): card list with inline condition/foil/qty edits,
batch confirm, 30-second undo, deck progress indicator.
New features:
- Deck mode (progress toward 40/60/99 card target)
- Scan history (persisted to localStorage)
- Sound feedback (Web Audio oscillator, configurable)
- Offline queue (localStorage persistence + auto-retry on reconnect)
- Camera flash/torch toggle
- Batch ownership API (replaces N+1 per-card fetches)
- Visibility pause (detection loop stops when tab is backgrounded)
Convoy: scanner-rebuild
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 09:42:58 -04:00
|
|
|
useEffect(() => {
|
|
|
|
|
startCamera();
|
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- one-shot mount
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const currentCount = queue.scannedCards?.length ?? 0;
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (currentCount > prevCountRef.current) {
|
|
|
|
|
const newest = queue.scannedCards[0];
|
2026-08-14 21:20:43 -04:00
|
|
|
const cardName = newest?.name || newest?.card?.name || 'Card';
|
feat(scanner): rebuild as mobile-first three-phase flow
Replace the desktop-first, everything-at-once scanner layout with a
phased mobile-optimized experience: Setup → Scanning → Review.
Phase 1 (Setup): destination picker, game filter, deck mode toggle,
scan history (last 5 sessions).
Phase 2 (Scanning): full-screen camera with auto-start, haptic + sound
feedback on card detection, torch/flash toggle, count pill, bottom-sheet
disambiguation (replaces full-screen modal).
Phase 3 (Review): card list with inline condition/foil/qty edits,
batch confirm, 30-second undo, deck progress indicator.
New features:
- Deck mode (progress toward 40/60/99 card target)
- Scan history (persisted to localStorage)
- Sound feedback (Web Audio oscillator, configurable)
- Offline queue (localStorage persistence + auto-retry on reconnect)
- Camera flash/torch toggle
- Batch ownership API (replaces N+1 per-card fetches)
- Visibility pause (detection loop stops when tab is backgrounded)
Convoy: scanner-rebuild
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 09:42:58 -04:00
|
|
|
|
|
|
|
|
navigator.vibrate?.(50);
|
|
|
|
|
sound.playSuccess();
|
|
|
|
|
|
|
|
|
|
clearTimeout(toastTimerRef.current);
|
2026-08-14 21:20:43 -04:00
|
|
|
setToast({ message: `${cardName} added`, visible: true, type: 'success' });
|
feat(scanner): rebuild as mobile-first three-phase flow
Replace the desktop-first, everything-at-once scanner layout with a
phased mobile-optimized experience: Setup → Scanning → Review.
Phase 1 (Setup): destination picker, game filter, deck mode toggle,
scan history (last 5 sessions).
Phase 2 (Scanning): full-screen camera with auto-start, haptic + sound
feedback on card detection, torch/flash toggle, count pill, bottom-sheet
disambiguation (replaces full-screen modal).
Phase 3 (Review): card list with inline condition/foil/qty edits,
batch confirm, 30-second undo, deck progress indicator.
New features:
- Deck mode (progress toward 40/60/99 card target)
- Scan history (persisted to localStorage)
- Sound feedback (Web Audio oscillator, configurable)
- Offline queue (localStorage persistence + auto-retry on reconnect)
- Camera flash/torch toggle
- Batch ownership API (replaces N+1 per-card fetches)
- Visibility pause (detection loop stops when tab is backgrounded)
Convoy: scanner-rebuild
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 09:42:58 -04:00
|
|
|
toastTimerRef.current = setTimeout(() => {
|
|
|
|
|
setToast((t) => ({ ...t, visible: false }));
|
|
|
|
|
}, TOAST_DURATION_MS);
|
|
|
|
|
}
|
|
|
|
|
prevCountRef.current = currentCount;
|
|
|
|
|
}, [currentCount]); // eslint-disable-line react-hooks/exhaustive-deps -- intentionally only reacts to count
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
return () => clearTimeout(toastTimerRef.current);
|
|
|
|
|
}, []);
|
|
|
|
|
|
2026-08-14 21:20:43 -04:00
|
|
|
useEffect(() => {
|
|
|
|
|
if (facingMode === 'user' && flash.flashOn) {
|
|
|
|
|
flash.toggleFlash();
|
|
|
|
|
}
|
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- only react to facing mode changes
|
|
|
|
|
}, [facingMode]);
|
|
|
|
|
|
|
|
|
|
const handleGalleryClick = useCallback(() => {
|
|
|
|
|
galleryInputRef.current?.click();
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const handleGalleryChange = useCallback(
|
|
|
|
|
async (event) => {
|
|
|
|
|
const file = event.target.files?.[0];
|
|
|
|
|
event.target.value = '';
|
|
|
|
|
if (!file) return;
|
|
|
|
|
|
|
|
|
|
const identify =
|
|
|
|
|
onGalleryIdentify ||
|
|
|
|
|
identification.identifyFromGalleryFile?.bind(identification);
|
|
|
|
|
|
|
|
|
|
if (!identify) return;
|
|
|
|
|
|
|
|
|
|
setGalleryBusy(true);
|
|
|
|
|
try {
|
|
|
|
|
await identify(file);
|
|
|
|
|
} finally {
|
|
|
|
|
setGalleryBusy(false);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[identification, onGalleryIdentify]
|
|
|
|
|
);
|
feat(scanner): rebuild as mobile-first three-phase flow
Replace the desktop-first, everything-at-once scanner layout with a
phased mobile-optimized experience: Setup → Scanning → Review.
Phase 1 (Setup): destination picker, game filter, deck mode toggle,
scan history (last 5 sessions).
Phase 2 (Scanning): full-screen camera with auto-start, haptic + sound
feedback on card detection, torch/flash toggle, count pill, bottom-sheet
disambiguation (replaces full-screen modal).
Phase 3 (Review): card list with inline condition/foil/qty edits,
batch confirm, 30-second undo, deck progress indicator.
New features:
- Deck mode (progress toward 40/60/99 card target)
- Scan history (persisted to localStorage)
- Sound feedback (Web Audio oscillator, configurable)
- Offline queue (localStorage persistence + auto-retry on reconnect)
- Camera flash/torch toggle
- Batch ownership API (replaces N+1 per-card fetches)
- Visibility pause (detection loop stops when tab is backgrounded)
Convoy: scanner-rebuild
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 09:42:58 -04:00
|
|
|
|
|
|
|
|
const foundCards = trackedCards.filter(
|
|
|
|
|
(c) => c.status === 'confirmed' || c.status === 'scanned'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const hasMetrics = videoMetrics.width > 0 && videoMetrics.height > 0;
|
2026-08-14 21:20:43 -04:00
|
|
|
const showFlash = flash.flashSupported && facingMode === 'environment';
|
|
|
|
|
const scanStatus = isCheckoutOpen
|
|
|
|
|
? 'Checkout open'
|
|
|
|
|
: isStreaming
|
|
|
|
|
? 'Scanning…'
|
|
|
|
|
: 'Starting camera…';
|
|
|
|
|
|
|
|
|
|
const switchCameraLabel =
|
|
|
|
|
facingMode === 'environment' ? 'Switch to front camera' : 'Switch to rear camera';
|
feat(scanner): rebuild as mobile-first three-phase flow
Replace the desktop-first, everything-at-once scanner layout with a
phased mobile-optimized experience: Setup → Scanning → Review.
Phase 1 (Setup): destination picker, game filter, deck mode toggle,
scan history (last 5 sessions).
Phase 2 (Scanning): full-screen camera with auto-start, haptic + sound
feedback on card detection, torch/flash toggle, count pill, bottom-sheet
disambiguation (replaces full-screen modal).
Phase 3 (Review): card list with inline condition/foil/qty edits,
batch confirm, 30-second undo, deck progress indicator.
New features:
- Deck mode (progress toward 40/60/99 card target)
- Scan history (persisted to localStorage)
- Sound feedback (Web Audio oscillator, configurable)
- Offline queue (localStorage persistence + auto-retry on reconnect)
- Camera flash/torch toggle
- Batch ownership API (replaces N+1 per-card fetches)
- Visibility pause (detection loop stops when tab is backgrounded)
Convoy: scanner-rebuild
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 09:42:58 -04:00
|
|
|
|
|
|
|
|
return (
|
2026-08-14 21:20:43 -04:00
|
|
|
<div className="relative w-full min-h-[100dvh] max-md:rounded-none max-md:min-h-[100dvh] overflow-hidden">
|
feat(scanner): rebuild as mobile-first three-phase flow
Replace the desktop-first, everything-at-once scanner layout with a
phased mobile-optimized experience: Setup → Scanning → Review.
Phase 1 (Setup): destination picker, game filter, deck mode toggle,
scan history (last 5 sessions).
Phase 2 (Scanning): full-screen camera with auto-start, haptic + sound
feedback on card detection, torch/flash toggle, count pill, bottom-sheet
disambiguation (replaces full-screen modal).
Phase 3 (Review): card list with inline condition/foil/qty edits,
batch confirm, 30-second undo, deck progress indicator.
New features:
- Deck mode (progress toward 40/60/99 card target)
- Scan history (persisted to localStorage)
- Sound feedback (Web Audio oscillator, configurable)
- Offline queue (localStorage persistence + auto-retry on reconnect)
- Camera flash/torch toggle
- Batch ownership API (replaces N+1 per-card fetches)
- Visibility pause (detection loop stops when tab is backgrounded)
Convoy: scanner-rebuild
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 09:42:58 -04:00
|
|
|
<div
|
2026-08-14 21:20:43 -04:00
|
|
|
className="absolute inset-0"
|
|
|
|
|
style={{ backgroundColor: 'var(--bg-tertiary)' }}
|
feat(scanner): rebuild as mobile-first three-phase flow
Replace the desktop-first, everything-at-once scanner layout with a
phased mobile-optimized experience: Setup → Scanning → Review.
Phase 1 (Setup): destination picker, game filter, deck mode toggle,
scan history (last 5 sessions).
Phase 2 (Scanning): full-screen camera with auto-start, haptic + sound
feedback on card detection, torch/flash toggle, count pill, bottom-sheet
disambiguation (replaces full-screen modal).
Phase 3 (Review): card list with inline condition/foil/qty edits,
batch confirm, 30-second undo, deck progress indicator.
New features:
- Deck mode (progress toward 40/60/99 card target)
- Scan history (persisted to localStorage)
- Sound feedback (Web Audio oscillator, configurable)
- Offline queue (localStorage persistence + auto-retry on reconnect)
- Camera flash/torch toggle
- Batch ownership API (replaces N+1 per-card fetches)
- Visibility pause (detection loop stops when tab is backgrounded)
Convoy: scanner-rebuild
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 09:42:58 -04:00
|
|
|
>
|
|
|
|
|
<video
|
|
|
|
|
ref={videoRef}
|
|
|
|
|
className="absolute inset-0 w-full h-full object-cover"
|
|
|
|
|
style={{ display: isStreaming ? 'block' : 'none' }}
|
|
|
|
|
autoPlay
|
|
|
|
|
playsInline
|
|
|
|
|
muted
|
|
|
|
|
aria-label="Card scanner camera feed"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{isStreaming &&
|
|
|
|
|
hasMetrics &&
|
|
|
|
|
foundCards.map((card) => (
|
2026-08-14 21:20:43 -04:00
|
|
|
<DetectionFrame key={card.id} card={card} videoMetrics={videoMetrics} />
|
feat(scanner): rebuild as mobile-first three-phase flow
Replace the desktop-first, everything-at-once scanner layout with a
phased mobile-optimized experience: Setup → Scanning → Review.
Phase 1 (Setup): destination picker, game filter, deck mode toggle,
scan history (last 5 sessions).
Phase 2 (Scanning): full-screen camera with auto-start, haptic + sound
feedback on card detection, torch/flash toggle, count pill, bottom-sheet
disambiguation (replaces full-screen modal).
Phase 3 (Review): card list with inline condition/foil/qty edits,
batch confirm, 30-second undo, deck progress indicator.
New features:
- Deck mode (progress toward 40/60/99 card target)
- Scan history (persisted to localStorage)
- Sound feedback (Web Audio oscillator, configurable)
- Offline queue (localStorage persistence + auto-retry on reconnect)
- Camera flash/torch toggle
- Batch ownership API (replaces N+1 per-card fetches)
- Visibility pause (detection loop stops when tab is backgrounded)
Convoy: scanner-rebuild
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 09:42:58 -04:00
|
|
|
))}
|
|
|
|
|
|
|
|
|
|
{isStreaming && (
|
2026-08-14 21:20:43 -04:00
|
|
|
<div className="absolute top-3 right-3 z-10 md:hidden">
|
|
|
|
|
<GlassSurface
|
|
|
|
|
tint="mid"
|
|
|
|
|
rim="subtle"
|
|
|
|
|
blur="mid"
|
feat(scanner): rebuild as mobile-first three-phase flow
Replace the desktop-first, everything-at-once scanner layout with a
phased mobile-optimized experience: Setup → Scanning → Review.
Phase 1 (Setup): destination picker, game filter, deck mode toggle,
scan history (last 5 sessions).
Phase 2 (Scanning): full-screen camera with auto-start, haptic + sound
feedback on card detection, torch/flash toggle, count pill, bottom-sheet
disambiguation (replaces full-screen modal).
Phase 3 (Review): card list with inline condition/foil/qty edits,
batch confirm, 30-second undo, deck progress indicator.
New features:
- Deck mode (progress toward 40/60/99 card target)
- Scan history (persisted to localStorage)
- Sound feedback (Web Audio oscillator, configurable)
- Offline queue (localStorage persistence + auto-retry on reconnect)
- Camera flash/torch toggle
- Batch ownership API (replaces N+1 per-card fetches)
- Visibility pause (detection loop stops when tab is backgrounded)
Convoy: scanner-rebuild
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 09:42:58 -04:00
|
|
|
className="flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-semibold shadow-lg"
|
2026-08-14 21:20:43 -04:00
|
|
|
style={{ color: 'var(--text-primary)' }}
|
feat(scanner): rebuild as mobile-first three-phase flow
Replace the desktop-first, everything-at-once scanner layout with a
phased mobile-optimized experience: Setup → Scanning → Review.
Phase 1 (Setup): destination picker, game filter, deck mode toggle,
scan history (last 5 sessions).
Phase 2 (Scanning): full-screen camera with auto-start, haptic + sound
feedback on card detection, torch/flash toggle, count pill, bottom-sheet
disambiguation (replaces full-screen modal).
Phase 3 (Review): card list with inline condition/foil/qty edits,
batch confirm, 30-second undo, deck progress indicator.
New features:
- Deck mode (progress toward 40/60/99 card target)
- Scan history (persisted to localStorage)
- Sound feedback (Web Audio oscillator, configurable)
- Offline queue (localStorage persistence + auto-retry on reconnect)
- Camera flash/torch toggle
- Batch ownership API (replaces N+1 per-card fetches)
- Visibility pause (detection loop stops when tab is backgrounded)
Convoy: scanner-rebuild
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 09:42:58 -04:00
|
|
|
>
|
|
|
|
|
<span
|
|
|
|
|
className="w-1.5 h-1.5 rounded-full animate-pulse"
|
2026-08-14 21:20:43 -04:00
|
|
|
style={{ backgroundColor: 'var(--color-error)' }}
|
feat(scanner): rebuild as mobile-first three-phase flow
Replace the desktop-first, everything-at-once scanner layout with a
phased mobile-optimized experience: Setup → Scanning → Review.
Phase 1 (Setup): destination picker, game filter, deck mode toggle,
scan history (last 5 sessions).
Phase 2 (Scanning): full-screen camera with auto-start, haptic + sound
feedback on card detection, torch/flash toggle, count pill, bottom-sheet
disambiguation (replaces full-screen modal).
Phase 3 (Review): card list with inline condition/foil/qty edits,
batch confirm, 30-second undo, deck progress indicator.
New features:
- Deck mode (progress toward 40/60/99 card target)
- Scan history (persisted to localStorage)
- Sound feedback (Web Audio oscillator, configurable)
- Offline queue (localStorage persistence + auto-retry on reconnect)
- Camera flash/torch toggle
- Batch ownership API (replaces N+1 per-card fetches)
- Visibility pause (detection loop stops when tab is backgrounded)
Convoy: scanner-rebuild
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 09:42:58 -04:00
|
|
|
aria-hidden="true"
|
|
|
|
|
/>
|
|
|
|
|
LIVE
|
2026-08-14 21:20:43 -04:00
|
|
|
</GlassSurface>
|
feat(scanner): rebuild as mobile-first three-phase flow
Replace the desktop-first, everything-at-once scanner layout with a
phased mobile-optimized experience: Setup → Scanning → Review.
Phase 1 (Setup): destination picker, game filter, deck mode toggle,
scan history (last 5 sessions).
Phase 2 (Scanning): full-screen camera with auto-start, haptic + sound
feedback on card detection, torch/flash toggle, count pill, bottom-sheet
disambiguation (replaces full-screen modal).
Phase 3 (Review): card list with inline condition/foil/qty edits,
batch confirm, 30-second undo, deck progress indicator.
New features:
- Deck mode (progress toward 40/60/99 card target)
- Scan history (persisted to localStorage)
- Sound feedback (Web Audio oscillator, configurable)
- Offline queue (localStorage persistence + auto-retry on reconnect)
- Camera flash/torch toggle
- Batch ownership API (replaces N+1 per-card fetches)
- Visibility pause (detection loop stops when tab is backgrounded)
Convoy: scanner-rebuild
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 09:42:58 -04:00
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<ScannerToast
|
|
|
|
|
message={toast.message}
|
|
|
|
|
visible={toast.visible}
|
|
|
|
|
type={toast.type}
|
|
|
|
|
/>
|
|
|
|
|
|
2026-08-14 21:20:43 -04:00
|
|
|
<ScannerScanPeek
|
|
|
|
|
key={latestPeekCard?.id ?? latestPeekCard?.name ?? 'peek'}
|
|
|
|
|
card={latestPeekCard}
|
|
|
|
|
onOpenCheckout={onOpenCheckout}
|
|
|
|
|
/>
|
feat(scanner): rebuild as mobile-first three-phase flow
Replace the desktop-first, everything-at-once scanner layout with a
phased mobile-optimized experience: Setup → Scanning → Review.
Phase 1 (Setup): destination picker, game filter, deck mode toggle,
scan history (last 5 sessions).
Phase 2 (Scanning): full-screen camera with auto-start, haptic + sound
feedback on card detection, torch/flash toggle, count pill, bottom-sheet
disambiguation (replaces full-screen modal).
Phase 3 (Review): card list with inline condition/foil/qty edits,
batch confirm, 30-second undo, deck progress indicator.
New features:
- Deck mode (progress toward 40/60/99 card target)
- Scan history (persisted to localStorage)
- Sound feedback (Web Audio oscillator, configurable)
- Offline queue (localStorage persistence + auto-retry on reconnect)
- Camera flash/torch toggle
- Batch ownership API (replaces N+1 per-card fetches)
- Visibility pause (detection loop stops when tab is backgrounded)
Convoy: scanner-rebuild
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 09:42:58 -04:00
|
|
|
|
|
|
|
|
{!isStreaming && (
|
|
|
|
|
<div className="absolute inset-0 flex items-center justify-center">
|
|
|
|
|
<div className="flex flex-col items-center gap-3">
|
|
|
|
|
<div
|
|
|
|
|
className="w-10 h-10 rounded-full border-2 border-t-transparent animate-spin"
|
|
|
|
|
style={{ borderColor: 'var(--accent-ember)', borderTopColor: 'transparent' }}
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
/>
|
|
|
|
|
<span
|
|
|
|
|
className="text-sm font-medium"
|
|
|
|
|
style={{ color: 'var(--text-secondary)' }}
|
|
|
|
|
>
|
|
|
|
|
Starting camera…
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-08-14 21:20:43 -04:00
|
|
|
<div
|
|
|
|
|
className="absolute top-0 left-0 right-0 z-30 px-3 pb-2"
|
|
|
|
|
style={{ paddingTop: 'calc(0.75rem + env(safe-area-inset-top, 0px))' }}
|
|
|
|
|
>
|
|
|
|
|
<GlassSurface
|
|
|
|
|
tint="mid"
|
|
|
|
|
rim="subtle"
|
|
|
|
|
blur="mid"
|
|
|
|
|
className="flex items-center justify-between gap-2 rounded-2xl px-2 py-2"
|
|
|
|
|
>
|
|
|
|
|
<ChromeIconButton onClick={onBack} label="Leave scanner">
|
|
|
|
|
<svg
|
|
|
|
|
width="18"
|
|
|
|
|
height="18"
|
|
|
|
|
viewBox="0 0 24 24"
|
|
|
|
|
fill="none"
|
|
|
|
|
stroke="currentColor"
|
|
|
|
|
strokeWidth="2"
|
|
|
|
|
strokeLinecap="round"
|
|
|
|
|
strokeLinejoin="round"
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
>
|
|
|
|
|
<path d="M19 12H5" />
|
|
|
|
|
<path d="M12 19l-7-7 7-7" />
|
|
|
|
|
</svg>
|
|
|
|
|
</ChromeIconButton>
|
feat(scanner): rebuild as mobile-first three-phase flow
Replace the desktop-first, everything-at-once scanner layout with a
phased mobile-optimized experience: Setup → Scanning → Review.
Phase 1 (Setup): destination picker, game filter, deck mode toggle,
scan history (last 5 sessions).
Phase 2 (Scanning): full-screen camera with auto-start, haptic + sound
feedback on card detection, torch/flash toggle, count pill, bottom-sheet
disambiguation (replaces full-screen modal).
Phase 3 (Review): card list with inline condition/foil/qty edits,
batch confirm, 30-second undo, deck progress indicator.
New features:
- Deck mode (progress toward 40/60/99 card target)
- Scan history (persisted to localStorage)
- Sound feedback (Web Audio oscillator, configurable)
- Offline queue (localStorage persistence + auto-retry on reconnect)
- Camera flash/torch toggle
- Batch ownership API (replaces N+1 per-card fetches)
- Visibility pause (detection loop stops when tab is backgrounded)
Convoy: scanner-rebuild
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 09:42:58 -04:00
|
|
|
|
2026-08-14 21:20:43 -04:00
|
|
|
<h1
|
|
|
|
|
className="text-base font-semibold truncate"
|
|
|
|
|
style={{ color: 'var(--text-primary)' }}
|
|
|
|
|
>
|
|
|
|
|
Scan Cards
|
|
|
|
|
</h1>
|
feat(scanner): rebuild as mobile-first three-phase flow
Replace the desktop-first, everything-at-once scanner layout with a
phased mobile-optimized experience: Setup → Scanning → Review.
Phase 1 (Setup): destination picker, game filter, deck mode toggle,
scan history (last 5 sessions).
Phase 2 (Scanning): full-screen camera with auto-start, haptic + sound
feedback on card detection, torch/flash toggle, count pill, bottom-sheet
disambiguation (replaces full-screen modal).
Phase 3 (Review): card list with inline condition/foil/qty edits,
batch confirm, 30-second undo, deck progress indicator.
New features:
- Deck mode (progress toward 40/60/99 card target)
- Scan history (persisted to localStorage)
- Sound feedback (Web Audio oscillator, configurable)
- Offline queue (localStorage persistence + auto-retry on reconnect)
- Camera flash/torch toggle
- Batch ownership API (replaces N+1 per-card fetches)
- Visibility pause (detection loop stops when tab is backgrounded)
Convoy: scanner-rebuild
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 09:42:58 -04:00
|
|
|
|
2026-08-14 21:20:43 -04:00
|
|
|
<ChromeIconButton
|
|
|
|
|
onClick={handleGalleryClick}
|
|
|
|
|
label="Choose image from gallery"
|
|
|
|
|
busy={galleryBusy}
|
|
|
|
|
>
|
|
|
|
|
{galleryBusy ? (
|
|
|
|
|
<div
|
|
|
|
|
className="w-4 h-4 rounded-full border-2 border-t-transparent animate-spin"
|
|
|
|
|
style={{ borderColor: 'var(--accent-ember)', borderTopColor: 'transparent' }}
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<svg
|
|
|
|
|
width="18"
|
|
|
|
|
height="18"
|
|
|
|
|
viewBox="0 0 24 24"
|
|
|
|
|
fill="none"
|
|
|
|
|
stroke="currentColor"
|
|
|
|
|
strokeWidth="2"
|
|
|
|
|
strokeLinecap="round"
|
|
|
|
|
strokeLinejoin="round"
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
>
|
|
|
|
|
<rect x="3" y="3" width="18" height="18" rx="2" />
|
|
|
|
|
<circle cx="8.5" cy="8.5" r="1.5" />
|
|
|
|
|
<path d="M21 15l-5-5L5 21" />
|
|
|
|
|
</svg>
|
|
|
|
|
)}
|
|
|
|
|
</ChromeIconButton>
|
|
|
|
|
</GlassSurface>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<input
|
|
|
|
|
ref={galleryInputRef}
|
|
|
|
|
type="file"
|
|
|
|
|
accept="image/*"
|
|
|
|
|
className="sr-only"
|
|
|
|
|
tabIndex={-1}
|
|
|
|
|
onChange={handleGalleryChange}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{isStreaming && (
|
|
|
|
|
<div
|
|
|
|
|
className="absolute bottom-0 left-0 right-0 z-30 px-3 pt-2"
|
|
|
|
|
style={{ paddingBottom: 'calc(0.75rem + env(safe-area-inset-bottom, 0px))' }}
|
|
|
|
|
>
|
|
|
|
|
<GlassSurface
|
|
|
|
|
tint="mid"
|
|
|
|
|
rim="subtle"
|
|
|
|
|
blur="mid"
|
|
|
|
|
className="flex items-center justify-between gap-2 rounded-2xl px-2 py-2"
|
|
|
|
|
>
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
{showFlash ? (
|
|
|
|
|
<ChromeIconButton
|
|
|
|
|
onClick={flash.toggleFlash}
|
|
|
|
|
label={flash.flashOn ? 'Turn off flash' : 'Turn on flash'}
|
|
|
|
|
pressed={flash.flashOn}
|
|
|
|
|
className={flash.flashOn ? 'ring-2' : ''}
|
|
|
|
|
>
|
|
|
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
|
|
|
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" />
|
|
|
|
|
</svg>
|
|
|
|
|
</ChromeIconButton>
|
|
|
|
|
) : (
|
|
|
|
|
<ChromeIconButton onClick={switchFacingMode} label={switchCameraLabel}>
|
|
|
|
|
<svg
|
|
|
|
|
width="18"
|
|
|
|
|
height="18"
|
|
|
|
|
viewBox="0 0 24 24"
|
|
|
|
|
fill="none"
|
|
|
|
|
stroke="currentColor"
|
|
|
|
|
strokeWidth="2"
|
|
|
|
|
strokeLinecap="round"
|
|
|
|
|
strokeLinejoin="round"
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
>
|
|
|
|
|
<path d="M11 19H5a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h6" />
|
|
|
|
|
<path d="M13 5h6a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-6" />
|
|
|
|
|
<path d="M8 12h8" />
|
|
|
|
|
<path d="m16 9 3 3-3 3" />
|
|
|
|
|
<path d="m8 15-3-3 3-3" />
|
|
|
|
|
</svg>
|
|
|
|
|
</ChromeIconButton>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{showFlash && (
|
|
|
|
|
<ChromeIconButton onClick={switchFacingMode} label={switchCameraLabel}>
|
|
|
|
|
<svg
|
|
|
|
|
width="18"
|
|
|
|
|
height="18"
|
|
|
|
|
viewBox="0 0 24 24"
|
|
|
|
|
fill="none"
|
|
|
|
|
stroke="currentColor"
|
|
|
|
|
strokeWidth="2"
|
|
|
|
|
strokeLinecap="round"
|
|
|
|
|
strokeLinejoin="round"
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
>
|
|
|
|
|
<path d="M11 19H5a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h6" />
|
|
|
|
|
<path d="M13 5h6a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-6" />
|
|
|
|
|
<path d="M8 12h8" />
|
|
|
|
|
<path d="m16 9 3 3-3 3" />
|
|
|
|
|
<path d="m8 15-3-3 3-3" />
|
|
|
|
|
</svg>
|
|
|
|
|
</ChromeIconButton>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<p
|
|
|
|
|
className="text-xs font-medium truncate text-center flex-1 px-1"
|
|
|
|
|
style={{ color: 'var(--text-secondary)' }}
|
|
|
|
|
aria-live="polite"
|
|
|
|
|
>
|
|
|
|
|
{scanStatus}
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<ScannerCountPill
|
|
|
|
|
count={resolvedCartCount}
|
|
|
|
|
onOpenCheckout={onOpenCheckout}
|
|
|
|
|
/>
|
|
|
|
|
</GlassSurface>
|
|
|
|
|
</div>
|
feat(scanner): rebuild as mobile-first three-phase flow
Replace the desktop-first, everything-at-once scanner layout with a
phased mobile-optimized experience: Setup → Scanning → Review.
Phase 1 (Setup): destination picker, game filter, deck mode toggle,
scan history (last 5 sessions).
Phase 2 (Scanning): full-screen camera with auto-start, haptic + sound
feedback on card detection, torch/flash toggle, count pill, bottom-sheet
disambiguation (replaces full-screen modal).
Phase 3 (Review): card list with inline condition/foil/qty edits,
batch confirm, 30-second undo, deck progress indicator.
New features:
- Deck mode (progress toward 40/60/99 card target)
- Scan history (persisted to localStorage)
- Sound feedback (Web Audio oscillator, configurable)
- Offline queue (localStorage persistence + auto-retry on reconnect)
- Camera flash/torch toggle
- Batch ownership API (replaces N+1 per-card fetches)
- Visibility pause (detection loop stops when tab is backgrounded)
Convoy: scanner-rebuild
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 09:42:58 -04:00
|
|
|
)}
|
|
|
|
|
|
2026-08-14 21:20:43 -04:00
|
|
|
<canvas ref={canvasRef} className="hidden" aria-hidden="true" />
|
|
|
|
|
<canvas ref={detectionCanvasRef} className="hidden" aria-hidden="true" />
|
|
|
|
|
|
feat(scanner): rebuild as mobile-first three-phase flow
Replace the desktop-first, everything-at-once scanner layout with a
phased mobile-optimized experience: Setup → Scanning → Review.
Phase 1 (Setup): destination picker, game filter, deck mode toggle,
scan history (last 5 sessions).
Phase 2 (Scanning): full-screen camera with auto-start, haptic + sound
feedback on card detection, torch/flash toggle, count pill, bottom-sheet
disambiguation (replaces full-screen modal).
Phase 3 (Review): card list with inline condition/foil/qty edits,
batch confirm, 30-second undo, deck progress indicator.
New features:
- Deck mode (progress toward 40/60/99 card target)
- Scan history (persisted to localStorage)
- Sound feedback (Web Audio oscillator, configurable)
- Offline queue (localStorage persistence + auto-retry on reconnect)
- Camera flash/torch toggle
- Batch ownership API (replaces N+1 per-card fetches)
- Visibility pause (detection loop stops when tab is backgrounded)
Convoy: scanner-rebuild
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 09:42:58 -04:00
|
|
|
{identification.disambiguation && (
|
|
|
|
|
<ScannerDisambiguation
|
|
|
|
|
disambiguation={identification.disambiguation}
|
|
|
|
|
submittingReview={identification.submittingReview}
|
|
|
|
|
onPick={identification.handleDisambiguationPick}
|
|
|
|
|
onNotInCatalog={identification.handleNotInCatalog}
|
|
|
|
|
onCancel={identification.cancelDisambiguation}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|