count down

This commit is contained in:
Alexander Gabriel 2026-05-27 00:27:23 +02:00
parent 99c6f5774f
commit 8d1faefb45

View File

@ -1,4 +1,7 @@
<div class="flex flex-col items-center w-full">
<div class="flex flex-col items-center w-full"
x-data="{ lightbox: null }"
x-on:keydown.escape.window="lightbox = null">
<div class="relative w-full max-w-2xl">
<video id="video" autoplay playsinline
class="w-full rounded-2xl shadow-2xl bg-gray-900 aspect-video object-cover"></video>
@ -6,12 +9,17 @@
<canvas id="canvas" class="hidden"></canvas>
<div id="flash" class="absolute inset-0 rounded-2xl bg-white opacity-0 pointer-events-none transition-opacity duration-150"></div>
<div id="countdown" class="absolute inset-0 rounded-2xl hidden items-center justify-center bg-black/40">
<span id="countdown-num" class="text-white font-bold drop-shadow-lg" style="font-size: 8rem; line-height: 1;"></span>
</div>
</div>
<button id="capture-btn"
wire:loading.attr="disabled"
@disabled($hasProcessing)
class="mt-8 px-10 py-4 bg-white text-gray-950 font-semibold text-lg rounded-full shadow-lg hover:bg-gray-200 active:scale-95 transition-all duration-150 disabled:opacity-50 disabled:cursor-not-allowed">
<span wire:loading.remove>Take Photo</span>
<span wire:loading.remove>{{ $hasProcessing ? 'Erstelle Bild...' : 'Foto aufnehmen' }}</span>
<span wire:loading>Saving…</span>
</button>
@ -24,7 +32,7 @@
@foreach ($recentPhotos as $photo)
<div class="relative">
@if ($photo['status'] === 'processing')
<div class="w-full aspect-square rounded-lg border border-gray-700 bg-gray-900 flex items-center justify-center">
<div class="w-full aspect-square rounded-lg border border-gray-700 bg-gray-900 flex flex-col items-center justify-center gap-2">
<svg class="w-8 h-8 text-gray-500 animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z"/>
@ -32,7 +40,8 @@
</div>
@else
<img src="{{ $photo['url'] }}" alt="Photo #{{ $photo['id'] }}"
class="w-full aspect-square object-cover rounded-lg border border-gray-700">
class="w-full aspect-square object-cover rounded-lg border border-gray-700 cursor-pointer"
x-on:click="lightbox = {{ Js::from($photo) }}">
<button wire:click="delete({{ $photo['id'] }})" wire:confirm="Delete this photo?"
class="absolute top-1 right-1 p-1 rounded bg-black/60 text-white hover:bg-red-600/80 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" viewBox="0 0 20 20" fill="currentColor">
@ -49,12 +58,36 @@
</div>
@endif
{{-- Lightbox --}}
<div x-show="lightbox"
wire:ignore
x-on:click.self="lightbox = null"
class="fixed inset-0 z-50 flex items-center justify-center bg-black/80 p-6"
style="display:none">
<div class="relative max-w-2xl w-full">
<img x-bind:src="lightbox?.url" alt="Photo"
class="w-full rounded-2xl shadow-2xl">
<div class="absolute bottom-4 right-4 rounded-xl overflow-hidden shadow-2xl bg-white p-1">
<canvas x-ref="qrCanvas"
x-effect="lightbox && $nextTick(() => window.generateQR(lightbox.url, $refs.qrCanvas))"></canvas>
</div>
<button x-on:click="lightbox = null"
class="absolute top-3 right-3 p-1.5 rounded-full bg-black/60 text-white hover:bg-black/80">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/>
</svg>
</button>
</div>
</div>
@script
<script>
const video = document.getElementById('video');
const canvas = document.getElementById('canvas');
const btn = document.getElementById('capture-btn');
const flash = document.getElementById('flash');
const countdown = document.getElementById('countdown');
const countdownNum = document.getElementById('countdown-num');
async function startCamera() {
try {
@ -71,8 +104,24 @@
setTimeout(() => flash.style.opacity = '0', 150);
}
function delay(ms) {
return new Promise(r => setTimeout(r, ms));
}
btn.addEventListener('click', async () => {
if (!video.srcObject) return;
if (!video.srcObject || btn.disabled) return;
btn.disabled = true;
countdown.classList.remove('hidden');
countdown.classList.add('flex');
for (let i = 5; i >= 1; i--) {
countdownNum.textContent = i;
await delay(1000);
}
countdown.classList.add('hidden');
countdown.classList.remove('flex');
triggerFlash();