Files
ladill-frontdesk/resources/views/frontdesk/kiosk/index.blade.php
T
isaaccladandCursor 7a9871f43f
Deploy Ladill Frontdesk / deploy (push) Successful in 36s
Add tablet camera scanning for employee badge QR on kiosk.
Uses html5-qrcode with rear camera preview on Scan badge, keeps external scanner fallback, and still requires PIN after a successful scan.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-28 21:40:54 +00:00

423 lines
30 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@php
$visitorTypeCards = collect($visitorTypes)
->map(fn ($label, $key) => ['key' => $key, 'label' => $label])
->values();
@endphp
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>Reception · {{ $organization->name }}</title>
@include('partials.favicon')
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600,700&display=swap" rel="stylesheet" />
<script>
window.__KIOSK__ = {
visitorTypes: @json($visitorTypeCards),
typeConfigs: @json($typeConfigs),
resetSeconds: {{ $resetSeconds }},
checkInUrl: @json($checkInUrl ?? route('frontdesk.kiosk.check-in')),
employeeKioskEnabled: @json($employeeKioskEnabled ?? false),
staffActionUrl: @json($staffActionUrl ?? ''),
stepOutReasons: @json($stepOutReasons ?? []),
};
</script>
@vite(['resources/css/app.css', 'resources/js/app.js'])
<style>
@keyframes kiosk-tap-pulse {
0%, 100% { transform: scale(1); box-shadow: 0 20px 40px -12px rgb(79 70 229 / 0.45); }
50% { transform: scale(1.02); box-shadow: 0 24px 48px -10px rgb(79 70 229 / 0.55); }
}
.kiosk-tap-btn { animation: kiosk-tap-pulse 2.4s ease-in-out infinite; }
#staff-qr-reader { min-height: 280px; }
#staff-qr-reader video { border-radius: 0.75rem; width: 100% !important; }
</style>
</head>
<body class="min-h-screen bg-slate-100 font-sans text-slate-900 antialiased" x-data="kioskFlow" x-init="startTimer()">
<div class="pointer-events-none fixed inset-0 overflow-hidden" aria-hidden="true">
<div class="absolute -left-24 top-0 h-72 w-72 rounded-full bg-indigo-200/40 blur-3xl"></div>
<div class="absolute -right-16 bottom-0 h-80 w-80 rounded-full bg-violet-200/40 blur-3xl"></div>
</div>
<div class="fixed inset-x-0 top-0 z-10 h-1.5 bg-gradient-to-r from-indigo-600 to-violet-600"></div>
@include('frontdesk.partials.kiosk-brand')
<div class="relative mx-auto flex min-h-screen max-w-3xl flex-col pt-20">
{{-- Welcome: tap to begin --}}
<template x-if="step === 'welcome'">
<div class="flex min-h-[calc(100vh-5rem)] flex-col items-center justify-center px-6 py-12 text-center">
<div class="max-w-xl">
<p class="text-sm font-semibold uppercase tracking-wider text-indigo-600">Reception</p>
<h1 class="mt-3 text-4xl font-bold tracking-tight text-slate-900 sm:text-5xl">
Welcome to {{ $organization->name }}
</h1>
<p class="mt-4 text-lg text-slate-500">Visitor check-in &amp; staff sign-in</p>
</div>
<button type="button"
@click="beginKiosk()"
class="kiosk-tap-btn btn-primary btn-primary-lg mt-16 w-full max-w-lg py-8 text-xl font-bold">
Tap to begin
</button>
</div>
</template>
{{-- Choose: visitor or staff --}}
<template x-if="step === 'choose'">
<div class="relative flex min-h-[calc(100vh-5rem)] flex-col items-center justify-center px-6 py-12">
<div class="absolute left-6 top-8 z-10">
@include('frontdesk.partials.kiosk-back')
</div>
<div class="max-w-xl text-center">
<h2 class="text-2xl font-bold text-slate-900 sm:text-3xl">Who are you?</h2>
<p class="mt-2 text-slate-500">Select an option to continue</p>
</div>
<div class="mt-12 grid w-full max-w-2xl gap-4 sm:grid-cols-2">
<button type="button"
@click="startVisitorFlow()"
class="group flex flex-col items-center rounded-3xl border-2 border-slate-200 bg-white px-6 py-10 text-center shadow-sm transition hover:border-indigo-300 hover:shadow-md active:scale-[0.98]">
<span class="flex h-16 w-16 items-center justify-center rounded-2xl bg-indigo-50 text-indigo-600 transition group-hover:bg-indigo-100">
<svg class="h-9 w-9" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M18 18.72a9.094 9.094 0 0 0 3.741-.479 3 3 0 0 0-4.682-2.72m.94 3.198.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0 1 12 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0 1 11.964-3.07M12 6.375a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0Zm8.25 2.25a2.625 2.625 0 1 1-5.25 0 2.625 2.625 0 0 1 5.25 0Z" />
</svg>
</span>
<span class="mt-5 text-xl font-bold text-slate-900">Visitor</span>
<span class="mt-1 text-sm text-slate-500">Check in as a guest</span>
</button>
<template x-if="employeeKioskEnabled">
<button type="button"
@click="startStaffFlow()"
class="group flex flex-col items-center rounded-3xl border-2 border-slate-200 bg-white px-6 py-10 text-center shadow-sm transition hover:border-indigo-300 hover:shadow-md active:scale-[0.98]">
<span class="flex h-16 w-16 items-center justify-center rounded-2xl bg-violet-50 text-violet-600 transition group-hover:bg-violet-100">
<svg class="h-9 w-9" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 9h3.75M15 12h3.75M15 15h3.75M4.5 19.5h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Zm6-10.125a1.875 1.875 0 1 1-3.75 0 1.875 1.875 0 0 1 3.75 0Zm1.294 6.336a6.721 6.721 0 0 1-3.17.789 6.721 6.721 0 0 1-3.168-.789 3.376 3.376 0 0 1 6.338 0Z" />
</svg>
</span>
<span class="mt-5 text-xl font-bold text-slate-900">Staff</span>
<span class="mt-1 text-sm text-slate-500">Sign in or step out</span>
</button>
</template>
</div>
</div>
</template>
{{-- Visitor type carousel --}}
<template x-if="step === 'type'">
<div class="relative flex min-h-[calc(100vh-5rem)] flex-col items-center justify-center px-6 py-8"
@touchstart="onTypeTouchStart($event)"
@touchend="onTypeTouchEnd($event)">
<div class="absolute left-6 top-8 z-10">
@include('frontdesk.partials.kiosk-back')
</div>
<h2 class="mb-10 text-center text-2xl font-semibold text-slate-900 sm:text-3xl">What brings you here?</h2>
<div class="flex w-full max-w-lg items-center gap-3">
<button type="button"
@click="prevType()"
:disabled="typeIndex === 0"
class="flex h-12 w-12 shrink-0 items-center justify-center rounded-full border border-slate-200 bg-white text-slate-600 shadow-sm transition hover:border-indigo-200 disabled:opacity-30"
aria-label="Previous option">
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" /></svg>
</button>
<div class="relative min-w-0 flex-1 overflow-hidden">
<div class="flex transition-transform duration-300 ease-out"
:style="`transform: translateX(-${typeIndex * 100}%)`">
<template x-for="type in visitorTypes" :key="type.key">
<div class="w-full shrink-0 px-1">
<button type="button"
@click="selectType(type.key)"
class="w-full rounded-3xl border-2 border-slate-200 bg-white px-6 py-14 text-center shadow-sm transition hover:border-indigo-300 hover:shadow-md active:scale-[0.98]">
<span class="block text-2xl font-bold text-slate-900" x-text="type.label"></span>
<span class="mt-3 block text-sm font-medium text-indigo-600">Tap to select</span>
</button>
</div>
</template>
</div>
</div>
<button type="button"
@click="nextType()"
:disabled="typeIndex === visitorTypes.length - 1"
class="flex h-12 w-12 shrink-0 items-center justify-center rounded-full border border-slate-200 bg-white text-slate-600 shadow-sm transition hover:border-indigo-200 disabled:opacity-30"
aria-label="Next option">
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" /></svg>
</button>
</div>
<div class="mt-8 flex justify-center gap-2">
<template x-for="(type, i) in visitorTypes" :key="'dot-' + type.key">
<button type="button"
@click="typeIndex = i; resetTimer()"
class="h-2.5 rounded-full transition-all duration-200"
:class="typeIndex === i ? 'w-8 bg-indigo-600' : 'w-2.5 bg-slate-300'"
:aria-label="'Go to ' + type.label"></button>
</template>
</div>
</div>
</template>
{{-- Multi-step check-in form --}}
<template x-if="step === 'details'">
<div class="relative flex min-h-[calc(100vh-5rem)] flex-col justify-center px-6 py-8">
<div class="absolute left-6 top-8 z-10">
@include('frontdesk.partials.kiosk-back')
</div>
<div class="mx-auto w-full max-w-lg rounded-3xl border border-slate-200 bg-white p-6 shadow-sm">
<div class="mb-6">
<div class="flex items-center justify-between text-xs font-medium text-slate-500">
<span x-text="typeConfigs[form.visitor_type]?.label"></span>
<span x-text="`Step ${formStep} of ${totalFormSteps()}`"></span>
</div>
<div class="mt-2 h-1.5 overflow-hidden rounded-full bg-slate-100">
<div class="h-full rounded-full bg-gradient-to-r from-indigo-600 to-violet-600 transition-all duration-300"
:style="`width: ${(formStep / totalFormSteps()) * 100}%`"></div>
</div>
<p class="mt-2 text-sm font-semibold text-slate-900" x-text="currentFormStepLabel()"></p>
</div>
<p x-show="errorMessage" x-cloak class="mb-4 rounded-xl bg-red-50 px-4 py-3 text-sm text-red-700" x-text="errorMessage"></p>
<div>
{{-- Step 1: Identity --}}
<template x-if="currentStepKind() === 'identity'">
<div class="space-y-4">
<input type="text" x-model="form.full_name" placeholder="Full name *" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
<input type="text" x-model="form.company" placeholder="Company" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
<input type="text" x-model="form.phone" placeholder="Phone" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
<input type="text" x-model="form.email" placeholder="Email" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
</div>
</template>
{{-- Step 2: Visit --}}
<template x-if="currentStepKind() === 'visit'">
<div class="space-y-4">
<select x-model="form.host_id" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
<option value="">Who are you visiting?</option>
@foreach ($hosts as $host)
<option value="{{ $host->id }}">{{ $host->name }}</option>
@endforeach
</select>
<select x-model="form.purpose" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
<option value="">Purpose of visit</option>
@foreach (config('frontdesk.visit_purposes') as $label)
<option value="{{ $label }}">{{ $label }}</option>
@endforeach
</select>
</div>
</template>
{{-- Step 3: Type-specific fields (when present) --}}
<template x-if="currentStepKind() === 'typeFields'">
<div class="space-y-4">
<template x-for="field in typeConfigs[form.visitor_type]?.fields || []" :key="field.name">
<div>
<label class="mb-1 block text-sm font-medium text-slate-600" x-text="field.label"></label>
<template x-if="field.type === 'textarea'">
<textarea x-model="form.type_fields[field.name]" rows="2" class="w-full rounded-xl border-slate-200 px-4 py-3 text-lg"></textarea>
</template>
<template x-if="field.type === 'checkbox'">
<label class="flex items-center gap-2 text-sm">
<input type="checkbox" :checked="form.type_fields[field.name]" @change="form.type_fields[field.name] = $event.target.checked" class="h-5 w-5 rounded">
Yes
</label>
</template>
<template x-if="field.type === 'date' || field.type === 'datetime-local' || field.type === 'text'">
<input :type="field.type" x-model="form.type_fields[field.name]" class="w-full rounded-xl border-slate-200 px-4 py-3 text-lg">
</template>
<template x-if="field.type === 'signature'">
<canvas :x-ref="'sig_' + field.name" width="400" height="120" class="w-full rounded-xl border border-slate-200 bg-slate-50"
@mousedown="startSign($event, field.name)" @mousemove="drawSign($event, field.name)" @mouseup="endSign(field.name)"
@touchstart.prevent="startSign($event, field.name)" @touchmove.prevent="drawSign($event, field.name)" @touchend.prevent="endSign(field.name)"></canvas>
<button type="button" @click="clearSign(field.name)" class="mt-1 text-xs text-slate-500">Clear signature</button>
</template>
</div>
</template>
</div>
</template>
{{-- Final step: Photo & finish --}}
<template x-if="currentStepKind() === 'finish'">
<div class="space-y-4">
<div class="rounded-xl border border-slate-200 p-4">
<p class="mb-2 text-sm font-medium text-slate-600">Visitor photo (optional)</p>
<video x-ref="camera" x-show="cameraActive" autoplay playsinline class="mb-2 w-full max-h-48 rounded-lg bg-slate-900 object-cover"></video>
<img x-show="form.photo_data" :src="form.photo_data" alt="Captured photo preview" class="mb-2 max-h-48 rounded-lg">
<div class="flex flex-wrap gap-2">
<button type="button" @click="startCamera()" class="rounded-lg bg-slate-100 px-3 py-2 text-sm">Open camera</button>
<button type="button" @click="capturePhoto()" class="rounded-lg bg-slate-100 px-3 py-2 text-sm">Capture</button>
<button type="button" @click="clearPhoto()" class="rounded-lg bg-slate-100 px-3 py-2 text-sm">Clear</button>
</div>
</div>
<template x-if="typeConfigs[form.visitor_type]?.requires_approval">
<p class="rounded-xl bg-amber-50 px-4 py-3 text-sm text-amber-800">Reception will review your visit before issuing a badge.</p>
</template>
<label class="flex items-center gap-3 text-sm">
<input type="checkbox" x-model="form.policies_accepted" class="h-5 w-5 rounded">
I have read and accept the visitor policies
</label>
</div>
</template>
<div class="mt-6">
<button type="button"
@click="handleContinue()"
:disabled="loading"
class="btn-primary w-full py-3 disabled:opacity-50">
<span x-text="loading ? 'Submitting…' : (isLastFormStep() ? 'Complete check-in' : 'Continue')"></span>
</button>
</div>
</div>
</div>
</div>
</template>
{{-- Employee: identify --}}
<template x-if="step === 'staff_identify'">
<div class="relative flex min-h-[calc(100vh-5rem)] flex-col justify-center px-6 py-8">
<div class="absolute left-6 top-8 z-10">
@include('frontdesk.partials.kiosk-back')
</div>
<div class="mx-auto w-full max-w-lg rounded-3xl border border-slate-200 bg-white p-6 shadow-sm">
<h2 class="text-2xl font-bold text-slate-900">Employee sign-in</h2>
<p class="mt-1 text-sm text-slate-500">Enter your employee code and PIN, or scan your badge with the camera.</p>
<p x-show="errorMessage" x-cloak class="mt-4 rounded-xl bg-red-50 px-4 py-3 text-sm text-red-700" x-text="errorMessage"></p>
<div class="mt-4 flex gap-2 text-sm">
<button type="button" @click="setStaffIdentifyMode('code')" :class="staffIdentifyMode === 'code' ? 'bg-indigo-100 text-indigo-800' : 'bg-slate-100 text-slate-600'" class="rounded-lg px-3 py-1.5 font-medium">Code</button>
<button type="button" @click="setStaffIdentifyMode('badge')" :class="staffIdentifyMode === 'badge' ? 'bg-indigo-100 text-indigo-800' : 'bg-slate-100 text-slate-600'" class="rounded-lg px-3 py-1.5 font-medium">Scan badge</button>
</div>
<div class="mt-6 space-y-4">
<template x-if="staffIdentifyMode === 'code'">
<input type="text" x-model="staffForm.employee_code" placeholder="Employee code" autocomplete="off" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg uppercase">
</template>
<template x-if="staffIdentifyMode === 'badge'">
<div class="space-y-3">
<div x-show="!staffQrScanned" id="staff-qr-reader" class="overflow-hidden rounded-xl border border-slate-200 bg-slate-900/5"></div>
<div x-show="staffQrScanned" x-cloak class="rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-6 text-center">
<p class="text-sm font-semibold text-emerald-800">Badge scanned</p>
<p class="mt-1 text-sm text-emerald-700">Enter your PIN below to continue.</p>
<button type="button" @click="rescanStaffBadge()" class="mt-3 text-sm font-medium text-indigo-600 hover:text-indigo-800">Scan again</button>
</div>
<p x-show="staffQrError" x-cloak class="rounded-xl bg-amber-50 px-4 py-3 text-sm text-amber-800" x-text="staffQrError"></p>
<details class="rounded-xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600">
<summary class="cursor-pointer font-medium text-slate-700">Using an external scanner?</summary>
<input type="text" x-model="staffForm.qr_token" placeholder="Scan or paste badge code" autocomplete="off" class="mt-3 w-full rounded-lg border-slate-200 px-3 py-3 text-base">
</details>
</div>
</template>
<input type="password" inputmode="numeric" x-model="staffForm.pin" placeholder="PIN (46 digits)" maxlength="6" autocomplete="off" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg tracking-widest">
</div>
<button type="button" @click="staffIdentify()" :disabled="loading" class="btn-primary mt-6 w-full py-3 disabled:opacity-50">
<span x-text="loading ? 'Checking…' : 'Continue'"></span>
</button>
</div>
</div>
</template>
{{-- Employee: actions --}}
<template x-if="step === 'staff_actions'">
<div class="relative flex min-h-[calc(100vh-5rem)] flex-col justify-center px-6 py-8">
<div class="absolute left-6 top-8 z-10">
@include('frontdesk.partials.kiosk-back')
</div>
<div class="mx-auto w-full max-w-lg rounded-3xl border border-slate-200 bg-white p-6 text-center shadow-sm">
<p class="text-sm font-medium text-indigo-600" x-text="staffEmployee?.status_label"></p>
<h2 class="mt-1 text-2xl font-bold text-slate-900" x-text="staffEmployee?.full_name"></h2>
<p class="mt-1 text-sm text-slate-500" x-show="staffEmployee?.destination" x-text="'Currently: ' + staffEmployee?.destination"></p>
<p x-show="errorMessage" x-cloak class="mt-4 rounded-xl bg-red-50 px-4 py-3 text-sm text-red-700" x-text="errorMessage"></p>
<div class="mt-6 space-y-3">
<template x-for="action in staffEmployee?.available_actions || []" :key="action">
<button type="button"
@click="action === 'step_out' ? openStaffStepOut() : staffPerform(action)"
:disabled="loading"
class="w-full rounded-xl border border-slate-200 py-4 text-lg font-semibold text-slate-800 transition hover:border-indigo-300 hover:bg-indigo-50 disabled:opacity-50"
x-text="staffActionLabel(action)"></button>
</template>
</div>
</div>
</div>
</template>
{{-- Employee: step out --}}
<template x-if="step === 'staff_step_out'">
<div class="relative flex min-h-[calc(100vh-5rem)] flex-col justify-center px-6 py-8">
<div class="absolute left-6 top-8 z-10">
@include('frontdesk.partials.kiosk-back')
</div>
<div class="mx-auto w-full max-w-lg rounded-3xl border border-slate-200 bg-white p-6 shadow-sm">
<h2 class="text-2xl font-bold text-slate-900">Where are you going?</h2>
<p class="mt-1 text-sm text-slate-500">Let reception know you're stepping out.</p>
<p x-show="errorMessage" x-cloak class="mt-4 rounded-xl bg-red-50 px-4 py-3 text-sm text-red-700" x-text="errorMessage"></p>
<div class="mt-6 space-y-4">
<select x-model="staffStepOut.step_out_reason" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
<template x-for="reason in stepOutReasons" :key="reason.key">
<option :value="reason.key" x-text="reason.label"></option>
</template>
</select>
<input type="text" x-model="staffStepOut.destination" placeholder="Destination (required for Other)" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
<input type="datetime-local" x-model="staffStepOut.expected_return_at" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
<input type="text" x-model="staffStepOut.notes" placeholder="Notes (optional)" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
</div>
<button type="button" @click="submitStaffStepOut()" :disabled="loading" class="btn-primary mt-6 w-full py-3 disabled:opacity-50">
<span x-text="loading ? 'Saving…' : 'Confirm step out'"></span>
</button>
</div>
</div>
</template>
{{-- Employee: branch mismatch confirm --}}
<template x-if="step === 'staff_branch_confirm'">
<div class="relative flex min-h-[calc(100vh-5rem)] flex-col justify-center px-6 py-8">
<div class="mx-auto w-full max-w-lg rounded-3xl border border-amber-200 bg-white p-6 shadow-sm">
<h2 class="text-xl font-bold text-slate-900">Different branch</h2>
<p class="mt-3 text-sm text-slate-600" x-text="staffBranchWarning"></p>
<div class="mt-6 flex gap-3">
<button type="button" @click="step = 'staff_actions'" class="flex-1 rounded-xl border border-slate-200 py-3 font-medium">Cancel</button>
<button type="button" @click="staffConfirmSignIn()" :disabled="loading" class="btn-primary flex-1 py-3 disabled:opacity-50">Sign in here</button>
</div>
</div>
</div>
</template>
{{-- Employee: done --}}
<template x-if="step === 'staff_done'">
<div class="flex min-h-[calc(100vh-5rem)] flex-col items-center justify-center px-6 py-8">
<div class="w-full max-w-lg rounded-3xl border border-slate-200 bg-white p-8 text-center shadow-sm">
<div class="mx-auto mb-4 flex h-20 w-20 items-center justify-center rounded-full bg-indigo-100 text-4xl">✓</div>
<h2 class="text-2xl font-bold" x-text="staffMessage"></h2>
<p class="mt-2 text-slate-600" x-text="staffEmployee?.full_name"></p>
<p class="mt-1 text-sm text-slate-500" x-text="staffEmployee?.status_label"></p>
<button type="button" @click="reset()" class="btn-primary btn-primary-lg mt-8 w-full">Done</button>
</div>
</div>
</template>
{{-- Done --}}
<template x-if="step === 'done'">
<div class="flex min-h-[calc(100vh-5rem)] flex-col items-center justify-center px-6 py-8">
<div class="w-full max-w-lg rounded-3xl border border-slate-200 bg-white p-8 text-center shadow-sm">
<div class="mx-auto mb-4 flex h-20 w-20 items-center justify-center rounded-full bg-indigo-100 text-4xl">✓</div>
<h2 class="text-2xl font-bold" x-text="result?.awaiting_approval ? 'Submitted for approval' : 'You\'re checked in!'"></h2>
<p class="mt-2 text-slate-600" x-text="result?.visitor_name"></p>
<template x-if="!result?.awaiting_approval">
<p class="mt-1 text-sm text-slate-500">Badge code: <span class="font-mono font-bold" x-text="result?.badge_code"></span></p>
</template>
<p class="mt-4 text-sm text-slate-500"
x-text="result?.awaiting_approval ? 'Please wait at reception.' : (result?.host_notified ? 'Your host has been notified.' : (result?.host_name ? 'Check in complete.' : 'Thank you for checking in.'))"></p>
<button type="button" @click="reset()" class="btn-primary btn-primary-lg mt-8 w-full">Done</button>
</div>
</div>
</template>
</div>
</body>
</html>