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
|
|
|
|
/* eslint-disable @next/next/no-img-element -- External card image URLs; next/image migration is out of scope. */
|
2026-08-14 21:20:43 -04:00
|
|
|
|
import { useEffect, useRef, useState } from 'react';
|
|
|
|
|
|
import { formatProcessedDestination, VOCAB } from '../../lib/collection-vocabulary.js';
|
2026-08-14 22:42:59 -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
|
|
|
|
|
|
|
|
|
|
const CONDITION_OPTIONS = ['NM', 'LP', 'MP', 'HP', 'DMG'];
|
|
|
|
|
|
|
2026-08-14 21:20:43 -04:00
|
|
|
|
function confidencePercent(confidence) {
|
|
|
|
|
|
if (confidence == null || Number.isNaN(confidence)) return null;
|
|
|
|
|
|
return confidence <= 1 ? Math.round(confidence * 100) : Math.round(confidence);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function confidenceRingColor(pct) {
|
|
|
|
|
|
if (pct == null) return 'var(--border)';
|
|
|
|
|
|
if (pct < 70) return 'var(--accent-ember)';
|
|
|
|
|
|
if (pct < 85) return 'var(--color-warning)';
|
|
|
|
|
|
return 'var(--color-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
|
|
|
|
export default function ReviewCardItem({
|
|
|
|
|
|
card,
|
|
|
|
|
|
collections,
|
|
|
|
|
|
decks,
|
|
|
|
|
|
onIncrement,
|
|
|
|
|
|
onDecrement,
|
|
|
|
|
|
onUpdateMetadata,
|
|
|
|
|
|
onRemove,
|
|
|
|
|
|
onUndo,
|
|
|
|
|
|
isAdding = false,
|
|
|
|
|
|
sessionDestination,
|
2026-08-14 21:20:43 -04:00
|
|
|
|
selected = false,
|
|
|
|
|
|
onToggleSelect,
|
|
|
|
|
|
showCheckbox = false,
|
|
|
|
|
|
confidence,
|
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
|
|
|
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
|
|
|
|
const menuRef = useRef(null);
|
|
|
|
|
|
|
|
|
|
|
|
const resolvedConfidence = confidence ?? card.confidence;
|
|
|
|
|
|
const confidencePct = confidencePercent(resolvedConfidence);
|
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 destinationLabel = card.processedAction
|
|
|
|
|
|
? formatProcessedDestination(card.processedAction)
|
2026-08-14 21:20:43 -04:00
|
|
|
|
: sessionDestination?.label ?? VOCAB.MY_COLLECTION;
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (!menuOpen) return undefined;
|
|
|
|
|
|
|
|
|
|
|
|
const handlePointerDown = (event) => {
|
|
|
|
|
|
if (menuRef.current && !menuRef.current.contains(event.target)) {
|
|
|
|
|
|
setMenuOpen(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('mousedown', handlePointerDown);
|
|
|
|
|
|
return () => document.removeEventListener('mousedown', handlePointerDown);
|
|
|
|
|
|
}, [menuOpen]);
|
|
|
|
|
|
|
|
|
|
|
|
const checkboxAriaLabel =
|
|
|
|
|
|
confidencePct != null
|
|
|
|
|
|
? `Select ${card.name}, ${confidencePct}% confidence`
|
|
|
|
|
|
: `Select ${card.name}`;
|
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 (
|
|
|
|
|
|
<div
|
2026-08-14 22:42:59 -04:00
|
|
|
|
className={`rounded-xl p-3 mb-3 transition-opacity duration-200 ${card.processed ? 'opacity-60' : ''}`}
|
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
|
|
|
|
style={{
|
2026-08-14 22:42:59 -04:00
|
|
|
|
backgroundColor: 'var(--bg-secondary)',
|
|
|
|
|
|
border: '1px solid var(--border)',
|
|
|
|
|
|
borderLeft: !card.processed && !showCheckbox ? '3px solid var(--accent-ember)' : undefined,
|
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 className="flex gap-3">
|
2026-08-14 21:20:43 -04:00
|
|
|
|
{showCheckbox && !card.processed && (
|
|
|
|
|
|
<label
|
|
|
|
|
|
className="flex-shrink-0 flex items-center justify-center"
|
|
|
|
|
|
style={{ minWidth: 44, minHeight: 44 }}
|
|
|
|
|
|
>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
checked={selected}
|
|
|
|
|
|
onChange={onToggleSelect}
|
|
|
|
|
|
className="w-5 h-5 rounded"
|
|
|
|
|
|
style={{ accentColor: 'var(--accent-ember)' }}
|
|
|
|
|
|
aria-label={checkboxAriaLabel}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
|
className="flex-shrink-0 w-10 h-14 rounded-lg overflow-hidden"
|
|
|
|
|
|
style={{
|
|
|
|
|
|
boxShadow: showCheckbox
|
|
|
|
|
|
? `0 0 0 2px ${confidenceRingColor(confidencePct)}`
|
|
|
|
|
|
: undefined,
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
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
|
|
|
|
{card.image_url ? (
|
|
|
|
|
|
<img
|
|
|
|
|
|
src={card.image_url}
|
|
|
|
|
|
alt={card.name}
|
|
|
|
|
|
className="w-full h-full object-cover"
|
|
|
|
|
|
loading="lazy"
|
|
|
|
|
|
/>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div
|
|
|
|
|
|
className="w-full h-full flex items-center justify-center rounded-lg text-xs"
|
|
|
|
|
|
style={{ backgroundColor: 'var(--bg-tertiary)', color: 'var(--text-secondary)' }}
|
|
|
|
|
|
>
|
|
|
|
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
|
|
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="flex-1 min-w-0">
|
|
|
|
|
|
<div className="flex items-start justify-between gap-2">
|
|
|
|
|
|
<div className="min-w-0">
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
2026-08-14 21:20:43 -04:00
|
|
|
|
{!showCheckbox && card.processed ? (
|
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
|
|
|
|
<svg className="w-4 h-4 flex-shrink-0" style={{ color: 'var(--accent-gold)' }} fill="currentColor" viewBox="0 0 20 20" aria-hidden="true">
|
|
|
|
|
|
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
|
|
|
|
|
</svg>
|
2026-08-14 21:20:43 -04:00
|
|
|
|
) : !showCheckbox && !card.processed ? (
|
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-2 h-2 rounded-full flex-shrink-0"
|
|
|
|
|
|
style={{ backgroundColor: 'var(--accent-flame)' }}
|
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
|
/>
|
2026-08-14 21:20:43 -04:00
|
|
|
|
) : 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
|
|
|
|
<h3
|
|
|
|
|
|
className="font-semibold text-sm truncate"
|
|
|
|
|
|
style={{ color: 'var(--text-primary)' }}
|
|
|
|
|
|
>
|
|
|
|
|
|
{card.name}
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p className="text-xs mt-0.5 truncate" style={{ color: 'var(--text-secondary)' }}>
|
|
|
|
|
|
{[card.set, card.cardNumber].filter(Boolean).join(' · ')}
|
|
|
|
|
|
</p>
|
2026-08-14 21:20:43 -04:00
|
|
|
|
{showCheckbox && confidencePct != null && (
|
|
|
|
|
|
<p className="text-xs mt-0.5" style={{ color: 'var(--text-secondary)' }}>
|
|
|
|
|
|
{confidencePct}% match
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
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
|
|
|
|
{showCheckbox && !card.processed ? (
|
|
|
|
|
|
<div className="relative flex-shrink-0" ref={menuRef}>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={() => setMenuOpen((open) => !open)}
|
|
|
|
|
|
className="w-8 h-8 flex items-center justify-center rounded-lg transition-colors duration-150 hover:opacity-80"
|
|
|
|
|
|
style={{ color: 'var(--text-secondary)', minWidth: 44, minHeight: 44 }}
|
|
|
|
|
|
aria-label={`More actions for ${card.name}`}
|
|
|
|
|
|
aria-expanded={menuOpen}
|
|
|
|
|
|
aria-haspopup="menu"
|
|
|
|
|
|
>
|
|
|
|
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
|
|
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 5v.01M12 12v.01M12 19v.01" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
{menuOpen && (
|
2026-08-14 22:42:59 -04:00
|
|
|
|
<GlassSurface
|
|
|
|
|
|
as="div"
|
2026-08-14 21:20:43 -04:00
|
|
|
|
role="menu"
|
2026-08-14 22:42:59 -04:00
|
|
|
|
tint="high"
|
|
|
|
|
|
rim="subtle"
|
|
|
|
|
|
blur="mid"
|
|
|
|
|
|
elevation="ambient"
|
|
|
|
|
|
className="absolute right-0 top-full mt-1 z-10 min-w-[10rem] rounded-xl py-1"
|
2026-08-14 21:20:43 -04:00
|
|
|
|
>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
role="menuitem"
|
|
|
|
|
|
className="w-full px-3 py-2 text-left text-sm hover:opacity-80"
|
|
|
|
|
|
style={{ color: 'var(--text-primary)', minHeight: 44 }}
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
onRemove();
|
|
|
|
|
|
setMenuOpen(false);
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
Remove
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<div className="px-3 py-2" role="none">
|
|
|
|
|
|
<label className="text-xs block mb-1" style={{ color: 'var(--text-secondary)' }}>
|
|
|
|
|
|
Condition
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<select
|
|
|
|
|
|
value={card.condition || 'NM'}
|
|
|
|
|
|
onChange={(e) => onUpdateMetadata({ condition: e.target.value })}
|
|
|
|
|
|
className="w-full px-2 py-1.5 rounded-lg text-xs"
|
|
|
|
|
|
style={{
|
|
|
|
|
|
backgroundColor: 'var(--bg-secondary)',
|
|
|
|
|
|
border: '1px solid var(--border)',
|
|
|
|
|
|
color: 'var(--text-primary)',
|
|
|
|
|
|
minHeight: 44,
|
|
|
|
|
|
}}
|
|
|
|
|
|
aria-label={`Condition for ${card.name}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
{CONDITION_OPTIONS.map((opt) => (
|
|
|
|
|
|
<option key={opt} value={opt}>{opt}</option>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
role="menuitem"
|
|
|
|
|
|
className="w-full px-3 py-2 text-left text-sm hover:opacity-80"
|
|
|
|
|
|
style={{ color: 'var(--text-primary)', minHeight: 44 }}
|
|
|
|
|
|
onClick={() => onUpdateMetadata({ isFoil: !card.isFoil })}
|
|
|
|
|
|
>
|
|
|
|
|
|
{card.isFoil ? 'Mark not foil' : 'Mark foil'}
|
|
|
|
|
|
</button>
|
2026-08-14 22:42:59 -04:00
|
|
|
|
</GlassSurface>
|
2026-08-14 21:20:43 -04:00
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : !card.processed ? (
|
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
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={onRemove}
|
|
|
|
|
|
className="flex-shrink-0 w-8 h-8 flex items-center justify-center rounded-lg transition-colors duration-150 hover:opacity-80"
|
|
|
|
|
|
style={{ color: 'var(--text-secondary)' }}
|
|
|
|
|
|
aria-label={`Remove ${card.name} from scan queue`}
|
|
|
|
|
|
>
|
|
|
|
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
|
|
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</button>
|
2026-08-14 21:20:43 -04:00
|
|
|
|
) : 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
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{card.processed ? (
|
|
|
|
|
|
<div className="flex items-center gap-2 mt-2">
|
|
|
|
|
|
<span className="text-xs" style={{ color: 'var(--text-secondary)' }}>
|
|
|
|
|
|
Added to {destinationLabel}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
{onUndo && (
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={onUndo}
|
|
|
|
|
|
className="text-xs font-medium underline transition-opacity duration-150 hover:opacity-70"
|
|
|
|
|
|
style={{ color: 'var(--accent-ember)', minHeight: '44px', display: 'inline-flex', alignItems: 'center' }}
|
|
|
|
|
|
aria-label={`Undo adding ${card.name}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
Undo
|
|
|
|
|
|
</button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<>
|
2026-08-14 21:20:43 -04:00
|
|
|
|
{!showCheckbox && (
|
|
|
|
|
|
<p className="text-xs mt-1" style={{ color: 'var(--text-secondary)' }}>
|
|
|
|
|
|
{card.condition || 'NM'} · {card.isFoil ? 'Foil' : 'Not Foil'} · Qty: {card.quantity || 1}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
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
|
|
|
|
{!showCheckbox && (
|
|
|
|
|
|
<div className="flex flex-col sm:flex-row gap-2 mt-2">
|
|
|
|
|
|
<label className="inline-flex items-center gap-1.5 text-xs" style={{ color: 'var(--text-secondary)' }}>
|
|
|
|
|
|
<span className="sr-only">Condition for {card.name}</span>
|
|
|
|
|
|
<select
|
|
|
|
|
|
value={card.condition || 'NM'}
|
|
|
|
|
|
onChange={(e) => onUpdateMetadata({ condition: e.target.value })}
|
|
|
|
|
|
className="px-2 py-1.5 rounded-lg text-xs"
|
|
|
|
|
|
style={{
|
|
|
|
|
|
backgroundColor: 'var(--bg-secondary)',
|
|
|
|
|
|
borderColor: 'var(--border)',
|
|
|
|
|
|
color: 'var(--text-primary)',
|
|
|
|
|
|
border: '1px solid var(--border)',
|
|
|
|
|
|
minHeight: '44px',
|
|
|
|
|
|
}}
|
|
|
|
|
|
aria-label={`Condition for ${card.name}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
{CONDITION_OPTIONS.map((opt) => (
|
|
|
|
|
|
<option key={opt} value={opt}>{opt}</option>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</label>
|
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
|
|
|
|
<label
|
|
|
|
|
|
className="inline-flex items-center gap-1.5 text-xs cursor-pointer"
|
|
|
|
|
|
style={{ color: 'var(--text-secondary)', minHeight: '44px' }}
|
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
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
checked={Boolean(card.isFoil)}
|
|
|
|
|
|
onChange={(e) => onUpdateMetadata({ isFoil: e.target.checked })}
|
|
|
|
|
|
className="w-4 h-4 rounded"
|
|
|
|
|
|
style={{ accentColor: 'var(--accent-ember)' }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<span>Foil</span>
|
|
|
|
|
|
</label>
|
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
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
<div className="inline-flex items-center gap-1 mt-2">
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={onDecrement}
|
|
|
|
|
|
disabled={isAdding || (card.quantity || 1) <= 1}
|
|
|
|
|
|
className="w-8 h-8 rounded-lg flex items-center justify-center text-sm font-bold transition-opacity duration-150 hover:opacity-80 disabled:opacity-30 disabled:cursor-not-allowed"
|
|
|
|
|
|
style={{
|
|
|
|
|
|
backgroundColor: 'var(--bg-secondary)',
|
|
|
|
|
|
color: 'var(--text-primary)',
|
|
|
|
|
|
border: '1px solid var(--border)',
|
|
|
|
|
|
minWidth: '44px',
|
|
|
|
|
|
minHeight: '44px',
|
|
|
|
|
|
}}
|
|
|
|
|
|
aria-label={`Decrease quantity of ${card.name}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
−
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<span
|
|
|
|
|
|
className="min-w-[1.5rem] text-center text-sm font-medium"
|
|
|
|
|
|
style={{ color: 'var(--text-primary)' }}
|
|
|
|
|
|
aria-live="polite"
|
|
|
|
|
|
aria-label={`Quantity: ${card.quantity || 1}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
{card.quantity || 1}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={onIncrement}
|
|
|
|
|
|
disabled={isAdding}
|
|
|
|
|
|
className="w-8 h-8 rounded-lg flex items-center justify-center text-sm font-bold transition-opacity duration-150 hover:opacity-80 disabled:opacity-30 disabled:cursor-not-allowed"
|
|
|
|
|
|
style={{
|
|
|
|
|
|
backgroundColor: 'var(--accent-ember)',
|
|
|
|
|
|
color: 'var(--text-primary-dark)',
|
|
|
|
|
|
minWidth: '44px',
|
|
|
|
|
|
minHeight: '44px',
|
|
|
|
|
|
}}
|
|
|
|
|
|
aria-label={`Increase quantity of ${card.name}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
+
|
|
|
|
|
|
</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
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-08-14 21:20:43 -04:00
|
|
|
|
{!showCheckbox && collections?.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
|
|
|
|
<div className="mt-2">
|
|
|
|
|
|
<select
|
|
|
|
|
|
value={card.destinationOverride || ''}
|
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
|
const val = e.target.value;
|
|
|
|
|
|
if (!val) {
|
|
|
|
|
|
onUpdateMetadata({ destinationOverride: null });
|
|
|
|
|
|
} else {
|
|
|
|
|
|
onUpdateMetadata({ destinationOverride: val });
|
|
|
|
|
|
}
|
|
|
|
|
|
}}
|
|
|
|
|
|
className="w-full px-2 py-1.5 rounded-lg text-xs"
|
|
|
|
|
|
style={{
|
|
|
|
|
|
backgroundColor: 'var(--bg-secondary)',
|
|
|
|
|
|
border: '1px solid var(--border)',
|
|
|
|
|
|
color: 'var(--text-secondary)',
|
|
|
|
|
|
minHeight: '44px',
|
|
|
|
|
|
}}
|
|
|
|
|
|
aria-label={`Change destination for ${card.name}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
<option value="">
|
|
|
|
|
|
{sessionDestination ? `Default (${sessionDestination.label})` : 'Change destination'}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
{collections.map((c) => (
|
|
|
|
|
|
<option key={`col-${c.id}`} value={`collection:${c.id}`}>
|
|
|
|
|
|
{c.name}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
))}
|
2026-08-14 21:20:43 -04:00
|
|
|
|
{decks?.map((d) => (
|
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
|
|
|
|
<option key={`deck-${d.id}`} value={`deck:${d.id}`}>
|
|
|
|
|
|
{d.name}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|