Files
ladill-events/resources/views/qr-codes/partials/customization-fields.blade.php
T
isaaccladandCursor 9015039e48
Deploy Ladill Events / deploy (push) Successful in 52s
Fix QR code previews not rendering on event and programme pages.
Remove a duplicate preview ref that targeted a hidden card, enable mobile preview for programmes, and harden customizer init timing.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-17 23:39:47 +00:00

529 lines
34 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
$style = \App\Support\Qr\QrStyleDefaults::merge($style ?? null);
$moduleStyles = $moduleStyles ?? \App\Support\Qr\QrModuleStyleCatalog::visible();
$cornerOuterStyles = $cornerOuterStyles ?? \App\Support\Qr\QrCornerStyleCatalog::outerStyles();
$cornerInnerStyles = $cornerInnerStyles ?? \App\Support\Qr\QrCornerStyleCatalog::innerStyles();
$frameStyles = $frameStyles ?? \App\Support\Qr\QrFrameStyleCatalog::visible();
$qrBits = [1,0,1,0,1, 0,1,1,0,0, 1,1,0,1,1, 0,0,1,0,1, 1,0,1,1,0];
@endphp
{{-- ── Quick style presets ──────────────────────────────────────────── --}}
<div class="overflow-hidden rounded-2xl border border-slate-200/80 bg-white shadow-sm">
<div class="border-b border-slate-100 bg-slate-50/70 px-5 py-3.5">
<p class="text-[10px] font-semibold uppercase tracking-[0.1em] text-slate-400">Quick styles</p>
</div>
<div class="grid grid-cols-4 gap-px bg-slate-100/80">
@foreach([
[
'key' => 'classic',
'label' => 'Classic',
'fg' => '#000000',
'bg' => '#ffffff',
'outer' => 'square',
'inner' => 'square',
'dot' => false,
],
[
'key' => 'rounded',
'label' => 'Rounded',
'fg' => '#111827',
'bg' => '#ffffff',
'outer' => 'rounded',
'inner' => 'square',
'dot' => false,
],
[
'key' => 'branded',
'label' => 'Branded',
'fg' => '#1d4ed8',
'bg' => '#ffffff',
'outer' => 'rounded',
'inner' => 'square',
'dot' => false,
],
[
'key' => 'dots',
'label' => 'Dots',
'fg' => '#0f172a',
'bg' => '#ffffff',
'outer' => 'square',
'inner' => 'dot',
'dot' => true,
],
] as $preset)
@php
$outerRadius = match($preset['outer']) { 'rounded' => '3px', 'circle' => '50%', default => '1px' };
$innerRadius = $preset['inner'] === 'dot' ? '50%' : '1px';
$dotRadius = $preset['dot'] ? '50%' : '1px';
@endphp
<button type="button"
@click="setStylePreset('{{ $preset['key'] }}')"
class="group flex flex-col items-center gap-2.5 bg-white px-3 py-4 text-center transition hover:bg-slate-50/80 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-400">
{{-- Mini QR illustration --}}
<span class="relative flex h-12 w-12 shrink-0 items-center justify-center overflow-hidden rounded-lg"
style="background-color: {{ $preset['bg'] }}">
{{-- Top-left finder --}}
<span class="absolute left-[4px] top-[4px] flex h-[14px] w-[14px] items-center justify-center"
style="border: 2.5px solid {{ $preset['fg'] }}; border-radius: {{ $outerRadius }}; background: transparent">
<span class="block h-[5px] w-[5px]"
style="background: {{ $preset['fg'] }}; border-radius: {{ $innerRadius }}"></span>
</span>
{{-- Top-right finder --}}
<span class="absolute right-[4px] top-[4px] flex h-[14px] w-[14px] items-center justify-center"
style="border: 2.5px solid {{ $preset['fg'] }}; border-radius: {{ $outerRadius }}; background: transparent">
<span class="block h-[5px] w-[5px]"
style="background: {{ $preset['fg'] }}; border-radius: {{ $innerRadius }}"></span>
</span>
{{-- Bottom-left finder --}}
<span class="absolute bottom-[4px] left-[4px] flex h-[14px] w-[14px] items-center justify-center"
style="border: 2.5px solid {{ $preset['fg'] }}; border-radius: {{ $outerRadius }}; background: transparent">
<span class="block h-[5px] w-[5px]"
style="background: {{ $preset['fg'] }}; border-radius: {{ $innerRadius }}"></span>
</span>
{{-- Data dots (5×3 grid, avoiding finder corners) --}}
<span class="absolute inset-0 flex items-center justify-center">
<span class="grid grid-cols-3 gap-[2px] translate-x-[5px]">
@foreach([1,0,1,0,1,0,1,1,0] as $bit)
<span class="h-[3px] w-[3px]"
style="{{ $bit ? 'background:' . $preset['fg'] . ';border-radius:' . $dotRadius : '' }}"></span>
@endforeach
</span>
</span>
</span>
<span class="text-[11px] font-semibold text-slate-600 group-hover:text-slate-900">{{ $preset['label'] }}</span>
</button>
@endforeach
</div>
</div>
<div class="overflow-hidden rounded-2xl border border-slate-200/80 bg-white shadow-sm">
{{-- Pill tab nav --}}
<div class="border-b border-slate-100 bg-slate-50/70 px-4 py-3">
<div class="flex gap-1 rounded-xl bg-slate-200/50 p-1">
@foreach([
['id' => 'body', 'label' => 'Body'],
['id' => 'colors', 'label' => 'Colors'],
['id' => 'eyes', 'label' => 'Eyes'],
['id' => 'logo', 'label' => 'Logo'],
['id' => 'frame', 'label' => 'Frame'],
] as $tab)
<button type="button"
@click="openSection = '{{ $tab['id'] }}'"
:class="openSection === '{{ $tab['id'] }}'
? 'bg-white shadow-sm text-slate-900'
: 'text-slate-500 hover:text-slate-700'"
class="flex-1 rounded-lg py-1.5 text-[11px] font-semibold transition-all duration-150">
{{ $tab['label'] }}
</button>
@endforeach
</div>
</div>
<div class="p-5">
{{-- ── Body ─────────────────────────────────────────────────────── --}}
<div x-show="openSection === 'body'" x-cloak
x-transition:enter="transition-opacity duration-150"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100">
<p class="mb-3 text-[10px] font-semibold uppercase tracking-[0.1em] text-slate-400">Dot shape</p>
<div class="grid grid-cols-2 gap-2.5">
@foreach($moduleStyles as $key => $meta)
<label class="group cursor-pointer">
<input type="radio" name="style[module_style]" value="{{ $key }}" x-model="moduleStyle" class="sr-only">
<span class="flex items-center gap-3 rounded-xl border-2 bg-white px-3.5 py-3 transition-all"
:class="moduleStyle === '{{ $key }}'
? 'border-indigo-500 bg-indigo-50/60'
: 'border-slate-200 group-hover:border-slate-300'">
<span class="flex h-11 w-11 shrink-0 items-center justify-center rounded-lg bg-slate-50 ring-1 ring-slate-200/60">
@if ($key === 'square')
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-slate-800" fill="currentColor">
<path d="M0,0V11H11V0H0ZM7,7h-3v-3h3v3ZM13,0V11h11V0H13Zm7,7h-3v-3h3v3ZM0,13v11H11V13H0Zm7,7h-3v-3h3v3Zm10-3h-4v-4h4v4Zm3,3h-3v-3h3v3Zm-3,4h-4v-4h4v4Zm7-7h-4v-4h4v4Z"/>
</svg>
@elseif ($key === 'dots')
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-slate-800" fill="currentColor">
<path d="M7,0h-3C1.794,0,0,1.794,0,4v3c0,2.206,1.794,4,4,4h3c2.206,0,4-1.794,4-4V4C11,1.794,9.206,0,7,0Zm2,7c0,1.103-.897,2-2,2h-3c-1.103,0-2-.897-2-2V4c0-1.103,.897-2,2-2h3c1.103,0,2,.897,2,2v3Zm-2-2v1c0,.552-.448,1-1,1h-1c-.552,0-1-.448-1-1v-1c0-.552,.448-1,1-1h1c.552,0,1,.448,1,1Zm10,6h3c2.206,0,4-1.794,4-4V4C24,1.794,22.206,0,20,0h-3C14.794,0,13,1.794,13,4v3c0,2.206,1.794,4,4,4Zm-2-7c0-1.103,.897-2,2-2h3c1.103,0,2,.897,2,2v3c0,1.103-.897,2-2,2h-3c-1.103,0-2-.897-2-2V4Zm2,2v-1c0-.552,.448-1,1-1h1c.552,0,1,.448,1,1v1c0,.552-.448,1-1,1h-1c-.552,0-1-.448-1-1ZM7,13h-3c-2.206,0-4,1.794-4,4v3c0,2.206,1.794,4,4,4h3c2.206,0,4-1.794,4-4v-3c0-2.206-1.794-4-4-4Zm2,7c0,1.103-.897,2-2,2h-3c-1.103,0-2-.897-2-2v-3c0-1.103,.897-2,2-2h3c1.103,0,2,.897,2,2v3Zm-2-2v1c0,.552-.448,1-1,1h-1c-.552,0-1-.448-1-1v-1c0-.552,.448-1,1-1h1c.552,0,1,.448,1,1Zm10-3.5v1c0,.828-.672,1.5-1.5,1.5h-1c-.828,0-1.5-.672-1.5-1.5v-1c0-.828,.672-1.5,1.5-1.5h1c.828,0,1.5,.672,1.5,1.5Zm3,4h0c0,.828-.672,1.5-1.5,1.5h0c-.828,0-1.5-.672-1.5-1.5h0c0-.828,.672-1.5,1.5-1.5h0c.828,0,1.5,.672,1.5,1.5Zm-3,3v1c0,.828-.672,1.5-1.5,1.5h-1c-.828,0-1.5-.672-1.5-1.5v-1c0-.828,.672-1.5,1.5-1.5h1c.828,0,1.5,.672,1.5,1.5Zm7-7v1c0,.828-.672,1.5-1.5,1.5h-1c-.828,0-1.5-.672-1.5-1.5v-1c0-.828,.672-1.5,1.5-1.5h1c.828,0,1.5,.672,1.5,1.5Z"/>
</svg>
@else
<span class="grid grid-cols-4 gap-[2px] p-1.5">
@foreach($qrBits as $bit)
<span class="block aspect-square {{ $bit ? ('bg-slate-800 ' . ($meta['circular'] ? 'rounded-full' : '')) : 'bg-transparent' }}"></span>
@endforeach
</span>
@endif
</span>
<span class="text-xs font-semibold text-slate-700">{{ $meta['label'] }}</span>
</span>
</label>
@endforeach
</div>
</div>
{{-- ── Colors ───────────────────────────────────────────────────── --}}
<div x-show="openSection === 'colors'" x-cloak
x-transition:enter="transition-opacity duration-150"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
class="space-y-5">
<div>
<p class="mb-3 text-[10px] font-semibold uppercase tracking-[0.1em] text-slate-400">Quick presets</p>
<div class="flex flex-wrap gap-2.5">
@foreach([
['fg' => '#000000', 'bg' => '#ffffff', 'label' => 'Classic'],
['fg' => '#1d4ed8', 'bg' => '#ffffff', 'label' => 'Blue'],
['fg' => '#047857', 'bg' => '#ffffff', 'label' => 'Green'],
['fg' => '#7c3aed', 'bg' => '#ffffff', 'label' => 'Purple'],
['fg' => '#c2410c', 'bg' => '#fff7ed', 'label' => 'Orange'],
['fg' => '#be185d', 'bg' => '#fdf4ff', 'label' => 'Pink'],
['fg' => '#0f172a', 'bg' => '#ecfdf5', 'label' => 'Dark teal'],
] as $preset)
<button type="button"
@click="fg = '{{ $preset['fg'] }}'; bg = '{{ $preset['bg'] }}'; schedulePreview()"
title="{{ $preset['label'] }}"
class="h-8 w-8 rounded-full border-2 border-white shadow ring-1 ring-slate-300/60 transition hover:scale-110 hover:ring-indigo-400"
style="background-color: {{ $preset['fg'] }}"></button>
@endforeach
</div>
</div>
<div class="grid gap-3 sm:grid-cols-2">
<div>
<label class="mb-1.5 block text-xs font-semibold text-slate-600">Foreground</label>
<div class="flex items-center gap-2 rounded-xl border border-slate-200 bg-slate-50/80 p-2">
<input type="color" name="style[foreground]" x-model="fg" @input="schedulePreview()"
class="h-9 w-10 shrink-0 cursor-pointer rounded-lg border-0 bg-transparent p-0.5">
<input type="text" x-model="fg" @change="schedulePreview()"
class="min-w-0 flex-1 rounded-lg border border-slate-200 bg-white px-2.5 py-1.5 font-mono text-sm focus:border-indigo-400 focus:outline-none">
</div>
</div>
<div>
<label class="mb-1.5 block text-xs font-semibold text-slate-600">Background</label>
<div class="flex items-center gap-2 rounded-xl border border-slate-200 bg-slate-50/80 p-2">
<input type="color" name="style[background]" x-model="bg" @input="schedulePreview()"
class="h-9 w-10 shrink-0 cursor-pointer rounded-lg border-0 bg-transparent p-0.5">
<input type="text" x-model="bg" @change="schedulePreview()"
class="min-w-0 flex-1 rounded-lg border border-slate-200 bg-white px-2.5 py-1.5 font-mono text-sm focus:border-indigo-400 focus:outline-none">
</div>
</div>
</div>
<div>
<p class="mb-3 text-[10px] font-semibold uppercase tracking-[0.1em] text-slate-400">Gradient</p>
<input type="hidden" name="style[gradient_type]" :value="gradientType">
<div class="flex gap-1 rounded-xl bg-slate-200/50 p-1">
@foreach([['none', 'None'], ['linear', 'Linear'], ['radial', 'Radial']] as [$val, $lbl])
<button type="button"
@click="gradientType = '{{ $val }}'"
:class="gradientType === '{{ $val }}' ? 'bg-white shadow-sm text-slate-900' : 'text-slate-500 hover:text-slate-700'"
class="flex-1 rounded-lg py-1.5 text-[11px] font-semibold transition-all duration-150">
{{ $lbl }}
</button>
@endforeach
</div>
<div x-show="gradientType !== 'none'" x-cloak class="mt-3 space-y-3">
<div class="grid gap-3 sm:grid-cols-2">
<div>
<label class="mb-1.5 block text-xs font-semibold text-slate-600">Start color</label>
<div class="flex items-center gap-2 rounded-xl border border-slate-200 bg-slate-50/80 p-2">
<input type="color" name="style[gradient_color1]" x-model="gradientColor1" @input="schedulePreview()"
class="h-9 w-10 shrink-0 cursor-pointer rounded-lg border-0 bg-transparent p-0.5">
<input type="text" x-model="gradientColor1" @change="schedulePreview()"
class="min-w-0 flex-1 rounded-lg border border-slate-200 bg-white px-2.5 py-1.5 font-mono text-sm focus:border-indigo-400 focus:outline-none">
</div>
</div>
<div>
<label class="mb-1.5 block text-xs font-semibold text-slate-600">End color</label>
<div class="flex items-center gap-2 rounded-xl border border-slate-200 bg-slate-50/80 p-2">
<input type="color" name="style[gradient_color2]" x-model="gradientColor2" @input="schedulePreview()"
class="h-9 w-10 shrink-0 cursor-pointer rounded-lg border-0 bg-transparent p-0.5">
<input type="text" x-model="gradientColor2" @change="schedulePreview()"
class="min-w-0 flex-1 rounded-lg border border-slate-200 bg-white px-2.5 py-1.5 font-mono text-sm focus:border-indigo-400 focus:outline-none">
</div>
</div>
</div>
<div x-show="gradientType === 'linear'" class="space-y-1">
<label class="flex items-center justify-between text-xs font-semibold text-slate-600">
<span>Rotation</span>
<span x-text="gradientRotation + '°'" class="font-normal text-slate-400"></span>
</label>
<input type="range" name="style[gradient_rotation]" min="0" max="360" x-model.number="gradientRotation"
class="w-full accent-indigo-600">
</div>
</div>
</div>
</div>
{{-- ── Eyes ─────────────────────────────────────────────────────── --}}
<div x-show="openSection === 'eyes'" x-cloak
x-transition:enter="transition-opacity duration-150"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
class="space-y-5">
<div>
<p class="mb-3 text-[10px] font-semibold uppercase tracking-[0.1em] text-slate-400">Outer frame</p>
<div class="grid grid-cols-3 gap-2.5">
@foreach([
['value' => 'square', 'label' => 'Square', 'shape' => 'h-7 w-7 border-[3px] border-slate-800'],
['value' => 'rounded', 'label' => 'Rounded', 'shape' => 'h-7 w-7 rounded-[5px] border-[3px] border-slate-800'],
['value' => 'circle', 'label' => 'Circle', 'shape' => 'h-7 w-7 rounded-full border-[3px] border-slate-800'],
] as $opt)
<label class="group cursor-pointer">
<input type="radio" name="style[finder_outer]" value="{{ $opt['value'] }}" x-model="finderOuter" class="sr-only">
<span class="flex flex-col items-center gap-2 rounded-xl border-2 bg-white p-3 transition-all"
:class="finderOuter === '{{ $opt['value'] }}'
? 'border-indigo-500 bg-indigo-50/60'
: 'border-slate-200 group-hover:border-slate-300'">
<span class="flex h-10 w-10 items-center justify-center rounded-lg bg-slate-50 ring-1 ring-slate-200/60">
<span class="{{ $opt['shape'] }}"></span>
</span>
<span class="text-[11px] font-semibold text-slate-600">{{ $opt['label'] }}</span>
</span>
</label>
@endforeach
</div>
</div>
<div>
<p class="mb-3 text-[10px] font-semibold uppercase tracking-[0.1em] text-slate-400">Inner dot</p>
<div class="grid grid-cols-3 gap-2.5">
@foreach([
['value' => 'square', 'label' => 'Square', 'shape' => 'h-5 w-5 rounded-[2px] bg-slate-800'],
['value' => 'rounded', 'label' => 'Rounded', 'shape' => 'h-5 w-5 rounded-[4px] bg-slate-800'],
['value' => 'dot', 'label' => 'Dot', 'shape' => 'h-5 w-5 rounded-full bg-slate-800'],
] as $opt)
<label class="group cursor-pointer">
<input type="radio" name="style[finder_inner]" value="{{ $opt['value'] }}" x-model="finderInner" class="sr-only">
<span class="flex flex-col items-center gap-2 rounded-xl border-2 bg-white p-3 transition-all"
:class="finderInner === '{{ $opt['value'] }}'
? 'border-indigo-500 bg-indigo-50/60'
: 'border-slate-200 group-hover:border-slate-300'">
<span class="flex h-9 w-9 items-center justify-center rounded-lg bg-slate-50 ring-1 ring-slate-200/60">
<span class="{{ $opt['shape'] }}"></span>
</span>
<span class="text-[11px] font-semibold text-slate-600">{{ $opt['label'] }}</span>
</span>
</label>
@endforeach
</div>
</div>
</div>
{{-- ── Logo ─────────────────────────────────────────────────────── --}}
<div x-show="openSection === 'logo'" x-cloak
x-transition:enter="transition-opacity duration-150"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
class="space-y-4">
<div>
<p class="text-xs font-semibold text-slate-700">Center logo</p>
<p class="mt-0.5 text-xs leading-5 text-slate-400">Placed in the center. Error correction is raised automatically when a logo is present.</p>
</div>
<label class="flex cursor-pointer flex-col items-center gap-3 rounded-xl border-2 border-dashed border-slate-200 bg-slate-50/60 py-8 text-center transition hover:border-indigo-400 hover:bg-indigo-50/20">
<span class="flex h-12 w-12 items-center justify-center rounded-full bg-white shadow-sm ring-1 ring-slate-200/60">
<svg class="h-6 w-6 text-slate-400" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5m-13.5-9L12 3m0 0 4.5 4.5M12 3v13.5"/>
</svg>
</span>
<div>
<p class="text-sm font-semibold text-slate-700">Click to upload</p>
<p class="text-xs text-slate-400">PNG, JPG, GIF, WebP · max 2 MB</p>
</div>
<input type="file" name="logo" accept="image/png,image/jpeg,image/gif,image/webp" x-ref="logoInput" @change="onLogoChange()" class="sr-only">
</label>
{{-- Logo options: shown when a logo is present (uploaded or existing) --}}
<div x-show="hasExistingLogo || _rawLogoFile" x-cloak class="space-y-4 border-t border-slate-100 pt-4">
{{-- Size --}}
<div>
<label class="flex items-center justify-between text-xs font-semibold text-slate-600">
<span>Logo size</span>
<span x-text="Math.round(logoSize * 100) + '%'" class="font-normal text-slate-400"></span>
</label>
<input type="range" min="10" max="40" step="1"
:value="Math.round(logoSize * 100)"
@input="logoSize = parseFloat($event.target.value) / 100"
class="mt-2 w-full accent-indigo-600">
<input type="hidden" name="style[logo_size]" :value="logoSize">
</div>
{{-- Padding --}}
<div>
<label class="flex items-center justify-between text-xs font-semibold text-slate-600">
<span>Padding</span>
<span x-text="logoMargin" class="font-normal text-slate-400"></span>
</label>
<input type="range" name="style[logo_margin]" min="0" max="15" x-model.number="logoMargin"
class="mt-2 w-full accent-indigo-600">
</div>
{{-- White background toggle --}}
<div class="flex items-center justify-between">
<span class="text-xs font-semibold text-slate-600">White background</span>
<button type="button" role="switch" :aria-checked="logoWhiteBg"
@click="logoWhiteBg = !logoWhiteBg"
:class="logoWhiteBg ? 'bg-indigo-500' : 'bg-slate-200'"
class="relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors focus:outline-none">
<span :class="logoWhiteBg ? 'translate-x-5' : 'translate-x-1'"
class="inline-block h-3.5 w-3.5 rounded-full bg-white shadow transition-transform"></span>
</button>
<input type="hidden" name="style[logo_white_bg]" :value="logoWhiteBg ? '1' : '0'">
</div>
{{-- Shape --}}
<div>
<p class="mb-2 text-[10px] font-semibold uppercase tracking-[0.1em] text-slate-400">Shape</p>
<div class="grid grid-cols-3 gap-2">
{{-- Original: landscape rectangle logo keeps its natural aspect ratio --}}
<button type="button"
@click="logoShape = 'none'"
:class="logoShape === 'none' ? 'border-indigo-500 bg-indigo-50/60' : 'border-slate-200 hover:border-slate-300'"
class="flex flex-col items-center gap-2 rounded-xl border-2 bg-white p-3 transition-all">
<span class="flex h-9 w-9 items-center justify-center rounded-lg bg-slate-50 ring-1 ring-slate-200/60">
<span class="h-4 w-7 rounded-[2px] bg-slate-800"></span>
</span>
<span class="text-[11px] font-semibold text-slate-600">Original</span>
</button>
{{-- Rounded: square crop with rounded corners --}}
<button type="button"
@click="logoShape = 'rounded'"
:class="logoShape === 'rounded' ? 'border-indigo-500 bg-indigo-50/60' : 'border-slate-200 hover:border-slate-300'"
class="flex flex-col items-center gap-2 rounded-xl border-2 bg-white p-3 transition-all">
<span class="flex h-9 w-9 items-center justify-center rounded-lg bg-slate-50 ring-1 ring-slate-200/60">
<span class="h-7 w-7 rounded-xl bg-slate-800"></span>
</span>
<span class="text-[11px] font-semibold text-slate-600">Rounded</span>
</button>
{{-- Circle: square crop clipped to circle --}}
<button type="button"
@click="logoShape = 'circle'"
:class="logoShape === 'circle' ? 'border-indigo-500 bg-indigo-50/60' : 'border-slate-200 hover:border-slate-300'"
class="flex flex-col items-center gap-2 rounded-xl border-2 bg-white p-3 transition-all">
<span class="flex h-9 w-9 items-center justify-center rounded-lg bg-slate-50 ring-1 ring-slate-200/60">
<span class="h-7 w-7 rounded-full bg-slate-800"></span>
</span>
<span class="text-[11px] font-semibold text-slate-600">Circle</span>
</button>
</div>
<input type="hidden" name="style[logo_shape]" :value="logoShape">
</div>
</div>
@if(!empty($showRemoveLogo))
<label class="flex items-center gap-2.5 text-sm text-slate-600">
<input type="checkbox" name="remove_logo" value="1" x-model="removeLogo" @change="schedulePreview()"
class="h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500">
Remove current logo
</label>
@endif
</div>
{{-- ── Frame ────────────────────────────────────────────────────── --}}
<div x-show="openSection === 'frame'" x-cloak
x-transition:enter="transition-opacity duration-150"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
class="space-y-5">
<div>
<p class="mb-3 text-[10px] font-semibold uppercase tracking-[0.1em] text-slate-400">Frame style</p>
<div class="grid grid-cols-2 gap-2.5">
@foreach($frameStyles as $key => $meta)
<label class="group cursor-pointer">
<input type="radio" name="style[frame_style]" value="{{ $key }}" x-model="frameStyle" class="sr-only">
<span class="flex items-center gap-3 rounded-xl border-2 bg-white p-3 transition-all"
:class="frameStyle === '{{ $key }}'
? 'border-indigo-500 bg-indigo-50/60'
: 'border-slate-200 group-hover:border-slate-300'">
<span class="flex h-10 w-9 shrink-0 items-end justify-center overflow-hidden rounded-lg border border-slate-200 bg-white p-1 shadow-sm">
@if($key === 'none')
<span class="h-8 w-8 rounded-sm bg-slate-200 opacity-40"></span>
@elseif($meta['mode'] === 'border')
<span class="h-8 w-8 rounded-sm border-2 border-slate-500 bg-slate-100"></span>
@elseif($meta['mode'] === 'label')
<span class="flex h-9 w-8 flex-col items-stretch gap-0.5">
<span class="flex-1 rounded-sm border border-slate-300 bg-slate-100"></span>
<span class="rounded-sm bg-slate-700 py-0.5 text-center text-[5px] font-bold leading-none text-white">SCAN</span>
</span>
@else
<span class="flex h-9 w-8 flex-col items-stretch gap-0.5">
<span class="flex-1 rounded-sm border border-slate-300 bg-slate-100"></span>
<span class="rounded-full bg-slate-700 py-0.5 text-center text-[5px] font-bold leading-none text-white">TAP</span>
</span>
@endif
</span>
<span class="min-w-0">
<span class="block text-xs font-semibold text-slate-800">{{ $meta['label'] }}</span>
<span class="block text-[10px] leading-4 text-slate-400">{{ $meta['description'] }}</span>
</span>
</span>
</label>
@endforeach
</div>
</div>
{{-- Frame colour --}}
<div x-show="frameStyle !== 'none'" x-cloak class="space-y-1">
<label class="block text-xs font-semibold text-slate-600">Frame colour</label>
<div class="flex gap-2">
<input type="color" name="style[frame_color]" x-model="frameColor" @input="schedulePreview()"
class="h-9 w-9 shrink-0 cursor-pointer rounded-lg border border-slate-200 bg-white p-0.5">
<input type="text" x-model="frameColor" @change="schedulePreview()" maxlength="7"
class="flex-1 rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm focus:border-indigo-400 focus:outline-none focus:ring-1 focus:ring-indigo-400/30">
</div>
</div>
{{-- Frame text -- only for frames that show a CTA label --}}
<div x-show="frameStyle === 'scan_me' || frameStyle === 'tap_to_scan'" x-cloak class="space-y-1">
<label class="flex items-center justify-between text-xs font-semibold text-slate-600">
<span>Label text</span>
<span class="font-normal text-slate-400" x-text="frameStyle === 'tap_to_scan' ? 'Default: TAP TO SCAN' : 'Default: SCAN ME'"></span>
</label>
<input type="text" name="style[frame_text]" x-model="frameText"
maxlength="100"
:placeholder="frameStyle === 'tap_to_scan' ? 'TAP TO SCAN' : 'SCAN ME'"
class="w-full rounded-xl border border-slate-200 bg-white px-3.5 py-2.5 text-sm focus:border-indigo-400 focus:outline-none focus:ring-1 focus:ring-indigo-400/30">
</div>
<div class="space-y-4 border-t border-slate-100 pt-4">
<div>
<label class="block text-xs font-semibold text-slate-600">Error correction</label>
<select name="style[error_correction]" x-model="ecc"
class="mt-1.5 w-full rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm focus:border-indigo-400 focus:outline-none">
<option value="L">Low 7% recovery</option>
<option value="M">Medium 15% recovery</option>
<option value="Q">Quartile 25% recovery</option>
<option value="H">High 30% recovery</option>
</select>
</div>
<div>
<label class="flex items-center justify-between text-xs font-semibold text-slate-600">
<span>Output scale</span>
<span x-text="scale + '×'" class="font-normal text-slate-400"></span>
</label>
<input type="range" name="style[scale]" min="4" max="16" x-model.number="scale"
class="mt-2 w-full accent-indigo-600">
</div>
<div>
<label class="flex items-center justify-between text-xs font-semibold text-slate-600">
<span>Quiet zone</span>
<span x-text="margin + ' modules'" class="font-normal text-slate-400"></span>
</label>
<input type="range" name="style[margin]" min="0" max="10" x-model.number="margin"
class="mt-2 w-full accent-indigo-600">
</div>
</div>
</div>
</div>
</div>