feat: leads QR UX — merged confirm modes, faster scanning, correct capture identity
- Merge Rapid + Qualify scan modes into single Confirm mode with two-button card:
"Add & Scan Next" (resets) and "Add & View Lead" (navigates to detail). Same
two-button pattern on the reenable card: "Restore & Scan Next" / "Restore & View Lead".
Stale 'qualify' localStorage values normalized to 'rapid' via $derived.by().
- QR scanner speed: fps 10→25, qrbox 82%→88%, useBarCodeDetectorIfSupported (native
BarcodeDetector API on Chrome/Edge — significantly faster than ZXing JS fallback)
- Fix capture identity stored in external_person_id / group:
licensed exhibit user → their email; shared passcode → 'shared_passcode' label
(not the raw passcode); Aether user bypassing exhibit sign-in → access_type string
('trusted', 'manager', 'super', etc.). Consistent across all three lead capture
components (single scanner, multi scanner, manual search).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -27,7 +27,7 @@
|
|||||||
let {
|
let {
|
||||||
start_qr_scanner = $bindable(true),
|
start_qr_scanner = $bindable(true),
|
||||||
on_qr_scan_result,
|
on_qr_scan_result,
|
||||||
qr_fps = 10,
|
qr_fps = 25,
|
||||||
qr_facing_mode = 'environment'
|
qr_facing_mode = 'environment'
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
@@ -90,9 +90,14 @@
|
|||||||
fps: qr_fps,
|
fps: qr_fps,
|
||||||
// Use a percentage of the viewfinder so it scales on any screen size
|
// Use a percentage of the viewfinder so it scales on any screen size
|
||||||
qrbox: (w: number, h: number) => {
|
qrbox: (w: number, h: number) => {
|
||||||
const side = Math.floor(Math.min(w, h) * 0.82);
|
const side = Math.floor(Math.min(w, h) * 0.88);
|
||||||
return { width: side, height: side };
|
return { width: side, height: side };
|
||||||
}
|
},
|
||||||
|
// Use native BarcodeDetector API on Chrome/Edge — significantly faster
|
||||||
|
// than the JS ZXing fallback used on Firefox/older Safari.
|
||||||
|
// Cast: experimentalFeatures exists at runtime but is missing from the
|
||||||
|
// html5-qrcode TypeScript type definitions for this version.
|
||||||
|
...({ experimentalFeatures: { useBarCodeDetectorIfSupported: true } } as { experimentalFeatures: { useBarCodeDetectorIfSupported: boolean } })
|
||||||
},
|
},
|
||||||
on_scan_success,
|
on_scan_success,
|
||||||
on_scan_error
|
on_scan_error
|
||||||
|
|||||||
@@ -186,7 +186,7 @@
|
|||||||
if (layout === 'badge_3.5x5.5_pvc') {
|
if (layout === 'badge_3.5x5.5_pvc') {
|
||||||
// 3.5" × 5.5" PVC card — single-sided, compact
|
// 3.5" × 5.5" PVC card — single-sided, compact
|
||||||
return {
|
return {
|
||||||
grp_name_title: '1.8in',
|
grp_name_title: '1.6in',
|
||||||
grp_name_title_flex: 'around',
|
grp_name_title_flex: 'around',
|
||||||
name: '1.4in',
|
name: '1.4in',
|
||||||
title: '0.55in',
|
title: '0.55in',
|
||||||
@@ -446,7 +446,7 @@
|
|||||||
m-0 p-0
|
m-0 p-0
|
||||||
px-1
|
px-1
|
||||||
overflow-clip
|
overflow-clip
|
||||||
flex flex-col gap-1
|
flex flex-col
|
||||||
items-stretch justify-between
|
items-stretch justify-between
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { liveQuery } from 'dexie';
|
import { liveQuery } from 'dexie';
|
||||||
import { db_events } from '$lib/ae_events/db_events';
|
import { db_events } from '$lib/ae_events/db_events';
|
||||||
import { ae_api } from '$lib/stores/ae_stores';
|
import { ae_api, ae_loc } from '$lib/stores/ae_stores';
|
||||||
import { events_loc } from '$lib/stores/ae_events_stores';
|
import { events_loc } from '$lib/stores/ae_events_stores';
|
||||||
import { events_func } from '$lib/ae_events/ae_events_functions';
|
import { events_func } from '$lib/ae_events/ae_events_functions';
|
||||||
import { Eye, LoaderCircle, Search, ShieldOff, UserPlus } from '@lucide/svelte';
|
import { Eye, LoaderCircle, Search, ShieldOff, UserPlus } from '@lucide/svelte';
|
||||||
@@ -86,8 +86,10 @@
|
|||||||
adding_id = badge_id;
|
adding_id = badge_id;
|
||||||
add_error_id = '';
|
add_error_id = '';
|
||||||
|
|
||||||
// Use the actual signed-in licensed user's email (stored in auth_exhibit_kv)
|
const kv = $events_loc.leads.auth_exhibit_kv?.[exhibit_id];
|
||||||
const user_email = $events_loc.leads.auth_exhibit_kv?.[exhibit_id]?.key || 'shared_passcode';
|
const user_email = kv?.type === 'licensed' && kv.key ? kv.key
|
||||||
|
: kv?.type === 'shared' ? 'shared_passcode'
|
||||||
|
: $ae_loc.access_type || 'anonymous';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await events_func.create_ae_obj__exhibit_tracking({
|
const result = await events_func.create_ae_obj__exhibit_tracking({
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
import { events_func } from '$lib/ae_events/ae_events_functions';
|
import { events_func } from '$lib/ae_events/ae_events_functions';
|
||||||
import Element_qr_scanner_v3 from '$lib/elements/element_qr_scanner_v3.svelte';
|
import Element_qr_scanner_v3 from '$lib/elements/element_qr_scanner_v3.svelte';
|
||||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||||
import { Camera, CircleAlert, CircleCheck, Eye, LoaderCircle, RefreshCw, RotateCcw, ShieldOff, UserPlus, X } from '@lucide/svelte';
|
import { Camera, CircleAlert, CircleCheck, Eye, LoaderCircle, RefreshCw, RotateCcw, ShieldOff, X } from '@lucide/svelte';
|
||||||
import { SvelteMap } from 'svelte/reactivity';
|
import { SvelteMap } from 'svelte/reactivity';
|
||||||
import type { ae_EventBadge } from '$lib/types/ae_types';
|
import type { ae_EventBadge } from '$lib/types/ae_types';
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function confirm_add_lead() {
|
async function confirm_add_lead(dest: 'scan_next' | 'view_lead' = 'scan_next') {
|
||||||
if (!found_badge || !found_badge.event_badge_id) {
|
if (!found_badge || !found_badge.event_badge_id) {
|
||||||
console.warn('[leads] Guard failed — event_badge_id missing. found_badge:', found_badge);
|
console.warn('[leads] Guard failed — event_badge_id missing. found_badge:', found_badge);
|
||||||
return;
|
return;
|
||||||
@@ -110,8 +110,14 @@
|
|||||||
|
|
||||||
scanning_status = 'adding';
|
scanning_status = 'adding';
|
||||||
|
|
||||||
// Use the actual signed-in licensed user's email
|
// Resolve who is capturing this lead:
|
||||||
const user_email = $events_loc.leads.auth_exhibit_kv?.[exhibit_id]?.key || 'shared_passcode';
|
// licensed exhibit user → their email (kv.key)
|
||||||
|
// shared passcode → 'shared_passcode' label (don't store the actual passcode)
|
||||||
|
// Aether user (no kv) → access_type string ('trusted', 'manager', 'super', etc.)
|
||||||
|
const kv = $events_loc.leads.auth_exhibit_kv?.[exhibit_id];
|
||||||
|
const user_email = kv?.type === 'licensed' && kv.key ? kv.key
|
||||||
|
: kv?.type === 'shared' ? 'shared_passcode'
|
||||||
|
: $ae_loc.access_type || 'anonymous';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await events_func.create_ae_obj__exhibit_tracking({
|
const result = await events_func.create_ae_obj__exhibit_tracking({
|
||||||
@@ -128,11 +134,11 @@
|
|||||||
scanning_status = 'success';
|
scanning_status = 'success';
|
||||||
if (on_lead_added) on_lead_added(found_badge);
|
if (on_lead_added) on_lead_added(found_badge);
|
||||||
|
|
||||||
if (scan_qualify === 'qualify' && new_tracking_id) {
|
if (dest === 'view_lead' && new_tracking_id) {
|
||||||
// Qualify mode: navigate directly to lead detail to fill in notes/qualifiers
|
// View Lead: navigate directly to lead detail to fill in notes/qualifiers
|
||||||
goto(`/events/${page.params.event_id}/leads/exhibit/${exhibit_id}/lead/${new_tracking_id}`);
|
goto(`/events/${page.params.event_id}/leads/exhibit/${exhibit_id}/lead/${new_tracking_id}`);
|
||||||
} else {
|
} else {
|
||||||
// Rapid/auto mode: auto-reset after 2 seconds to scan the next person
|
// Scan Next / auto mode: auto-reset after 2 seconds to scan the next person
|
||||||
setTimeout(reset_scanner, 2000);
|
setTimeout(reset_scanner, 2000);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -167,7 +173,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function confirm_reenable_lead() {
|
async function confirm_reenable_lead(dest: 'scan_next' | 'view_lead' = 'scan_next') {
|
||||||
// Re-activate a lead that was previously removed (enable=false).
|
// Re-activate a lead that was previously removed (enable=false).
|
||||||
// existing_tracking_id is already set from the map or the API fallback search.
|
// existing_tracking_id is already set from the map or the API fallback search.
|
||||||
if (!existing_tracking_id) return;
|
if (!existing_tracking_id) return;
|
||||||
@@ -185,9 +191,11 @@
|
|||||||
new_tracking_id = existing_tracking_id;
|
new_tracking_id = existing_tracking_id;
|
||||||
scanning_status = 'success';
|
scanning_status = 'success';
|
||||||
if (on_lead_added && found_badge) on_lead_added(found_badge);
|
if (on_lead_added && found_badge) on_lead_added(found_badge);
|
||||||
// Re-enabled lead: success card shows "View Details" link — user navigates manually.
|
if (dest === 'view_lead' && new_tracking_id) {
|
||||||
// Auto-reset after 2s so the scanner is ready for the next badge.
|
goto(`/events/${page.params.event_id}/leads/exhibit/${exhibit_id}/lead/${new_tracking_id}`);
|
||||||
setTimeout(reset_scanner, 2000);
|
} else {
|
||||||
|
setTimeout(reset_scanner, 2000);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
scanning_status = 'error';
|
scanning_status = 'error';
|
||||||
error_msg = 'Failed to restore lead. Please try again.';
|
error_msg = 'Failed to restore lead. Please try again.';
|
||||||
@@ -247,23 +255,25 @@
|
|||||||
<p class="opacity-70 text-sm">This lead was removed. Re-activate to restore their record including any saved notes and responses.</p>
|
<p class="opacity-70 text-sm">This lead was removed. Re-activate to restore their record including any saved notes and responses.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<!-- Two-button confirm — same pattern as the main confirm card -->
|
||||||
type="button"
|
<div class="grid grid-cols-2 gap-3">
|
||||||
class="w-full rounded-xl py-5 font-bold text-base flex items-center justify-center gap-2 bg-warning-500 text-white shadow-md hover:brightness-110 active:brightness-90 transition-all cursor-pointer"
|
<button
|
||||||
onclick={confirm_reenable_lead}
|
type="button"
|
||||||
>
|
class="rounded-xl py-5 font-bold text-sm flex flex-col items-center justify-center gap-2 bg-warning-500 text-white shadow-md hover:brightness-110 active:brightness-90 transition-all cursor-pointer"
|
||||||
<RotateCcw size="1.5em" />
|
onclick={() => confirm_reenable_lead('scan_next')}
|
||||||
Re-activate Lead
|
>
|
||||||
</button>
|
<Camera size="1.5em" />
|
||||||
|
Restore & Scan Next
|
||||||
<a
|
</button>
|
||||||
href={`/events/${page.params.event_id}/leads/exhibit/${exhibit_id}/lead/${existing_tracking_id}`}
|
<button
|
||||||
class="btn btn-sm w-full preset-outlined-warning"
|
type="button"
|
||||||
class:hidden={!$ae_loc.trusted_access}
|
class="rounded-xl py-5 font-bold text-sm flex flex-col items-center justify-center gap-2 bg-secondary-500 text-white shadow-md hover:brightness-110 active:brightness-90 transition-all cursor-pointer"
|
||||||
>
|
onclick={() => confirm_reenable_lead('view_lead')}
|
||||||
<Eye size="1em" />
|
>
|
||||||
View Existing Record
|
<Eye size="1.5em" />
|
||||||
</a>
|
Restore & View Lead
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -313,32 +323,36 @@
|
|||||||
<p class="opacity-70">{found_badge?.affiliations || ''}</p>
|
<p class="opacity-70">{found_badge?.affiliations || ''}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if scan_qualify === 'auto'}
|
{#if scan_qualify === 'auto' || scanning_status === 'adding'}
|
||||||
<!-- Auto mode: no confirm buttons — adding happens automatically -->
|
<!-- Auto mode or mid-add: no buttons — adding happens automatically / in progress -->
|
||||||
<div class="flex items-center justify-center gap-3 py-3 opacity-70">
|
<div class="flex items-center justify-center gap-3 py-3 opacity-70">
|
||||||
<LoaderCircle class="animate-spin" size="1.5em" />
|
<LoaderCircle class="animate-spin" size="1.5em" />
|
||||||
<span class="font-bold">Auto-adding...</span>
|
<span class="font-bold">{scan_qualify === 'auto' ? 'Auto-adding...' : 'Adding Lead...'}</span>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<button
|
<!-- Two-button confirm: staff chooses what to do after adding this lead -->
|
||||||
type="button"
|
<div class="grid grid-cols-2 gap-3">
|
||||||
class="w-full rounded-xl py-5 font-bold text-base flex items-center justify-center gap-2 bg-primary-500 text-white shadow-md hover:brightness-110 active:brightness-90 transition-all cursor-pointer disabled:opacity-50"
|
<button
|
||||||
disabled={scanning_status === 'adding'}
|
type="button"
|
||||||
onclick={confirm_add_lead}
|
class="rounded-xl py-5 font-bold text-sm flex flex-col items-center justify-center gap-2 bg-primary-500 text-white shadow-md hover:brightness-110 active:brightness-90 transition-all cursor-pointer"
|
||||||
>
|
onclick={() => confirm_add_lead('scan_next')}
|
||||||
{#if scanning_status === 'adding'}
|
>
|
||||||
<LoaderCircle class="animate-spin" size="1.5em" />
|
<Camera size="1.5em" />
|
||||||
Adding Lead...
|
Add & Scan Next
|
||||||
{:else}
|
</button>
|
||||||
<UserPlus size="1.5em" />
|
<button
|
||||||
Add as Lead
|
type="button"
|
||||||
{/if}
|
class="rounded-xl py-5 font-bold text-sm flex flex-col items-center justify-center gap-2 bg-secondary-500 text-white shadow-md hover:brightness-110 active:brightness-90 transition-all cursor-pointer"
|
||||||
</button>
|
onclick={() => confirm_add_lead('view_lead')}
|
||||||
|
>
|
||||||
|
<Eye size="1.5em" />
|
||||||
|
Add & View Lead
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="w-full rounded-lg py-3 text-sm font-medium flex items-center justify-center gap-2 border border-surface-500/40 hover:bg-surface-200-800 transition-colors cursor-pointer opacity-70 disabled:opacity-30"
|
class="w-full rounded-lg py-3 text-sm font-medium flex items-center justify-center gap-2 border border-surface-500/40 hover:bg-surface-200-800 transition-colors cursor-pointer opacity-70"
|
||||||
disabled={scanning_status === 'adding'}
|
|
||||||
onclick={reset_scanner}
|
onclick={reset_scanner}
|
||||||
>
|
>
|
||||||
<X size="1em" />
|
<X size="1em" />
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { liveQuery } from 'dexie';
|
import { liveQuery } from 'dexie';
|
||||||
import { db_events } from '$lib/ae_events/db_events';
|
import { db_events } from '$lib/ae_events/db_events';
|
||||||
import { ae_api } from '$lib/stores/ae_stores';
|
import { ae_api, ae_loc } from '$lib/stores/ae_stores';
|
||||||
import { events_loc } from '$lib/stores/ae_events_stores';
|
import { events_loc } from '$lib/stores/ae_events_stores';
|
||||||
import { events_func } from '$lib/ae_events/ae_events_functions';
|
import { events_func } from '$lib/ae_events/ae_events_functions';
|
||||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||||
@@ -182,7 +182,10 @@
|
|||||||
if (item.status !== 'ready' || !item.badge?.event_badge_id) return;
|
if (item.status !== 'ready' || !item.badge?.event_badge_id) return;
|
||||||
item.status = 'adding';
|
item.status = 'adding';
|
||||||
|
|
||||||
const user_email = $events_loc.leads.auth_exhibit_kv?.[exhibit_id]?.key || 'shared_passcode';
|
const kv = $events_loc.leads.auth_exhibit_kv?.[exhibit_id];
|
||||||
|
const user_email = kv?.type === 'licensed' && kv.key ? kv.key
|
||||||
|
: kv?.type === 'shared' ? 'shared_passcode'
|
||||||
|
: $ae_loc.access_type || 'anonymous';
|
||||||
try {
|
try {
|
||||||
const result = await events_func.create_ae_obj__exhibit_tracking({
|
const result = await events_func.create_ae_obj__exhibit_tracking({
|
||||||
api_cfg: $ae_api,
|
api_cfg: $ae_api,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
* auto: no confirm — badge is added immediately on scan → auto-reset
|
* auto: no confirm — badge is added immediately on scan → auto-reset
|
||||||
* multi: BarcodeDetector batch scan → grid of confirm cards
|
* multi: BarcodeDetector batch scan → grid of confirm cards
|
||||||
*/
|
*/
|
||||||
import { Bot, ChevronDown, ClipboardList, Layers, QrCode, Search, Zap } from '@lucide/svelte';
|
import { Bot, ChevronDown, Layers, QrCode, Search, Zap } from '@lucide/svelte';
|
||||||
import Comp_lead_qr_scanner from './ae_comp__lead_qr_scanner.svelte';
|
import Comp_lead_qr_scanner from './ae_comp__lead_qr_scanner.svelte';
|
||||||
import Comp_lead_qr_scanner_multi from './ae_comp__lead_qr_scanner_multi.svelte';
|
import Comp_lead_qr_scanner_multi from './ae_comp__lead_qr_scanner_multi.svelte';
|
||||||
import Comp_lead_manual_search from './ae_comp__lead_manual_search.svelte';
|
import Comp_lead_manual_search from './ae_comp__lead_manual_search.svelte';
|
||||||
@@ -32,8 +32,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Scan qualify mode (persisted per exhibit)
|
// Scan qualify mode (persisted per exhibit)
|
||||||
type ScanQualifyMode = 'rapid' | 'qualify' | 'auto' | 'multi';
|
// 'qualify' was merged into 'rapid' — normalize stale localStorage values
|
||||||
let scan_qualify = $derived(($events_loc.leads.tab_scan_qualify?.[exhibit_id] ?? 'rapid') as ScanQualifyMode);
|
type ScanQualifyMode = 'rapid' | 'auto' | 'multi';
|
||||||
|
let scan_qualify = $derived.by(() => {
|
||||||
|
const raw = $events_loc.leads.tab_scan_qualify?.[exhibit_id] ?? 'rapid';
|
||||||
|
// 'qualify' was merged into 'rapid' — normalize stale localStorage values
|
||||||
|
return (raw === 'qualify' ? 'rapid' : raw) as ScanQualifyMode;
|
||||||
|
});
|
||||||
|
|
||||||
function set_scan_qualify(new_mode: ScanQualifyMode) {
|
function set_scan_qualify(new_mode: ScanQualifyMode) {
|
||||||
if (!$events_loc.leads.tab_scan_qualify) $events_loc.leads.tab_scan_qualify = {};
|
if (!$events_loc.leads.tab_scan_qualify) $events_loc.leads.tab_scan_qualify = {};
|
||||||
@@ -55,10 +60,9 @@
|
|||||||
desc: string;
|
desc: string;
|
||||||
icon: any;
|
icon: any;
|
||||||
}> = [
|
}> = [
|
||||||
{ value: 'rapid', label: 'Rapid', desc: 'Confirm · then auto-reset', icon: Zap },
|
{ value: 'rapid', label: 'Confirm', desc: 'Tap Add & Scan or Add & View', icon: Zap },
|
||||||
{ value: 'qualify', label: 'Qualify', desc: 'Confirm · open lead detail', icon: ClipboardList },
|
{ value: 'auto', label: 'Auto', desc: 'Auto-add · no tap needed', icon: Bot },
|
||||||
{ value: 'auto', label: 'Auto', desc: 'Auto-add · no tap needed', icon: Bot },
|
{ value: 'multi', label: 'Multi', desc: 'Batch scan up to 4 badges', icon: Layers },
|
||||||
{ value: 'multi', label: 'Multi', desc: 'Batch scan up to 4 badges', icon: Layers },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
let active_mode = $derived(qr_modes.find(m => m.value === scan_qualify) ?? qr_modes[0]);
|
let active_mode = $derived(qr_modes.find(m => m.value === scan_qualify) ?? qr_modes[0]);
|
||||||
@@ -95,7 +99,6 @@
|
|||||||
<!-- Colored icon pill -->
|
<!-- Colored icon pill -->
|
||||||
<span class="p-1.5 rounded-lg shrink-0 text-white"
|
<span class="p-1.5 rounded-lg shrink-0 text-white"
|
||||||
class:bg-primary-500={scan_qualify === 'rapid'}
|
class:bg-primary-500={scan_qualify === 'rapid'}
|
||||||
class:bg-secondary-500={scan_qualify === 'qualify'}
|
|
||||||
class:bg-tertiary-500={scan_qualify === 'auto'}
|
class:bg-tertiary-500={scan_qualify === 'auto'}
|
||||||
class:bg-warning-500={scan_qualify === 'multi'}
|
class:bg-warning-500={scan_qualify === 'multi'}
|
||||||
>
|
>
|
||||||
@@ -117,7 +120,7 @@
|
|||||||
|
|
||||||
<!-- Options grid (2×2) — shown when trigger is tapped -->
|
<!-- Options grid (2×2) — shown when trigger is tapped -->
|
||||||
{#if show_mode_opts}
|
{#if show_mode_opts}
|
||||||
<div class="mt-1.5 grid grid-cols-2 gap-2 p-2 bg-surface-50-900 rounded-xl border border-surface-500/20 shadow-lg">
|
<div class="mt-1.5 grid grid-cols-3 gap-2 p-2 bg-surface-50-900 rounded-xl border border-surface-500/20 shadow-lg">
|
||||||
{#each qr_modes as m}
|
{#each qr_modes as m}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -132,7 +135,6 @@
|
|||||||
>
|
>
|
||||||
<span class="p-1.5 rounded-lg text-white"
|
<span class="p-1.5 rounded-lg text-white"
|
||||||
class:bg-primary-500={m.value === 'rapid'}
|
class:bg-primary-500={m.value === 'rapid'}
|
||||||
class:bg-secondary-500={m.value === 'qualify'}
|
|
||||||
class:bg-tertiary-500={m.value === 'auto'}
|
class:bg-tertiary-500={m.value === 'auto'}
|
||||||
class:bg-warning-500={m.value === 'multi'}
|
class:bg-warning-500={m.value === 'multi'}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user