<script lang="ts">
/**
 * Badge Print Controls — right-panel for the print page (landscape layout).
 *
 * Renders a per-field accordion panel: each field shows its current value, font
 * size controls (if applicable), and an inline edit form gated by access level.
 *
 * Access levels:
 *   Basic authenticated → can edit professional_title_override, affiliations_override,
 *                         location_override only; all fields have font controls.
 *   Trusted (no edit mode) → can also edit full_name_override without enabling edit mode.
 *   Trusted / Admin + edit mode → can edit all fields (same scope as Badge Review page).
 *
 * Font sizes flow back to the parent via $bindable() props so the badge render
 * (ae_comp__badge_obj_view.svelte) stays in sync without prop-drilling through
 * a third component.
 *
 * preview_overrides: merged on top of the saved badge object before passing to
 * the badge render — gives real-time preview as the user types without saving.
 */

import type { key_val } from '$lib/stores/ae_stores';
import { ae_api, ae_loc } from '$lib/stores/ae_stores';
import { events_func } from '$lib/ae_events/ae_events_functions';
import { browser } from '$app/environment';
import { goto } from '$app/navigation';
import { badges_loc } from '$lib/stores/ae_events_stores__badges.svelte';
import {
    Check,
    ChevronDown,
    ChevronRight,
    Eye,
    EyeOff,
    Info,
    LoaderCircle,
    Pencil,
    Printer,
    RotateCcw,
    X
} from '@lucide/svelte';
interface Props {
    event_id: string;
    event_badge_id: string;
    lq__event_badge_obj?: any;
    lq__event_badge_template_obj?: any;
    /** Font size for name field (px). null = auto. $bindable so parent can forward to badge render. */
    font_size_name?: number | null;
    /** Font size for professional title (px). null = auto. */
    font_size_title?: number | null;
    /** Font size for affiliations (px). null = auto. */
    font_size_affiliations?: number | null;
    /** Font size for location (px). null = auto. */
    font_size_location?: number | null;
    /**
     * Real-time preview overrides. Set by this component while a field is open;
     * the parent passes it straight to the badge render so the badge updates live
     * as the user types — no save required.
     */
    preview_overrides?: Record<string, any> | null;
    /**
     * Per-browser print position calibration (mm). Stored in localStorage by the
     * parent page so each workstation/printer can be tuned independently.
     * Positive X = shift right; positive Y = shift down.
     */
    print_offset_x?: number;
    print_offset_y?: number;
    /**
     * Hides the page header and sys bar for a clean badge-preview workspace.
     * Toggle with the [H] keyboard shortcut or this button. Stored in localStorage.
     */
    hide_chrome?: boolean;
    /**
     * When true (default), header images stretch to 100% badge width.
     * Toggle off to see the image at its natural pixel size for calibration.
     * Stored in localStorage.
     */
    banner_full_width?: boolean;
    /** When true (default), name uses padding to prefer two-line layout. Stored in localStorage. */
    name_two_lines?: boolean;
    log_lvl?: number;
}

let {
    event_id,
    event_badge_id,
    lq__event_badge_obj,
    lq__event_badge_template_obj,
    font_size_name = $bindable(null),
    font_size_title = $bindable(null),
    font_size_affiliations = $bindable(null),
    font_size_location = $bindable(null),
    preview_overrides = $bindable(null),
    print_offset_x = $bindable(0),
    print_offset_y = $bindable(0),
    hide_chrome = $bindable(false),
    banner_full_width = $bindable(true),
    name_two_lines = $bindable(true),
    log_lvl = 0
}: Props = $props();

// --- Access level ---
// trusted_access is true for Trusted and every level above it (Administrator,
// Manager, Super). No need to OR in administrator_access — it's already covered.
let is_trusted = $derived($ae_loc.trusted_access === true);
// public_access — site-wide passcode or higher. Badge print kiosks run at this level.
// Sufficient for first-print only; reprints require trusted + edit_mode.
let is_public = $derived($ae_loc.public_access === true);
// Minimum bar to edit any field. Anonymous/public users see values but cannot edit.
let is_auth = $derived($ae_loc.authenticated_access === true);

// IMPORTANT: $ae_loc.edit_mode is the GLOBAL AE Edit Mode — a UI preference that
// reveals editable fields, debug info, and advanced options across the whole app.
// It is NOT the same as the badge-specific edit state (which no longer exists in
// the v2 badge render — all badge editing happens here in the controls panel).
// This component NEVER writes to $ae_loc.edit_mode. Read-only usage only:
// — used here to allow reprinting an already-printed badge when global edit mode is active.
let is_global_edit_mode = $derived($ae_loc.edit_mode === true);
let is_admin = $derived($ae_loc.administrator_access === true);

// --- Per-template controls config ---
// Stored in event_badge_template.other_json as { controls_cfg: { shown?, auth_editable? } }.
//   shown:        array of field keys to render; omit/null = all shown.
//   auth_editable: fields editable by authenticated (non-trusted) users; omit/null = defaults below.
// trusted + edit_mode always overrides config — sees and edits all fields.
// WHY: allows locking down a production template so attendees can only touch the
// fields the event coordinator explicitly permits, without touching the backend.
interface ControlsCfg {
    shown?: string[];
    auth_editable?: string[];
}
let template_controls_cfg = $derived.by((): ControlsCfg | null => {
    try {
        const raw = $lq__event_badge_template_obj?.cfg_json ?? null;
        if (raw) {
            const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;
            if (parsed?.controls_cfg) return parsed.controls_cfg as ControlsCfg;
        }
    } catch {
        // fall through to other_json fallback
    }
    try {
        const raw2 = $lq__event_badge_template_obj?.other_json ?? null;
        if (raw2) {
            const parsed2 = typeof raw2 === 'string' ? JSON.parse(raw2) : raw2;
            return (parsed2?.controls_cfg ?? null) as ControlsCfg | null;
        }
    } catch {
        return null;
    }
    return null;
});

// Default auth-editable fields when the template has no explicit config.
// Covers the fields an attendee most commonly needs to fix at the badge table.
const DEFAULT_AUTH_EDITABLE = [
    'title',
    'affiliations',
    'location',
    'allow_tracking',
    'pronouns'
];

// Default shown fields in the controls panel when the template has no explicit controls_cfg.
// Pronouns and lead scanning are off by default — most events don't expose them.
// WHY: prevents clutter for attendees at the badge table; events that need them must opt in via template config.
const DEFAULT_SHOWN = ['name', 'title', 'affiliations', 'location'];

/** Is this field card shown in the panel at all? trusted+edit always sees all fields. */
function field_shown(field: string): boolean {
    if (is_trusted && is_global_edit_mode) return true;
    const cfg = template_controls_cfg;
    if (!cfg?.shown) return DEFAULT_SHOWN.includes(field);
    return cfg.shown.includes(field);
}

/**
 * Can the current user edit this field?
 * - No authenticated_access → never (display only).
 * - trusted + edit_mode → always (all fields).
 * - trusted (no edit_mode) → name always editable; other fields follow auth_editable list.
 * - authenticated only → field must be in template's auth_editable list (or defaults).
 */
function field_editable(field: string): boolean {
    if (!is_auth) return false;
    if (is_trusted && is_global_edit_mode) return true;
    // Trusted staff can fix a name without enabling full edit mode —
    // same visibility as title/affiliations but scoped to trusted only.
    if (is_trusted && field === 'name') return true;
    const auth_editable =
        template_controls_cfg?.auth_editable ?? DEFAULT_AUTH_EDITABLE;
    return auth_editable.includes(field);
}

// --- Section collapse state ---
// Attendee fields start open (primary use); staff starts closed to save space.
// Toggling one collapses the other (auto-collapse, same pattern as launcher sections).
let attendee_open = $state(true);
let staff_open = $state(false);

function toggle_section(which: 'attendee' | 'staff') {
    if (which === 'attendee') {
        attendee_open = !attendee_open;
        if (attendee_open) staff_open = false;
    } else {
        staff_open = !staff_open;
        if (staff_open) attendee_open = false;
    }
}

// --- Print ---
let print_count = $derived($lq__event_badge_obj?.print_count ?? 0);
let is_printed = $derived(print_count >= 1);

// Print is available to Public+ for first prints (kiosk use case).
// Reprints require Trusted + global edit mode.
let can_print = $derived((is_public && !is_printed) || (is_trusted && is_global_edit_mode));

type PrintStatus = 'idle' | 'loading' | 'done' | 'error';
let print_status: PrintStatus = $state('idle');

// Navigate back to badge search after print completes.
// goto() is the default: faster SvelteKit client-side navigation, no full reload.
// Falls back to window.location.href if badges_loc.print_nav_use_goto is disabled —
// useful as an on-site escape hatch if the goto() path causes unexpected issues.
function nav_to_badges() {
    if (!browser) return;
    if (badges_loc.current.print_nav_use_goto) {
        goto(`/events/${event_id}/badges`);
    } else {
        window.location.href = `/events/${event_id}/badges`;
    }
}

async function handle_print_badge() {
    if (!$lq__event_badge_obj?.event_badge_id) return;
    print_status = 'loading';

    // MariaDB datetime columns do not accept ISO 8601 with milliseconds or 'Z' suffix.
    // Slice to 'YYYY-MM-DDTHH:MM:SS' — the format the DB expects.
    const now = new Date().toISOString().slice(0, 19);
    const is_first_print = print_count === 0;
    const data_to_update: key_val = {
        print_count: print_count + 1,
        print_last_datetime: now
    };
    if (is_first_print) data_to_update.print_first_datetime = now;

    // Offline or API unreachable: skip the PATCH and print immediately.
    // Never block a physical print for a network issue — staff can correct the
    // count manually in the Admin section after connectivity is restored.
    if (!navigator.onLine || $ae_loc?.account_id === 'ghost') {
        if (browser) window.print();
        print_status = 'error';
        await new Promise<void>((r) => setTimeout(r, 4000));
        nav_to_badges();
        return;
    }

    try {
        const result = await events_func.update_ae_obj__event_badge({
            api_cfg: $ae_api,
            event_id,
            event_badge_id: $lq__event_badge_obj.event_badge_id,
            data_kv: data_to_update,
            log_lvl
        });

        // update_ae_obj__event_badge returns false/null on API failure without throwing.
        // A falsy result means the PATCH failed (e.g. 400/401/500) — the DB was NOT updated.
        // We still print the physical badge (never block the physical print), but show a
        // visible error so staff can catch a runaway "already printed" situation and fix
        // the count manually via the Admin print count field in the Staff section.
        if (!result) {
            console.error('Badge print controls: PATCH returned falsy — DB count not updated.');
            print_status = 'error';
            // Still fire the physical print — the attendee must get their badge.
            if (browser) window.print();
            // Hold the error state long enough for a kiosk operator to notice before
            // the loop returns to search for the next attendee.
            await new Promise<void>((r) => setTimeout(r, 4000));
            nav_to_badges();
            return;
        }

        print_status = 'done';
        // Trigger browser print dialog after count is recorded
        if (browser) window.print();
        // Brief success flash, then return to badge search
        await new Promise<void>((r) => setTimeout(r, 1000));
        // Full navigation back to badge search — avoids goto() lint rule in child components
        nav_to_badges();
    } catch (err) {
        console.error('Badge print controls: print error:', err);
        print_status = 'error';
        if (browser) window.print();
        await new Promise<void>((r) => setTimeout(r, 4000));
        nav_to_badges();
    }
}

// --- Badge type list from template ---
// Each item: { code: string, name: string }. Used for the badge type dropdown.
// Falls back to a default list when the template has no badge_type_list configured.
// Keep in sync with ae_comp__badge_search.svelte and ae_comp__badge_create_form.svelte.
// TODO: drive all three from the template so this is event-agnostic.
const default_badge_type_code_li = [
    { code: 'attendee', name: 'In-Person Attendee' },
    { code: 'sponsor', name: 'Adapt26 Sponsor' },
    { code: 'staff', name: 'Staff' },
    { code: 'guest', name: 'Guest' },
    { code: 'volunteer', name: 'Volunteer' },
    { code: 'member', name: 'Member' },
    { code: 'nonmember', name: 'Non-Member' },
    { code: 'test', name: 'Test' }
];
let badge_type_code_li = $derived.by((): { code: string; name: string }[] => {
    const raw = $lq__event_badge_template_obj?.badge_type_list;
    if (!raw) return default_badge_type_code_li;
    try {
        const parsed = JSON.parse(raw);
        return Array.isArray(parsed) && parsed.length > 0 ? parsed : default_badge_type_code_li;
    } catch {
        return default_badge_type_code_li;
    }
});

// --- Print position offset configuration ---
const OFFSET_STEP = 0.5; // mm per click
const OFFSET_MAX = 15; // mm in either direction

// --- Font size configuration ---
const FONT_SIZE_STEP = 2;
const FONT_SIZE_DEFAULTS = {
    name: 58,
    title: 34,
    affiliations: 38,
    location: 34
} as const;
const FONT_SIZE_MIN = {
    name: 20,
    title: 14,
    affiliations: 14,
    location: 14
} as const;
const FONT_SIZE_MAX = {
    name: 80,
    title: 56,
    affiliations: 60,
    location: 56
} as const;

function font_size_adjust(
    field: 'name' | 'title' | 'affiliations' | 'location',
    delta: number
) {
    const cur =
        field === 'name'
            ? font_size_name
            : field === 'title'
              ? font_size_title
              : field === 'affiliations'
                ? font_size_affiliations
                : font_size_location;
    const next =
        cur === null
            ? FONT_SIZE_DEFAULTS[field]
            : Math.max(
                  FONT_SIZE_MIN[field],
                  Math.min(FONT_SIZE_MAX[field], cur + delta)
              );
    if (field === 'name') font_size_name = next;
    else if (field === 'title') font_size_title = next;
    else if (field === 'affiliations') font_size_affiliations = next;
    else if (field === 'location') font_size_location = next;
}

function font_size_reset(
    field: 'name' | 'title' | 'affiliations' | 'location'
) {
    if (field === 'name') font_size_name = null;
    else if (field === 'title') font_size_title = null;
    else if (field === 'affiliations') font_size_affiliations = null;
    else if (field === 'location') font_size_location = null;
}

// --- Accordion: one field section open at a time ---
let active_field: string | null = $state(null);

// --- Unsaved-change guard ---
// When the user tries to close or switch away from a dirty field, we DON'T
// silently discard. Instead we set pending_close = true, which turns the Save
// button bright red + pulsing so they notice they have unsaved changes.
// A second close attempt (while pending_close is already true) actually discards.
// WHY: kiosk attendees at a conference are confused when changes disappear without
// warning; they can't lose a professional title or affiliation mid-queue.
let pending_close = $state(false);

/** Returns the dirty state for the given field key. */
function is_dirty_for_field(field_key: string): boolean {
    switch (field_key) {
        case 'name': return is_dirty_name;
        case 'title': return is_dirty_title;
        case 'affiliations': return is_dirty_affiliations;
        case 'location': return is_dirty_location;
        case 'pronouns': return is_dirty_pronouns;
        case 'allow_tracking': return is_dirty_allow_tracking;
        case 'badge_type': return is_dirty_badge_type;
        default: return false;
    }
}

function toggle_field(key: string) {
    // Capture whether the caller is re-clicking the field that's already open.
    const was_open = active_field === key;

    if (active_field && !was_open) {
        // User is trying to switch to a different field.
        if (is_dirty_for_field(active_field) && !pending_close) {
            // Dirty — warn instead of switching. The Save button turns red so
            // the user knows they need to save (or cancel) the current field first.
            pending_close = true;
            return; // block the switch
        }
        // Either clean, or second attempt after warning — discard and switch.
        cancel_field(active_field);
    }

    if (was_open) {
        // Clicking the same field header again: same guard as the X button.
        cancel_field(key);
        return;
    }

    pending_close = false;
    active_field = key;
}

// --- Editable field state ---
// Initialized from badge obj; reset when badge obj changes (same pattern as
// ae_comp__badge_obj_view.svelte). The effective value (override ?? base) is
// pre-merged so the input shows what would actually print.
let edit_full_name_override: string = $state('');
let edit_professional_title_override: string = $state('');
let edit_affiliations_override: string = $state('');
let edit_location_override: string = $state('');
let edit_pronouns_override: string = $state('');
let edit_allow_tracking: boolean = $state(false);
// Effective badge type code (badge_type_code_override ?? badge_type_code)
let edit_badge_type_code: string | null = $state(null);

$effect(() => {
    if ($lq__event_badge_obj) {
        edit_full_name_override =
            $lq__event_badge_obj.full_name_override ??
            $lq__event_badge_obj.full_name ??
            '';
        edit_professional_title_override =
            $lq__event_badge_obj.professional_title_override ??
            $lq__event_badge_obj.professional_title ??
            '';
        edit_affiliations_override =
            $lq__event_badge_obj.affiliations_override ??
            $lq__event_badge_obj.affiliations ??
            '';
        edit_location_override =
            $lq__event_badge_obj.location_override ??
            $lq__event_badge_obj.location ??
            '';
        edit_pronouns_override =
            $lq__event_badge_obj.pronouns_override ??
            $lq__event_badge_obj.pronouns ??
            '';
        edit_allow_tracking = $lq__event_badge_obj.allow_tracking ?? false;
        edit_badge_type_code =
            $lq__event_badge_obj.badge_type_code_override ??
            $lq__event_badge_obj.badge_type_code ??
            null;
    }
});

// --- Per-field save status ---
type SaveStatus = 'idle' | 'saving' | 'done' | 'error';
let field_save_status: Record<string, SaveStatus> = $state({});

async function save_field(field_key: string, data_kv: key_val) {
    if (!$lq__event_badge_obj?.event_badge_id) return;
    field_save_status = { ...field_save_status, [field_key]: 'saving' };
    try {
        await events_func.update_ae_obj__event_badge({
            api_cfg: $ae_api,
            event_id,
            event_badge_id: $lq__event_badge_obj.event_badge_id,
            data_kv,
            log_lvl
        });
        field_save_status = { ...field_save_status, [field_key]: 'done' };
        pending_close = false;
        // liveQuery auto-refreshes the badge; close the accordion after a brief confirmation.
        setTimeout(() => {
            field_save_status = { ...field_save_status, [field_key]: 'idle' };
            active_field = null;
        }, 1200);
    } catch (err) {
        console.error(
            `Badge print controls: save error for "${field_key}":`,
            err
        );
        field_save_status = { ...field_save_status, [field_key]: 'error' };
        setTimeout(() => {
            field_save_status = { ...field_save_status, [field_key]: 'idle' };
        }, 2500);
    }
}

function cancel_field(field_key: string) {
    // First cancel attempt while dirty → warn, don't discard.
    // Second attempt (pending_close already set) → actually discard.
    if (is_dirty_for_field(field_key) && !pending_close) {
        pending_close = true;
        return;
    }

    pending_close = false;

    if (!$lq__event_badge_obj) {
        active_field = null;
        return;
    }
    switch (field_key) {
        case 'name':
            edit_full_name_override =
                $lq__event_badge_obj.full_name_override ??
                $lq__event_badge_obj.full_name ??
                '';
            break;
        case 'title':
            edit_professional_title_override =
                $lq__event_badge_obj.professional_title_override ??
                $lq__event_badge_obj.professional_title ??
                '';
            break;
        case 'affiliations':
            edit_affiliations_override =
                $lq__event_badge_obj.affiliations_override ??
                $lq__event_badge_obj.affiliations ??
                '';
            break;
        case 'location':
            edit_location_override =
                $lq__event_badge_obj.location_override ??
                $lq__event_badge_obj.location ??
                '';
            break;
        case 'pronouns':
            edit_pronouns_override =
                $lq__event_badge_obj.pronouns_override ??
                $lq__event_badge_obj.pronouns ??
                '';
            break;
        case 'allow_tracking':
            edit_allow_tracking = $lq__event_badge_obj.allow_tracking ?? false;
            break;
        case 'badge_type':
            edit_badge_type_code =
                $lq__event_badge_obj.badge_type_code_override ??
                $lq__event_badge_obj.badge_type_code ??
                null;
            break;
    }
    active_field = null;
}

// --- Display helpers ---
function get_display(override_key: string, base_key: string): string {
    return (
        $lq__event_badge_obj?.[override_key] ??
        $lq__event_badge_obj?.[base_key] ??
        ''
    );
}

// Real-time preview: keep preview_overrides in sync with whatever field is currently
// open and being edited. The badge render merges this on top of the saved badge object
// so changes show up immediately as the user types. Clears when no field is open.
$effect(() => {
    if (!active_field) {
        preview_overrides = null;
        return;
    }
    switch (active_field) {
        case 'name':
            preview_overrides = {
                full_name_override: edit_full_name_override || null
            };
            break;
        case 'title':
            preview_overrides = {
                professional_title_override:
                    edit_professional_title_override || null
            };
            break;
        case 'affiliations':
            preview_overrides = {
                affiliations_override: edit_affiliations_override || null
            };
            break;
        case 'location':
            preview_overrides = {
                location_override: edit_location_override || null
            };
            break;
        case 'pronouns':
            preview_overrides = {
                pronouns_override: edit_pronouns_override || null
            };
            break;
        case 'allow_tracking':
            preview_overrides = { allow_tracking: edit_allow_tracking };
            break;
        case 'badge_type':
            preview_overrides = {
                badge_type_code_override: edit_badge_type_code,
                badge_type_override: edit_badge_type_code
                    ? (badge_type_code_li.find(
                          (item) => item.code === edit_badge_type_code
                      )?.name ?? edit_badge_type_code)
                    : null
            };
            break;
        default:
            preview_overrides = null;
    }
});

// Effective badge type display name: priority matches ae_comp__badge_obj_view.svelte
let badge_type_display = $derived(
    $lq__event_badge_obj?.badge_type_override ??
        $lq__event_badge_obj?.badge_type ??
        badge_type_code_li.find(
            (item) =>
                item.code ===
                ($lq__event_badge_obj?.badge_type_code_override ??
                    $lq__event_badge_obj?.badge_type_code)
        )?.name ??
        $lq__event_badge_obj?.badge_type_code_override ??
        $lq__event_badge_obj?.badge_type_code ??
        ''
);

// --- Debug Outlines Toggle ---
let debug_outlines = $state(false);
$effect(() => {
    if (browser) {
        if (debug_outlines) {
            document.documentElement.classList.add('debug_outlines');
        } else {
            document.documentElement.classList.remove('debug_outlines');
        }
    }
    return () => {
        if (browser)
            document.documentElement.classList.remove('debug_outlines');
    };
});

// --- Focus management: focus the input when its accordion opens ---
// rAF gives the CSS accordion one repaint tick before focus() is called,
// avoiding jumping to an invisible (height: 0) element.
let input_ref_name: HTMLInputElement | undefined = $state();
let input_ref_title: HTMLInputElement | undefined = $state();
let input_ref_affiliations: HTMLTextAreaElement | undefined = $state();
let input_ref_location: HTMLInputElement | undefined = $state();
let input_ref_pronouns: HTMLInputElement | undefined = $state();
let input_ref_allow_tracking: HTMLInputElement | undefined = $state();
let select_ref_badge_type: HTMLSelectElement | undefined = $state();

$effect(() => {
    const field = active_field;
    if (!field) return;
    // Wait for the accordion CSS animation (185ms ease-out) to finish before focusing.
    // requestAnimationFrame fires at ~16ms — the input is still 0px / overflow:hidden
    // at that point, so focus() silently fails. 210ms clears the animation safely.
    setTimeout(() => {
        switch (field) {
            case 'name':
                input_ref_name?.focus();
                break;
            case 'title':
                input_ref_title?.focus();
                break;
            case 'affiliations':
                input_ref_affiliations?.focus();
                break;
            case 'location':
                input_ref_location?.focus();
                break;
            case 'pronouns':
                input_ref_pronouns?.focus();
                break;
            case 'allow_tracking':
                input_ref_allow_tracking?.focus();
                break;
            case 'badge_type':
                select_ref_badge_type?.focus();
                break;
        }
    }, 210);
});

// --- Dirty detection: compare live edit value against the last-saved badge obj ---
// Save button is hidden when the value matches saved state (no unsaved change);
// shown with a warning/attention style the moment something changes.
// Using string comparison for text fields; boolean comparison for allow_tracking;
// null-safe equality for badge_type_code (null vs null must be equal).
let is_dirty_name = $derived(
    edit_full_name_override !==
        ($lq__event_badge_obj?.full_name_override ??
            $lq__event_badge_obj?.full_name ??
            '')
);
let is_dirty_title = $derived(
    edit_professional_title_override !==
        ($lq__event_badge_obj?.professional_title_override ??
            $lq__event_badge_obj?.professional_title ??
            '')
);
let is_dirty_affiliations = $derived(
    edit_affiliations_override !==
        ($lq__event_badge_obj?.affiliations_override ??
            $lq__event_badge_obj?.affiliations ??
            '')
);
let is_dirty_location = $derived(
    edit_location_override !==
        ($lq__event_badge_obj?.location_override ??
            $lq__event_badge_obj?.location ??
            '')
);
let is_dirty_pronouns = $derived(
    edit_pronouns_override !==
        ($lq__event_badge_obj?.pronouns_override ??
            $lq__event_badge_obj?.pronouns ??
            '')
);
let is_dirty_allow_tracking = $derived(
    edit_allow_tracking !== ($lq__event_badge_obj?.allow_tracking ?? false)
);
let is_dirty_badge_type = $derived(
    edit_badge_type_code !==
        ($lq__event_badge_obj?.badge_type_code_override ??
            $lq__event_badge_obj?.badge_type_code ??
            null)
);

// --- Lock / Reset font sizes to badge cfg_json ---
// "Lock Sizes" saves the current font_size_* values into event_badge.cfg_json.font_sizes.
// Any kiosk that opens this badge will then load those sizes instead of auto-sizing.
// "Reset to Auto" saves null for all fields, restoring auto-sizing everywhere.
// Trusted-only — attendees at the badge table can't permanently alter the layout.
type LockStatus = 'idle' | 'saving' | 'done' | 'error';
let lock_sizes_status: LockStatus = $state('idle');

// True when at least one size is non-null (i.e. user has adjusted something)
let has_any_size_override = $derived(
    font_size_name !== null ||
    font_size_title !== null ||
    font_size_affiliations !== null ||
    font_size_location !== null
);

// Detect whether current state differs from what is saved in badge cfg_json
// so we can highlight the button when there are unsaved size changes.
let saved_font_sizes = $derived.by(() => {
    try {
        const cfg = typeof $lq__event_badge_obj?.cfg_json === 'string'
            ? JSON.parse($lq__event_badge_obj.cfg_json)
            : ($lq__event_badge_obj?.cfg_json ?? {});
        return cfg?.font_sizes ?? null;
    } catch {
        return null;
    }
});
let sizes_are_dirty = $derived(
    font_size_name !== (saved_font_sizes?.name ?? null) ||
    font_size_title !== (saved_font_sizes?.title ?? null) ||
    font_size_affiliations !== (saved_font_sizes?.affiliations ?? null) ||
    font_size_location !== (saved_font_sizes?.location ?? null)
);

async function lock_font_sizes() {
    if (!$lq__event_badge_obj?.event_badge_id) return;
    lock_sizes_status = 'saving';
    try {
        // Merge into existing cfg_json — preserve any other keys that may be there
        const existing_cfg = (() => {
            try {
                return typeof $lq__event_badge_obj.cfg_json === 'string'
                    ? JSON.parse($lq__event_badge_obj.cfg_json)
                    : ($lq__event_badge_obj.cfg_json ?? {});
            } catch { return {}; }
        })();
        const new_cfg = {
            ...existing_cfg,
            font_sizes: {
                name: font_size_name,
                title: font_size_title,
                affiliations: font_size_affiliations,
                location: font_size_location
            }
        };
        await events_func.update_ae_obj__event_badge({
            api_cfg: $ae_api,
            event_id,
            event_badge_id: $lq__event_badge_obj.event_badge_id,
            data_kv: { cfg_json: JSON.stringify(new_cfg) },
            log_lvl
        });
        lock_sizes_status = 'done';
        setTimeout(() => { lock_sizes_status = 'idle'; }, 1500);
    } catch (err) {
        console.error('Badge print controls: lock font sizes error:', err);
        lock_sizes_status = 'error';
        setTimeout(() => { lock_sizes_status = 'idle'; }, 2500);
    }
}

async function reset_font_sizes_to_auto() {
    // Clear all local size state
    font_size_name = null;
    font_size_title = null;
    font_size_affiliations = null;
    font_size_location = null;
    if (!$lq__event_badge_obj?.event_badge_id) return;
    // Persist the reset back to the badge — removes the locked sizes server-side
    lock_sizes_status = 'saving';
    try {
        const existing_cfg = (() => {
            try {
                return typeof $lq__event_badge_obj.cfg_json === 'string'
                    ? JSON.parse($lq__event_badge_obj.cfg_json)
                    : ($lq__event_badge_obj.cfg_json ?? {});
            } catch { return {}; }
        })();
        const new_cfg = { ...existing_cfg };
        delete new_cfg.font_sizes;
        await events_func.update_ae_obj__event_badge({
            api_cfg: $ae_api,
            event_id,
            event_badge_id: $lq__event_badge_obj.event_badge_id,
            data_kv: { cfg_json: Object.keys(new_cfg).length ? JSON.stringify(new_cfg) : null },
            log_lvl
        });
        lock_sizes_status = 'done';
        setTimeout(() => { lock_sizes_status = 'idle'; }, 1500);
    } catch (err) {
        console.error('Badge print controls: reset font sizes error:', err);
        lock_sizes_status = 'error';
        setTimeout(() => { lock_sizes_status = 'idle'; }, 2500);
    }
}

// --- Auto-save font sizes on change ---
// When a trusted user adjusts font sizes the change is saved automatically after a
// short debounce so they don't have to find and click "Lock Sizes" every time.
// WHY: the Lock Sizes button is in the Staff section (starts collapsed) and users
// routinely adjusted font sizes and then reloaded, losing their changes.
// reset_font_sizes_to_auto handles its own save, so we only auto-save when at
// least one size is non-null (i.e. the "reset to auto" path is excluded here).
let _font_auto_save_timer: ReturnType<typeof setTimeout> | null = null;
$effect(() => {
    // Re-read the dirty+override signals inside the effect so Svelte tracks them.
    const dirty = sizes_are_dirty;
    const has_override = has_any_size_override;
    if (!dirty || !has_override || !is_trusted || !$lq__event_badge_obj?.event_badge_id) {
        return;
    }
    if (_font_auto_save_timer !== null) clearTimeout(_font_auto_save_timer);
    _font_auto_save_timer = setTimeout(() => {
        _font_auto_save_timer = null;
        lock_font_sizes();
    }, 600);
    return () => {
        if (_font_auto_save_timer !== null) {
            clearTimeout(_font_auto_save_timer);
            _font_auto_save_timer = null;
        }
    };
});

// TC modal ref for the lead scanning terms & conditions dialog
let tc_dialog_ref: HTMLDialogElement | undefined;

// allow_tracking accordion auto-expands to show the checkbox whenever the
// preference is enabled — even with no field being edited — so the current
// state is always visible at a glance. Closes while another field is open.
let allow_tracking_open = $derived(
    active_field === 'allow_tracking' ||
        (active_field === null && $lq__event_badge_obj?.allow_tracking === true)
);
</script>

<!-- ============================================================
     Field card snippets
     ============================================================ -->

<!-- Compact font size row. "Font" text label removed (sr-only for a11y).
     Only +/−/value/reset visible — saves vertical space in the tight panel. -->
{#snippet font_ctrl(key: 'name' | 'title' | 'affiliations' | 'location')}
    {@const cur =
        key === 'name'
            ? font_size_name
            : key === 'title'
              ? font_size_title
              : key === 'affiliations'
                ? font_size_affiliations
                : font_size_location}
    <div
        class="flex items-center gap-1 border-t border-gray-100 px-2 py-0.5 dark:border-gray-800"
        role="group"
        aria-label="{key} font size controls">
        <span class="sr-only">Font size for {key}:</span>
        <button
            type="button"
            class="btn btn-xs preset-tonal-surface h-6 min-h-0 px-2 text-base leading-none"
            onclick={() => font_size_adjust(key, -FONT_SIZE_STEP)}
            disabled={cur !== null && cur <= FONT_SIZE_MIN[key]}
            title="Smaller font"
            aria-label="Decrease {key} font size">−</button>
        <span
            class="w-9 text-center font-mono text-[11px] text-gray-500 tabular-nums"
            aria-live="polite">
            {cur !== null ? `${cur}px` : 'Auto'}
        </span>
        <button
            type="button"
            class="btn btn-xs preset-tonal-surface h-6 min-h-0 px-2 text-base leading-none"
            onclick={() => font_size_adjust(key, FONT_SIZE_STEP)}
            disabled={cur !== null && cur >= FONT_SIZE_MAX[key]}
            title="Larger font"
            aria-label="Increase {key} font size">+</button>
        {#if cur !== null}
            <button
                type="button"
                class="btn btn-xs preset-tonal-surface h-6 min-h-0 px-1.5 opacity-60"
                onclick={() => font_size_reset(key)}
                title="Reset to auto"
                aria-label="Reset {key} font size to auto">↺</button>
        {/if}
    </div>
{/snippet}

<!-- Save/cancel row (inside accordion edit forms).
     on_revert is optional — shown only when a saved override exists.
     is_dirty: Save uses warning style when changed, invisible when clean.
     Layout always fixed: [Revert (or gap)] [Save] [X]
     Buttons are always rendered to keep X pinned at the right end. -->
{#snippet field_actions(
    field_key: string,
    on_save: () => void,
    on_cancel: () => void,
    on_revert: (() => void) | undefined = undefined,
    is_dirty = false
)}
    {@const status = field_save_status[field_key]}
    {@const save_visible = is_dirty || (status && status !== 'idle')}
    <!-- btn + preset-filled-* goes transparent on gray/surface backgrounds (Skeleton v4
         CSS variable specificity issue — see GUIDE__AE_UI_Style_Guidelines.md §12).
         Use direct Tailwind token classes for all three buttons here. -->
    <div class="mt-2 flex gap-2">
        <!-- Revert: always occupies left slot; invisible when no override exists -->
        <button
            type="button"
            class="flex h-8 shrink-0 cursor-pointer items-center justify-center rounded-lg px-2
                   bg-warning-500/15 text-warning-700 dark:text-warning-300
                   hover:bg-warning-500/30 transition-colors"
            class:invisible={!on_revert}
            class:pointer-events-none={!on_revert}
            onclick={on_revert}
            disabled={!on_revert || status === 'saving'}
            tabindex={on_revert ? 0 : -1}
            title="Remove override — restore original imported value"
            aria-label="Revert to original value"
            aria-hidden={!on_revert}><RotateCcw size="13" /></button>
        <!-- Save: always in centre slot; invisible+inert when clean.
             Turns bright red + pulses when pending_close is set — the user tried
             to close while dirty. They must save OR press X again to discard. -->
        <button
            type="button"
            class="flex h-8 flex-1 cursor-pointer items-center justify-center gap-1
                   rounded-lg px-3 text-sm font-semibold text-white transition-colors
                   disabled:cursor-not-allowed disabled:opacity-50"
            class:invisible={!save_visible}
            class:pointer-events-none={!save_visible}
            class:bg-error-500={(pending_close && is_dirty && (!status || status === 'idle')) || (status === 'error' && !pending_close)}
            class:animate-pulse={pending_close && is_dirty && (!status || status === 'idle')}
            class:bg-warning-500={!pending_close && save_visible && is_dirty && (!status || status === 'idle')}
            class:bg-surface-400={status === 'saving'}
            class:bg-success-500={status === 'done'}
            disabled={!save_visible || status === 'saving'}
            tabindex={save_visible ? 0 : -1}
            onclick={on_save}
            title={pending_close ? 'Unsaved changes — save, or press × to discard' : 'Save changes'}
            aria-label="Save changes"
            aria-hidden={!save_visible}>
            {#if status === 'saving'}
                <LoaderCircle size="14" class="animate-spin" /> Saving…
            {:else if status === 'done'}
                <Check size="14" /> Saved
            {:else if status === 'error' && !pending_close}
                Error — retry
            {:else if pending_close}
                Save or × to discard
            {:else}
                <Check size="14" /> Save
            {/if}
        </button>
        <!-- Cancel: always visible at right end.
             When pending_close is active, turns red so the user knows
             a second tap will actually discard their edit. -->
        <button
            type="button"
            class="flex h-8 cursor-pointer items-center justify-center rounded-lg px-2 transition-colors"
            class:bg-surface-200-800={!pending_close}
            class:hover:bg-surface-300-700={!pending_close}
            class:bg-error-500={pending_close}
            class:text-white={pending_close}
            onclick={on_cancel}
            title={pending_close ? 'Discard changes' : 'Cancel'}
            aria-label={pending_close ? 'Discard changes' : 'Cancel editing'}>
            <X size="14" />
        </button>
    </div>
{/snippet}

<!-- ============================================================
     Main panel
     ============================================================ -->

<!-- Print button row — canonical (70%) + quick print (30%).
     Canonical: increments print_count, fires window.print(), navigates back to search.
     Quick print: just window.print() — no count increment, no navigation.
     Both: Trusted+ only; edit mode allows reprints. -->
{#if can_print}
    <!-- Tinted card visually lifts the print action above the field list -->
    <div class="bg-primary-50/40 dark:bg-primary-950/20 mb-2 rounded-lg p-2">
        <div class="flex gap-1.5">
            <!-- Canonical Print Badge: 70% — tracks count, returns to search -->
            <button
                type="button"
                class="btn border-primary-200/50 dark:border-primary-800/30 flex flex-7 items-center justify-center gap-2 border"
                class:preset-filled-primary={print_status === 'idle'}
                class:preset-tonal-surface={print_status === 'loading'}
                class:preset-filled-success={print_status === 'done'}
                class:preset-tonal-error={print_status === 'error'}
                onclick={handle_print_badge}
                disabled={print_status === 'loading' || print_status === 'done'}
                title={is_printed
                    ? `Reprint badge (printed ${print_count}×)`
                    : 'Print badge now'}
                data-testid="badge-print-btn">
                {#if print_status === 'loading'}
                    <LoaderCircle size="14" class="animate-spin" /> Printing…
                {:else if print_status === 'done'}
                    <Check size="14" /> Printed!
                {:else if print_status === 'error'}
                    Printed — count NOT saved (see staff)
                {:else}
                    <Printer size="14" />
                    {is_printed ? `Reprint (${print_count}×)` : 'Print Badge'}
                {/if}
            </button>

            {#if $ae_loc.edit_mode && $ae_loc.trusted_access}
            <!-- Quick print: 30% — window.print() only, no count, no navigation.
                 Use when you need to reprint mid-workflow without resetting search state
                 or when the count should not change (e.g. a test print). -->
            <button
                type="button"
                class="btn preset-tonal-warning border-warning-200/50 dark:border-warning-800/30 flex flex-3 items-center justify-center gap-1 border text-xs"
                onclick={() => window.print()}
                title="Print without tracking — no count increment, stays on this page">
                <Printer size="12" />
                Test
            </button>
            {/if}
        </div>
        {#if is_printed && print_status === 'idle'}
            <p
                class="mt-1 text-center text-[10px] text-amber-600 dark:text-amber-400">
                Already printed {print_count}×
            </p>
        {/if}
    </div>
{/if}

<!-- Already-printed notice for public/attendee users who can't reprint.
     Without this they see a blank controls panel with no explanation. -->
{#if !can_print && is_printed && !is_trusted}
    <div class="mb-2 rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-center text-sm text-amber-800 dark:border-amber-800 dark:bg-amber-950/30 dark:text-amber-300">
        This badge has already been printed.
        <br />
        <span class="text-xs opacity-70">Please see event staff if you need assistance.</span>
    </div>
{/if}

<div class="space-y-1 text-sm">
    <!-- Attendee section header — clickable, collapses staff when opened -->
    <button
        type="button"
        class="flex w-full items-center justify-between rounded px-1 py-0.5 transition-colors hover:bg-gray-100 dark:hover:bg-gray-800"
        onclick={() => toggle_section('attendee')}
        aria-expanded={attendee_open}>
        <span
            class="text-[9px] font-semibold tracking-widest text-gray-400 uppercase dark:text-gray-500"
            >Attendee info</span>
        {#if attendee_open}
            <ChevronDown size="10" class="opacity-40" />
        {:else}
            <ChevronRight size="10" class="opacity-40" />
        {/if}
    </button>

    <div class="ctrl-accordion" class:open={attendee_open}>
        <div class="ctrl-accordion-inner space-y-1">
            <!-- === NAME ===
         Font controls: always visible (all can adjust for readability at kiosk).
         Edit form: gated by field_editable('name') — by default trusted+edit only;
         can be opened to authenticated users via template controls_cfg.auth_editable. -->
            {#if field_shown('name')}
                <div
                    class="field-card overflow-hidden rounded-lg"
                    class:field-card--active={active_field === 'name'}>
                    <div class="flex items-center gap-2 px-2 pt-1 pb-0.5">
                        <div class="min-w-0 flex-1">
                            <p class="field-label">Name</p>
                            {#if get_display('full_name_override', 'full_name')}
                                <p class="truncate leading-snug font-bold">
                                    {get_display(
                                        'full_name_override',
                                        'full_name'
                                    )}
                                </p>
                            {:else}
                                <p class="text-xs text-gray-400 italic">
                                    {field_editable('name')
                                        ? 'Tap ✎ to add'
                                        : 'Not set'}
                                </p>
                            {/if}
                        </div>
                        {#if field_editable('name')}
                            <button
                                type="button"
                                class="btn btn-xs preset-tonal-primary shrink-0 transition-colors"
                                onclick={() => toggle_field('name')}
                                aria-expanded={active_field === 'name'}
                                aria-controls="field-form-name"
                                title="Edit name"
                                aria-label="Edit name">
                                {#if active_field === 'name'}<ChevronDown
                                        size="12" />{:else}<Pencil
                                        size="12" />{/if}
                            </button>
                        {/if}
                    </div>
                    {@render font_ctrl('name')}
                    <!-- Accordion drawer: grid-template-rows 0fr→1fr animates height smoothly. -->
                    <div
                        class="ctrl-accordion"
                        class:open={active_field === 'name' &&
                            field_editable('name')}>
                        <div class="ctrl-accordion-inner">
                            <div
                                id="field-form-name"
                                class="border-t border-gray-200 bg-gray-50 px-2 pt-1.5 pb-2 dark:border-gray-700 dark:bg-gray-800">
                                {#if is_global_edit_mode}
                                    <label
                                        for="ctrl-full-name"
                                        class="mb-1 block text-[10px] text-gray-400">
                                        Name override — empty clears back to
                                        original
                                    </label>
                                {/if}
                                <input
                                    id="ctrl-full-name"
                                    name="ctrl-full-name"
                                    type="text"
                                    class="input w-full"
                                    bind:this={input_ref_name}
                                    bind:value={edit_full_name_override}
                                    placeholder={$lq__event_badge_obj?.full_name ??
                                        'Full name'} />
                                {@render field_actions(
                                    'name',
                                    () =>
                                        save_field('name', {
                                            full_name_override:
                                                edit_full_name_override || null
                                        }),
                                    () => cancel_field('name'),
                                    $lq__event_badge_obj?.full_name_override
                                        ? () =>
                                              save_field('name', {
                                                  full_name_override: null
                                              })
                                        : undefined,
                                    is_dirty_name
                                )}
                            </div>
                        </div>
                    </div>
                </div>
            {/if}<!-- field_shown: name -->

            <!-- === PROFESSIONAL TITLE === -->
            {#if field_shown('title')}
                <div
                    class="field-card overflow-hidden rounded-lg"
                    class:field-card--active={active_field === 'title'}>
                    <div class="flex items-center gap-2 px-2 pt-1 pb-0.5">
                        <div class="min-w-0 flex-1">
                            <p class="field-label">Title</p>
                            {#if get_display('professional_title_override', 'professional_title')}
                                <p class="truncate leading-snug">
                                    {get_display(
                                        'professional_title_override',
                                        'professional_title'
                                    )}
                                </p>
                            {:else}
                                <p class="text-xs text-gray-400 italic">
                                    Tap ✎ to add
                                </p>
                            {/if}
                        </div>
                        {#if field_editable('title')}
                            <button
                                type="button"
                                class="btn btn-xs preset-tonal-primary shrink-0 transition-colors"
                                onclick={() => toggle_field('title')}
                                aria-expanded={active_field === 'title'}
                                aria-controls="field-form-title"
                                title="Edit professional title"
                                aria-label="Edit professional title">
                                {#if active_field === 'title'}<ChevronDown
                                        size="12" />{:else}<Pencil
                                        size="12" />{/if}
                            </button>
                        {/if}
                    </div>
                    {@render font_ctrl('title')}
                    <div
                        class="ctrl-accordion"
                        class:open={active_field === 'title' &&
                            field_editable('title')}>
                        <div class="ctrl-accordion-inner">
                            <div
                                id="field-form-title"
                                class="border-t border-gray-200 bg-gray-50 px-2 pt-1.5 pb-2 dark:border-gray-700 dark:bg-gray-800">
                                {#if is_global_edit_mode}
                                    <label
                                        for="ctrl-pro-title"
                                        class="mb-1 block text-[10px] text-gray-400"
                                        >Professional title</label>
                                {/if}
                                <input
                                    id="ctrl-pro-title"
                                    name="ctrl-pro-title"
                                    type="text"
                                    class="input w-full"
                                    bind:this={input_ref_title}
                                    bind:value={
                                        edit_professional_title_override
                                    }
                                    placeholder={$lq__event_badge_obj?.professional_title ??
                                        'Professional title'} />
                                {@render field_actions(
                                    'title',
                                    () =>
                                        save_field('title', {
                                            professional_title_override:
                                                edit_professional_title_override ||
                                                null
                                        }),
                                    () => cancel_field('title'),
                                    $lq__event_badge_obj?.professional_title_override
                                        ? () =>
                                              save_field('title', {
                                                  professional_title_override:
                                                      null
                                              })
                                        : undefined,
                                    is_dirty_title
                                )}
                            </div>
                        </div>
                    </div>
                </div>
            {/if}<!-- field_shown: title -->

            <!-- === AFFILIATIONS === -->
            {#if field_shown('affiliations')}
                <div
                    class="field-card overflow-hidden rounded-lg"
                    class:field-card--active={active_field === 'affiliations'}>
                    <div class="flex items-center gap-2 px-2 pt-1 pb-0.5">
                        <div class="min-w-0 flex-1">
                            <p class="field-label">Affiliations</p>
                            {#if get_display('affiliations_override', 'affiliations')}
                                <p class="line-clamp-2 leading-snug">
                                    {get_display(
                                        'affiliations_override',
                                        'affiliations'
                                    )}
                                </p>
                            {:else}
                                <p class="text-xs text-gray-400 italic">
                                    Tap ✎ to add
                                </p>
                            {/if}
                        </div>
                        {#if field_editable('affiliations')}
                            <button
                                type="button"
                                class="btn btn-xs preset-tonal-primary shrink-0 transition-colors"
                                onclick={() => toggle_field('affiliations')}
                                aria-expanded={active_field === 'affiliations'}
                                aria-controls="field-form-affiliations"
                                title="Edit affiliations"
                                aria-label="Edit affiliations">
                                {#if active_field === 'affiliations'}<ChevronDown
                                        size="12" />{:else}<Pencil
                                        size="12" />{/if}
                            </button>
                        {/if}
                    </div>
                    {@render font_ctrl('affiliations')}
                    <div
                        class="ctrl-accordion"
                        class:open={active_field === 'affiliations' &&
                            field_editable('affiliations')}>
                        <div class="ctrl-accordion-inner">
                            <div
                                id="field-form-affiliations"
                                class="border-t border-gray-200 bg-gray-50 px-2 pt-1.5 pb-2 dark:border-gray-700 dark:bg-gray-800">
                                {#if is_global_edit_mode}
                                    <label
                                        for="ctrl-affiliations"
                                        class="mb-1 block text-[10px] text-gray-400"
                                        >Affiliations / organization</label>
                                {/if}
                                <textarea
                                    id="ctrl-affiliations"
                                    name="ctrl-affiliations"
                                    class="textarea w-full"
                                    rows="2"
                                    bind:this={input_ref_affiliations}
                                    bind:value={edit_affiliations_override}
                                    placeholder={$lq__event_badge_obj?.affiliations ??
                                        'Organization / affiliations'}
                                ></textarea>
                                {@render field_actions(
                                    'affiliations',
                                    () =>
                                        save_field('affiliations', {
                                            affiliations_override:
                                                edit_affiliations_override ||
                                                null
                                        }),
                                    () => cancel_field('affiliations'),
                                    $lq__event_badge_obj?.affiliations_override
                                        ? () =>
                                              save_field('affiliations', {
                                                  affiliations_override: null
                                              })
                                        : undefined,
                                    is_dirty_affiliations
                                )}
                            </div>
                        </div>
                    </div>
                </div>
            {/if}<!-- field_shown: affiliations -->

            <!-- === LOCATION === -->
            {#if field_shown('location')}
                <div
                    class="field-card overflow-hidden rounded-lg"
                    class:field-card--active={active_field === 'location'}>
                    <div class="flex items-center gap-2 px-2 pt-1 pb-0.5">
                        <div class="min-w-0 flex-1">
                            <p class="field-label">Location</p>
                            {#if get_display('location_override', 'location')}
                                <p class="truncate leading-snug">
                                    {get_display(
                                        'location_override',
                                        'location'
                                    )}
                                </p>
                            {:else}
                                <p class="text-xs text-gray-400 italic">
                                    Tap ✎ to add
                                </p>
                            {/if}
                        </div>
                        {#if field_editable('location')}
                            <button
                                type="button"
                                class="btn btn-xs preset-tonal-primary shrink-0 transition-colors"
                                onclick={() => toggle_field('location')}
                                aria-expanded={active_field === 'location'}
                                aria-controls="field-form-location"
                                title="Edit location"
                                aria-label="Edit location">
                                {#if active_field === 'location'}<ChevronDown
                                        size="12" />{:else}<Pencil
                                        size="12" />{/if}
                            </button>
                        {/if}
                    </div>
                    {@render font_ctrl('location')}
                    <div
                        class="ctrl-accordion"
                        class:open={active_field === 'location' &&
                            field_editable('location')}>
                        <div class="ctrl-accordion-inner">
                            <div
                                id="field-form-location"
                                class="border-t border-gray-200 bg-gray-50 px-2 pt-1.5 pb-2 dark:border-gray-700 dark:bg-gray-800">
                                {#if is_global_edit_mode}
                                    <label
                                        for="ctrl-location"
                                        class="mb-1 block text-[10px] text-gray-400"
                                        >City / State / Country</label>
                                {/if}
                                <input
                                    id="ctrl-location"
                                    name="ctrl-location"
                                    type="text"
                                    class="input w-full"
                                    bind:this={input_ref_location}
                                    bind:value={edit_location_override}
                                    placeholder={$lq__event_badge_obj?.location ??
                                        'City, State / Country'} />
                                {@render field_actions(
                                    'location',
                                    () =>
                                        save_field('location', {
                                            location_override:
                                                edit_location_override || null
                                        }),
                                    () => cancel_field('location'),
                                    $lq__event_badge_obj?.location_override
                                        ? () =>
                                              save_field('location', {
                                                  location_override: null
                                              })
                                        : undefined,
                                    is_dirty_location
                                )}
                            </div>
                        </div>
                    </div>
                </div>
            {/if}<!-- field_shown: location -->

            <!-- === ALLOW TRACKING (Lead Scanning) === -->
            {#if false}<!-- AXONIUS 2026: Lead scanning canceled — restore by changing to {#if field_shown('allow_tracking')} -->
                <div
                    class="field-card overflow-hidden rounded-lg"
                    class:field-card--active={active_field ===
                        'allow_tracking'}>
                    <div class="flex items-center gap-2 px-2 py-1">
                        <div class="min-w-0 flex-1">
                            <p class="field-label">Lead Scanning</p>
                            <p class="text-xs leading-snug">
                                {$lq__event_badge_obj?.allow_tracking
                                    ? 'Allowed'
                                    : 'Not allowed'}
                            </p>
                        </div>
                        {#if field_editable('allow_tracking')}
                            <button
                                type="button"
                                class="btn btn-xs preset-tonal-primary shrink-0 transition-colors"
                                onclick={() => toggle_field('allow_tracking')}
                                aria-expanded={active_field ===
                                    'allow_tracking'}
                                aria-controls="field-form-allow-tracking"
                                title="Edit lead scanning preference"
                                aria-label="Edit lead scanning">
                                {#if active_field === 'allow_tracking'}<ChevronDown
                                        size="12" />{:else}<Pencil
                                        size="12" />{/if}
                            </button>
                        {/if}
                    </div>
                    <div
                        class="ctrl-accordion"
                        class:open={allow_tracking_open &&
                            field_editable('allow_tracking')}>
                        <div class="ctrl-accordion-inner">
                            <div
                                id="field-form-allow-tracking"
                                class="border-t border-gray-200 bg-gray-50 px-2 pt-1.5 pb-2 dark:border-gray-700 dark:bg-gray-800">
                                <label
                                    class="flex cursor-pointer items-center gap-2 select-none">
                                    <input
                                        id="ctrl-allow-tracking"
                                        name="ctrl-allow-tracking"
                                        type="checkbox"
                                        class="checkbox"
                                        bind:this={input_ref_allow_tracking}
                                        bind:checked={edit_allow_tracking} />
                                    <span class="text-xs"
                                        >Allow exhibitor lead scanning</span>
                                </label>
                                <!-- Inline actions — not shared snippet because this field adds a TC info button.
                         Layout always fixed: [Save] [X] [ⓘ] — Save invisible when clean. -->
                                <div class="mt-2 flex gap-2">
                                    <button
                                        type="button"
                                        class="btn btn-sm flex-1 transition-colors"
                                        class:invisible={!is_dirty_allow_tracking &&
                                            (!field_save_status[
                                                'allow_tracking'
                                            ] ||
                                                field_save_status[
                                                    'allow_tracking'
                                                ] === 'idle')}
                                        class:pointer-events-none={!is_dirty_allow_tracking &&
                                            (!field_save_status[
                                                'allow_tracking'
                                            ] ||
                                                field_save_status[
                                                    'allow_tracking'
                                                ] === 'idle')}
                                        class:preset-filled-warning={is_dirty_allow_tracking &&
                                            (!field_save_status[
                                                'allow_tracking'
                                            ] ||
                                                field_save_status[
                                                    'allow_tracking'
                                                ] === 'idle')}
                                        class:preset-tonal-surface={field_save_status[
                                            'allow_tracking'
                                        ] === 'saving'}
                                        class:preset-filled-success={field_save_status[
                                            'allow_tracking'
                                        ] === 'done'}
                                        class:preset-tonal-error={field_save_status[
                                            'allow_tracking'
                                        ] === 'error'}
                                        disabled={!is_dirty_allow_tracking ||
                                            field_save_status[
                                                'allow_tracking'
                                            ] === 'saving'}
                                        tabindex={is_dirty_allow_tracking
                                            ? 0
                                            : -1}
                                        onclick={() =>
                                            save_field('allow_tracking', {
                                                allow_tracking:
                                                    edit_allow_tracking
                                            })}
                                        title="Save changes"
                                        aria-label="Save changes"
                                        aria-hidden={!is_dirty_allow_tracking &&
                                            (!field_save_status[
                                                'allow_tracking'
                                            ] ||
                                                field_save_status[
                                                    'allow_tracking'
                                                ] === 'idle')}>
                                        {#if field_save_status['allow_tracking'] === 'saving'}
                                            <LoaderCircle
                                                size="14"
                                                class="mr-1 animate-spin" /> Saving…
                                        {:else if field_save_status['allow_tracking'] === 'done'}
                                            <Check size="14" class="mr-1" /> Saved
                                        {:else if field_save_status['allow_tracking'] === 'error'}
                                            Error — retry
                                        {:else}
                                            <Check size="14" class="mr-1" /> Save
                                        {/if}
                                    </button>
                                    <button
                                        type="button"
                                        class="btn btn-sm preset-tonal-surface"
                                        onclick={() =>
                                            cancel_field('allow_tracking')}
                                        title="Cancel"
                                        aria-label="Cancel editing"
                                        ><X size="14" /></button>
                                    <button
                                        type="button"
                                        class="btn btn-sm preset-tonal-surface shrink-0"
                                        onclick={() =>
                                            tc_dialog_ref?.showModal()}
                                        title="View lead scanning terms & conditions"
                                        aria-label="View lead scanning terms and conditions"
                                        ><Info size="14" /></button>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            {/if}<!-- field_shown: allow_tracking -->

            <!-- === PRONOUNS === -->
            {#if false}<!-- AXONIUS 2026: not on this badge template — restore by changing to {#if field_shown('pronouns')} -->
                <div
                    class="field-card overflow-hidden rounded-lg"
                    class:field-card--active={active_field === 'pronouns'}>
                    <div class="flex items-center gap-2 px-2 py-1">
                        <div class="min-w-0 flex-1">
                            <p class="field-label">Pronouns</p>
                            {#if get_display('pronouns_override', 'pronouns')}
                                <p class="truncate leading-snug">
                                    {get_display(
                                        'pronouns_override',
                                        'pronouns'
                                    )}
                                </p>
                            {:else}
                                <p class="text-xs text-gray-400 italic">
                                    Tap ✎ to add
                                </p>
                            {/if}
                        </div>
                        {#if field_editable('pronouns')}
                            <button
                                type="button"
                                class="btn btn-xs preset-tonal-primary shrink-0 transition-colors"
                                onclick={() => toggle_field('pronouns')}
                                aria-expanded={active_field === 'pronouns'}
                                aria-controls="field-form-pronouns"
                                title="Edit pronouns"
                                aria-label="Edit pronouns">
                                {#if active_field === 'pronouns'}<ChevronDown
                                        size="12" />{:else}<Pencil
                                        size="12" />{/if}
                            </button>
                        {/if}
                    </div>
                    <div
                        class="ctrl-accordion"
                        class:open={active_field === 'pronouns' &&
                            field_editable('pronouns')}>
                        <div class="ctrl-accordion-inner">
                            <div
                                id="field-form-pronouns"
                                class="border-t border-gray-200 bg-gray-50 px-2 pt-1.5 pb-2 dark:border-gray-700 dark:bg-gray-800">
                                <input
                                    id="ctrl-pronouns"
                                    name="ctrl-pronouns"
                                    type="text"
                                    class="input w-full"
                                    bind:this={input_ref_pronouns}
                                    bind:value={edit_pronouns_override}
                                    placeholder={$lq__event_badge_obj?.pronouns ??
                                        'e.g. they/them'} />
                                {@render field_actions(
                                    'pronouns',
                                    () =>
                                        save_field('pronouns', {
                                            pronouns_override:
                                                edit_pronouns_override || null
                                        }),
                                    () => cancel_field('pronouns'),
                                    $lq__event_badge_obj?.pronouns_override
                                        ? () =>
                                              save_field('pronouns', {
                                                  pronouns_override: null
                                              })
                                        : undefined,
                                    is_dirty_pronouns
                                )}
                            </div>
                        </div>
                    </div>
                </div>
            {/if}<!-- field_shown: pronouns -->
        </div>
        <!-- ctrl-accordion-inner attendee -->
    </div>
    <!-- ctrl-accordion attendee -->

    <!-- === STAFF-ONLY FIELDS === -->
    {#if is_trusted}
        <!-- Staff section header — clickable, collapses attendee when opened -->
        <button
            type="button"
            class="flex w-full items-center justify-between rounded px-1 py-0.5 transition-colors hover:bg-gray-100 dark:hover:bg-gray-800"
            onclick={() => toggle_section('staff')}
            aria-expanded={staff_open}>
            <span
                class="text-[9px] font-semibold tracking-widest text-gray-400 uppercase dark:text-gray-500"
                >Staff adjustments</span>
            {#if staff_open}
                <ChevronDown size="10" class="opacity-40" />
            {:else}
                <ChevronRight size="10" class="opacity-40" />
            {/if}
        </button>

        <div class="ctrl-accordion" class:open={staff_open}>
            <div class="ctrl-accordion-inner space-y-1 pt-0.5">
                <!-- === BADGE TYPE === (only when template defines badge_type_list) -->
                {#if badge_type_code_li.length > 0}
                    <div
                        class="field-card overflow-hidden rounded-lg"
                        class:field-card--active={active_field === 'badge_type'}>
                        <div class="flex items-center gap-2 px-2 py-1">
                            <div class="min-w-0 flex-1">
                                <p class="field-label">Badge Type</p>
                                {#if badge_type_display}
                                    <p class="truncate leading-snug">
                                        {badge_type_display}
                                    </p>
                                {:else}
                                    <p class="text-xs text-gray-400 italic">
                                        Tap ✎ to assign
                                    </p>
                                {/if}
                            </div>
                            <button
                                type="button"
                                class="btn btn-xs preset-tonal-primary shrink-0 transition-colors"
                                onclick={() => toggle_field('badge_type')}
                                aria-expanded={active_field === 'badge_type'}
                                aria-controls="field-form-badge-type"
                                title="Edit badge type"
                                aria-label="Edit badge type">
                                {#if active_field === 'badge_type'}<ChevronDown
                                        size="12" />{:else}<Pencil
                                        size="12" />{/if}
                            </button>
                        </div>
                        <div
                            class="ctrl-accordion"
                            class:open={active_field === 'badge_type'}>
                            <div class="ctrl-accordion-inner">
                                <div
                                    id="field-form-badge-type"
                                    class="border-t border-gray-200 bg-gray-50 px-2 pt-1.5 pb-2 dark:border-gray-700 dark:bg-gray-800">
                                    <select
                                        id="ctrl-badge-type"
                                        name="ctrl-badge-type"
                                        class="select w-full"
                                        bind:this={select_ref_badge_type}
                                        bind:value={edit_badge_type_code}>
                                        <option value={null}
                                            >— Select badge type —</option>
                                        {#each badge_type_code_li as item (item.code)}
                                            <option value={item.code}
                                                >{item.name}</option>
                                        {/each}
                                    </select>
                                    {@render field_actions(
                                        'badge_type',
                                        () =>
                                            save_field('badge_type', {
                                                badge_type_code_override:
                                                    edit_badge_type_code,
                                                // Keep badge_type_override in sync with the name.
                                                // See PROJECT__AE_Events_Badges_Review_Print.md for edge-case notes.
                                                badge_type_override:
                                                    edit_badge_type_code
                                                        ? (badge_type_code_li.find(
                                                              (item) =>
                                                                  item.code ===
                                                                  edit_badge_type_code
                                                          )?.name ??
                                                          edit_badge_type_code)
                                                        : null
                                            }),
                                        () => cancel_field('badge_type'),
                                        $lq__event_badge_obj?.badge_type_code_override
                                            ? () =>
                                                  save_field('badge_type', {
                                                      badge_type_code_override:
                                                          null,
                                                      badge_type_override: null
                                                  })
                                            : undefined,
                                        is_dirty_badge_type
                                    )}
                                </div>
                            </div>
                        </div>
                    </div>
                {/if}

                <!-- === HIDE BADGE ===
                     Removes this badge from search results for all users.
                     Use for duplicates, test records, or badges that should not be printed.
                     Trusted + Edit Mode. Visible even when badge is already hidden so staff
                     can recover it without needing to enable Show Hidden in search first. -->
                <div class="flex items-center gap-2 px-2 py-1">
                    <button
                        type="button"
                        class="btn btn-sm w-full justify-between"
                        class:preset-tonal-warning={$lq__event_badge_obj?.hide}
                        class:preset-tonal-surface={!$lq__event_badge_obj?.hide}
                        disabled={field_save_status['hide'] === 'saving'}
                        onclick={() => save_field('hide', { hide: !$lq__event_badge_obj?.hide })}
                        title={$lq__event_badge_obj?.hide
                            ? 'Badge is hidden — click to unhide and restore to search results'
                            : 'Hide this badge from search results'}>
                        <span class="flex items-center gap-1.5">
                            {#if field_save_status['hide'] === 'saving'}
                                <LoaderCircle size="13" class="animate-spin shrink-0" />
                                {$lq__event_badge_obj?.hide ? 'Unhiding…' : 'Hiding…'}
                            {:else if $lq__event_badge_obj?.hide}
                                <Eye size="13" class="shrink-0" />
                                Unhide Badge
                            {:else}
                                <EyeOff size="13" class="shrink-0" />
                                Hide Badge
                            {/if}
                        </span>
                        {#if field_save_status['hide'] === 'done'}
                            <Check size="13" class="text-success-500 shrink-0" />
                        {/if}
                    </button>
                </div>

                <!-- === PRINT COUNT (Admin+) ===
                     Correction tool for when the physical count diverges from the DB
                     (e.g. badge was printed offline or kiosk count reset). Does NOT
                     change print_first_datetime / print_last_datetime — timestamps
                     are only written by the actual Print Badge action. -->
                {#if is_admin}
                    <div class="px-2 py-1">
                        <p class="field-label mb-1">
                            Print Count
                            <span class="ml-1 text-[9px] font-normal text-gray-400 normal-case tracking-normal">Admin · timestamps unchanged</span>
                        </p>
                        <div class="flex items-center gap-2">
                            <input
                                type="number"
                                min="0"
                                value={print_count}
                                onblur={(e) => {
                                    const v = Math.max(0, Math.round(Number((e.currentTarget as HTMLInputElement).value)));
                                    if (v !== print_count) save_field('print_count', { print_count: v });
                                }}
                                onkeydown={(e) => { if (e.key === 'Enter') (e.currentTarget as HTMLInputElement).blur(); }}
                                disabled={field_save_status['print_count'] === 'saving'}
                                class="input input-sm w-20 text-center font-mono" />
                            {#if field_save_status['print_count'] === 'saving'}
                                <LoaderCircle size="13" class="animate-spin opacity-70" />
                            {:else if field_save_status['print_count'] === 'done'}
                                <Check size="13" class="text-success-500" />
                            {/if}
                        </div>
                    </div>
                {/if}

                <!-- Chrome visibility toggle: hides page header + sys bar for a clean workspace.
             Keyboard shortcut [H] does the same thing from anywhere on the page. -->
                <div class="flex items-center gap-2 px-2 py-1">
                    <button
                        type="button"
                        class="btn btn-sm w-full justify-between"
                        class:preset-tonal-warning={hide_chrome}
                        class:preset-tonal-surface={!hide_chrome}
                        onclick={() => (hide_chrome = !hide_chrome)}
                        title={hide_chrome
                            ? 'Show site nav, footer, and menu bar'
                            : 'Hide site nav, footer, and menu bar for a clean workspace'}>
                        <span class="flex items-center gap-1.5">
                            {#if hide_chrome}
                                <Eye size="13" class="shrink-0" />
                                Show nav/footer/menu
                            {:else}
                                <EyeOff size="13" class="shrink-0" />
                                Hide nav/footer/menu
                            {/if}
                        </span>
                        <span class="text-[10px] opacity-40">[H]</span>
                    </button>
                </div>

                <!-- Name layout: two-line (preferred) vs single-line -->
                <div class="flex items-center gap-2 px-2 py-1">
                    <label
                        class="flex cursor-pointer items-center gap-2 select-none">
                        <input
                            type="checkbox"
                            class="checkbox"
                            bind:checked={name_two_lines} />
                        <span class="text-xs">Name: prefer two lines</span>
                    </label>
                </div>

                <!-- Banner width toggle: 100% (production) vs natural size (dev/calibration) -->
                <div class="flex items-center gap-2 px-2 py-1">
                    <label
                        class="flex cursor-pointer items-center gap-2 select-none">
                        <input
                            type="checkbox"
                            class="checkbox"
                            bind:checked={banner_full_width} />
                        <span class="text-xs">Banner full width</span>
                    </label>
                </div>

                <!-- Debug Outlines Toggle (for print testing) -->
                <div class="flex items-center gap-2 px-2 py-1">
                    <label
                        class="flex cursor-pointer items-center gap-2 select-none">
                        <input
                            type="checkbox"
                            class="checkbox"
                            bind:checked={debug_outlines} />
                        <span class="text-xs">Show debug outlines</span>
                    </label>
                </div>

                <!-- === LOCK FONT SIZES ===
                     Saves the current font_size_* values into event_badge.cfg_json.font_sizes
                     so they survive page reloads and apply on every kiosk, not just this machine.
                     Use this for attendees with long names that auto-sizing can't handle well.
                     Trusted-only. -->
                <div class="space-y-1 px-1 pt-1">
                    <p class="field-label px-1">Locked Font Sizes</p>
                    <div class="flex gap-1.5">
                        <button
                            type="button"
                            class="btn btn-xs flex-1 transition-colors {lock_sizes_status === 'done' ? 'preset-filled-success' : lock_sizes_status === 'error' ? 'preset-tonal-error' : lock_sizes_status === 'saving' ? 'preset-tonal-surface' : sizes_are_dirty ? 'preset-filled-warning' : 'preset-tonal-surface'}"
                            disabled={lock_sizes_status === 'saving' || !has_any_size_override}
                            onclick={lock_font_sizes}
                            title="Save current font sizes to this badge record — applies on all kiosks">
                            {#if lock_sizes_status === 'saving'}
                                <LoaderCircle size="11" class="mr-1 animate-spin" /> Saving…
                            {:else if lock_sizes_status === 'done'}
                                <Check size="11" class="mr-1" /> Locked
                            {:else if lock_sizes_status === 'error'}
                                Error
                            {:else}
                                🔒 Lock Sizes
                            {/if}
                        </button>
                        {#if saved_font_sizes || has_any_size_override}
                            <button
                                type="button"
                                class="btn btn-xs preset-tonal-warning shrink-0"
                                disabled={lock_sizes_status === 'saving'}
                                onclick={reset_font_sizes_to_auto}
                                title="Reset all font sizes to auto and clear saved sizes from badge record">
                                ↺ Auto
                            </button>
                        {/if}
                    </div>
                    {#if saved_font_sizes}
                        <p class="px-1 text-[9px] text-green-600 dark:text-green-400">
                            Sizes locked on this badge
                            {#if saved_font_sizes.name}· Name {saved_font_sizes.name}px{/if}
                            {#if saved_font_sizes.title}· Title {saved_font_sizes.title}px{/if}
                        </p>
                    {/if}
                </div>

                <!-- Print Position Offset
             Per-browser calibration for physical printer alignment.
             Stored in localStorage by the parent page — each workstation keeps
             its own values across sessions without any server config change.
             Positive X = shift right; positive Y = shift down. 0.5mm steps. -->
                <div class="px-2 py-1">
                    <p class="field-label mb-1">
                        Print position
                        <span
                            class="ml-1 text-[9px] font-normal tracking-normal text-gray-400 normal-case"
                            >saved to browser</span>
                    </p>
                    <div class="space-y-1">
                        <!-- X axis: left ← / right → -->
                        <div
                            class="flex items-center gap-1"
                            role="group"
                            aria-label="Horizontal print offset">
                            <span class="w-8 shrink-0 text-[10px] text-gray-400"
                                >L/R</span>
                            <button
                                type="button"
                                class="btn btn-xs preset-tonal-surface h-6 min-h-0 px-2 text-base leading-none"
                                onclick={() =>
                                    (print_offset_x = Math.max(
                                        -OFFSET_MAX,
                                        print_offset_x - OFFSET_STEP
                                    ))}
                                disabled={print_offset_x <= -OFFSET_MAX}
                                title="Shift badge left"
                                aria-label="Shift left">−</button>
                            <span
                                class="w-14 text-center font-mono text-[11px] tabular-nums"
                                class:text-gray-400={print_offset_x === 0}
                                class:text-amber-600={print_offset_x !== 0}
                                aria-live="polite"
                                >{print_offset_x > 0
                                    ? '+'
                                    : ''}{print_offset_x.toFixed(1)} mm</span>
                            <button
                                type="button"
                                class="btn btn-xs preset-tonal-surface h-6 min-h-0 px-2 text-base leading-none"
                                onclick={() =>
                                    (print_offset_x = Math.min(
                                        OFFSET_MAX,
                                        print_offset_x + OFFSET_STEP
                                    ))}
                                disabled={print_offset_x >= OFFSET_MAX}
                                title="Shift badge right"
                                aria-label="Shift right">+</button>
                        </div>
                        <!-- Y axis: up ↑ / down ↓ -->
                        <div
                            class="flex items-center gap-1"
                            role="group"
                            aria-label="Vertical print offset">
                            <span class="w-8 shrink-0 text-[10px] text-gray-400"
                                >U/D</span>
                            <button
                                type="button"
                                class="btn btn-xs preset-tonal-surface h-6 min-h-0 px-2 text-base leading-none"
                                onclick={() =>
                                    (print_offset_y = Math.max(
                                        -OFFSET_MAX,
                                        print_offset_y - OFFSET_STEP
                                    ))}
                                disabled={print_offset_y <= -OFFSET_MAX}
                                title="Shift badge up"
                                aria-label="Shift up">−</button>
                            <span
                                class="w-14 text-center font-mono text-[11px] tabular-nums"
                                class:text-gray-400={print_offset_y === 0}
                                class:text-amber-600={print_offset_y !== 0}
                                aria-live="polite"
                                >{print_offset_y > 0
                                    ? '+'
                                    : ''}{print_offset_y.toFixed(1)} mm</span>
                            <button
                                type="button"
                                class="btn btn-xs preset-tonal-surface h-6 min-h-0 px-2 text-base leading-none"
                                onclick={() =>
                                    (print_offset_y = Math.min(
                                        OFFSET_MAX,
                                        print_offset_y + OFFSET_STEP
                                    ))}
                                disabled={print_offset_y >= OFFSET_MAX}
                                title="Shift badge down"
                                aria-label="Shift down">+</button>
                        </div>
                    </div>
                    {#if print_offset_x !== 0 || print_offset_y !== 0}
                        <button
                            type="button"
                            class="btn btn-xs preset-tonal-warning mt-1.5 w-full"
                            onclick={() => {
                                print_offset_x = 0;
                                print_offset_y = 0;
                            }}
                            title="Reset print position to center"
                            >↺ Reset position</button>
                    {/if}
                </div>

                <!-- === DEBUG JSON (Admin only) ===
                     Collapsible raw object dumps for troubleshooting badge and template data.
                     whitespace-pre + overflow-x-auto: lines never wrap, scroll horizontally instead. -->
                {#if is_admin}
                    <div class="space-y-1 px-1 pt-2 border-t border-gray-100 dark:border-gray-800">
                        <p class="field-label px-1">Debug</p>

                        <details class="rounded border border-gray-200 dark:border-gray-700">
                            <summary class="cursor-pointer select-none px-2 py-1 text-[10px] font-semibold text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-zinc-800 transition-colors">
                                Badge Object
                                <span class="ml-1 font-mono font-normal opacity-60">{$lq__event_badge_obj?.event_badge_id}</span>
                            </summary>
                            <pre class="m-0 max-h-96 overflow-x-auto overflow-y-auto whitespace-pre rounded-b border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-zinc-900 p-2 text-[11px] leading-snug text-gray-800 dark:text-zinc-200">{JSON.stringify($lq__event_badge_obj, null, 2)}</pre>
                        </details>

                        <details class="rounded border border-gray-200 dark:border-gray-700">
                            <summary class="cursor-pointer select-none px-2 py-1 text-[10px] font-semibold text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-zinc-800 transition-colors">
                                Badge Template
                                <span class="ml-1 font-mono font-normal opacity-60">{$lq__event_badge_template_obj?.event_badge_template_id}</span>
                            </summary>
                            <pre class="m-0 max-h-96 overflow-x-auto overflow-y-auto whitespace-pre rounded-b border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-zinc-900 p-2 text-[11px] leading-snug text-gray-800 dark:text-zinc-200">{JSON.stringify($lq__event_badge_template_obj, null, 2)}</pre>
                        </details>
                    </div>
                {/if}

            </div>
            <!-- ctrl-accordion-inner staff -->
        </div>
        <!-- ctrl-accordion staff -->
    {/if}<!-- end is_trusted -->
</div>

<!-- ============================================================
     Lead scanning Terms & Conditions modal
     Native <dialog> (showModal) for accessibility — blocks page,
     traps focus, closable via Escape key or the buttons below.
     Clicking the backdrop (outside the content box) also closes.
     ============================================================ -->
<dialog
    bind:this={tc_dialog_ref}
    class="mx-auto mt-[10vh] w-full max-w-md rounded-xl border border-gray-200 bg-white p-0 shadow-2xl dark:border-gray-700 dark:bg-gray-900"
    onclick={(e) => {
        if (e.target === tc_dialog_ref) tc_dialog_ref?.close();
    }}>
    <div class="max-h-[80vh] overflow-y-auto p-5">
        <div class="mb-4 flex items-start justify-between gap-3">
            <h2 class="text-base leading-snug font-semibold">
                Lead Scanning — Terms &amp; Conditions
            </h2>
            <button
                type="button"
                class="btn btn-xs preset-tonal-surface mt-0.5 shrink-0"
                onclick={() => tc_dialog_ref?.close()}
                aria-label="Close"><X size="13" /></button>
        </div>
        <div class="space-y-3 text-sm text-gray-700 dark:text-gray-300">
            <p>
                By enabling lead scanning on this badge, you agree to the
                following:
            </p>
            <ul class="list-disc space-y-1.5 pl-5 text-[13px]">
                <li>
                    Exhibitors and sponsors may scan your badge barcode to
                    capture your contact information.
                </li>
                <li>
                    Information collected may include your name, professional
                    title, organization, and location as shown on your badge.
                </li>
                <li>
                    Each exhibitor may use this information to follow up with
                    you after the event for business or promotional purposes.
                </li>
                <li>
                    Every exhibitor is individually responsible for handling
                    your data in compliance with applicable privacy laws
                    (including GDPR and CCPA where applicable).
                </li>
                <li>
                    Disabling this option prevents future scans; it does not
                    erase data already collected by exhibitors prior to the
                    change.
                </li>
            </ul>
            <p
                class="mt-1 border-t border-gray-100 pt-2 text-[11px] text-gray-500 dark:border-gray-800 dark:text-gray-400">
                This preference may be updated at any time by event staff at the
                registration desk.
            </p>
        </div>
        <!-- Allow tracking toggle — the attendee makes their choice here -->
        <div class="mt-4 border-t border-gray-100 pt-4 dark:border-gray-800">
            <p class="mb-2 text-sm font-semibold">Your preference:</p>
            <div class="flex flex-col gap-2 sm:flex-row">
                <button
                    type="button"
                    class="btn btn-sm flex-1 border-2 font-semibold transition-colors"
                    class:bg-green-600={edit_allow_tracking}
                    class:text-white={edit_allow_tracking}
                    class:border-green-600={edit_allow_tracking}
                    class:bg-white={!edit_allow_tracking}
                    class:text-gray-500={!edit_allow_tracking}
                    class:border-gray-300={!edit_allow_tracking}
                    onclick={() => { edit_allow_tracking = true; }}>
                    ✓ Allow lead scanning
                </button>
                <button
                    type="button"
                    class="btn btn-sm flex-1 border-2 font-semibold transition-colors"
                    class:bg-red-600={!edit_allow_tracking}
                    class:text-white={!edit_allow_tracking}
                    class:border-red-600={!edit_allow_tracking}
                    class:bg-white={edit_allow_tracking}
                    class:text-gray-500={edit_allow_tracking}
                    class:border-gray-300={edit_allow_tracking}
                    onclick={() => { edit_allow_tracking = false; }}>
                    ✕ Do not allow
                </button>
            </div>
        </div>

        <div class="mt-4 flex justify-end gap-2">
            <button
                type="button"
                class="btn btn-sm preset-tonal-surface"
                onclick={() => tc_dialog_ref?.close()}>Cancel</button>
            <button
                type="button"
                class="btn btn-sm preset-filled-primary"
                onclick={() => {
                    save_field('allow_tracking', { allow_tracking: edit_allow_tracking });
                    tc_dialog_ref?.close();
                }}>Save & Close</button>
        </div>
    </div>
</dialog>

<style>
/* ---- Field card: visual "zoom" (elevation + ring) on the active card ----
       box-shadow is used so the effect doesn't affect layout (no reflow).
       The ring is rendered as an outer glow — does NOT require removing overflow:hidden.
       transition-* properties animate the change smoothly when toggling. */
.field-card {
    border: 1px solid rgb(229 231 235); /* gray-200 */
    transition:
        border-color 160ms ease-out,
        box-shadow 160ms ease-out;
}
:global(.dark) .field-card {
    border-color: rgb(55 65 81); /* gray-700 */
}
.field-card--active {
    border-color: rgb(99 102 241 / 0.45); /* indigo-500, muted */
    box-shadow:
        0 0 0 3px rgb(99 102 241 / 0.18),
        /* soft outer glow */ 0 4px 14px rgb(0 0 0 / 0.11); /* elevation shadow */
}
:global(.dark) .field-card--active {
    border-color: rgb(129 140 248 / 0.45); /* indigo-400 */
    box-shadow:
        0 0 0 3px rgb(129 140 248 / 0.14),
        0 4px 14px rgb(0 0 0 / 0.35);
}

/* Compact section label used on each field row header */
.field-label {
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: rgb(156 163 175); /* gray-400 */
    font-weight: 600;
    line-height: 1.2;
}

/* ---- Smooth accordion: CSS grid-template-rows trick ----
       0fr  → the row collapses to 0 (hidden).
       1fr  → the row expands to its natural content height.
       The inner wrapper needs overflow:hidden so min-content is truly 0 when collapsed. */
.ctrl-accordion {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows 185ms ease-out;
}
.ctrl-accordion.open {
    grid-template-rows: 1fr;
}
.ctrl-accordion-inner {
    overflow: hidden;
}

/* ---- Active card: scale up value text and form inputs for accessibility ----
       Physical font-size increase (not transform: scale) so touch targets and text
       genuinely get larger — benefits all users, not just visual appearance.
       Transition on font-size animates the magnification smoothly.
       ~150% of text-sm (0.875rem) = 1.3rem for the value display line.
       Form inputs go to 1.05rem — larger but still contained in the 240px form width. */
.flex-1 > p:not(.field-label) {
    transition:
        font-size 180ms ease-out,
        line-height 180ms ease-out;
}
.field-card--active .flex-1 > p:not(.field-label) {
    font-size: 1.3rem;
    line-height: 1.3;
}
.field-card--active .ctrl-accordion-inner input,
.field-card--active .ctrl-accordion-inner textarea,
.field-card--active .ctrl-accordion-inner select {
    font-size: 1.05rem;
}

/* Placeholder text must read clearly as a hint, not as filled content.
   The placeholders show the current badge value (e.g. "John Smith") so
   without this they look identical to a filled-in field. */
.ctrl-accordion-inner input::placeholder,
.ctrl-accordion-inner textarea::placeholder {
    color: #9ca3af; /* gray-400 — visually distinct from input text */
    opacity: 1;     /* Firefox sets opacity: 0.54 by default; override it */
}
:global(.dark) .ctrl-accordion-inner input::placeholder,
:global(.dark) .ctrl-accordion-inner textarea::placeholder {
    color: #6b7280; /* gray-500 in dark mode — readable but clearly a hint */
}

/* ---- Entrance animation for form content ----
       Triggered each time .open is applied to the parent accordion.
       Pairs with the height animation: content fades + zooms in from
       slightly above while the card expands downward smoothly.
       Similar in effect to tailwindcss-animate `animate-in zoom-in-95 fade-in-0
       slide-in-from-top-2`. Uses scoped @keyframes so no plugin needed. */
.ctrl-accordion.open .ctrl-accordion-inner > div {
    animation: field-form-enter 200ms ease-out both;
}
@keyframes field-form-enter {
    from {
        opacity: 0;
        transform: scale(0.97) translateY(-5px);
    }
    to {
        opacity: 1;
        transform: none;
    }
}

/* Native <dialog> backdrop — dimmed + blurred overlay */
dialog::backdrop {
    background: rgb(0 0 0 / 0.55);
    backdrop-filter: blur(3px);
}
</style>
