(function($) {
    'use strict';
    // Yatoon booking frontend.

    // -- Multi-location: inject yatoon_location_id into every AJAX POST -------
    // yatoonData.locationId is set by wp_localize_script when [yatoon_booking location="N"] is used.
    if ( typeof yatoonData !== 'undefined' && yatoonData.locationId ) {
        $.ajaxSetup({
            beforeSend: function(xhr, settings) {
                if ( settings.type === 'POST' && settings.data ) {
                    if ( typeof settings.data === 'string' ) {
                        settings.data += '&yatoon_location_id=' + encodeURIComponent(yatoonData.locationId);
                    } else if ( typeof settings.data === 'object' ) {
                        settings.data.yatoon_location_id = yatoonData.locationId;
                    }
                }
            }
        });
    }

    // -- US Phone number validation (hoisted to IIFE scope so class methods can access) --
    function validateUSPhone(raw) {
        const digits = (raw || '').replace(/\D/g, '');
        const d = (digits.length === 11 && digits[0] === '1') ? digits.slice(1) : digits;
        if (d.length !== 10) return 'Please enter a 10-digit US phone number.';
        const area     = d.slice(0, 3);
        const exchange = d.slice(3, 6);
        if (area[0] === '0' || area[0] === '1')     return 'Invalid area code. US area codes cannot start with 0 or 1.';
        if (exchange[0] === '0' || exchange[0] === '1') return 'Invalid phone number format.';
        if (new Set(d).size === 1) return 'Please enter a valid phone number.';
        const fake = ['1234567890','0123456789','9876543210','5555555555','1231231234'];
        if (fake.includes(d)) return 'Please enter a valid phone number.';
        return '';
    }

    function showPhoneError(msg) {
        let $err = $('#sbs-phone-error');
        if (!$err.length) {
            $err = $('<p id="sbs-phone-error" style="color:#dc2626;font-size:12px;margin-top:4px;"></p>');
            $('#sbs-customer-phone').after($err);
        }
        $err.text(msg).toggle(!!msg);
        $('#sbs-customer-phone').css('border-color', msg ? '#dc2626' : '');
    }

    class SquareBookingForm {
	        constructor() {
	            this._stateVersion = '3.5.0-booking-flow';
	            this.currentStep = 1;
            this.selectedService = null;
            this.selectedCategory = null; // FIX: track parent category
            this.selectedOptions = [];
            this.selectedStaff = null;
            this.selectedDateTime = null;
            
            // Cart: stores completed appointments (each with its own time)
            this.bookingCart = [];

            // pendingServices: multiple services+staff being built into ONE appointment
            // e.g. [{service, options, staff, guestLabel}, ...]
            // They all share the same start time chosen in step 4
            this.pendingServices = [];

            // Guest booking: label for the NEXT person being added (null = 'Me')
            this._nextGuestLabel = null;    // e.g. 'Me', 'Guest', 'Guest 2'
            this._guestCount     = 0;       // how many guests added so far
            this._staffCache     = {};      // service_id -> [{id,name,image_url}]

            // Track removed items to prevent re-adding them
            this._pendingAdd = false;
            this._optionsRequest = null;
            this._staffRequest = null;
            this._timesRequest = null;
            this._optionsSeq = 0;
            this._staffSeq = 0;
            this._timesSeq = 0;
            this._dateAvailability = {};
            
            this.init();
        }

        // -- Scroll Helper ----------------------------------------------------
        _scrollToFormTop() {
            const $target = $('.sbs-booking-container');
            if (!$target.length) return;

            // WP admin bar (logged-in users)
            let topBarH = 0;
            const $adminBar = $('#wpadminbar');
            if ($adminBar.length) topBarH += ($adminBar.outerHeight() || 0);

            // Manual offset from plugin settings
            const manualOffset = (yatoonData && yatoonData.headerOffset) ? parseInt(yatoonData.headerOffset) : 0;

            if (manualOffset > 0) {
                // Use configured value - but on mobile the nav is often shorter, cap it
                const isMobile = window.innerWidth <= 768;
                topBarH += isMobile ? Math.min(manualOffset, 90) : manualOffset;
            } else {
                // Auto-detect: scan all elements fixed/sticky at top of viewport
                $('*').each(function() {
                    try {
                        const el = this;
                        const pos = window.getComputedStyle(el).position;
                        if ((pos === 'fixed' || pos === 'sticky') && el.offsetHeight > 20) {
                            const rect = el.getBoundingClientRect();
                            if (rect.top <= 8 && rect.bottom > 20 && rect.width >= window.innerWidth * 0.45) {
                                topBarH = Math.max(topBarH, rect.bottom);
                            }
                        }
                    } catch(e) {}
                });
            }

            const mobileExtraGap = window.innerWidth <= 768 ? 18 : 0;
            const scrollTo = Math.max(0, $target.offset().top - topBarH - 12 - mobileExtraGap);

            $('html, body').stop(true, true);
            window.scrollTo({ top: scrollTo, behavior: 'instant' });
            setTimeout(() => window.scrollTo({ top: scrollTo, behavior: 'instant' }), 50);
            setTimeout(() => window.scrollTo({ top: scrollTo, behavior: 'instant' }), 200);
        }

        _scrollToStaffChoices() {
            const $target = $('.sbs-step-3 .sbs-staff-grid');
            if (!$target.length) {
                this._scrollToFormTop();
                return;
            }

            // Use the same top-offset logic as _scrollToFormTop, but anchor the
            // staff cards themselves so mobile users see the full choice set.
            const originalOffset = (yatoonData && yatoonData.headerOffset) ? yatoonData.headerOffset : 0;
            this._scrollToFormTop();
            setTimeout(() => {
                let topBarH = 0;
                const $adminBar = $('#wpadminbar');
                if ($adminBar.length) topBarH += ($adminBar.outerHeight() || 0);
                const manualOffset = originalOffset ? parseInt(originalOffset) : 0;
                if (manualOffset > 0) {
                    topBarH += window.innerWidth <= 768 ? Math.min(manualOffset, 90) : manualOffset;
                } else {
                    $('*').each(function() {
                        try {
                            const pos = window.getComputedStyle(this).position;
                            if ((pos === 'fixed' || pos === 'sticky') && this.offsetHeight > 20) {
                                const rect = this.getBoundingClientRect();
                                if (rect.top <= 8 && rect.bottom > 20 && rect.width >= window.innerWidth * 0.45) {
                                    topBarH = Math.max(topBarH, rect.bottom);
                                }
                            }
                        } catch(e) {}
                    });
                }
                const scrollTo = Math.max(0, $target.offset().top - topBarH - 32);
                window.scrollTo({ top: scrollTo, behavior: 'instant' });
            }, 80);
        }

        _esc(value) {
            return String(value || '').replace(/[&<>"']/g, function(ch) {
                return {
                    '&': '&amp;',
                    '<': '&lt;',
                    '>': '&gt;',
                    '"': '&quot;',
                    "'": '&#039;'
                }[ch];
            });
        }

        // -- Session Persistence ----------------------------------------------
        _saveSession() {
            try {
	                const state = {
	                    version:          this._stateVersion,
	                    currentStep:      this.currentStep,
                    selectedService:  this.selectedService,
                    selectedCategory: this.selectedCategory,
                    selectedOptions:  this.selectedOptions,
                    selectedStaff:    this.selectedStaff,
                    selectedDateTime: this.selectedDateTime,
                    bookingCart:      this.bookingCart,
                    pendingServices:  this.pendingServices,
                    _nextGuestLabel:  this._nextGuestLabel,
                    _guestCount:      this._guestCount,
                    groupIntent:     !!window._sbsGroupIntent,
                };
                sessionStorage.setItem('yatoon_booking_state', JSON.stringify(state));
            } catch(e) {}
        }

        _restoreSession() {
            try {
                const raw = sessionStorage.getItem('yatoon_booking_state');
                if (!raw) return false;
	                const state = JSON.parse(raw);
	                if (!state || (!state.selectedService && !(state.pendingServices || []).length && !(state.bookingCart || []).length)) return false;
	                if (state.version !== this._stateVersion) {
	                    this._clearSession();
	                    return false;
	                }
                this.currentStep      = state.currentStep      || 1;
                this.selectedService  = state.selectedService  || null;
                this.selectedCategory = state.selectedCategory || null;
                this.selectedOptions  = state.selectedOptions  || [];
                this.selectedStaff    = state.selectedStaff    || null;
                this.selectedDateTime = state.selectedDateTime || null;
                this.bookingCart      = state.bookingCart      || [];
                this.pendingServices  = state.pendingServices  || [];
                this._nextGuestLabel  = state._nextGuestLabel  || null;
                this._guestCount      = state._guestCount      || 0;
                window._sbsGroupIntent = !!state.groupIntent;
                return true;
            } catch(e) { return false; }
        }

        _clearSession() {
            try { sessionStorage.removeItem('yatoon_booking_state'); } catch(e) {}
        }

        _abortLookupRequests() {
            ['_optionsRequest', '_staffRequest', '_timesRequest'].forEach((key) => {
                const req = this[key];
                if (req && req.readyState !== 4 && typeof req.abort === 'function') {
                    try { req.abort(); } catch(e) {}
                }
                this[key] = null;
            });
        }
        // --------------------------------------------------------------------

        _updateOptionActionSummary() {
            const $step = $('.sbs-step-2');
            const $summary = $step.find('.sbs-option-action-summary');
            const $next = $step.find('.sbs-btn-next');
            if (!$summary.length || !$next.length) return;

            const selected = this.selectedOptions || [];
            const mainSelected = selected.filter(o => !o.is_addon);
            const addonSelected = selected.filter(o => o.is_addon);
            const requiresMain = !!this._optionHasMainGroup;
            const canContinue = !requiresMain || mainSelected.length > 0;
            const price = this._optionsPrice(this.selectedService, selected);
            const duration = selected.length
                ? selected.reduce((sum, o) => sum + (parseInt(o.duration) || 0), 0)
                : parseInt((this.selectedService || {}).duration || 0);
            const addonText = addonSelected.length
                ? ` + ${addonSelected.length} add-on${addonSelected.length > 1 ? 's' : ''}`
                : '';
            const title = canContinue
                ? `${mainSelected.length ? mainSelected[0].name : ((this.selectedService || {}).name || 'Service')}${addonText}`
                : 'Choose one option to continue';
            const meta = canContinue
                ? `${duration || 0} min - ${price > 0 ? '$' + price.toFixed(2) : 'Price varies'}`
                : 'Add-ons are optional after the main choice';

            $summary.html(`
                <div class="sbs-option-action-title">${this._esc(title)}</div>
                <div class="sbs-option-action-meta">${this._esc(meta)}</div>
            `).toggleClass('is-ready', canContinue);
            $next
                .prop('disabled', false)
                .attr('aria-disabled', canContinue ? 'false' : 'true')
                .toggleClass('is-disabled', !canContinue);
        }

        _ensureOptionActionSummary() {
            const $nav = $('.sbs-step-2 .sbs-navigation-buttons');
            if (!$nav.length) return;
            if (!$nav.find('.sbs-option-action-summary').length) {
                $nav.prepend('<div class="sbs-option-action-summary" aria-live="polite"></div>');
            }
            this._updateOptionActionSummary();
        }

        _stepCopy(step) {
            const map = {
                1: { title: 'Choose service', hint: 'Pick the service you want to book.' },
                2: { title: 'Customize', hint: 'Choose your option and any helpful add-ons.' },
                3: { title: 'Choose staff', hint: 'Any available staff is fastest, or choose your preferred person.' },
                4: { title: 'Choose time', hint: 'Pick a date and available time.' },
                5: { title: 'Review your booking', hint: 'Check your appointment and enter your details.' }
            };
            return map[step] || map[1];
        }

        _updateMobileFlowHeader() {
            let $header = $('#sbs-mobile-flow-header');
            if (!$header.length) {
                $header = $('<div id="sbs-mobile-flow-header" class="sbs-mobile-flow-header" aria-live="polite"></div>');
                const $title = $('#sbs-main-title');
                if ($title.length) {
                    $title.after($header);
                } else {
                    $('.sbs-booking-main, .sbs-booking-form').first().prepend($header);
                }
            }
            const copy = this._stepCopy(this.currentStep);
            const selectedText = this._selectionSummaryLabel();
            const progress = Math.max(0, Math.min(100, ((this.currentStep - 1) / 4) * 100));
            $header.html(`
                <div>
                    <span class="sbs-mobile-flow-step">Step ${this.currentStep} of 5</span>
                    <strong>${this._esc(copy.title)}</strong>
                    <p>${this._esc(copy.hint)}</p>
                </div>
                ${selectedText ? `<span class="sbs-mobile-flow-count">${this._esc(selectedText)}</span>` : ''}
                <span class="sbs-mobile-flow-progress" aria-hidden="true"><i style="width:${progress}%"></i></span>
            `);
        }

        _optionAllowsDesignUpload(option) {
            if (!option) return false;
            if (option.allowDesignUpload) return true;
            const name = String(option.name || '').toLowerCase();
            return name.includes('nail art') || name.includes('custom design');
        }

        _parkDesignUploadCard() {
            const $card = $('.yatoon-design-upload-card');
            const $container = $('.sbs-options-container');
            if ($card.length && $container.length && $card.closest('.sbs-options-container').length) {
                $container.after($card.detach());
            }
        }

        _syncDesignUploadCard(focusWhenOpened = false, preferredOptionItem = null) {
            const selectedAllows = !!(this.selectedService && this.selectedService.allowDesignUpload);
            const selectedOptionAllows = (this.selectedOptions || []).some(option =>
                this._optionAllowsDesignUpload(option)
            );
            const pendingAllows = (this.pendingServices || []).some(item =>
                item && (
                    (item.service && item.service.allowDesignUpload)
                    || (item.options || []).some(option => this._optionAllowsDesignUpload(option))
                )
            );
            const cartAllows = (this.bookingCart || []).some(item =>
                (item.services || []).some(service =>
                    service && (
                        (service.service && service.service.allowDesignUpload)
                        || (service.options || []).some(option => this._optionAllowsDesignUpload(option))
                    )
                )
            );
            const allowed = selectedAllows || selectedOptionAllows || pendingAllows || cartAllows;
            const $card = $('.yatoon-design-upload-card');
            const wasVisible = $card.hasClass('is-visible');
            $card.toggleClass('is-visible', allowed);
            if (!allowed) {
                this._parkDesignUploadCard();
                const input = document.getElementById('sbs-design-reference');
                if (input) input.value = '';
                $('#sbs-design-preview').empty().hide();
                return;
            }

            const qualifyingIds = (this.selectedOptions || [])
                .filter(option => this._optionAllowsDesignUpload(option))
                .map(option => String(option.id));
            let $target = preferredOptionItem && preferredOptionItem.jquery
                ? preferredOptionItem
                : $();
            if (
                !$target.length
                || !$target.hasClass('selected')
                || !qualifyingIds.includes(String($target.find('.sbs-option-checkbox').val()))
            ) {
                $target = $('.sbs-option-item.selected').filter(function() {
                    return qualifyingIds.includes(String($(this).find('.sbs-option-checkbox').val()));
                }).last();
            }

            if ($target.length) {
                $target.after($card.detach());
                const targetId = String($target.find('.sbs-option-checkbox').val());
                const targetOption = (this.selectedOptions || []).find(option => String(option.id) === targetId);
                const targetName = String((targetOption && targetOption.name) || '')
                    .replace(/\s*\([^)]*\)\s*$/, '')
                    .trim();
                $card.find('.yatoon-design-upload-copy strong').text(
                    targetName ? 'Upload a reference photo for ' + targetName : 'Upload a reference photo'
                );
            } else {
                this._parkDesignUploadCard();
                $card.find('.yatoon-design-upload-copy strong').text('Upload a reference photo');
            }

            if (focusWhenOpened && (!wasVisible || $target.length) && $card.length) {
                setTimeout(() => {
                    $card.get(0).scrollIntoView({ behavior: 'smooth', block: 'nearest' });
                }, 60);
            }
        }

        _timeBucket(display, startAt) {
            let hour = 12;
            if (startAt) {
                const d = new Date(startAt);
                if (!isNaN(d.getTime())) hour = d.getHours();
            } else {
                const match = String(display || '').match(/(\d{1,2})(?::\d{2})?\s*(AM|PM)?/i);
                if (match) {
                    hour = parseInt(match[1], 10);
                    const ap = (match[2] || '').toUpperCase();
                    if (ap === 'PM' && hour < 12) hour += 12;
                    if (ap === 'AM' && hour === 12) hour = 0;
                }
            }
            if (hour < 12) return 'Morning';
            if (hour < 17) return 'Afternoon';
            return 'Evening';
        }

        _showToast(message, type = 'info') {
            let $toast = $('#sbs-toast');
            if (!$toast.length) {
                $toast = $('<div id="sbs-toast" role="status" aria-live="polite"></div>');
                $('body').append($toast);
            }
            $toast
                .removeClass('show error success info')
                .addClass(type)
                .text(message || '');
            clearTimeout(this._toastTimer);
            setTimeout(function() { $toast.addClass('show'); }, 10);
            this._toastTimer = setTimeout(function() {
                $toast.removeClass('show');
            }, 4200);
        }

        _clearMainOptionRequired() {
            $('#sbs-main-option-required-message').remove();
            $('.sbs-option-section-main').removeClass('sbs-option-required-pulse');
            $('.sbs-option-section-main .sbs-option-item')
                .removeClass('sbs-option-required-outline')
                .css({ borderColor: '', boxShadow: '' });
        }

        _showMainOptionRequired() {
            const $section = $('.sbs-option-section-main').first();
            if (!$section.length) {
                this._showToast('Please choose one main option before continuing.', 'error');
                return;
            }

            let $message = $('#sbs-main-option-required-message');
            if (!$message.length) {
                $message = $('<div id="sbs-main-option-required-message" role="alert" aria-live="assertive"></div>');
                $section.prepend($message);
            }

            $message
                .text('Please choose one main option before continuing. Add-ons are optional after that.')
                .css({
                    background: '#fff7ed',
                    border: '1px solid #fb923c',
                    color: '#9a3412',
                    borderRadius: '8px',
                    fontWeight: '700',
                    margin: '0 0 12px',
                    padding: '10px 12px'
                });
            $section.addClass('sbs-option-required-pulse');
            $section.find('.sbs-option-item')
                .addClass('sbs-option-required-outline')
                .css({
                    borderColor: '#fb923c',
                    boxShadow: '0 0 0 2px rgba(251, 146, 60, 0.18)'
                });

            const top = Math.max(0, $section.offset().top - 100);
            $('html, body').stop(true).animate({ scrollTop: top }, 220);

            clearTimeout(this._mainOptionRequiredTimer);
            this._mainOptionRequiredTimer = setTimeout(() => {
                $('.sbs-option-section-main').removeClass('sbs-option-required-pulse');
            }, 1800);
        }

        init() {
            if (!yatoonData || yatoonData.immersiveMode !== false) {
                $('body').addClass('sbs-booking-immersive');
            }
            this.bindEvents();
            this._bindSummaryEditButtons();
            this.initWeekView();
            this.initMobileCart();
            this._updateMobileCartBar();
            this._bindFormPolish();
            this._updateMobileFlowHeader();
            this._syncDesignUploadCard();

            // Intercept browser Back/Forward so it navigates within the widget
            const self = this;
            history.replaceState({ sbsStep: 1 }, '', window.location.href);
            window.addEventListener('popstate', function(e) {
                // Only handle if we're on a page with the booking widget
                if (!$('#sbs-booking-form').length) return;
                if (e.state && e.state.sbsStep) {
                    // Browser went back to a recorded step - simulate our Back button
                    self._handleBrowserBack();
                } else {
                    // No sbs state - push a new entry to stay on this page
                    history.pushState({ sbsStep: self.currentStep }, '', window.location.href);
                }
                // Always prevent leaving the page while widget is active
                return false;
            });

            // Start cart bar hidden - will slide up when a service is selected
            $('#sbs-mobile-cart-bar').removeClass('sbs-bar-visible').css('display', 'none');
            // Text Us starts at bottom:24px (CSS default), no inline override needed

            // Restore previous session state if available
            if (this._restoreSession()) {
                const self = this;
                // Re-populate hidden fields
                if (self.selectedService) {
                    $('#sbs-selected-service-id').val(self.selectedService.id);
                    // Mark service as selected in DOM (step 1 list)
                    $(`.sbs-service-item[data-service-id="${self.selectedService.id}"]`).addClass('selected');
                    // Hide empty guide since we have a service
                    $('#sbs-sidebar-empty-guide').hide();
                }
                if (self.selectedStaff) {
                    $('#sbs-selected-staff-id').val(self.selectedStaff.id);
                }
                if (self.selectedOptions.length) {
                    $('#sbs-selected-options').val(JSON.stringify(self.selectedOptions.map(o => o.id)));
                }
                self.updateSummary();
                self._updateMobileCartBar();

                // Navigate to the correct step
                if (self.currentStep === 2) {
                    self.loadServiceOptions();
                } else if (self.currentStep === 3) {
                    self.goToStep(3);
                    self.loadAvailableStaff();
                } else if (self.currentStep === 4) {
                    self.goToStep(4);
                } else if (self.currentStep === 5) {
                    self.goToStep(5);
                    self.updateSummaryForCheckout();
                } else {
                    self.goToStep(self.currentStep);
                }
            } else {
                this._applyInstantBookingParams();
            }
        }

        _bindFormPolish() {
            $(document).on('focus blur input', '.sbs-form-group input, .sbs-form-group textarea', function(e) {
                const $field = $(this);
                const $group = $field.closest('.sbs-form-group');
                $group.toggleClass('is-focused', e.type === 'focus');
                $group.toggleClass('has-value', !!String($field.val() || '').trim());
            });
        }

        _applyInstantBookingParams() {
            const params = new URLSearchParams(window.location.search || '');
            const serviceId = params.get('yatoon_service') || params.get('service_id') || params.get('sbs_service');
            const category = params.get('yatoon_category') || params.get('category') || params.get('sbs_category');
            const staffId = params.get('yatoon_staff') || params.get('staff_id') || params.get('sbs_staff');

            if (category) {
                const cat = String(category).trim().toLowerCase();
                const $tab = $('.sbs-cat-tab, .sbs-category-tab').filter(function() {
                    const dataCat = String($(this).data('cat') || $(this).data('category') || '').toLowerCase();
                    const text = String($(this).text() || '').trim().toLowerCase().replace(/\s+/g, '-');
                    return dataCat === cat || text === cat || text === cat.replace(/\s+/g, '-');
                }).first();
                if ($tab.length) {
                    setTimeout(function() { $tab.trigger('click'); }, 120);
                }
            }

            if (!serviceId) return;
            const $service = $('.sbs-service-item').filter(function() {
                return String($(this).data('service-id')) === String(serviceId);
            }).first();
            if (!$service.length) return;

            if (staffId) {
                this._instantStaffId = String(staffId);
            }

            setTimeout(() => {
                $service.addClass('yatoon-instant-target');
                $service.trigger('click');
            }, 240);
        }
        
        getFirstName(fullName) {
            if (!fullName) return '';
            if (fullName === 'Any staff') return 'Any Staff Available';
            return fullName.trim().split(/\s+/)[0];
        }

        _staffDisplayName(name) {
            return name === 'Any staff' ? 'Any Staff Available' : (name || 'Any Staff Available');
        }

        _peopleCount(services) {
            const labels = [];
            (services || []).forEach((s) => {
                const label = s && s.guestLabel ? s.guestLabel : 'Me';
                if (!labels.includes(label)) labels.push(label);
            });
            return labels.length || 1;
        }

        _selectionSummaryLabel() {
            let services = [];
            if (this.bookingCart && this.bookingCart.length) {
                this.bookingCart.forEach((item) => {
                    if (item.services && item.services.length) {
                        services = services.concat(item.services);
                    } else if (item.service) {
                        services.push({ service: item.service, guestLabel: item.guestLabel || 'Me' });
                    }
                });
            } else if (this.pendingServices && this.pendingServices.length) {
                services = this.pendingServices.slice();
            } else if (this.selectedService) {
                services = [{ service: this.selectedService, guestLabel: this._nextGuestLabel || 'Me' }];
            }

            const serviceCount = services.length;
            if (!serviceCount) return '';
            const peopleCount = this._peopleCount(services);
            const svcText = serviceCount + (serviceCount === 1 ? ' service' : ' services');
            return peopleCount > 1 ? (svcText + ' for ' + peopleCount + ' people') : svcText;
        }

        _getNextGuestLabel(existingLabels) {
            const labels = existingLabels || [];
            const guestLabels = labels.filter(label => label && label !== 'Me');
            if (!guestLabels.length) return 'Guest 1';
            let maxGuestNum = 0;
            guestLabels.forEach(label => {
                const match = String(label).match(/^Guest(?:\s+(\d+))?$/i);
                if (match) maxGuestNum = Math.max(maxGuestNum, parseInt(match[1] || '1', 10));
            });
            let nextNum = Math.max(maxGuestNum + 1, guestLabels.length + 1);
            while (labels.indexOf('Guest ' + nextNum) !== -1) nextNum++;
            return 'Guest ' + nextNum;
        }

        _displayGuestLabel(label) {
            return label === 'Guest' ? 'Guest 1' : (label || 'Me');
        }

        _maxGuestsTogether() {
            const raw = yatoonData && yatoonData.maxGuestsTogether;
            const parsed = parseInt(raw, 10);
            if (isNaN(parsed)) return 2;
            return Math.max(0, parsed);
        }

        _isGroupAppointmentSelected() {
            const $selected = $('.sbs-appt-type-card.active').first();
            const type = String($selected.data('type') || '').toLowerCase();
            // The appointment-type cards are hidden while choosing services and
            // can be re-rendered/restored without their active class. The stored
            // intent is the authoritative state throughout the multi-step flow.
            return !!window._sbsGroupIntent || type === 'group';
        }

        _guestLabelsInProgress() {
            const seen = [];
            (this.pendingServices || []).forEach((service) => {
                const label = service.guestLabel || 'Me';
                if (label !== 'Me' && seen.indexOf(label) === -1) {
                    seen.push(label);
                }
            });
            return seen;
        }

        _addGuestTogether() {
            const maxGuests = this._maxGuestsTogether();
            if (maxGuests <= 0) {
                this._showToast('Guest booking is not available for this visit.', 'error');
                return false;
            }

            const existingGuestLabels = this._guestLabelsInProgress();
            if (existingGuestLabels.length >= maxGuests) {
                const guestWord = maxGuests === 1 ? 'guest' : 'guests';
                this._showToast('You can add up to ' + maxGuests + ' ' + guestWord + ' with this booking.', 'error');
                return false;
            }

            const existingLabels = (this.pendingServices || []).map((service) => service.guestLabel || 'Me');
            const newLabel = this._getNextGuestLabel(existingLabels);
            this._guestCount = Math.max(this._guestCount || 0, existingGuestLabels.length) + 1;
            this._promptGuestName(newLabel);
            return true;
        }

        _normalizeGuestName(value) {
            return String(value || '').replace(/\s+/g, ' ').trim().slice(0, 40);
        }

        _promptGuestName(defaultLabel) {
            const self = this;
            const fallbackLabel = defaultLabel || this._getNextGuestLabel((this.pendingServices || []).map(s => s.guestLabel || 'Me'));
            $('#sbs-guest-name-overlay').remove();

            const safeLabel = this._esc(this._displayGuestLabel(fallbackLabel));
            const overlay = $(`<div id="sbs-guest-name-overlay" style="
                position:fixed;inset:0;background:rgba(17,24,39,.32);z-index:9995;
                display:flex;align-items:center;justify-content:center;padding:18px;">
                <div style="background:#fff;border-radius:16px;width:100%;max-width:420px;box-shadow:0 18px 50px rgba(15,23,42,.18);padding:20px;">
                    <h3 style="margin:0 0 6px;font-size:20px;line-height:1.2;color:#111827;font-weight:800;">${safeLabel} name</h3>
                    <p style="margin:0 0 14px;font-size:13px;line-height:1.45;color:#6b7280;">Name is optional. You can keep ${safeLabel}.</p>
                    <input type="text" id="sbs-guest-name-input" maxlength="40" autocomplete="off" placeholder="Name (optional)"
                        style="width:100%;box-sizing:border-box;border:1px solid #d1d5db;border-radius:10px;padding:12px 13px;font-size:15px;color:#111827;outline:none;">
                    <div style="display:flex;gap:10px;justify-content:flex-end;margin-top:16px;flex-wrap:wrap;">
                        <button type="button" id="sbs-guest-use-default"
                            style="background:#fff;color:#374151;border:1px solid #d1d5db;border-radius:999px;padding:11px 16px;font-size:13px;font-weight:700;cursor:pointer;">
                            Use ${safeLabel}
                        </button>
                        <button type="button" id="sbs-guest-save-name"
                            style="background:#43a047;color:#fff;border:none;border-radius:999px;padding:11px 18px;font-size:13px;font-weight:800;cursor:pointer;">
                            Save &amp; Continue
                        </button>
                    </div>
                </div>
            </div>`);

            const continueWith = function(label) {
                const normalized = self._normalizeGuestName(label) || fallbackLabel;
                if (normalized.toLowerCase() === 'me') {
                    self._showToast('Please use a guest name instead of Me.', 'error');
                    return;
                }
                const existing = self._guestLabelsInProgress().map(existingLabel => existingLabel.toLowerCase());
                if (normalized !== fallbackLabel && existing.indexOf(normalized.toLowerCase()) !== -1) {
                    self._showToast('That guest is already added. Add a service to that guest or use a different name.', 'error');
                    return;
                }
                overlay.remove();
                self._enterServiceSelection(normalized, true);
            };

            $('body').append(overlay);
            const $input = overlay.find('#sbs-guest-name-input');
            setTimeout(() => $input.trigger('focus'), 40);
            overlay.on('click', '#sbs-guest-use-default', function() {
                continueWith(fallbackLabel);
            });
            overlay.on('click', '#sbs-guest-save-name', function() {
                continueWith($input.val());
            });
            overlay.on('keydown', '#sbs-guest-name-input', function(e) {
                if (e.key === 'Enter') {
                    e.preventDefault();
                    continueWith($input.val());
                } else if (e.key === 'Escape') {
                    e.preventDefault();
                    overlay.remove();
                    self._showGuestReviewScreen();
                }
            });
            overlay.on('click', function(e) {
                if (e.target === overlay[0]) $input.trigger('focus');
            });
        }

        _serviceDuration(s) {
            if (!s) return 0;
            const opts = s.options || [];
            const optsDur = opts.reduce((sum, o) => sum + (parseInt(o.duration) || 0), 0);
            const baseDur = parseInt(s.service && s.service.duration) || 0;
            return Math.max(1, optsDur > 0 ? optsDur : baseDur);
        }

        _durationByGuest(services) {
            const groups = {};
            (services || []).forEach((s) => {
                const label = s.guestLabel || 'Me';
                groups[label] = (groups[label] || 0) + this._serviceDuration(s);
            });
            const totals = Object.values(groups);
            return {
                groups,
                totalDuration: totals.length ? Math.max.apply(null, totals) : 0
            };
        }

        _getItemVisitDuration(item) {
            if (item && item.services && item.services.length) {
                return this._durationByGuest(item.services).totalDuration;
            }
            return this.getItemDuration(item);
        }

        _formatItemTimeRange(item, dateStr) {
            const visitDuration = this._getItemVisitDuration(item);
            const startLabel = (item && item.dateTime && item.dateTime.display) ? String(item.dateTime.display).trim() : '';
            const startAt = item && item.dateTime ? item.dateTime.start_at : null;
            if (!startLabel || !visitDuration) {
                return dateStr && startLabel ? `${dateStr} at ${startLabel}` : (dateStr || startLabel || '');
            }
            const startDate = startAt ? new Date(startAt) : null;
            if (startDate && !isNaN(startDate.getTime())) {
                const endDate = new Date(startDate.getTime() + visitDuration * 60000);
                const endLabel = endDate.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true });
                return `${dateStr} &bull; ${startLabel} &ndash; ${endLabel}`;
            }
            return `${dateStr} &bull; ${startLabel}`;
        }

        _serviceOffsetsByGuest(services) {
            const totals = {};
            return (services || []).map((s) => {
                const label = s.guestLabel || 'Me';
                const offset = totals[label] || 0;
                totals[label] = offset + this._serviceDuration(s);
                return offset;
            });
        }

        // -- Time-aware staff conflict detection --------------------------------
        // All guests in a booking share the SAME start time (they run in parallel),
        // so a guest's Nth service overlaps in real time with whatever "Me" (or any
        // other guest) has scheduled during that same window - NOT with services
        // that simply come earlier in the pendingServices array.
        //
        // Returns the set of staff IDs (as strings) already booked (via
        // pendingServices) during the real-time window [myStart, myEnd), where
        // myStart/myEnd are minutes-from-appointment-start.
        //
        // excludeEntry: pass the pendingServices entry to skip (e.g. when editing
        // a service already in the list, so it doesn't conflict with itself).
        _computeBusyStaffIds(myStart, myEnd, excludeEntry) {
            const self = this;
            const busyIds = new Set();
            const offsets = self._serviceOffsetsByGuest(self.pendingServices);
            self.pendingServices.forEach(function(ps, i) {
                if (excludeEntry && ps === excludeEntry) return; // skip self
                if (!ps.staff || !ps.staff.id || Number(ps.staff.id) === 0) return; // "Any staff" doesn't block anyone specific
                const start = offsets[i];
                const end   = start + self._serviceDuration(ps);
                if (start < myEnd && end > myStart) {
                    busyIds.add(String(ps.staff.id));
                }
            });
            return busyIds;
        }

        // Start offset (minutes from appointment start) for a NEW service about to be
        // added for guestLabel, given what's already in pendingServices for that guest.
        _nextOffsetForGuest(guestLabel) {
            const label = guestLabel || 'Me';
            return (this.pendingServices || [])
                .filter(function(ps) { return (ps.guestLabel || 'Me') === label; })
                .reduce((sum, ps) => sum + this._serviceDuration(ps), 0);
        }

        _splitCartItemByGuest(item) {
            const services = item && item.services
                ? item.services
                : [{ service: item.service, options: item.options || [], staff: item.staff }];
            const groups = {};
            services.forEach((s, originalIndex) => {
                const label = s.guestLabel || 'Me';
                if (!groups[label]) groups[label] = [];
                groups[label].push({ ...s, _originalIndex: originalIndex });
            });
            return Object.keys(groups).map((label) => {
                const grouped = groups[label];
                return {
                    ...item,
                    services: grouped,
                    service: grouped[0].service,
                    options: grouped[0].options || [],
                    staff: grouped[0].staff,
                    guestLabel: label
                };
            });
        }

        _guestLabelsInCart() {
            const labels = [];
            (this.bookingCart || []).forEach((item) => {
                const services = item.services || [{ service: item.service, options: item.options || [], staff: item.staff }];
                services.forEach((s) => {
                    const label = s.guestLabel || 'Me';
                    if (label !== 'Me' && !labels.includes(label)) labels.push(label);
                });
            });
            return labels;
        }

        _renderGuestContactFields() {
            const $wrap = $('#sbs-guest-contact-fields');
            if (!$wrap.length) return;
            $wrap.empty().hide();
        }

        _collectGuestContacts() {
            const contacts = {};
            $('.sbs-guest-contact-card').each(function() {
                const label = $(this).data('guest-label') || '';
                if (!label) return;
                const name = String($(this).find('.sbs-guest-name').val() || '').trim();
                const phone = String($(this).find('.sbs-guest-phone').val() || '').trim();
                if (name || phone) {
                    contacts[label] = { name, phone };
                }
            });
            return contacts;
        }

        // Filter out option names that simply repeat the service name or category name
        // e.g. "Custom Design / Nail Art + Custom Design / Nail Art" ->just show service name
        _filterOptNames(options, service) {
            if (!options || options.length === 0) return [];
            const svcName  = (service && service.name     || '').trim().toLowerCase();
            const catName  = (service && service.category || '').trim().toLowerCase();
            const fullName = catName ? (catName + ' \u2014 ' + svcName) : svcName;
            return options.filter(o => {
                 if (o.is_addon) return true; // always keep add-ons
                const n = (o.name || '').trim().toLowerCase();
                return n !== svcName && n !== catName && n !== fullName;
            });
        }

        _optLabel(options, service) {
            const filtered = this._filterOptNames(options, service);
            return filtered.length > 0 ? filtered.map(o => o.name).join(', ') : '';
        }

        _cleanServiceLabelText(label) {
            return (label || '').replace(/^\s*with\s+/i, '').trim();
        }

        _splitServiceDisplayNote(label) {
            const text = (label || '').trim();
            const match = text.match(/\s*\((price\s+varies[^)]*)\)\s*$/i);
            if (!match) return { name: text, note: '' };
            return {
                name: text.slice(0, match.index).trim(),
                note: match[1].trim()
            };
        }

        _svcDisplayLabelHtml(service, options) {
            const parts = this._splitServiceDisplayNote(this._svcDisplayLabel(service, options));
            const note = parts.note
                ? ` <span class="sbs-service-note">${this._esc(parts.note)}</span>`
                : '';
            return `${this._esc(parts.name)}${note}`;
        }

        _cleanOptionLabelText(label, serviceName) {
            let text = (label || '').trim();
            const svc = (serviceName || '').trim();
            if (svc && text.toLowerCase().indexOf(svc.toLowerCase()) === 0) {
                text = text.slice(svc.length).replace(/^[\s\-\u2013\u2014:]+/, '').trim();
            }
            return text;
        }

        // Returns a concise display label for a service+options combo.
        // Priority: include the parent service before a meaningful main option.
        //   - Has meaningful options ->show "Service - Option", e.g. "Regular Pedicure - Regular Polish/No Polish"
        //   - No options (or options just repeat service name) ->show service name only, e.g. "Acrylic Gel"
        // The category prefix (e.g. "Nail Enhancements - ") is intentionally omitted
        // to keep the display short and clean.
        _svcDisplayLabel(service, options) {
            if (!service) return '';
            const svcName = (service.name || '').trim();
            const opts    = options || [];
            if (opts.length === 0) return this._cleanServiceLabelText(svcName);

            // Separate main (single-select) from add-ons
            const mainOpts  = opts.filter(o => !o.is_addon);
            const addonOpts = opts.filter(o => o.is_addon);

            // Base: always keep the parent service visible; append the concrete option when useful.
            const filteredMain = this._filterOptNames(mainOpts, service);
            const mainName = filteredMain
                .map(o => this._cleanOptionLabelText(o.name, svcName))
                .filter(Boolean)
                .join(', ');
            let baseName = mainName
                ? (svcName + ' - ' + mainName)
                : svcName;

            // Collect all extra names to append: add-ons + any non-addon opts with different names
            // This catches cases where group_name mismatch causes add-ons to appear as mainOpts
            const extraNames = [];
            // Add-ons always appended
            addonOpts.forEach(o => {
                const n = this._cleanOptionLabelText(o.name, svcName);
                if (n && !extraNames.includes(n)) extraNames.push(n);
            });
            // Also append main opts whose names differ from service AND from baseName
            mainOpts.forEach(o => {
                const n = this._cleanOptionLabelText(o.name, svcName);
                if (n.toLowerCase() !== svcName.toLowerCase() &&
                    mainName.toLowerCase().indexOf(n.toLowerCase()) === -1 &&
                    baseName.toLowerCase().indexOf(n.toLowerCase()) === -1 &&
                    !extraNames.includes(n)) {
                    extraNames.push(n);
                }
            });

            if (extraNames.length > 0) {
                return this._cleanServiceLabelText(baseName + ', ' + extraNames.join(', '));
            }
            return this._cleanServiceLabelText(baseName);
        }

        // Returns the correct confirm button label based on cart size (single vs plural)
        // and applies the matching CSS class for styling.
        _applyConfirmBtn() {
            const isMulti = this.bookingCart.length > 1;
            const label = isMulti
                ? ((yatoonData.btnLabels && yatoonData.btnLabels.confirm) || 'Confirm All Bookings')
                : ((yatoonData.btnLabels && yatoonData.btnLabels.confirm_single) || 'Confirm Booking');
            const $btn = $('.sbs-btn-submit');
            $btn.text(label);
            if (isMulti) {
                $btn.removeClass('sbs-btn-single');
            } else {
                $btn.addClass('sbs-btn-single');
            }
        }

        _requestCustomerFormSubmit() {
            const form = $('#sbs-customer-form').get(0);
            if (!form) {
                this.submitBooking();
                return;
            }

            if (typeof form.requestSubmit === 'function') {
                form.requestSubmit();
                return;
            }

            if (typeof form.checkValidity === 'function' && !form.checkValidity()) {
                if (typeof form.reportValidity === 'function') {
                    form.reportValidity();
                }
                return;
            }

            this.submitBooking();
        }
        
        _getCurrentPrice() {
            return this._optionsPrice(this.selectedService, this.selectedOptions);
        }

        _getCurrentDuration() {
            if (this.selectedOptions.length > 0) {
                const mainOpts  = this.selectedOptions.filter(o => !o.is_addon);
                const addonOpts = this.selectedOptions.filter(o => o.is_addon);
                const mainDur   = mainOpts.length > 0
                    ? Math.max(...mainOpts.map(o => o.duration || 0))
                    : (this.selectedService ? (this.selectedService.duration || 0) : 0);
                const addonDur  = addonOpts.reduce((s, o) => s + (o.duration || 0), 0);
                return mainDur + addonDur;
            }
            return this.selectedService ? (this.selectedService.duration || 0) : 0;
        }
        
        getItemPrice(item) {
            // Multi-service appointment: sum all services
            const services = item.services || [{ service: item.service, options: item.options }];
            return services.reduce((total, s) => total + this._optionsPrice(s.service, s.options), 0);
        }

        // Price for one service + its selected options: base price, adjusted by the
        // selected Main option's own modifier (if any), plus every selected Add-on's
        // modifier stacked on top. Add-ons must be summed (not maxed) so a genuinely
        // priced add-on (e.g. French Tips) actually adds to the total instead of being
        // silently dropped whenever a cheaper/free add-on is also selected.
        _optionsPrice(service, options) {
            const opts = options || [];
            const base = (service && service.price) || 0;
            if (opts.length === 0) return base;
            const mainOpt   = opts.find(o => !o.is_addon);
            const addonOpts = opts.filter(o => o.is_addon);
            const hasModifiers = opts.some(o => typeof o.price_modifier === 'number' && !isNaN(o.price_modifier));
            if (!hasModifiers) {
                // Fallback for any option objects that predate the price_modifier field
                // (shouldn't normally happen, but avoids a hard crash).
                return Math.max(...opts.map(o => o.price || 0));
            }
            const mainPrice   = mainOpt ? (base + (mainOpt.price_modifier || 0)) : base;
            const addonExtra  = addonOpts.reduce((sum, o) => sum + (o.price_modifier || 0), 0);
            return mainPrice + addonExtra;
        }

        getItemDuration(item) {
            // Multi-service appointment: sum all durations
            const services = item.services || [{ service: item.service, options: item.options || [] }];
            return services.reduce((total, s) => {
                const opts = s.options || [];
                if (opts.length === 0) return total + (parseInt(s.service && s.service.duration) || 0);
                const mainOpts  = opts.filter(o => !o.is_addon);
                const addonOpts = opts.filter(o => o.is_addon);
                const mainDur   = mainOpts.length > 0
                    ? Math.max(...mainOpts.map(o => parseInt(o.duration) || 0))
                    : (parseInt(s.service && s.service.duration) || 0);
                const addonDur  = addonOpts.reduce((d, o) => d + (parseInt(o.duration) || 0), 0);
                return total + mainDur + addonDur;
            }, 0);
        }

        // Prompt user: add more services or proceed to time selection
        _showAddMorePrompt() {
            const self = this;
            $('#sbs-add-more-overlay').remove();

            // Work out who was just added (last pendingService) and next guest label
            const lastService = self.pendingServices[self.pendingServices.length - 1];
            const currentPersonLabel = (lastService && lastService.guestLabel) ? lastService.guestLabel : 'Me';
            const hasMultiplePeople = self.pendingServices.some(s => s.guestLabel && s.guestLabel !== 'Me');
            const nextGuestLabel = self._getNextGuestLabel(self.pendingServices.map(s => s.guestLabel || 'Me'));

            // Badge color for currentPersonLabel
            const isMe = currentPersonLabel === 'Me';
            const badgeBg    = isMe ? '#dbeafe' : '#dcfce7';
            const badgeColor = isMe ? '#1d4ed8' : '#16a34a';
            const personBadge = `<span style="display:inline-block;background:${badgeBg};color:${badgeColor};border-radius:4px;font-size:13px;font-weight:700;padding:2px 8px;margin-left:6px;">${self._esc(self._displayGuestLabel(currentPersonLabel))}</span>`;

            const overlay = $(`
                <div id="sbs-add-more-overlay" style="
                    position:fixed;inset:0;background:rgba(0,0,0,0.5);
                    z-index:9999;display:flex;align-items:center;justify-content:center;
                    padding:20px;box-sizing:border-box;">
                    <div style="
                        background:#fff;border-radius:20px;padding:28px 24px;
                        max-width:380px;width:100%;box-shadow:0 12px 40px rgba(0,0,0,0.2);">
                        <h3 style="margin:0 0 6px;font-size:19px;font-weight:700;color:#1a1a1a;text-align:center;">What would you like to do?</h3>
                        <p style="margin:0 0 18px;font-size:14px;color:#6b7280;text-align:center;">Add more to this appointment or select a time.</p>
                        <div style="display:flex;flex-direction:column;gap:10px;">
                            <button id="sbs-add-more-yes" style="
                                padding:12px 16px;border:2px solid var(--sbs-primary,#ec4899);
                                background:#fff;color:var(--sbs-primary,#ec4899);
                                border-radius:12px;font-size:15px;font-weight:600;cursor:pointer;text-align:left;display:flex;align-items:center;flex-wrap:wrap;gap:4px;">
                                <span>&#10133; Add Another Service for</span>${personBadge}
                            </button>
                            <button id="sbs-add-more-guest" style="
                                padding:12px 16px;border:2px solid #16a34a;
                                background:#f0fdf4;color:#16a34a;
                                border-radius:12px;font-size:15px;font-weight:600;cursor:pointer;text-align:left;">
                                &#128101; Add Guest Together
                            </button>
                            <button id="sbs-add-more-no" style="
                                padding:12px 16px;border:none;
                                background:#111;color:#fff;
                                border-radius:12px;font-size:15px;font-weight:600;cursor:pointer;text-align:center;">
                                Select Time &#8594;
                            </button>
                        </div>
                    </div>
                </div>
            `);

            $('body').append(overlay);

            $('#sbs-add-more-yes').on('click', function() {
                $('#sbs-add-more-overlay').remove();
                // Set _nextGuestLabel so Step 1 knows we're adding for the same person
                self._enterServiceSelection(currentPersonLabel, true);
            });

            $('#sbs-add-more-guest').on('click', function(e) {
                e.preventDefault();
                e.stopPropagation();
                $('#sbs-add-more-overlay').remove();
                self._addGuestTogether();
            });

            $('#sbs-add-more-no').on('click', function() {
                $('#sbs-add-more-overlay').remove();
                self.goToStep(4);
            });

            overlay.on('click', function(e) {
                if (e.target === overlay[0]) {
                    $('#sbs-add-more-overlay').remove();
                    self.goToStep(4);
                }
            });
        }

        // Show a green banner at the top of Step 1 indicating who we are selecting for
        _showGuestPickBanner(label) {
            const self = this;
            // Remove any existing banner first (prevent duplicates)
            $('#sbs-guest-pick-banner').remove();
            const $banner = $(`
                <div id="sbs-guest-pick-banner" class="sbs-guest-pick-banner" style="
                    background:#f0fdf4;border:1.5px solid #16a34a;border-radius:12px;
                    padding:14px 16px;margin:0 0 18px;box-sizing:border-box;min-height:68px;overflow:visible;
                    display:flex;align-items:center;justify-content:space-between;line-height:1.3;">
                    <span style="font-size:14px;font-weight:600;color:#16a34a;">
                        &#128101; Selecting service for <strong>${label}</strong>
                    </span>
                    <button id="sbs-guest-banner-cancel" style="
                        background:none;border:none;color:#6b7280;font-size:13px;cursor:pointer;">
                        Cancel
                    </button>
                </div>
            `);
            // Prepend immediately - guard against double-fire by checking if already present
            if (!$('#sbs-guest-pick-banner').length) {
                $('.sbs-step-1').prepend($banner);
            }
            $('#sbs-guest-banner-cancel').on('click', function() {
                $('#sbs-guest-pick-banner').remove();
                self._nextGuestLabel = null;
                self._guestCount = Math.max(0, self._guestCount - 1);
                self._saveSession();
            });
        }

        // Fetch staff for a service (with cache) - used for inline dropdown
        _fetchStaffForService(serviceId, cb) {
            if (this._staffCache[serviceId] && this._staffCache[serviceId].length > 0) {
                cb(this._staffCache[serviceId]);
                return;
            }
            const preloaded = this._getPreloadedServiceStaff(serviceId);
            if (preloaded && preloaded.staff && preloaded.staff.length > 0) {
                this._staffCache[serviceId] = preloaded.staff;
                cb(preloaded.staff);
                return;
            }
            const self = this;
            $.ajax({
                url: yatoonData.ajax_url, type: 'POST',
                data: { action: 'yatoon_get_available_staff', nonce: yatoonData.nonce, service_id: serviceId },
                success: function(r) {
                    if (r.success && r.data.staff) {
                        self._staffCache[serviceId] = r.data.staff;
                        cb(r.data.staff);
                    } else { cb([]); }
                },
                error: function() { cb([]); }
            });
        }

        // Build an inline staff <select> for a service entry
        _buildStaffSelect(svcEntry, pendingIdx) {
            const self = this;
            const svcId = svcEntry.service ? svcEntry.service.id : null;
            const current = svcEntry.staff;
            const cached = svcId ? (self._staffCache[svcId] || null) : null;

            if (!cached) {
                // Fetch async and re-render summary/cart when done
                if (svcId) {
                    self._fetchStaffForService(svcId, function() {
                        self.updateSummary();
                        self.renderCart();
                        if (self.currentStep === 4) self._showPendingServicesSummary();
                    });
                }
                const n = current ? self.getFirstName(current.name) : 'Any';
                return `<span style="color:#6b7280;font-size:12px;">&#128100; ${n}</span>`;
            }

            if (cached.length <= 1) {
                const n = current ? self.getFirstName(current.name) : 'Any';
                return `<span style="color:#6b7280;font-size:12px;">&#128100; ${n}</span>`;
            }

            // -- Time-aware staff conflict detection ------------------------
            // Guests run in PARALLEL (they all start at the same appointment time),
            // while each guest's own services run sequentially one after another.
            // So the correct window for THIS service is its offset within its own
            // guest's timeline, and it can conflict with ANY guest's services that
            // overlap that same real-time window (not just services listed earlier).
            const myIdx = self.pendingServices.indexOf(svcEntry);
            const offsetsAll = self._serviceOffsetsByGuest(self.pendingServices);
            const myStart = myIdx >= 0 ? offsetsAll[myIdx] : self._nextOffsetForGuest(svcEntry.guestLabel);
            const myDur   = self._serviceDuration(svcEntry);
            const myEnd   = myStart + myDur;

            // Staff is busy if another guest's (or my own other) service overlaps [myStart, myEnd)
            const busyIds = self._computeBusyStaffIds(myStart, myEnd, svcEntry);

            const opts = cached.map(function(st) {
                const sel     = current && String(st.id) === String(current.id) ? ' selected' : '';
                const isBusy  = st.id > 0 && busyIds.has(String(st.id));
                const dis     = isBusy ? ' disabled' : '';
                const sty     = isBusy ? ' style="color:#d1d5db;"' : '';
                const sfx     = isBusy ? ' (busy)' : '';
                return `<option value="${st.id}" data-name="${st.name}"${sel}${dis}${sty}>${self.getFirstName(st.name)}${sfx}</option>`;
            }).join('');

            return `<select class="sbs-inline-staff-sel"
                style="font-size:12px;color:#374151;border:1px solid #d1d5db;border-radius:6px;
                       padding:3px 6px;background:#f9fafb;cursor:pointer;max-width:130px;"
                title="Change staff"
                data-svc-id="${svcId}"
                data-pending-idx="${typeof pendingIdx === 'number' ? pendingIdx : ''}">${opts}</select>`;
        }

        // Show a compact summary of pending services above the calendar in step 4
        _showPendingServicesSummary() {
            if (!this.pendingServices.length) return;

            // Build one row per service with Edit + Remove buttons
            const self = this;
            const totalServices = this.pendingServices.length;
            // Count services per person so UP/DOWN only appear when a person has 2+ services
            const personServiceCount = {};
            this.pendingServices.forEach(s => {
                const lbl = s.guestLabel || 'Me';
                personServiceCount[lbl] = (personServiceCount[lbl] || 0) + 1;
            });
            // Group display by person: Me first, then guests in order of first appearance
            // Each entry keeps its original idx so Edit/Remove/UP/DOWN still work correctly
            const personOrder2 = [];
            this.pendingServices.forEach((s) => {
                const lbl = s.guestLabel || 'Me';
                if (!personOrder2.includes(lbl)) personOrder2.push(lbl);
            });
            const hasMultiplePeople = personOrder2.some(lbl => lbl !== 'Me');
            const displayOrder = [];
            personOrder2.forEach(lbl => {
                this.pendingServices.forEach((s, idx) => {
                    if ((s.guestLabel || 'Me') === lbl) displayOrder.push({ s, idx });
                });
            });
            const lines = displayOrder.map(({ s, idx }) => {
                const svcName   = this._svcDisplayLabelHtml(s.service, s.options);
                // FIX: hide option names that duplicate the service name or category name (e.g. "Custom Design / Nail Art + Custom Design / Nail Art")
                const _filteredOpts = this._filterOptNames(s.options, s.service);
                const optName   = ''; // Now handled inside _svcDisplayLabel
                const staffName = s.staff ? '&middot; ' + this.getFirstName(s.staff.name) : '';
                const staffSelectHtml = this._buildStaffSelect(s, idx);
                // Duration = main option (single-select) duration + add-on durations
                // If no options, use service base duration
                const _mainOpts  = s.options.filter(o => !o.is_addon);
                const _addonOpts = s.options.filter(o => o.is_addon);
                const _mainDur   = _mainOpts.length > 0
                    ? Math.max(..._mainOpts.map(o => o.duration || 0))
                    : (s.service.duration || 0);
                const _addonDur  = _addonOpts.reduce((d, o) => d + (o.duration || 0), 0);
                const dur        = _mainDur + _addonDur;
                const price     = this._optionsPrice(s.service, s.options);

                // Order buttons only shown when this person has 2+ services.
                const personLbl = s.guestLabel || 'Me';
                const personCount = personServiceCount[personLbl] || 1;
                const upBtn   = (idx > 0 && personCount > 1 && (this.pendingServices[idx-1].guestLabel || 'Me') === personLbl)
                    ? `<button type="button" class="sbs-banner-move-up" data-idx="${idx}"
                            title="Move this service earlier"
                            style="background:#fff;color:#374151;border:1px solid #d1d5db;border-radius:6px;padding:3px 7px;font-size:12px;cursor:pointer;line-height:1;">Up</button>`
                    : '';
                const downBtn = (idx < totalServices - 1 && personCount > 1 && (this.pendingServices[idx+1].guestLabel || 'Me') === personLbl)
                    ? `<button type="button" class="sbs-banner-move-down" data-idx="${idx}"
                            title="Move this service later"
                            style="background:#fff;color:#374151;border:1px solid #d1d5db;border-radius:6px;padding:3px 7px;font-size:12px;cursor:pointer;line-height:1;">Down</button>`
                    : '';

                let orderLabel = '';
                if (!hasMultiplePeople && totalServices > 1) {
                    const samePersonBefore = this.pendingServices.slice(0, idx).filter(ps => (ps.guestLabel || 'Me') === personLbl);
                    const prevDur = samePersonBefore.reduce((sum, ps) => sum + this._serviceDuration(ps), 0);
                    const serviceNo = samePersonBefore.length + 1;
                    orderLabel = `<span style="display:inline-block;background:#dbeafe;color:#1d4ed8;border-radius:4px;font-size:10px;font-weight:700;padding:1px 5px;margin-right:4px;">${serviceNo}${prevDur ? ' (+' + prevDur + 'min)' : ''}</span>`;
                }

                const guestBadge = s.guestLabel && s.guestLabel !== 'Me'
                    ? `<span style="display:inline-block;background:#dcfce7;color:#16a34a;border-radius:4px;font-size:10px;font-weight:700;padding:1px 5px;margin-right:4px;">${this._esc(this._displayGuestLabel(s.guestLabel))}</span>`
                    : (s.guestLabel === 'Me' && totalServices > 1
                        ? `<span style="display:inline-block;background:#dbeafe;color:#1d4ed8;border-radius:4px;font-size:10px;font-weight:700;padding:1px 5px;margin-right:4px;">Me</span>`
                        : '');
                const guestSep = guestBadge ? `<span style="color:#9ca3af;margin-right:4px;">-</span>` : '';
                return `<div class="sbs-banner-service-row" data-idx="${idx}"
                     style="display:flex;align-items:center;justify-content:space-between;gap:8px;padding:7px 0;border-bottom:1px solid #dcfce7;">
                    <div style="flex:1;min-width:0;">
                        <div style="font-size:13px;color:#1a1a1a;font-weight:500;line-height:1.3;">${guestBadge}${guestSep}${orderLabel}${svcName}${optName}</div>
                        <div style="font-size:12px;color:#6b7280;margin-top:3px;">${staffSelectHtml} &nbsp;&middot;&nbsp; ${dur}min &nbsp;&middot;&nbsp; $${price.toFixed(2)}</div>
                    </div>
                    <div style="display:flex;gap:4px;flex-shrink:0;align-items:center;">
                        ${upBtn}${downBtn}
                        <button type="button" class="sbs-banner-edit-btn" data-idx="${idx}"
                                style="background:#fff;color:#15803d;border:1px solid #86efac;border-radius:6px;padding:4px 10px;font-size:11px;font-weight:700;cursor:pointer;white-space:nowrap;">\u270f\ufe0f Edit</button>
                        <button type="button" class="sbs-banner-remove-btn" data-idx="${idx}"
                                style="background:#fff;color:#dc2626;border:1px solid #fca5a5;border-radius:6px;padding:4px 8px;font-size:12px;font-weight:700;cursor:pointer;">&times;</button>
                    </div>
                </div>`;
            }).join('');

            const total = this._durationByGuest(this.pendingServices).totalDuration;

            // Group services by person label
                const personGroups = {};
                const personOrder  = [];
                self.pendingServices.forEach(function(s) {
                const lbl = s.guestLabel || 'Me';
                if (!personGroups[lbl]) { personGroups[lbl] = []; personOrder.push(lbl); }
                personGroups[lbl].push(s);
            });
            const isGroupMode = personOrder.length > 1 || (personOrder.length === 1 && personOrder[0] !== 'Me');
            const durationCopy = isGroupMode
                ? ''
                : '';

            const html = `<div id="sbs-pending-services-banner" style="background:#f0fdf4;border:1px solid #bbf7d0;border-radius:10px;padding:14px 16px;margin-bottom:16px;">
                <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
                    <div style="font-size:12px;font-weight:700;color:#15803d;">&#9989; Services in this appointment:</div>
                </div>
                <div id="sbs-banner-rows">${lines}</div>
                <div style="display:flex;flex-wrap:wrap;gap:8px;margin-top:10px;padding-top:10px;border-top:1px solid #dcfce7;">
                    ${personOrder.map(lbl => `<button type="button" class="sbs-add-service-for-person sbs-btn sbs-btn-add-more-banner sbs-add-service-to-appt" data-person-label="${this._esc(lbl)}"
                        style="white-space:nowrap;font-size:12px;">${(yatoonData.btnLabels && yatoonData.btnLabels.add_for_person) || '+ Add Service for'} ${this._esc(this._displayGuestLabel(lbl))}</button>`).join('')}
                    <button type="button" id="sbs-banner-add-guest" class="sbs-btn"
                        style="white-space:nowrap;font-size:12px;background:#f0fdf4;color:#16a34a;border:1.5px solid #16a34a;">
                        ${(yatoonData.btnLabels && yatoonData.btnLabels.add_guest) || '&#128101; Add Guest Together'}
                    </button>
                </div>
                ${durationCopy ? `<div style="font-size:12px;color:#6b7280;margin-top:8px;">${durationCopy}</div>` : ''}
            </div>`;

            const existing = $('#sbs-pending-services-banner');
            if (existing.length) existing.replaceWith(html);
            else $('.sbs-step-4 h2').after(html);

            // Rebind banner Edit / Remove (use document delegation to survive re-renders)
            $(document).off('click.bannerEdit').on('click.bannerEdit', '.sbs-banner-edit-btn', function() {
                const idx = parseInt($(this).data('idx'));
                self._editPendingService(idx);
            });
            $(document).off('click.bannerRemove').on('click.bannerRemove', '.sbs-banner-remove-btn', function() {
                const idx = parseInt($(this).data('idx'));
                self._removePendingService(idx);
            });
            $(document).off('change.bannerInlineStaff').on('change.bannerInlineStaff', '#sbs-pending-services-banner .sbs-inline-staff-sel', function() {
                const idx     = parseInt($(this).data('pending-idx'));
                const newId   = parseInt($(this).val());
                const newName = $(this).find('option:selected').data('name');
                if (!Number.isNaN(idx) && self.pendingServices[idx]) {
                    self.pendingServices[idx].staff = { id: newId, name: newName };
                    self.selectedDateTime = null;
                    $('#sbs-selected-start-at').val('');
                    $('.sbs-time-slot').removeClass('selected');
                    self._saveSession();
                    self.updateSummary();
                    self._showPendingServicesSummary();
                    const selDate = $('.sbs-day-button.selected').data('date');
                    if (selDate && self.currentStep === 4) {
                        self.loadAvailableTimes(selDate);
                    }
                }
            });

            // -- Move service UP in pendingServices (swap with previous) --------
            $(document).off('click.bannerMoveUp').on('click.bannerMoveUp', '.sbs-banner-move-up', function() {
                const idx = parseInt($(this).data('idx'));
                if (idx <= 0 || idx >= self.pendingServices.length) return;
                // Swap idx and idx-1
                [self.pendingServices[idx - 1], self.pendingServices[idx]] =
                    [self.pendingServices[idx], self.pendingServices[idx - 1]];
                self._saveSession();
                self._showPendingServicesSummary();
                // Reload times for the new order
                const selDate = $('.sbs-day-button.selected').data('date');
                if (selDate) self.loadAvailableTimes(selDate);
            });

            // -- Move service DOWN in pendingServices (swap with next) ----------
            $(document).off('click.bannerMoveDown').on('click.bannerMoveDown', '.sbs-banner-move-down', function() {
                const idx = parseInt($(this).data('idx'));
                if (idx < 0 || idx >= self.pendingServices.length - 1) return;
                // Swap idx and idx+1
                [self.pendingServices[idx], self.pendingServices[idx + 1]] =
                    [self.pendingServices[idx + 1], self.pendingServices[idx]];
                self._saveSession();
                self._showPendingServicesSummary();
                // Reload times for the new order
                const selDate = $('.sbs-day-button.selected').data('date');
                if (selDate) self.loadAvailableTimes(selDate);
            });

            // Sync right-side summary + mobile cart bar
            this.updateSummary();
        }

        _syncSidebarWithPending() {
            // Deprecated: now handled by updateSummary()
            this.updateSummary();
        }

        _syncSidebarWithPending_OLD() {
            if (!this.pendingServices.length) return;
            const all = this.pendingServices;
            // Service names
            const svcLines = all.map(s => {
                const name = s.service.category ? s.service.category + ' - ' + s.service.name : s.service.name;
                const opt  = s.options.length > 0 ? ' + ' + s.options.map(o => o.name).join(', ') : '';
                return name + opt;
            });
            const svcText = svcLines.length === 1 ? svcLines[0] : svcLines.length + ' services: ' + svcLines.join(', ');
            $('#sbs-summary-service').text(svcText).css('color', '#1a1a1a');
            $('#sbs-edit-service-btn, #sbs-remove-service-btn').hide();

            // Show/hide sidebar empty guide - check cart AND pending services
            const hasAnyService = svcLines.length > 0 || (this.bookingCart && this.bookingCart.length > 0);
            $('#sbs-sidebar-empty-guide').toggle(!hasAnyService);
            $('#sbs-booking-summary-panel').toggle(hasAnyService);

            // Staff names
            const staffNames = [...new Set(all.map(s => s.staff ? this.getFirstName(s.staff.name) : null).filter(Boolean))];
            $('#sbs-summary-staff').text(staffNames.length ? staffNames.join(' & ') : 'Not selected').css('color', staffNames.length ? '#1a1a1a' : '#9ca3af');

            // Options
            const allOpts = all.flatMap(s => s.options.map(o => o.name));
            if (allOpts.length) {
                $('#sbs-summary-options').text(allOpts.join(', '));
                $('#sbs-summary-options-container').show();
            } else {
                $('#sbs-summary-options-container').hide();
            }

            // Duration + total
            const totalDur = this._durationByGuest(all).totalDuration;
            const totalPrice = all.reduce((sum, s) => sum + this._optionsPrice(s.service, s.options), 0);
            $('#sbs-summary-duration').text(totalDur ? 'Appointment Length: ' + totalDur + ' minutes' : '-');
            $('#sbs-summary-total').text(totalPrice ? '$' + totalPrice.toFixed(2) : '-');
        }

        bindEvents() {
            const self = this;

            // Progress bar - clicking any reached/completed step jumps there
            // Use both 'click' and touchend for mobile reliability
            function handleProgStepTap(el) {
                const $el = $(el);
                const target = parseInt($el.data('go'));
                if (!$el.hasClass('active') && !$el.hasClass('completed')) return;
                if (target === self.currentStep) return;
                if (target < self.currentStep) {
                    if (target <= 1) { self.selectedService = null; self.selectedCategory = null; }
                    if (target <= 2) self.selectedOptions = [];
                    if (target <= 3) self.selectedStaff = null;
                    if (target <= 4) self.selectedDateTime = null;
                }
                self.goToStep(target, true);  // scroll=true: user explicitly navigated
                if (target === 3) self.loadAvailableStaff();
            }
            $(document).on('click', '.sbs-prog-step', function(e) {
                e.preventDefault();
                handleProgStepTap(this);
            });
            // touchend fallback for mobile (avoids 300ms delay and scroll cancel issues)
            let _progTouchMoved = false;
            $(document).on('touchstart', '.sbs-prog-step', function() { _progTouchMoved = false; });
            $(document).on('touchmove', '.sbs-prog-step', function() { _progTouchMoved = true; });
            $(document).on('touchend', '.sbs-prog-step', function(e) {
                if (_progTouchMoved) return;
                e.preventDefault();
                handleProgStepTap(this);
            });

            // More dropdown
            let _moreOpen = false;

            function _ensureMoreDropdownItems() {
                const wrap = document.querySelector('.sbs-category-tabs');
                const dd   = document.getElementById('sbs-cat-more-dropdown');
                if (!wrap || !dd) return false;
                if (dd.children.length > 0) return true;

                const overflowTabs = Array.from(wrap.querySelectorAll('.sbs-cat-tab.sbs-cat-overflow:not(.sbs-cat-more-btn)'));
                dd.innerHTML = '';
                overflowTabs.forEach(t => {
                    if (!t.dataset.cat) return;
                    const btn = document.createElement('button');
                    btn.type = 'button';
                    btn.className = 'sbs-cat-dropdown-item' + (t.classList.contains('active') ? ' active' : '');
                    btn.dataset.cat = t.dataset.cat;
                    btn.textContent = t.textContent.trim();
                    dd.appendChild(btn);
                });
                return dd.children.length > 0;
            }

            function _positionDropdown() {
                const btn = document.getElementById('sbs-cat-more-btn');
                const dd  = document.getElementById('sbs-cat-more-dropdown');
                if (!btn || !dd) return;
                const r   = btn.getBoundingClientRect();
                const host = document.querySelector('.sbs-step-1') || document.querySelector('.sbs-booking-main') || document.body;
                const hr   = host.getBoundingClientRect();
                const hostW = host.clientWidth || window.innerWidth;
                const ddW = 180;
                let left = r.right - hr.left - ddW;
                if (left < 6) left = 6;
                if (left + ddW > hostW - 6) left = hostW - ddW - 6;
                dd.style.top  = (r.bottom - hr.top + 6) + 'px';
                dd.style.left = left + 'px';
                dd.style.width = Math.min(ddW, hostW - 12) + 'px';
            }
            function _setMoreButtonState(open) {
                const $button = $('#sbs-cat-more-btn');
                const selectedLabel = $button.attr('data-selected-label') || 'More';
                $button.text(selectedLabel + (open ? ' ⌃' : ' ⌄'));
                $button.attr('aria-expanded', open ? 'true' : 'false');
            }
            function _openMore() {
                _adjustTabVisibility();
                if (!_ensureMoreDropdownItems()) return;
                _positionDropdown();
                $('#sbs-cat-more-dropdown').addClass('open');
                _setMoreButtonState(true);
                _moreOpen = true;
            }
            function _closeMore() {
                $('#sbs-cat-more-dropdown').removeClass('open');
                _setMoreButtonState(false);
                _moreOpen = false;
            }

            let _lastMoreTap = 0;
            $(document).on('click touchend', '#sbs-cat-more-btn', function(e) {
                const now = Date.now();
                if (now - _lastMoreTap < 220) return;
                _lastMoreTap = now;
                e.preventDefault();
                e.stopPropagation();
                _moreOpen = $('#sbs-cat-more-dropdown').hasClass('open');
                _moreOpen ? _closeMore() : _openMore();
            });
            $(document).on('click touchend', function(e) {
                if (_moreOpen && !$(e.target).is('#sbs-cat-more-btn') && !$(e.target).closest('#sbs-cat-more-dropdown').length) _closeMore();
            });
            $(document).on('click touchend', '.sbs-cat-dropdown-item', function(e) {
                e.preventDefault();
                e.stopPropagation();
                const slug = $(this).data('cat');
                _closeMore();
                $('.sbs-cat-tab').removeClass('active');
                $('.sbs-cat-dropdown-item').removeClass('active');
                $(this).addClass('active');
                $('#sbs-cat-more-btn').addClass('has-active');
                const $heading = $('#cat-' + slug);
                if ($heading.length) {
                    const navH = ($('.sbs-category-tabs-wrap').outerHeight() || 0) + 8;
                    setTimeout(function() {
                        $('html, body').animate({ scrollTop: $heading.offset().top - navH }, 350);
                    }, 30);
                }
            });
            $(window).on('scroll.moreDD resize.moreDD', function() {
                if (_moreOpen) _positionDropdown();
            });

            // Drawer swipe
            let touchStartY = 0;
            $(document).on('touchstart', '#sbs-mobile-drawer', function(e) {
                touchStartY = e.originalEvent.touches[0].clientY;
            });
            $(document).on('touchend', '#sbs-mobile-drawer', function(e) {
                const diff = e.originalEvent.changedTouches[0].clientY - touchStartY;
                if (diff > 60) self._closeDrawer();
            });

            // Text Us - auto-collapse to icon-only after 4 seconds
            const $textUsBtn = $('#sbs-text-us-btn');
            if ($textUsBtn.length) {
                setTimeout(function() {
                    $textUsBtn.addClass('sbs-textus-collapsed');
                }, 4000);
                // Re-expand on click (before opening modal), re-collapse after modal closes
                $textUsBtn.on('mouseenter touchstart', function() {
                    $textUsBtn.removeClass('sbs-textus-collapsed');
                });
            }

            // Text Us modal
            $(document).on('click', '#sbs-text-us-btn', function() {
                $('#sbs-text-us-modal').addClass('open');
                $('#sbs-text-us-form').show();
                $('#sbs-text-success').hide();
            });
            $(document).on('click', '.sbs-text-modal-close, .sbs-text-modal', function(e) {
                if (e.target === this) {
                    $('#sbs-text-us-modal').removeClass('open');
                    // Re-collapse after modal closes
                    setTimeout(function() { $textUsBtn.addClass('sbs-textus-collapsed'); }, 1500);
                }
            });
            $(document).on('submit', '#sbs-text-us-form', function(e) {
                e.preventDefault();
                const $form = $(this);
                const $btn  = $form.find('.sbs-text-submit-btn');
                const data  = {
                    action: 'yatoon_send_contact_message',
                    nonce:  yatoonData.nonce,
                    name:   $form.find('[name="name"]').val(),
                    phone:  $form.find('[name="phone"]').val(),
                    message: $form.find('[name="message"]').val()
                };
                $btn.prop('disabled', true).text('Sending...');
                $.post(yatoonData.ajax_url, data, function(resp) {
                    if (resp.success) {
                        $form.hide();
                        $('#sbs-text-success').show();
                        setTimeout(function() {
                            $('#sbs-text-us-modal').removeClass('open');
                            $form[0].reset();
                            $btn.prop('disabled', false).text('Send message');
                            setTimeout(function() { $textUsBtn.addClass('sbs-textus-collapsed'); }, 500);
                        }, 2000);
                    } else {
                        self._showToast('Failed to send. Please try again.', 'error');
                        $btn.prop('disabled', false).text('Send message');
                    }
                }).fail(function() {
                    self._showToast('Network error. Please try again.', 'error');
                    $btn.prop('disabled', false).text('Send message');
                });
            });

            // Remove fade hint when tabs are scrolled to end
            $(document).on('scroll', '.sbs-category-tabs', function() {
                const el = this;
                const atEnd = el.scrollLeft + el.clientWidth >= el.scrollWidth - 5;
                $(el).toggleClass('scrolled-end', atEnd);
            });

            // Dynamically adjust which tabs are visible vs. in More dropdown
            function _adjustTabVisibility() {
                const wrap = document.querySelector('.sbs-category-tabs');
                const moreBtn = document.getElementById('sbs-cat-more-btn');
                if (!wrap) return;

                const allTabs = Array.from(wrap.querySelectorAll('.sbs-cat-tab:not(.sbs-cat-more-btn)'));
                if (!allTabs.length) return;

                if (window.innerWidth >= 769) {
                    allTabs.forEach(t => {
                        t.classList.remove('sbs-cat-overflow');
                        t.style.display = 'inline-flex';
                        t.style.fontSize = '';
                        t.style.padding = '';
                    });
                    const dd = document.getElementById('sbs-cat-more-dropdown');
                    if (!moreBtn || allTabs.length <= 4) {
                        if (moreBtn) {
                            moreBtn.classList.remove('sbs-more-needed');
                            moreBtn.style.display = 'none';
                        }
                        if (dd) {
                            dd.classList.remove('open');
                            dd.innerHTML = '';
                        }
                        return;
                    }

                    moreBtn.classList.add('sbs-more-needed');
                    moreBtn.style.display = 'inline-flex';
                    moreBtn.style.fontSize = '';
                    moreBtn.style.padding = '';
                    const visibleCount = 4;
                    const hiddenTabs = allTabs.slice(visibleCount);
                    hiddenTabs.forEach(t => {
                        t.classList.add('sbs-cat-overflow');
                        t.style.display = 'none';
                    });
                    if (dd) {
                        dd.innerHTML = '';
                        hiddenTabs.forEach(t => {
                            const btn = document.createElement('button');
                            btn.type = 'button';
                            btn.className = 'sbs-cat-dropdown-item' + (t.classList.contains('active') ? ' active' : '');
                            btn.dataset.cat = t.dataset.cat;
                            btn.textContent = t.textContent.trim();
                            dd.appendChild(btn);
                        });
                    }
                    return;
                }

                if (!moreBtn) return;

                const w = window.innerWidth;
                const MIN_FONT = w >= 414 ? 8.6 : 9;
                const MAX_FONT = w >= 414 ? 10.2 : w < 390 ? 9.8 : 10.6;

                // Reset all tabs
                allTabs.forEach(t => {
                    t.classList.remove('sbs-cat-overflow');
                    t.style.display = '';
                    t.style.fontSize = '';
                    t.style.padding = '';
                });
                moreBtn.classList.remove('sbs-more-needed');
                moreBtn.style.display = 'none';
                moreBtn.style.fontSize = '';
                moreBtn.style.padding = '';

                // Get true usable width (account for container padding)
                const wrapStyle = window.getComputedStyle(wrap);
                const padL = parseFloat(wrapStyle.paddingLeft)  || 0;
                const padR = parseFloat(wrapStyle.paddingRight) || 0;
                const trueW = ((wrap.parentElement ? wrap.parentElement.clientWidth : wrap.clientWidth) - padL - padR);

                // If everything can fit at the mobile max size, show all tabs without More.
                _scaleFontToFit(allTabs, trueW, MIN_FONT, MAX_FONT);
                const allFit = allTabs.reduce((sum, t) => sum + t.offsetWidth, 0) <= trueW;
                if (allFit) {
                    moreBtn.classList.remove('sbs-more-needed');
                    moreBtn.style.display = 'none';
                    return;
                }

                // Measure More button at MAX_FONT
                moreBtn.style.fontSize = MAX_FONT + 'px';
                moreBtn.style.padding = Math.round(MAX_FONT * 0.55) + 'px ' + Math.round(MAX_FONT * 0.58) + 'px';
                moreBtn.style.display = 'inline-flex';
                const moreBtnW = moreBtn.offsetWidth || 44;
                moreBtn.style.display = 'none';

                const available = trueW - moreBtnW - 8;

                let visibleTarget = w >= 414 ? 3 : 2;
                visibleTarget = Math.min(visibleTarget, allTabs.length - 1);
                const minVisibleTarget = w >= 414 ? Math.min(3, allTabs.length - 1) : 1;
                while (visibleTarget > minVisibleTarget) {
                    const testTabs = allTabs.slice(0, visibleTarget);
                    _scaleFontToFit(testTabs, available, MIN_FONT, MAX_FONT);
                    const testW = testTabs.reduce((sum, t) => sum + t.offsetWidth, 0);
                    if (testW <= available) break;
                    visibleTarget--;
                }

                const visibleTabs = allTabs.slice(0, visibleTarget);
                const hiddenTabs  = allTabs.slice(visibleTarget);

                hiddenTabs.forEach(t => {
                    t.classList.add('sbs-cat-overflow');
                    t.style.display = 'none';
                });

                _scaleFontToFit(visibleTabs, available, MIN_FONT, MAX_FONT);

                const finalSize = parseFloat(visibleTabs[0].style.fontSize) || MAX_FONT;
                moreBtn.classList.add('sbs-more-needed');
                moreBtn.style.fontSize = finalSize + 'px';
                moreBtn.style.padding = Math.round(finalSize * 0.55) + 'px ' + Math.round(finalSize * 0.58) + 'px';
                moreBtn.style.display = 'inline-flex';

                // Rebuild dropdown
                const dd = document.getElementById('sbs-cat-more-dropdown');
                if (dd) {
                    dd.innerHTML = '';
                    hiddenTabs.forEach(t => {
                        const btn = document.createElement('button');
                        btn.type = 'button';
                        btn.className = 'sbs-cat-dropdown-item' + (t.classList.contains('active') ? ' active' : '');
                        btn.dataset.cat = t.dataset.cat;
                        btn.textContent = t.textContent.trim();
                        dd.appendChild(btn);
                    });
                }

                const atEnd = wrap.scrollLeft + wrap.clientWidth >= wrap.scrollWidth - 5;
                $(wrap).toggleClass('scrolled-end', atEnd);
            }

            function _scaleFontToFit(tabs, availableW, minFont, maxFont) {
                if (!tabs.length) return;
                let lo = minFont, hi = maxFont, bestSize = minFont;
                for (let iter = 0; iter < 8; iter++) {
                    const mid = (lo + hi) / 2;
                    tabs.forEach(t => {
                        t.style.fontSize = mid + 'px';
                        t.style.padding = Math.round(mid * 0.6) + 'px ' + Math.round(mid * 0.75) + 'px';
                    });
                    let totalW = 0;
                    tabs.forEach(t => { totalW += t.offsetWidth; });
                    if (totalW <= availableW) { bestSize = mid; lo = mid; }
                    else { hi = mid; }
                }
                tabs.forEach(t => {
                    t.style.fontSize = bestSize + 'px';
                    t.style.padding = Math.round(bestSize * 0.6) + 'px ' + Math.round(bestSize * 0.75) + 'px';
                });
            }

            // Run on load (after fonts/layout settle) and on resize
            setTimeout(_adjustTabVisibility, 400);
            $(window).on('resize.sbsTabs', function() {
                setTimeout(_adjustTabVisibility, 100);
            });

            // Compact service search (filters services list by name/description)
            $(document).on('input', '#sbs-service-search', function() {
                const q = $(this).val().trim().toLowerCase();
                $('#sbs-service-search-clear').toggle(q.length > 0);

                const $items = $('.sbs-services-list .sbs-service-item');
                const $headings = $('.sbs-services-list .sbs-category-title');

                if (!q) {
                    $items.removeClass('sbs-search-hidden');
                    $headings.removeClass('sbs-search-hidden');
                    $('#sbs-service-search-empty').hide();
                    return;
                }

                $items.each(function() {
                    const name = ($(this).data('service-name') || '').toString().toLowerCase();
                    const desc = ($(this).data('service-desc') || '').toString().toLowerCase();
                    const match = name.indexOf(q) !== -1 || desc.indexOf(q) !== -1;
                    $(this).toggleClass('sbs-search-hidden', !match);
                });

                $headings.each(function() {
                    const $visibleSiblings = $(this).nextUntil('.sbs-category-title', '.sbs-service-item').not('.sbs-search-hidden');
                    $(this).toggleClass('sbs-search-hidden', $visibleSiblings.length === 0);
                });

                const anyVisible = $items.not('.sbs-search-hidden').length > 0;
                $('#sbs-service-search-empty').toggle(!anyVisible);
            });
            $(document).on('click', '#sbs-service-search-clear', function() {
                $('#sbs-service-search').val('').trigger('input').trigger('focus');
            });

            // Keep the original 4.2.3 text-link appearance while reliably
            // expanding the complete description without inline JavaScript.
            $(document).on('click', '.sbs-read-more', function(e) {
                e.preventDefault();
                e.stopPropagation();
                const $button = $(this);
                const $wrap = $button.closest('.sbs-service-desc-wrap');
                const $short = $wrap.find('.sbs-desc-short').first();
                const $full = $wrap.find('.sbs-desc-full').first();
                if (!$short.length || !$full.length) return;
                const willExpand = $button.attr('aria-expanded') !== 'true';
                $short.prop('hidden', willExpand);
                $full.prop('hidden', !willExpand);
                $button.attr('aria-expanded', willExpand ? 'true' : 'false');
                $button.text(willExpand ? $button.data('show-less') : $button.data('read-more'));
            });

            // Category tabs - scroll to section
            $(document).on('click', '.sbs-cat-tab:not(.sbs-cat-more-btn)', function() {
                const slug = $(this).data('cat');
                if (!slug) return;
                const $heading = $('#cat-' + slug);
                if ($heading.length) {
                    const navH = $('.sbs-category-tabs-wrap').outerHeight() || 0;
                    $('html, body').animate({ scrollTop: $heading.offset().top - navH - 12 }, 350);
                }
                $('.sbs-cat-tab').removeClass('active');
                $('.sbs-cat-dropdown-item').removeClass('active');
                $('#sbs-cat-more-btn').removeClass('has-active');
                $(this).addClass('active');
            });

            // Highlight active tab while scrolling
            $(window).on('scroll.sbsTab', function() {
                if (!$('.sbs-category-tabs-wrap').is(':visible')) return;
                if ($('.sbs-category-tabs').attr('data-yatoon-active-category') === 'all') return;
                const navH = ($('.sbs-category-tabs-wrap').outerHeight() || 0) + 20;
                let current = null;
                $('.sbs-category-title').each(function() {
                    if ($(window).scrollTop() >= $(this).offset().top - navH) current = $(this).attr('id');
                });
                if (current) {
                    const slug = current.replace('cat-', '');
                    const $mainTab = $('.sbs-cat-tab[data-cat="' + slug + '"]:not(.sbs-cat-more-btn)');
                    const $dropItem = $('.sbs-cat-dropdown-item[data-cat="' + slug + '"]');
                    if ($mainTab.length && !$mainTab.hasClass('sbs-cat-overflow')) {
                        $('.sbs-cat-tab').removeClass('active');
                        $('.sbs-cat-dropdown-item').removeClass('active');
                        $('#sbs-cat-more-btn').removeClass('has-active');
                        $mainTab.addClass('active');
                    } else if ($dropItem.length) {
                        $('.sbs-cat-tab').removeClass('active');
                        $('.sbs-cat-dropdown-item').removeClass('active');
                        $dropItem.addClass('active');
                        $('#sbs-cat-more-btn').addClass('has-active');
                    }
                }
            });

            $(document).on('click', '#yatoon-ai-service-submit', function(e) {
                e.preventDefault();
                const ai = (typeof yatoonData !== 'undefined' && yatoonData.ai) ? yatoonData.ai : {};
                const $btn = $(this);
                const $need = $('#yatoon-ai-service-need');
                const $status = $('#yatoon-ai-service-status');
                const $result = $('#yatoon-ai-service-result');
                const need = ($need.val() || '').trim();

                if (!ai.serviceHelperEnabled || !ai.available) {
                    $status.text(ai.status || 'AI is not available yet.');
                    return;
                }
                if (need.length < 3) {
                    $status.text('Tell us what you are looking for first.');
                    $need.trigger('focus');
                    return;
                }

                $btn.prop('disabled', true).text('Finding matches...');
                $status.text('');
                $result.hide().empty();

                $.post(yatoonData.ajax_url, {
                    action: 'yatoon_ai_service_recommendations',
                    nonce: yatoonData.nonce,
                    need: need
                }).done(function(resp) {
                    if (resp && resp.success && resp.data && resp.data.answer) {
                        let recHtml = '';
                        const recs = Array.isArray(resp.data.recommendations) ? resp.data.recommendations : [];
                        if (recs.length) {
                            recHtml += '<div class="yatoon-ai-recommendation-actions">';
                            recHtml += '<strong>Recommended services</strong>';
                            recHtml += '<div class="yatoon-ai-recommendation-list">';
                            recs.forEach(function(rec) {
                                const name = $('<div>').text(rec.name || 'Service').html();
                                const duration = parseInt(rec.duration, 10) || 0;
                                const price = parseFloat(rec.price) || 0;
                                const meta = [
                                    duration ? duration + ' min' : '',
                                    price ? '$' + price.toFixed(2).replace(/\.00$/, '') : ''
                                ].filter(Boolean).join(' - ');
                                const nextOpening = rec.next_opening ? $('<div>').text(rec.next_opening).html() : '';
                                recHtml += '<button type="button" class="yatoon-ai-pick-service" data-service-id="' + parseInt(rec.id, 10) + '">';
                                recHtml += '<span>' + name + '</span>';
                                if (meta) recHtml += '<small>' + meta + '</small>';
                                if (nextOpening) recHtml += '<small>Next: ' + nextOpening + '</small>';
                                recHtml += '</button>';
                            });
                            recHtml += '</div>';
                            recHtml += '</div>';
                        }
                        $result.html(recHtml + resp.data.answer).show();
                    } else {
                        const msg = resp && resp.data && resp.data.message ? resp.data.message : 'AI could not help right now.';
                        $status.text(msg);
                    }
                }).fail(function() {
                    $status.text('AI could not help right now.');
                }).always(function() {
                    $btn.prop('disabled', false).text('Ask AI for Recommendations');
                });
            });

            $(document).on('click', '.yatoon-ai-toggle', function(e) {
                e.preventDefault();
                const $helper = $(this).closest('.yatoon-ai-helper');
                $helper.removeClass('is-collapsed').addClass('is-expanded');
                $(this).attr('aria-expanded', 'true').hide();
            });

            $(document).on('click', '.yatoon-ai-collapse', function(e) {
                e.preventDefault();
                const $helper = $(this).closest('.yatoon-ai-helper');
                $helper.removeClass('is-expanded').addClass('is-collapsed');
                let $toggle = $helper.children('.yatoon-ai-toggle');
                if (!$toggle.length) {
                    const label = $.trim($helper.find('.yatoon-ai-helper-header strong').first().text()) || 'Need help choosing?';
                    $toggle = $('<button type="button" class="yatoon-ai-toggle" aria-expanded="false"></button>').text(label);
                    $helper.children('.yatoon-ai-helper-header').after($toggle);
                }
                $toggle.attr('aria-expanded', 'false').show();
            });

            $(document).on('click', '.yatoon-ai-pick-service', function(e) {
                e.preventDefault();
                const serviceId = parseInt($(this).data('service-id'), 10);
                if (!serviceId) return;

                const $service = $('.sbs-service-item[data-service-id="' + serviceId + '"]').first();
                if (!$service.length) {
                    $('#yatoon-ai-service-status').text('This recommended service is not visible on the menu right now.');
                    return;
                }

                const navH = ($('.sbs-category-tabs-wrap').outerHeight() || 0) + 20;
                $('html, body').animate({ scrollTop: Math.max(0, $service.offset().top - navH - 12) }, 280, function() {
                    $service.trigger('click');
                    $service.addClass('yatoon-ai-picked-pulse');
                    setTimeout(function() { $service.removeClass('yatoon-ai-picked-pulse'); }, 900);
                });
            });

            // Step 1: Service selection
            let _serviceTouchMoved = false;
            let _lastServiceTap = 0;
            function _handleServiceSelection(e, trigger) {
                if (e && $(e.target).closest('a, .sbs-read-more, .sbs-service-readmore').length) return;
                const now = Date.now();
                if (now - _lastServiceTap < 260) {
                    if (e) e.preventDefault();
                    return;
                }
                _lastServiceTap = now;
                if (e) {
                    e.preventDefault();
                    e.stopPropagation();
                }
                self._abortLookupRequests();
                self._hardUnlockBookingSurface();

                const $item = $(trigger).hasClass('sbs-service-item') ? $(trigger) : $(trigger).closest('.sbs-service-item');
                $('.sbs-service-item, .sbs-service-card').removeClass('selected');
                $item.addClass('selected');
                
                // FIX: capture category from nearest heading for summary display
                const $heading = $item.prevAll('.sbs-category-title').first();
                self.selectedCategory = $heading.length ? $heading.text().trim() : null;
                
                const _newServiceName = ($item.data('service-name') || '').toLowerCase().trim();

                // Duplicate check at service selection using service ID + square_id + name fallback
                if (self.pendingServices.length > 0) {
                    const _newSvcId     = parseInt($item.data('service-id'));
                    const _newSquareId  = ($item.data('square-id') || '').trim();
                    const _newSvcName   = ($item.data('service-name') || '').toLowerCase().trim();
                    // The person currently being added for
                    const _currentPerson = self._nextGuestLabel || 'Me';
                    // Check main services by ID - only flag as duplicate if SAME person already has it
                    const _dupInMain = self.pendingServices.find(ps =>
                        parseInt(ps.service.id) === _newSvcId &&
                        (ps.guestLabel || 'Me') === _currentPerson
                    );
                    // Check options: by linked_service_id, square_id match, OR name similarity - same person only
                    const _dupInOpts = self.pendingServices.find(ps =>
                        (ps.guestLabel || 'Me') === _currentPerson &&
                        ps.options && ps.options.find(o => {
                            if (parseInt(o.linked_service_id) === _newSvcId) return true;
                            // Match by square_id if available on both sides
                            if (_newSquareId && o.square_id && _newSquareId === o.square_id) return true;
                            const oName = (o.name || '').toLowerCase().trim();
                            return !!(oName && _newSvcName && (oName.includes(_newSvcName) || _newSvcName.includes(oName)));
                        })
                    );
                    if (_dupInMain || _dupInOpts) {
                        const _svcName = $item.data('service-name');
                        const _$savedItem = $item;
                        $('#sbs-dup-overlay').remove();
                        $('body').append(`
                            <div id="sbs-dup-overlay" style="position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:99999;display:flex;align-items:center;justify-content:center;padding:20px;">
                                <div style="background:#fff;border-radius:16px;padding:28px 24px;max-width:380px;width:100%;text-align:center;box-shadow:0 20px 60px rgba(0,0,0,0.3);">
                                    <div style="font-size:32px;margin-bottom:12px;">!</div>
                                    <h3 style="margin:0 0 8px;font-size:17px;font-weight:700;color:#111;">You already added "${_svcName}" to your booking</h3>
                                    <p style="margin:0 0 22px;font-size:14px;color:#6b7280;">Would you like to add it again, or was that an accidental tap?</p>
                                    <div style="display:flex;gap:10px;justify-content:center;">
                                        <button id="sbs-dup-cancel" style="flex:1;padding:11px 0;border-radius:8px;border:1.5px solid #e5e7eb;background:#fff;font-size:14px;font-weight:600;cursor:pointer;color:#374151;">Dismiss</button>
                                        <button id="sbs-dup-confirm" style="flex:1;padding:11px 0;border-radius:8px;border:none;background:#16a34a;color:#fff;font-size:14px;font-weight:600;cursor:pointer;">Add Anyway</button>
                                    </div>
                                </div>
                            </div>
                        `);
                        $('#sbs-dup-cancel').on('click', function() {
                            $('#sbs-dup-overlay').remove();
                            // Just close - stay on service menu so client can continue browsing
                        });
                        $('#sbs-dup-confirm').on('click', function() {
                            $('#sbs-dup-overlay').remove();
                            // Proceed: set service and go to Step 2 (Options)
                            self.selectedService = {
                                id:       _$savedItem.data('service-id'),
                                name:     _$savedItem.data('service-name'),
                                price:    parseFloat(_$savedItem.data('service-price')),
                                duration: parseInt(_$savedItem.data('service-duration')),
                                description: _$savedItem.data('service-desc') || '',
                                image_url: _$savedItem.data('service-image') || '',
                                category: self.selectedCategory,
                                requirePolicy: parseInt(_$savedItem.data('require-policy')) === 1,
                                requireConsent: parseInt(_$savedItem.data('require-consent')) === 1,
                                allowStaffSelect: parseInt(_$savedItem.data('allow-staff-select')) !== 0,
                                allowDesignUpload: parseInt(_$savedItem.data('allow-design-upload')) === 1
                            };
                            $('#sbs-selected-service-id').val(self.selectedService.id);
                            self.selectedOptions = [];
                            self.selectedStaff = null;
                            self.selectedDateTime = null;
                            self._saveSession();
                            self.updateSummary();
                            self._scrollToFormTop();
                            self.loadServiceOptions();
                            self.goToStep(2);
                        });
                        return;
                    }
                }

                self.selectedService = {
                    id:       $item.data('service-id'),
                    name:     $item.data('service-name'),
                    price:    parseFloat($item.data('service-price')),
                    duration: parseInt($item.data('service-duration')),
                    description: $item.data('service-desc') || '',
                    image_url: $item.data('service-image') || '',
                    category: self.selectedCategory,
                    requirePolicy: parseInt($item.data('require-policy')) === 1,
                    requireConsent: parseInt($item.data('require-consent')) === 1,
                    allowStaffSelect: parseInt($item.data('allow-staff-select')) !== 0,
                    allowDesignUpload: parseInt($item.data('allow-design-upload')) === 1
                };
                
                $('#sbs-selected-service-id').val(self.selectedService.id);
                // FIX: clear options on new service selection
                self.selectedOptions = [];
                self.selectedStaff = null;
                self.selectedDateTime = null;
                
                self.updateSummary();
                self._updateMobileCartBar(); // only shows if cart has items

                // Scroll to top immediately - works for both mobile and desktop
                self._scrollToFormTop();

                self._saveSession();
                $('#sbs-guest-pick-banner').remove(); // clear guest banner if present
                self.loadServiceOptions();
            }
            $(document).on('click', '.sbs-service-item, .sbs-book-btn', function(e) {
                _handleServiceSelection(e, this);
            });
            $(document).on('touchstart', '.sbs-service-item, .sbs-book-btn', function() {
                _serviceTouchMoved = false;
            });
            $(document).on('touchmove', '.sbs-service-item, .sbs-book-btn', function() {
                _serviceTouchMoved = true;
            });
            $(document).on('touchend', '.sbs-service-item, .sbs-book-btn', function(e) {
                if (_serviceTouchMoved) return;
                _handleServiceSelection(e, this);
            });

            // Step 2: Options selection
            $(document).on('change', '.sbs-option-checkbox', function() {
                const optionItem = $(this).closest('.sbs-option-item');
                const groupEl    = optionItem.closest('.sbs-option-group');
                const isMulti    = groupEl.data('multi') === 1 || groupEl.data('multi') === '1';

                if ($(this).is(':checked')) {
                    // For main (single-select) groups: uncheck + deselect all siblings first
                    if (!isMulti) {
                        groupEl.find('.sbs-option-checkbox').not(this).each(function() {
                            const sibItem = $(this).closest('.sbs-option-item');
                            const sibId   = parseInt($(this).val());
                            $(this).prop('checked', false);
                            sibItem.removeClass('selected');
                            self.selectedOptions = self.selectedOptions.filter(o => o.id !== sibId);
                        });
                    }
                    optionItem.addClass('selected');
                    const newOptId = parseInt($(this).val());
                    // Guard: don't push duplicate (can happen in edit mode where options are pre-restored)
                    if (!self.selectedOptions.some(o => o.id === newOptId)) {
                        self.selectedOptions.push({
                            id:                newOptId,
                            name:              optionItem.find('.sbs-option-name').text(),
                            price:             parseFloat(optionItem.data('absolute-price')),
                            price_modifier:    parseFloat(optionItem.data('price')) || 0,
                            duration:          parseInt(optionItem.data('duration')),
                            group:             optionItem.data('group') || '',
                            is_addon:          optionItem.hasClass('sbs-option-addon'),
                            linked_service_id: parseInt(optionItem.data('linked-service-id')) || null,
                            allowDesignUpload: parseInt(optionItem.data('allow-design-upload')) === 1,
                            square_id:         (optionItem.data('square-id') || '').toString().trim()
                        });
                    }
                } else {
                    optionItem.removeClass('selected');
                    const optionId = parseInt($(this).val());
                    self.selectedOptions = self.selectedOptions.filter(opt => opt.id !== optionId);
                }
                $('#sbs-selected-options').val(JSON.stringify(self.selectedOptions.map(o => o.id)));
                if ((self.selectedOptions || []).some(function(o) { return !o.is_addon; })) {
                    self._clearMainOptionRequired();
                }
                self._updateOptionActionSummary();
                self._updateMobileCartBar();
                self._syncDesignUploadCard(true, optionItem);
                self._saveSession();
            });

            // Make the full option card tappable on mobile, not only the hidden checkbox/label.
            $(document).on('click', '.sbs-option-item', function(e) {
                if ($(e.target).is('input, label') || $(e.target).closest('label').length) return;
                const $cb = $(this).find('.sbs-option-checkbox').first();
                if ($cb.length) {
                    $cb.prop('checked', !$cb.prop('checked')).trigger('change');
                }
            });

            // Step 3: Staff selection
            $(document).on('click', '.sbs-staff-card', function() {
                self._selectStaffCard($(this));
            });

            // Time slot selection
            $(document).on('click', '.sbs-next-opening-chip', function() {
                const startAt = $(this).data('start-at');
                const $slot = $('.sbs-time-slot').filter(function() {
                    return String($(this).data('start-at')) === String(startAt);
                }).first();
                if ($slot.length) {
                    $slot.trigger('click');
                    const slotTop = Math.max(0, $slot.offset().top - 140);
                    $('html, body').animate({ scrollTop: slotTop }, 180);
                }
            });

            $(document).on('click', '.sbs-time-slot', function() {
                // Hard-blocked slots (all eligible staff busy) - not selectable
                if ($(this).hasClass('sbs-time-slot-unavail')) {
                    self._showToast('No staff are available at this time. Please choose a different time.', 'error');
                    return;
                }
                // Busy slots: warn but allow selection only if another staff can cover the service
                if ($(this).hasClass('sbs-time-slot-busy')) {
                    const busyIdsRaw   = $(this).data('busy-staff-ids');
                    const busyStaffIds = busyIdsRaw ? String(busyIdsRaw).split(',').map(Number).filter(Boolean) : [];

                    // For each conflicting service, check if another eligible staff member exists.
                    // If the service can ONLY be done by the busy staff member ->hard block.
                    const canSwitch  = []; // { ps, name } - services that can be reassigned
                    const cantSwitch = []; // name - services with only one possible staff

                    if (busyStaffIds.length > 0 && self.pendingServices.length > 0) {
                        self.pendingServices.forEach(function(ps) {
                            if (!ps.staff || !busyStaffIds.includes(Number(ps.staff.id))) return;
                            const name      = self.getFirstName(ps.staff.name);
                            const eligible  = ps.eligibleStaffIds || [];
                            // Other eligible staff = eligible minus the busy one
                            const otherFree = eligible.filter(sid => !busyStaffIds.includes(sid));
                            if (otherFree.length > 0) {
                                canSwitch.push({ ps: ps, name: name });
                            } else {
                                // Only this staff can do the service - cannot reassign
                                cantSwitch.push(name);
                            }
                        });
                    }

                    if (cantSwitch.length > 0) {
                        // At least one service has no substitute - this time slot is not viable
                        const svcName = self.pendingServices.find(ps =>
                            ps.staff && busyStaffIds.includes(Number(ps.staff.id))
                        );
                        const svcLabel = (svcName && svcName.service) ? svcName.service.name : 'this service';
                        self._showToast(
                            cantSwitch.join(', ') + ' is busy at this time AND is the only staff who can do "' + svcLabel + '".\n\n' +
                            'Please choose a different time.',
                            'error'
                        );
                        return;
                    }

                    if (canSwitch.length > 0) {
                        const names = canSwitch.map(x => x.name).join(', ');
                        if (!confirm(
                            names + ' is busy at this time (already in Appointment 1).\n\n' +
                            'Tap OK to continue - the system will assign another available staff member.\n\n' +
                            'Tap Cancel to choose a different time.'
                        )) return;
                        // Switch to Any staff only for services that have alternatives
                        canSwitch.forEach(function(x) {
                            x.ps.staff = { id: 0, name: 'Any staff' };
                        });
                        self._showPendingServicesSummary();
                        // fall through to normal slot selection below
                    } else {
                        self._showToast('No alternative staff available. Please choose a different time.', 'error');
                        return;
                    }
                }
                $('.sbs-time-slot').removeClass('selected');
                $(this).addClass('selected');
                const selectedDate = $('.sbs-day-button.selected').data('date') || $('#sbs-date-picker').val();
                const clickedStartAt = $(this).data('start-at');

                // -- Staff time-overlap guard -------------------------------------
                // Check that the new appointment doesn't overlap with an existing cart
                // item for the SAME named staff member (not "Any staff").
                //
                // Overlap condition:  newStart < existingEnd  AND  newEnd > existingStart
                //
                // "Any staff" (id=0) is skipped here because the backend/Square handles
                // those conflicts - we can only check named staff client-side.
                // -----------------------------------------------------------------

                // Build the new appointment's named staff IDs and total duration
                const newStaffIds = self.pendingServices.length > 0
                    ? self.pendingServices.map(s => s.staff ? s.staff.id : 0)
                    : (self.selectedStaff ? [self.selectedStaff.id] : [0]);

                // -- Compute new appointment's total duration ---------------------
                // Use a minimum of 1 minute so that exact same-time checks still work
                const newDuration = Math.max(1, (function() {
                    if (self.pendingServices.length > 0) {
                        return self._durationByGuest(self.pendingServices).totalDuration;
                    }
                    const opts = self.selectedOptions || [];
                    const fromOpts = opts.reduce((sum, o) => sum + (parseInt(o.duration) || 0), 0);
                    return fromOpts || parseInt((self.selectedService || {}).duration || 0);
                })());

                const newStartMs = new Date(clickedStartAt).getTime();
                const newEndMs   = newStartMs + newDuration * 60000;

                let conflictMsg = null;
                for (const cartItem of self.bookingCart) {
                    if (!cartItem.dateTime || !cartItem.dateTime.start_at) continue;

                    // Named staff in this cart item - each service can have its own staff
                    const cartServices = cartItem.services
                        || [{ service: cartItem.service, options: cartItem.options || [], staff: cartItem.staff }];
                    const cartStaffIds = cartServices.map(s => s.staff ? s.staff.id : 0);

                    // Only flag named staff conflicts (id !== 0)
                    const namedOverlapIds = newStaffIds.filter(id => id !== 0 && cartStaffIds.includes(id));
                    if (namedOverlapIds.length === 0) continue;

                    // Compute cart item's time window
                    // Use minimum 1 min so same-start-time is always caught
                    const cartStartMs  = new Date(cartItem.dateTime.start_at).getTime();
                    const cartDuration = Math.max(1, self.getItemDuration(cartItem));
                    const cartEndMs    = cartStartMs + cartDuration * 60000;

                    // Overlap: intervals [newStart, newEnd) and [cartStart, cartEnd) intersect
                    // Use <= for end comparisons so same start time is always a conflict
                    if (newStartMs < cartEndMs && newEndMs > cartStartMs) {
                        const staffName = cartServices
                            .filter(s => s.staff && namedOverlapIds.includes(s.staff.id))
                            .map(s => self.getFirstName(s.staff.name))[0] || 'A staff member';
                        const cartEndTime = new Date(cartEndMs).toLocaleTimeString('en-US',
                            { hour: 'numeric', minute: '2-digit', hour12: true });
                        conflictMsg = `${staffName} is already booked until ${cartEndTime} in Appointment ${self.bookingCart.indexOf(cartItem) + 1}. Please choose a time after ${cartEndTime}.`;
                        break;
                    }
                }
                if (conflictMsg) {
                    self._showToast(conflictMsg, 'error');
                    return;
                }
                // ----------------------------------------------------------------

                const staffMapRaw = $(this).data('staff-map');
                self.selectedDateTime = {
                    date:      selectedDate,
                    time:      $(this).data('time'),
                    display:   $(this).find('.sbs-slot-time').first().text() || $(this).data('time'),
                    start_at:  clickedStartAt,
                    staff_map: staffMapRaw || null   // {s_idx: sq_staff_id} from Square real-time query
                };
                $('#sbs-selected-start-at').val(self.selectedDateTime.start_at);
                self._saveSession();
                self.addToCart();
                self.goToStep(5);
            });

            // Navigation
            $(document).on('click', '.sbs-btn-back', function() {
                const prevStep = self.currentStep - 1;

                // -- Back from step 5 (checkout) ->step 4 (time picker) -------
                // addToCart() already moved pendingServices into bookingCart and
                // cleared everything. We need to pop the last cart item back out
                // so the time picker knows which service/staff to query.
                if (self.currentStep === 5 && self.bookingCart.length > 0) {
                    const last = self.bookingCart.pop();
                    self.pendingServices  = last.services ? [...last.services] : [];
                    self.selectedDateTime = null;
                    self._saveSession();
                    self._showPendingServicesSummary();
                    self.goToStep(4);
                    return;
                }

                // -- Back from step 4 (time picker) ---------------------------
                if (self.currentStep === 4) {
                    if (self.pendingServices.length > 0) {
                        self._backFromTimeToStaff();
                        return;
                    } else if (false && self.bookingCart.length > 0) {
                        // Services in pendingServices - user just wants a different
                        // time. Stay on step 4 and reload slots for current date.
                        self.selectedDateTime = null;
                        self._saveSession();
                        const $slots = $('.sbs-time-slots');
                        $slots.html('<div class="sbs-loading"><span class="spinner"></span> Loading available times...</div>');
                        const $selectedBtn = $('.sbs-day-button.selected:not(.disabled)');
                        if ($selectedBtn.length) {
                            const date = $selectedBtn.data('date');
                            if (date) self.loadAvailableTimes(date);
                        }
                        self._updateMobileCartBar();
                        return;
                    } else if (self.bookingCart.length > 0) {
                        // Cart has items but pendingServices is empty - pop last
                        // cart item back so time picker can reload correctly.
                        const last = self.bookingCart.pop();
                        self.pendingServices  = last.services ? [...last.services] : [];
                        self.selectedDateTime = null;
                        self._saveSession();
                        self._showPendingServicesSummary();
                        const $slots = $('.sbs-time-slots');
                        $slots.html('<div class="sbs-loading"><span class="spinner"></span> Loading available times...</div>');
                        const $selectedBtn = $('.sbs-day-button.selected:not(.disabled)');
                        if ($selectedBtn.length) {
                            const date = $selectedBtn.data('date');
                            if (date) self.loadAvailableTimes(date);
                        }
                        self._updateMobileCartBar();
                        return;
                    } else {
                        // Nothing selected at all - go back to step 3
                        self.goToStep(prevStep);
                        return;
                    }
                }

                self.goToStep(prevStep);
            });
	            $(document).on('click', '.sbs-btn-next, #sbs-sidebar-next-btn', function(e) {
	                e.preventDefault();
	                if ($(this).prop('disabled')) return;
                const isCheckoutStepActive = self.currentStep === 5 || $('.sbs-step-5').hasClass('active');
                const isSidebarConfirm = this.id === 'sbs-sidebar-next-btn'
                    && /confirm booking|book now/i.test($.trim($(this).text()))
                    && isCheckoutStepActive;
                if (isCheckoutStepActive || isSidebarConfirm) {
                    self._requestCustomerFormSubmit();
                } else if (self.currentStep === 4 && self.selectedDateTime) {
                    self.goToStep(5);
                } else if (self.currentStep === 1 && self.pendingServices.length > 0) {
                    if (self._isGroupAppointmentSelected()) self._showGuestReviewScreen();
                    else self.goToStep(4, true);
                } else if (self.currentStep === 2) {
                    const needsMain = !!self._optionHasMainGroup;
                    const hasMain = (self.selectedOptions || []).some(function(o) { return !o.is_addon; });
                    if (needsMain && !hasMain) {
                        self._showToast('Please choose one main option before continuing.', 'error');
                        self._showMainOptionRequired();
                        return;
                    }
                    self.loadAvailableStaff();
                } else if (self.currentStep === 3) {
                    self._commitSelectedStaff();
                }
            });
            
            // "Add Service for [Person]" in step 4 - go back to step 1, tag with person label
            $(document).on('click', '.sbs-add-service-to-appt', function() {
                const personLabel = $(this).data('person-label') || null;
                self._enterServiceSelection(personLabel, true);
            });

            $(document).on('click', '.sbs-review-services-btn', function(e) {
                e.preventDefault();
                if (self.pendingServices && self.pendingServices.length > 0) {
                    self._showGuestReviewScreen();
                    return;
                }
                self._enterServiceSelection(null, true);
            });

            // "Add Guest" button in step 4 banner
            $(document).on('click', '#sbs-banner-add-guest', function(e) {
                e.preventDefault();
                self._addGuestTogether();
            });

            // "Add Another Appointment" in step 5 - fully new appointment, clear pending
            $(document).on('click', '.sbs-add-more-service', function() {
                // Block if single-appointment mode is enabled
                if (yatoonData.multiApptEnabled === false) return;
                const MAX_APPOINTMENTS = 2;
                if (self.bookingCart.length >= MAX_APPOINTMENTS) {
                    // Show limit reached dialog
                    const $overlay = $(`
                        <div style="position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:9999;display:flex;align-items:center;justify-content:center;padding:20px;">
                            <div style="background:#fff;border-radius:16px;padding:28px 24px;max-width:360px;width:100%;text-align:center;box-shadow:0 20px 60px rgba(0,0,0,0.3);">
                                <div style="font-size:36px;margin-bottom:12px;">!</div>
                                <h3 style="margin:0 0 8px;font-size:18px;font-weight:700;color:#111;">Maximum 2 Appointments</h3>
                                <p style="color:#6b7280;font-size:14px;margin:0 0 20px;line-height:1.5;">You can book up to 2 appointments at a time. Please complete your current booking first, then book again for more.</p>
                                <button id="sbs-limit-ok" style="background:#ec4899;color:#fff;border:none;border-radius:8px;padding:12px 32px;font-size:15px;font-weight:600;cursor:pointer;width:100%;">Got it</button>
                            </div>
                        </div>`);
                    $('body').append($overlay);
                    $overlay.on('click', '#sbs-limit-ok', function() { $overlay.remove(); });
                    return;
                }
                self.pendingServices  = [];
                self._enterServiceSelection(null, true);
            });
            
            // FIX: Summary remove (X) buttons in sidebar
            $(document).on('click', '.sbs-summary-remove-btn', function() {
                const action = $(this).data('remove');
                self._handleSummaryRemove(action);
            });
            
            // Cart item remove/edit - with confirmation dialog
            $(document).on('click', '.sbs-cart-remove', function() {
                const index = parseInt($(this).data('index'));
                self._confirmRemove(index, false);
            });
            $(document).on('click', '.sbs-cart-edit', function() {
                const index = parseInt($(this).data('index'));
                self.editCartItem(index);
            });
            $(document).on('click', '#sbs-sidebar-edit-cart', function() {
                if (self.bookingCart.length > 0) self.goToStep(5);
            });

            $(document).on('change', '#sbs-design-reference', function() {
                const file = this.files && this.files[0] ? this.files[0] : null;
                const $preview = $('#sbs-design-preview').empty();
                if (!file) {
                    $preview.hide();
                    return;
                }
                if (!/^image\//.test(file.type || '')) {
                    self._showToast('Please choose an image file for the design reference.', 'error');
                    this.value = '';
                    $preview.hide();
                    return;
                }
                if (file.size > 10 * 1024 * 1024) {
                    self._showToast('Please choose a design image smaller than 10 MB.', 'error');
                    this.value = '';
                    $preview.hide();
                    return;
                }
                const reader = new FileReader();
                reader.onload = function(e) {
                    $preview.html('<img src="' + e.target.result + '" alt=""> <span>' + self._esc(file.name) + '</span>').show();
                };
                reader.readAsDataURL(file);
            });

            // Form submission
            $(document).on('submit', '#sbs-customer-form', function(e) {
                e.preventDefault();
                self.submitBooking();
            });
            $(document).on('click', '.sbs-btn-submit', function(e) {
                e.preventDefault();
                self._requestCustomerFormSubmit();
            });

            $(document).on('click', '.sbs-btn-new-booking', function() {
                self.reset();
            });

            $(document).on('click', '.sbs-success-book-again', function() {
                const bookUrl = (window.yatoonData && yatoonData.bookNewUrl) ? yatoonData.bookNewUrl : '';
                if (bookUrl) {
                    window.location.href = bookUrl;
                    return;
                }
                self.reset();
                self._scrollToFormTop();
            });
        }
        
        // FIX: handle summary sidebar remove (X) buttons
        _handleSummaryRemove(action) {
            if (action === 'service') {
                this.selectedService = null;
                this.selectedCategory = null;
                this.selectedOptions = [];
                this.selectedStaff = null;
                this.selectedDateTime = null;
                this.goToStep(1);
            } else if (action === 'options') {
                this.selectedOptions = [];
                $('#sbs-selected-options').val('');
                this.updateSummary();
            } else if (action === 'staff') {
                this.selectedStaff = null;
                this.selectedDateTime = null;
                this.goToStep(3);
                this.loadAvailableStaff();
            } else if (action === 'datetime') {
                this.selectedDateTime = null;
                $('#sbs-selected-start-at').val('');
                this.goToStep(4);
            }
        }

        _prepareForServiceSelection() {
            this._closeDrawer();
            this._hardUnlockBookingSurface();
            $('.sbs-service-item, .sbs-service-card').removeClass('selected');
            this.selectedService  = null;
            this.selectedCategory = null;
            this.selectedOptions  = [];
            this.selectedStaff    = null;
            this.selectedDateTime = null;
            $('#sbs-selected-service-id, #sbs-selected-staff-id, #sbs-selected-start-at, #sbs-selected-options').val('');
        }

        _enterServiceSelection(personLabel, scrollToTop = true) {
            this._nextGuestLabel = personLabel || null;
            this._abortLookupRequests();
            this._hardUnlockBookingSurface();
            $('.sbs-service-item, .sbs-service-card').removeClass('selected');
            this.selectedService  = null;
            this.selectedCategory = null;
            this.selectedOptions  = [];
            this.selectedStaff    = null;
            this.selectedDateTime = null;
            $('#sbs-selected-service-id, #sbs-selected-staff-id, #sbs-selected-start-at, #sbs-selected-options').val('');
            this.currentStep = 1;
            this._saveSession();
            this.goToStep(1, scrollToTop);
            if (this._nextGuestLabel && this._nextGuestLabel !== 'Me') {
                this._showGuestPickBanner(this._nextGuestLabel);
            } else if (this.pendingServices && this.pendingServices.length > 0) {
                // Already have services (e.g. adding more for Me) - show Me banner
                this._showMeBanner(personLabel || 'Me');
            } else {
                $('#sbs-guest-pick-banner').remove();
            }
            // Hide the appointment-type selector once user is actively adding services
            // (i.e. has pending services already, or is adding for a specific person)
            const hasPending = this.pendingServices && this.pendingServices.length > 0;
            const isAddingForPerson = !!this._nextGuestLabel;
            if (hasPending || isAddingForPerson) {
                $('#sbs-appt-type-wrap').hide();
            } else {
                $('#sbs-appt-type-wrap').show();
            }
            this.updateSummary();
            this._updateMobileCartBar();
            setTimeout(() => this._hardUnlockBookingSurface(), 0);
            setTimeout(() => this._hardUnlockBookingSurface(), 180);
        }

        _showMeBanner(label) {
            const self = this;
            $('#sbs-guest-pick-banner').remove();
            const displayLabel = label === 'Me' ? 'yourself' : label;
            const $banner = $(`
                <div id="sbs-guest-pick-banner" class="sbs-guest-pick-banner" style="
                    background:#eff6ff;border:1.5px solid #3b82f6;border-radius:12px;
                    padding:14px 16px;margin:0 0 18px;box-sizing:border-box;min-height:68px;overflow:visible;
                    display:flex;align-items:center;justify-content:space-between;line-height:1.3;">
                    <span style="font-size:14px;font-weight:600;color:#1d4ed8;">
                        &#128100; Adding service for <strong>${displayLabel}</strong>
                    </span>
                    <button id="sbs-guest-banner-cancel" style="
                        background:none;border:none;color:#6b7280;font-size:13px;cursor:pointer;">
                        Cancel
                    </button>
                </div>
            `);
            // Prepend immediately - guard against double-fire
            if (!$('#sbs-guest-pick-banner').length) {
                $('.sbs-step-1').prepend($banner);
            }
            $('#sbs-guest-banner-cancel').on('click', function() {
                $('#sbs-guest-pick-banner').remove();
                self._nextGuestLabel = null;
                self._saveSession();
            });
        }

        _hardUnlockBookingSurface() {
            $('#sbs-add-more-overlay, #sbs-dup-overlay, #sbs-confirm-overlay').remove();
            $('#sbs-drawer-backdrop').removeClass('open').css('display', '');
            $('#sbs-mobile-drawer').removeClass('open').css('display', 'none');
            $('#sbs-cat-more-dropdown').removeClass('open').css({ top: '', left: '', width: '' });
            const $moreButton = $('#sbs-cat-more-btn');
            $moreButton.text(($moreButton.attr('data-selected-label') || 'More') + ' ⌄').attr('aria-expanded', 'false');
            $('.sbs-form-step').css('pointer-events', '');
            $('body, html, .sbs-booking-container, .sbs-form-step, .sbs-services-list').css('pointer-events', '');
        }

        _backFromTimeToStaff() {
            this.selectedDateTime = null;
            $('#sbs-selected-start-at').val('');

            if (this.pendingServices.length > 0) {
                const current = this.pendingServices.pop();
                this.selectedService = current.service ? { ...current.service } : null;
                this.selectedOptions = current.options ? [...current.options] : [];
                this.selectedStaff = current.staff ? { ...current.staff } : null;
                this._nextGuestLabel = current.guestLabel && current.guestLabel !== 'Me' ? current.guestLabel : null;
                if (this.selectedService) $('#sbs-selected-service-id').val(this.selectedService.id);
                if (this.selectedStaff) $('#sbs-selected-staff-id').val(this.selectedStaff.id);
                $('#sbs-selected-options').val(JSON.stringify(this.selectedOptions.map(o => o.id)));
            }

            if (!this.selectedService) return false;

            this._saveSession();
            this.updateSummary();
            this.goToStep(3);
            this.loadAvailableStaff();
            setTimeout(() => this._scrollToStaffChoices(), 120);
            return true;
        }

        _selectStaffCard($card) {
            if (!$card || !$card.length) return;
            $('.sbs-staff-card').removeClass('selected');
            $card.addClass('selected');
            this.selectedStaff = {
                id:   $card.data('staff-id'),
                name: $card.data('staff-name')
            };
            $('#sbs-selected-staff-id').val(this.selectedStaff.id);
            this.updateSummary();
            this._updateMobileCartBar();
            this._saveSession();
        }

        _commitSelectedStaff() {
            if (!this.selectedService) {
                if (this.pendingServices.length > 0) {
                    this.goToStep(4, true);
                    return;
                }
                this._showToast('Please choose a service first.', 'error');
                this.goToStep(1, true);
                return;
            }

            if (!this.selectedStaff) {
                const $selected = $('.sbs-staff-card.selected').first();
                if ($selected.length) {
                    this._selectStaffCard($selected);
                } else {
                    this._showToast('Please choose a staff member to continue.', 'error');
                    return;
                }
            }

            const eligible = (this.selectedService && this.selectedService._eligibleStaffIds) || [];
            const label = this._nextGuestLabel || 'Me';
            this.pendingServices.push({
                service:          { ...this.selectedService },
                options:          [...this.selectedOptions],
                staff:            { ...this.selectedStaff },
                eligibleStaffIds: eligible,
                guestLabel:       label
            });

            this._nextGuestLabel = null;
            if (this.selectedService && !this._staffCache[this.selectedService.id]) {
                this._staffCache[this.selectedService.id] = [];
            }

            this.selectedService  = null;
            this.selectedCategory = null;
            this.selectedOptions  = [];
            this.selectedStaff    = null;
            this.selectedDateTime = null;
            $('#sbs-selected-service-id, #sbs-selected-staff-id, #sbs-selected-start-at, #sbs-selected-options').val('');

            this._saveSession();
            if (this._isGroupAppointmentSelected()) {
                this._showGuestReviewScreen();
            } else {
                this._showPendingServicesSummary();
                this.goToStep(4, true);
            }
        }

        // -- Fresha-style "Add guests and services" review screen -----------------
        // Shown after every service selection. Lets users see who has what service,
        // add more services per person, add guests, or continue to date/time.
        _showGuestReviewScreen() {
            const self = this;
            if (!this.pendingServices || !this.pendingServices.length) {
                this.goToStep(4, true);
                return;
            }

            const maxGuests = this._maxGuestsTogether();

            // Group services by person
            const personGroups = {};
            const personOrder  = [];
            this.pendingServices.forEach(function(s) {
                const lbl = s.guestLabel || 'Me';
                if (!personGroups[lbl]) { personGroups[lbl] = []; personOrder.push(lbl); }
                personGroups[lbl].push(s);
            });

            const totalPrice    = this.pendingServices.reduce((sum, s) => {
                const p = s.options && s.options.length > 0
                    ? s.options.reduce((t, o) => t + (parseFloat(o.price) || 0), 0)
                    : (parseFloat(s.service && s.service.price) || 0);
                return sum + p;
            }, 0);
            const totalDuration = this._durationByGuest(this.pendingServices).totalDuration;
            const totalServices = this.pendingServices.length;

            // Avatar colors for people
            const avatarColors = [
                { bg: '#ec4899', text: '#fff' },   // Me = pink (brand color)
                { bg: '#dbeafe', text: '#1d4ed8' }, // Guest 1 = blue
                { bg: '#dcfce7', text: '#16a34a' }, // Guest 2 = green
                { bg: '#fef9c3', text: '#92400e' }, // Guest 3 = yellow
                { bg: '#ede9fe', text: '#6d28d9' }, // Guest 4 = purple
            ];

            // Build person rows
            const personRows = personOrder.map(function(lbl, personIdx) {
                const services  = personGroups[lbl];
                const isMe      = lbl === 'Me';
                const displayLbl = self._displayGuestLabel(lbl);
                const safeDisplayLbl = self._esc(displayLbl);
                const safeDataLbl = self._esc(lbl);
                const initial   = isMe ? (yatoonData.clientInitial || 'Y') : displayLbl.charAt(0).toUpperCase();
                const safeInitial = self._esc(initial);
                const addServiceFor = lbl === 'Me' ? 'ME' : displayLbl.toUpperCase();
                const color     = avatarColors[personIdx % avatarColors.length];
                const guestIdx  = isMe ? 0 : (personOrder.indexOf(lbl));

                const svcLines = services.map(function(s, svcIdx) {
                    const svcName = self._svcDisplayLabelHtml(s.service, s.options);
                    const staffName = s.staff ? s.staff.name : '';
                    const dur = self._serviceDuration(s);
                    const idx = self.pendingServices.indexOf(s);
                    return `<div class="sbs-gr-svc-row" style="display:flex;align-items:center;justify-content:space-between;gap:8px;padding:6px 0;${svcIdx < services.length - 1 ? 'border-bottom:1px solid #f3f4f6;' : ''}">
                        <div style="flex:1;min-width:0;">
                            <div style="font-size:14px;font-weight:500;color:#1a1a1a;line-height:1.3;">${svcName}</div>
                            <div style="font-size:12px;color:#9ca3af;margin-top:2px;">${dur} min${staffName ? ' &bull; ' + self.getFirstName(staffName) : ''}</div>
                        </div>
                        <button type="button" class="sbs-gr-remove-svc" data-idx="${idx}"
                            style="background:none;border:none;color:#d1d5db;font-size:18px;cursor:pointer;padding:4px;line-height:1;flex-shrink:0;" title="Remove">&times;</button>
                    </div>`;
                }).join('');

                return `<div class="sbs-gr-person-row" style="background:#fff;border:1px solid #f3f4f6;border-radius:14px;padding:14px 16px;display:flex;align-items:flex-start;gap:14px;">
                    <div style="flex-shrink:0;width:42px;height:42px;border-radius:50%;background:${color.bg};display:flex;align-items:center;justify-content:center;font-weight:700;font-size:16px;color:${color.text};">
                        ${safeInitial}
                    </div>
                    <div style="flex:1;min-width:0;">
                        <div style="font-weight:700;font-size:15px;color:#1a1a1a;margin-bottom:8px;">${lbl === 'Me' ? self._esc(yatoonData.clientName || 'Me') : safeDisplayLbl}</div>
                        <div class="sbs-gr-svc-list">${svcLines}</div>
                        <button type="button" class="sbs-gr-add-svc sbs-add-service-to-appt" data-person-label="${safeDataLbl}"
                            style="margin-top:10px;background:#fafafa;border:1.5px dashed #a7b0bf;border-radius:8px;padding:7px 14px;font-size:13px;color:#374151;font-weight:700;cursor:pointer;width:100%;text-align:left;">
                            + ADD SERVICE FOR ${self._esc(addServiceFor)}
                        </button>
                    </div>
                    ${personOrder.length > 1 ? `<button type="button" class="sbs-gr-remove-person" data-person="${safeDataLbl}"
                        style="background:none;border:none;color:#9ca3af;font-size:20px;cursor:pointer;padding:0 0 0 4px;line-height:1;flex-shrink:0;" title="Remove ${safeDisplayLbl}">&times;</button>` : ''}
                </div>`;
            }).join('');

            // Can we add more guests?
            const guestCount = personOrder.filter(l => l !== 'Me').length;
            const canAddGuest = maxGuests > 0 && guestCount < maxGuests;
            const nextGuestLabel = this._displayGuestLabel(this._getNextGuestLabel(personOrder));
            const addGuestBtn = canAddGuest ? `
                <button type="button" id="sbs-gr-add-guest"
                    style="display:flex;align-items:center;gap:10px;background:#fff;border:1.5px dashed #d1d5db;border-radius:14px;padding:14px 16px;cursor:pointer;width:100%;text-align:left;">
                    <div style="width:42px;height:42px;border-radius:50%;border:2px dashed #d1d5db;display:flex;align-items:center;justify-content:center;font-size:20px;color:#9ca3af;flex-shrink:0;">+</div>
                    <span style="font-size:14px;font-weight:600;color:#374151;">ADD ${nextGuestLabel.toUpperCase()}</span>
                </button>` : '';

            const overlay = $(`<div id="sbs-gr-overlay" style="
                position:fixed;inset:0;background:#f9fafb;z-index:9990;
                display:flex;flex-direction:column;overflow:hidden;">
                <!-- Header -->
                <div style="background:#fff;border-bottom:1px solid #f3f4f6;padding:16px 20px;display:flex;align-items:center;gap:12px;flex-shrink:0;">
                    <button type="button" id="sbs-gr-back" style="background:none;border:none;font-size:20px;cursor:pointer;color:#111;padding:0 4px;line-height:1;">&#8592;</button>
                    <div style="flex:1;">
                        <h2 style="margin:0;font-size:20px;font-weight:700;color:#1a1a1a;line-height:1.2;">Add guests and services</h2>
                        <p style="margin:4px 0 0;font-size:13px;color:#9ca3af;">Add up to ${maxGuests > 0 ? maxGuests : 4} guests; everyone starts together</p>
                    </div>
                    <button type="button" id="sbs-gr-close" style="background:none;border:none;font-size:22px;cursor:pointer;color:#9ca3af;padding:0 4px;line-height:1;">&times;</button>
                </div>

                <!-- Person list - scrollable -->
                <div style="flex:1;overflow-y:auto;padding:16px 20px;display:flex;flex-direction:column;gap:12px;">
                    <div id="sbs-gr-person-list" style="display:flex;flex-direction:column;gap:12px;">
                        ${personRows}
                    </div>
                    <div id="sbs-gr-add-guest-wrap">
                        ${addGuestBtn}
                    </div>
                </div>

                <!-- Footer bar -->
                <div style="background:#fff;border-top:1px solid #f3f4f6;padding:14px 20px;display:flex;align-items:center;justify-content:space-between;flex-shrink:0;">
                    <div>
                        <div style="font-size:18px;font-weight:700;color:#1a1a1a;">$${totalPrice.toFixed(0)}</div>
                        <div style="font-size:12px;color:#9ca3af;">${totalServices} service${totalServices !== 1 ? 's' : ''} &bull; ${totalDuration} min</div>
                    </div>
                    <button type="button" id="sbs-gr-continue"
                        style="background:#43a047;color:#fff;border:none;border-radius:50px;padding:14px 32px;font-size:15px;font-weight:700;cursor:pointer;white-space:nowrap;">
                        Continue
                    </button>
                </div>
            </div>`);

            $('body').append(overlay);

            // Back
            overlay.on('click', '#sbs-gr-back', function() {
                overlay.remove();
                self.goToStep(3, true);
            });
            // Close (X) -> go to step 4, keep what we have
            overlay.on('click', '#sbs-gr-close', function() {
                overlay.remove();
                self._showPendingServicesSummary();
                self.goToStep(4, true);
            });

            // Continue
            overlay.on('click', '#sbs-gr-continue', function() {
                overlay.remove();
                self._showPendingServicesSummary();
                self.goToStep(4, true);
            });

            // Add guest
            overlay.on('click', '#sbs-gr-add-guest', function() {
                overlay.remove();
                self._addGuestTogether();
            });

            // Add service for person (delegated from document since it re-enters step 1)
            $(document).off('click.grAddSvc').on('click.grAddSvc', '.sbs-gr-add-svc', function() {
                const lbl = $(this).data('person-label') || null;
                overlay.remove();
                self._enterServiceSelection(lbl, true);
            });

            // Remove individual service row
            overlay.on('click', '.sbs-gr-remove-svc', function() {
                const idx = parseInt($(this).data('idx'));
                if (!isNaN(idx)) {
                    self._removePendingService(idx);
                    if (self.pendingServices.length === 0) {
                        overlay.remove();
                        self.goToStep(1, true);
                    } else {
                        // Re-render
                        overlay.remove();
                        self._showGuestReviewScreen();
                    }
                }
            });

            // Remove entire person
            overlay.on('click', '.sbs-gr-remove-person', function() {
                const lbl = $(this).data('person') || '';
                if (!lbl) return;
                // Remove all services for this person
                self.pendingServices = self.pendingServices.filter(s => (s.guestLabel || 'Me') !== lbl);
                self._saveSession();
                if (self.pendingServices.length === 0) {
                    overlay.remove();
                    self.goToStep(1, true);
                } else {
                    overlay.remove();
                    self._showGuestReviewScreen();
                }
            });
        }

        goToStep(step, scrollToTop = false) {
            this.currentStep = step;
            this._saveSession(); // always persist current step
            this._syncDesignUploadCard();

            // Push a history entry so browser Back stays inside the booking widget
            if (step > 1) {
                history.pushState({ sbsStep: step }, '', window.location.href);
            } else {
                // On step 1, replace so there's always one baseline entry
                history.replaceState({ sbsStep: 1 }, '', window.location.href);
            }
            
            $('#sbs-main-title').show();
            this._updateMobileFlowHeader();
            $('.sbs-booking-summary').toggleClass('sbs-checkout-card', step === 5);
            if (step !== 5) {
                $('#sbs-checkout-card-heading').remove();
            }
            
            if (step === 1) {
                $('#sbs-progress-bar').hide();
                // Show appointment-type selector only when no services are pending
                // (i.e. fresh start). Hide it once the user is actively adding services.
                const hasPendingOnStep1 = this.pendingServices && this.pendingServices.length > 0;
                const isAddingForPersonOnStep1 = !!this._nextGuestLabel;
                if (hasPendingOnStep1 || isAddingForPersonOnStep1) {
                    $('#sbs-appt-type-wrap').hide();
                } else {
                    // Only show if max-guests > 0 (the outer ready() block already hid it if 0)
                    const maxG = parseInt($('#sbs-appt-type-wrap').data('max-guests') || 0, 10);
                    if (maxG > 0) $('#sbs-appt-type-wrap').show();
                }
            } else {
                $('#sbs-progress-bar').show();
                $('.sbs-prog-step').each(function() {
                    const s = parseInt($(this).data('go'));
                    $(this).removeClass('active completed clickable');
                    if (s === step) $(this).addClass('active');
                    if (s < step)  $(this).addClass('completed clickable');
                    // Never set disabled - we handle navigation in the click handler
                    $(this).prop('disabled', false);
                });
                $('.sbs-prog-line').each(function(i) {
                    $(this).toggleClass('done', i < step - 1);
                });
            }
            
            if (step === 2) {
                $('#sbs-sidebar-next-btn').show();
                $('#sbs-cart-next-btn').show();
            } else {
                $('#sbs-sidebar-next-btn').hide();
                $('#sbs-cart-next-btn').hide();
            }

            // Cart bar visibility always driven by cart contents, not step number
            this._updateMobileCartBar();
            
            $('.sbs-form-step').removeClass('active');
            $(`.sbs-step-${step}`).addClass('active');

            // Update step bubbles: completed = green check, active = primary color, future = grey
            $('.sbs-booking-steps .sbs-step').each(function() {
                const s = parseInt($(this).data('step'));
                $(this).removeClass('active completed');
                if (s === step) $(this).addClass('active');
                if (s < step)  $(this).addClass('completed');
            });
            // Animate progress bar between bubbles (0% at step1, 100% at step5)
            const totalSteps = 5;
            const pct = step <= 1 ? 0 : Math.round(((step - 1) / (totalSteps - 1)) * 100);
            $('#sbs-step-progress').css('width', pct + '%');
            
            // Only scroll when explicitly requested (e.g. progress bar click), NOT on service/option clicks
            if (step > 1 && scrollToTop) {
                this._scrollToFormTop();
            }
            
            if (step === 4) {
                this.updateSummary();
                const self = this;
                // -- FIX: Always clear stale time slots when entering step 4 --
                // This prevents clients from booking a time that was loaded for
                // a different staff/service combination.
                $('.sbs-time-slots').html(
                    '<div class="sbs-loading"><span class="spinner"></span> Loading available times...</div>'
                );
                setTimeout(function() {
                    // Re-select the currently highlighted date (or default to today/first available)
                    let $selectedBtn = $('.sbs-day-button.selected:not(.disabled)');
                    if (!$selectedBtn.length) $selectedBtn = $('.sbs-day-button.today:not(.disabled)');
                    if (!$selectedBtn.length) $selectedBtn = $('.sbs-day-button:not(.disabled)').first();
                    if ($selectedBtn.length) {
                        $('.sbs-day-button').removeClass('selected');
                        $selectedBtn.addClass('selected');
                        const date = $selectedBtn.data('date');
                        if (date) self.loadAvailableTimes(date);
                    }
                }, 150);

                window._sbsGroupIntent = false;
            }
            
            if (step === 1 || (step >= 3 && step !== 4)) {
                // Restore static sidebar rows (hidden on step 5), hide step-5-only total
                $('#sbs-summary-row-service, #sbs-summary-row-staff, #sbs-summary-row-datetime, #sbs-summary-row-duration, #sbs-summary-row-total').show();
                $('#sbs-step5-total').hide();
                this.updateSummary();
            }
            if (step === 5) {
                this.updateSummary();
                this.updateSummaryForCheckout();
                this._renderGuestContactFields();
                this._updateLegalBoxes();
                // -- Always reset the submit button when entering step 5 ------
                // Prevents the button staying disabled after Back ->re-enter
                $('.sbs-btn-submit').prop('disabled', false);
                this._applyConfirmBtn();
            }
        }

        _selectedCheckoutServices() {
            return (this.bookingCart || []).flatMap(function(item) {
                if (item.services && item.services.length) {
                    return item.services.map(function(s) { return s.service || {}; });
                }
                return [item.service || {}];
            });
        }

        _needsPolicyBox() {
            const serviceNeedsPolicy = this._selectedCheckoutServices().some(function(s) {
                return !!s.requirePolicy;
            });
            return !!(yatoonData.policyEnabled || serviceNeedsPolicy);
        }

        _needsPolicyAck() {
            const serviceNeedsPolicy = this._selectedCheckoutServices().some(function(s) {
                return !!s.requirePolicy;
            });
            return !!((yatoonData.policyEnabled && yatoonData.policyRequireAck) || serviceNeedsPolicy);
        }

        _needsConsent() {
            const serviceNeedsConsent = this._selectedCheckoutServices().some(function(s) {
                return !!s.requireConsent;
            });
            return !!(yatoonData.consentEnabled || serviceNeedsConsent);
        }

        _updateLegalBoxes() {
            const needsPolicyBox = this._needsPolicyBox();
            const needsPolicyAck = this._needsPolicyAck();
            const needsConsent = this._needsConsent();

            $('.sbs-policy-box').toggle(needsPolicyBox);
            $('#sbs-policy-acknowledged').prop('required', needsPolicyAck);
            if (!needsPolicyBox) {
                $('#sbs-policy-acknowledged').prop('checked', false);
            }

            $('.sbs-consent-box').toggle(needsConsent);
            $('#sbs-consent-accepted').prop('required', needsConsent);
            $('#sbs-consent-signature').prop('required', needsConsent);
            if (!needsConsent) {
                $('#sbs-consent-accepted').prop('checked', false);
                $('#sbs-consent-signature').val('');
            }
        }

        _getPreloadedServiceOptions(serviceId) {
            const map = (typeof yatoonData !== 'undefined' && yatoonData.serviceOptions) ? yatoonData.serviceOptions : null;
            if (!map) return null;
            return map[String(serviceId)] || map[serviceId] || null;
        }

        _renderServiceOptionsPayload(payload) {
            const self = this;
            const container = $('.sbs-options-container');
            self._parkDesignUploadCard();
            if (!payload || !Array.isArray(payload.options) || payload.options.length === 0) {
                return false;
            }
                        // Group options by group_name. null/'' = "Main" group (single-select).
                        const groups = {};
                        payload.options.forEach(function(option) {
                            const g = option.group_name || '';
                            if (!groups[g]) groups[g] = [];
                            groups[g].push(option);
                        });
                        self._optionHasMainGroup = Object.keys(groups).some(function(groupKey) {
                            const key = (groupKey || '').trim().toLowerCase();
                            return !(key === 'add-on' || key === 'add on' || /^add[\s\-_]?on$/i.test(key));
                        });
                        const orderedGroupKeys = Object.keys(groups).sort(function(a, b) {
                            const aAddon = /^add[\s\-_]?on$/i.test((a || '').trim()) || (a || '').trim().toLowerCase() === 'add on';
                            const bAddon = /^add[\s\-_]?on$/i.test((b || '').trim()) || (b || '').trim().toLowerCase() === 'add on';
                            if (aAddon === bAddon) return 0;
                            return aAddon ? 1 : -1;
                        });
                        const mainOptionsForPrice = orderedGroupKeys
                            .filter(function(groupKey) {
                                const key = (groupKey || '').trim().toLowerCase();
                                return !(key === 'add-on' || key === 'add on' || /^add[\s\-_]?on$/i.test(key));
                            })
                            .flatMap(function(groupKey) { return groups[groupKey] || []; });

                        const serviceName = self._esc((self.selectedService && self.selectedService.name) || 'Service');
                        const serviceDesc = self._esc((self.selectedService && self.selectedService.description) || '');
                        const serviceImg  = self._esc((self.selectedService && self.selectedService.image_url) || '');
                        const labels = (typeof yatoonData !== 'undefined' && yatoonData.bookingLabels) ? yatoonData.bookingLabels : {};
                        const labelText = function(key, fallback) {
                            return self._esc(labels[key] || fallback);
                        };
                        const basePrice   = parseFloat((self.selectedService && self.selectedService.price) || payload.base_price || 0);
                        const baseDur     = parseInt((self.selectedService && self.selectedService.duration) || 0);
                        let priceText = basePrice > 0 ? `$${basePrice.toFixed(2)}` : 'Price varies';
                        let durationText = baseDur ? `${baseDur} min+` : '';
                        if (mainOptionsForPrice.length > 0) {
                            const mainPrices = mainOptionsForPrice
                                .map(function(option) { return parseFloat(option.absolute_price); })
                                .filter(function(price) { return !isNaN(price) && price > 0; });
                            const mainDurations = mainOptionsForPrice
                                .map(function(option) { return parseInt(option.duration_minutes); })
                                .filter(function(duration) { return !isNaN(duration) && duration > 0; });
                            if (mainPrices.length > 0) {
                                const minPrice = Math.min.apply(null, mainPrices);
                                const maxPrice = Math.max.apply(null, mainPrices);
                                priceText = minPrice === maxPrice ? `$${minPrice.toFixed(2)}` : `from $${minPrice.toFixed(2)}`;
                            }
                            if (mainDurations.length > 0) {
                                const minDuration = Math.min.apply(null, mainDurations);
                                const maxDuration = Math.max.apply(null, mainDurations);
                                durationText = minDuration === maxDuration ? `${minDuration} min` : `${minDuration}-${maxDuration} min`;
                            }
                        }
                        let html = `
                            <div class="sbs-service-detail-panel">
                                ${serviceImg ? `<div class="sbs-service-detail-image"><img src="${serviceImg}" alt="${serviceName}" loading="lazy" style="display:block;width:100%;height:auto;"></div>` : ''}
                                <div class="sbs-service-detail-copy">
                                    <h3 class="sbs-service-detail-title">${serviceName}</h3>
                                    <div class="sbs-service-detail-meta">
                                        <span>${durationText}</span>
                                        <span>${priceText}</span>
                                    </div>
                                    ${serviceDesc ? `<p class="sbs-service-detail-desc">${serviceDesc}</p>` : ''}
                                </div>
                            </div>
                            <div class="sbs-related-options-head">
                                <div>
                                    <div class="sbs-related-eyebrow">${labelText('relatedEyebrow', 'Related choices')}</div>
                                    <h4>${labelText('relatedTitle', 'Choose what fits your visit')}</h4>
                                </div>
                                <span>${labelText('relatedHint', 'Tap to select')}</span>
                            </div>
                        `;

                        orderedGroupKeys.forEach(function(groupKey) {
                            const isAddon = /^add[\s\-_]?on$/i.test(groupKey.trim()) || groupKey.trim().toLowerCase() === 'add on';
                            const groupLabel = isAddon ? labelText('addonGroupLabel', 'Recommended add-ons') : labelText('mainGroupLabel', 'Choose one option');
                            const groupHelp  = isAddon ? labelText('addonGroupHelp', 'Optional extras you can add to this appointment.') : labelText('mainGroupHelp', 'Pick the main style or variation for this service.');
                            let addonRank = 0;

                            html += `
                                <section class="sbs-option-section ${isAddon ? 'sbs-option-section-addon' : 'sbs-option-section-main'}">
                                    <div class="sbs-option-section-head">
                                        <h4>${groupLabel}</h4>
                                        <p>${groupHelp}</p>
                                    </div>
                                    <div class="sbs-option-group" data-group="${self._esc(groupKey)}" data-multi="${isAddon ? '1' : '0'}">
                            `;

                            groups[groupKey].forEach(function(option) {
                                if (isAddon) addonRank++;
                                const absolutePrice = parseFloat(option.absolute_price);
                                const priceVaries = absolutePrice <= 0 || /price varies/i.test(String(option.name || ''));
                                const priceDisplay  = priceVaries ? 'Price varies' : `$${absolutePrice.toFixed(2)}`;

                                let displayName = option.name;
                                if (option.name.toLowerCase() === 'regular' || option.name === self.selectedService.name) {
                                    displayName = self.selectedService.name;
                                }
                                const safeName = self._esc(displayName);
                                const safeDesc = self._esc(option.description || '');
                                const badgeChoice = String(option.badge_label || '').toLowerCase();
                                let badge = '';
                                if (isAddon) {
                                    if (badgeChoice === 'recommended') {
                                        badge = labelText('recommendedBadge', 'Recommended');
                                    } else if (badgeChoice === 'popular') {
                                        badge = labelText('popularBadge', 'Popular');
                                    } else if (badgeChoice === 'addon') {
                                        badge = labelText('addonBadge', 'Add-on');
                                    } else if (badgeChoice !== 'none') {
                                        badge = addonRank === 1 ? labelText('recommendedBadge', 'Recommended') : (addonRank === 2 ? labelText('popularBadge', 'Popular') : labelText('addonBadge', 'Add-on'));
                                    }
                                }

                                const inputType = isAddon ? 'checkbox' : 'checkbox'; // keep checkbox for CSS; single-select enforced via JS
                                html += `
                                    <div class="sbs-option-item ${isAddon ? 'sbs-option-addon' : 'sbs-option-main'}"
                                         data-price="${option.price_modifier}"
                                         data-absolute-price="${absolutePrice}"
                                         data-duration="${option.duration_minutes}"
                                         data-group="${groupKey}"
                                         data-linked-service-id="${option.linked_service_id || ''}"
                                         data-allow-design-upload="${parseInt(option.allow_design_upload || 0)}"
                                         data-square-id="${option.square_id || ''}">
                                        <input type="checkbox" class="sbs-option-checkbox" value="${option.id}" id="option-${option.id}">
                                        <label for="option-${option.id}" class="sbs-option-info">
                                            ${badge ? `<span class="sbs-option-badge">${badge}</span>` : ''}
                                            <div class="sbs-option-name">${safeName}</div>
                                            ${safeDesc ? `<div class="sbs-option-description">${safeDesc}</div>` : ''}
                                            <div class="sbs-option-duration">${option.duration_minutes} min</div>
                                        </label>
                                        <span class="sbs-option-price ${priceVaries ? 'is-variable' : ''}">${priceDisplay}</span>
                                        <span class="sbs-option-check" aria-hidden="true">&#10003;</span>
                                    </div>
                                `;
                            });

                            html += '</div></section>';
                        });

                        container.html(html);
                        self._ensureOptionActionSummary();

                        // -- EDIT MODE: restore previously selected options --------------
                        // When the user clicks EDIT on a pending service, _editPendingService
                        // pre-populates self.selectedOptions with the existing choices.
                        // We must reflect that state in the UI checkboxes - otherwise the
                        // user sees an empty form, re-selects everything, and ends up with
                        // duplicates (old options + new options stacked on top).
                        if (self.selectedOptions && self.selectedOptions.length > 0) {
                            const existingIds = self.selectedOptions.map(o => o.id);
                            existingIds.forEach(function(optId) {
                                const $cb = container.find('.sbs-option-checkbox[value="' + optId + '"]');
                                if ($cb.length) {
                                    $cb.prop('checked', true);
                                    $cb.closest('.sbs-option-item').addClass('selected');
                                }
                            });
                        }
                        self._updateOptionActionSummary();
                        self._syncDesignUploadCard();

                        // setTimeout 100ms ensures iOS Safari doesn't override our scroll
                        setTimeout(function() { self._scrollToFormTop(); }, 100);
            return true;
        }

        loadServiceOptions() {
            const self = this;
            const container = $('.sbs-options-container');
            if (!self.selectedService || !self.selectedService.id) return;
            const requestServiceId = self.selectedService.id;
            const requestSeq = ++self._optionsSeq;
            if (self._optionsRequest && self._optionsRequest.readyState !== 4) {
                try { self._optionsRequest.abort(); } catch(e) {}
            }
            self._parkDesignUploadCard();
            container.html('<div class="sbs-loading"><span class="spinner"></span> Loading options...</div>');
            self.goToStep(2, true);

            const preloaded = self._getPreloadedServiceOptions(requestServiceId);
            if (preloaded) {
                self._optionsRequest = null;
                if (self._renderServiceOptionsPayload(preloaded)) {
                    return;
                }
                container.html('');
                self.selectedOptions = [];
                self.goToStep(3, true);
                self.loadAvailableStaff();
                setTimeout(function() { self._scrollToFormTop(); }, 100);
                return;
            }
            
            self._optionsRequest = $.ajax({
                url: yatoonData.ajax_url,
                type: 'POST',
                timeout: 30000,
                data: {
                    action:     'yatoon_get_service_options',
                    nonce:      yatoonData.nonce,
                    service_id: requestServiceId
                },
                success: function(response) {
                    if (requestSeq !== self._optionsSeq || !self.selectedService || String(self.selectedService.id) !== String(requestServiceId)) return;
                    if (response && response.success && response.data && Array.isArray(response.data.options) && response.data.options.length > 0) {
                        self._renderServiceOptionsPayload(response.data);
                    } else {
                        container.html('');
                        self.selectedOptions = [];
                        self.goToStep(3, true);
                        self.loadAvailableStaff();
                        setTimeout(function() { self._scrollToFormTop(); }, 100);
                    }
                },
                error: function(xhr, status) {
                    if (requestSeq !== self._optionsSeq || status === 'abort' || (xhr && xhr.status === 0)) return;
                    container.html(
                        '<div class="sbs-error">Failed to load options. Please tap Back and choose the service again.</div>'
                    );
                    self._ensureOptionActionSummary();
                },
                complete: function() {
                    if (requestSeq === self._optionsSeq && self._optionsRequest && self._optionsRequest.readyState === 4) {
                        self._optionsRequest = null;
                    }
                }
            });
        }

        _bindSummaryEditButtons() {
            const self = this;
            $(document).on('click', '.sbs-summary-edit-btn', function() {
                const step = parseInt($(this).data('goto'));
                if (!step) return;
                if (step === 3) {
                    self.goToStep(3, true);
                    self.loadAvailableStaff();
                } else {
                    self.goToStep(step, true);
                }
            });
        }

        _getPreloadedServiceStaff(serviceId) {
            const map = (typeof yatoonData !== 'undefined' && yatoonData.serviceStaff) ? yatoonData.serviceStaff : null;
            if (!map) return null;
            return map[String(serviceId)] || map[serviceId] || null;
        }

        _renderStaffList(staffList) {
            const self = this;
            const container = $('.sbs-staff-grid');
            if (!Array.isArray(staffList) || staffList.length === 0) {
                container.html('<p>No staff available. Please try again later.</p>');
                return false;
            }

            const eligibleIds = staffList.filter(s => parseInt(s.id) !== 0).map(s => Number(s.id));
            if (self.selectedService) self.selectedService._eligibleStaffIds = eligibleIds;
            if (self.selectedService) self._staffCache[self.selectedService.id] = staffList;

            // -- Time-aware staff conflict detection ------------------------------
            // If other services are already queued up (this guest's earlier services in
            // the same visit, or another guest's service running in parallel at the same
            // start time), hide any named staff member who is still occupied during this
            // service's real-time window. They automatically reappear once whatever they
            // were doing ends before this service's window starts.
            const myLabel = self._nextGuestLabel || 'Me';
            const myStart = self._nextOffsetForGuest(myLabel);
            const myDur   = self._serviceDuration({ service: self.selectedService, options: self.selectedOptions });
            const myEnd   = myStart + myDur;
            const busyIds = self._computeBusyStaffIds(myStart, myEnd, null);

            const visibleStaffList = busyIds.size
                ? staffList.filter(s => parseInt(s.id) === 0 || !busyIds.has(String(s.id)))
                : staffList;
            const namedVisible = visibleStaffList.filter(s => parseInt(s.id) !== 0);

            if (self.selectedService && self.selectedService.allowStaffSelect === false) {
                self.selectedStaff = { id: 0, name: 'Any staff' };
                self._commitSelectedStaff();
                return true;
            }

            // Every named staff member is busy during this window - don't silently fall
            // back to "Any staff" (that could double-book someone); let the customer know.
            if (busyIds.size > 0 && namedVisible.length === 0) {
                container.html('<p>No staff are free for this service at that time. Please choose a different service order, or pick another time.</p>');
                return false;
            }

            if (visibleStaffList.length === 1) {
                const staff = visibleStaffList[0];
                self.selectedStaff = { id: staff.id, name: staff.name, image_url: staff.image_url };
                self._commitSelectedStaff();
                return true;
            }

            let html = '';
            visibleStaffList.forEach(function(staff) {
                const displayName = self.getFirstName(staff.name);
                const isAny = parseInt(staff.id) === 0 || String(staff.name || '').toLowerCase().indexOf('any') !== -1;
                html += `
                    <div class="sbs-staff-card ${isAny ? 'sbs-staff-card-any' : ''}" data-staff-id="${staff.id}" data-staff-name="${self._esc(staff.name)}">
                        ${staff.image_url ? `<div class="sbs-staff-image"><img src="${self._esc(staff.image_url)}" alt="${self._esc(displayName)}"></div>` : `<div class="sbs-staff-image sbs-staff-initial">${isAny ? 'Any' : self._esc(displayName.charAt(0))}</div>`}
                        <div class="sbs-staff-name">${self._esc(displayName)}</div>
                        ${isAny ? `<div class="sbs-staff-bio">Fastest available for an opening</div>` : (staff.bio ? `<div class="sbs-staff-bio">${self._esc(staff.bio)}</div>` : '') }
                    </div>
                `;
            });

            container.html(html);
            const selectedStaffId = self.selectedStaff ? String(self.selectedStaff.id) : '';
            const $selectedCard = selectedStaffId
                ? container.find('.sbs-staff-card').filter(function() {
                    return String($(this).data('staff-id')) === selectedStaffId;
                }).first()
                : $();
            const $anyCard = container.find('.sbs-staff-card-any').first();
            if ($selectedCard.length) {
                self._selectStaffCard($selectedCard);
            } else if (self._instantStaffId) {
                const $instantStaff = container.find('.sbs-staff-card').filter(function() {
                    return String($(this).data('staff-id')) === String(self._instantStaffId);
                }).first();
                if ($instantStaff.length) self._selectStaffCard($instantStaff);
                else if ($anyCard.length) self._selectStaffCard($anyCard);
            } else if ($anyCard.length) {
                self._selectStaffCard($anyCard);
            }
            self._instantStaffId = null;
            setTimeout(() => self._scrollToStaffChoices(), 50);
            return true;
        }

        loadAvailableStaff() {
            const self = this;
            const container = $('.sbs-staff-grid');
            if (!self.selectedService || !self.selectedService.id) return;
            const requestServiceId = self.selectedService.id;
            const requestSeq = ++self._staffSeq;
            if (self._staffRequest && self._staffRequest.readyState !== 4) {
                try { self._staffRequest.abort(); } catch(e) {}
            }
            container.html('<div class="sbs-loading"><span class="spinner"></span> Loading staff...</div>');
            self.goToStep(3);

            const preloaded = self._getPreloadedServiceStaff(requestServiceId);
            if (preloaded && Array.isArray(preloaded.staff)) {
                self._staffRequest = null;
                self._renderStaffList(preloaded.staff);
                return;
            }
            
            self._staffRequest = $.ajax({
                url: yatoonData.ajax_url,
                type: 'POST',
                timeout: 30000,
                data: {
                    action:     'yatoon_get_available_staff',
                    nonce:      yatoonData.nonce,
                    service_id: requestServiceId
                },
                success: function(response) {
                    if (requestSeq !== self._staffSeq || !self.selectedService || String(self.selectedService.id) !== String(requestServiceId)) return;
                    if (response && response.success && response.data && Array.isArray(response.data.staff)) {
                        self._renderStaffList(response.data.staff);
                    } else {
                        container.html('<p>No staff available. Please try again later.</p>');
                    }
                },
                error: function(xhr, status) {
                    if (requestSeq !== self._staffSeq || status === 'abort' || (xhr && xhr.status === 0)) return;
                    container.html('<div class="sbs-error">Could not load staff. Please tap Back and choose the service again.</div>');
                },
                complete: function() {
                    if (requestSeq === self._staffSeq && self._staffRequest && self._staffRequest.readyState === 4) {
                        self._staffRequest = null;
                    }
                }
            });
        }

        loadAvailableTimes(date) {
            const self = this;
            const container = $('.sbs-time-slots');
            $('#sbs-next-openings').hide().empty();
            self._updateDateAvailabilityMeter(date, null);

            // -- FIX: Multi-service support ------------------------------------
            // When pendingServices has items (user added multiple services/staff),
            // we must query using ALL staff IDs so the backend finds slots where
            // every staff member is free at the same time.
            let activeSvc, activeStaff, activeOptions, allStaffIds, allOptionIds;
            // main_option_ids: ONLY the primary (non-addon) option per service - used for Square availability query
            // addon_option_ids: Add-on options - used for duration/price display only, NOT for availability
            let mainOptionIds = [], addonOptionIds = [];

            // Per-staff option IDs: [[optId,...], [optId,...]] - same index order as allStaffIds
            let allOptionIdsPerStaff = [];

            if (self.pendingServices.length > 0) {
                activeSvc     = self.pendingServices[0].service;
                activeStaff   = self.pendingServices[0].staff;
                activeOptions = self.pendingServices[0].options;
                // NOTE: Do NOT use .filter(Boolean) here - that would strip id=0 ("Any staff"),
                // which must be passed to the backend so it knows to apply Case B expansion logic.
                allStaffIds   = self.pendingServices.map(s => s.staff ? s.staff.id : 0);
                allOptionIds  = self.pendingServices.flatMap(s => s.options.map(o => o.id));
                // For multi-service, split main vs addon per service.
                // IMPORTANT: if filtering out addons leaves an empty list, fall back to ALL options -                 // some services mark their primary option (e.g. "Full Set") as is_addon by mistake,
                // and sending empty option_ids causes the backend to pick the wrong Square variation.
                addonOptionIds = self.pendingServices.flatMap(s => s.options.filter(o => o.is_addon).map(o => o.id));
                mainOptionIds  = self.pendingServices.flatMap(s => {
                    const main = s.options.filter(o => !o.is_addon).map(o => o.id);
                    return main.length > 0 ? main : s.options.map(o => o.id); // fallback: all options
                });
                allOptionIdsPerStaff = self.pendingServices.map(s => {
                    const main = s.options.filter(o => !o.is_addon).map(o => o.id);
                    return main.length > 0 ? main : s.options.map(o => o.id); // fallback: all options
                });
            } else {
                activeSvc     = self.selectedService;
                activeStaff   = self.selectedStaff;
                activeOptions = self.selectedOptions;
                allStaffIds   = activeStaff ? [activeStaff.id] : [];
                allOptionIds  = activeOptions.map(o => o.id);
                // KEY FIX: Only main (non-addon) options determine which Square variation to query.
                // Add-ons are extra time/price but NOT separate Square service variations.
                // Using addon variation IDs in search_availability returns wrong duration results.
                // HOWEVER: if filtering out addons leaves nothing, fall back to all options -                 // the primary option may be incorrectly tagged as addon in some services.
                mainOptionIds  = activeOptions.filter(o => !o.is_addon).map(o => o.id);
                if (mainOptionIds.length === 0) mainOptionIds = activeOptions.map(o => o.id);
                addonOptionIds = activeOptions.filter(o => o.is_addon).map(o => o.id);
                allOptionIdsPerStaff = [mainOptionIds];
            }

            if (!activeSvc) {
                container.html('<p style="color:#c00;text-align:center;padding:20px;">Please select a service first.</p>');
                return;
            }
            const requestSeq = ++self._timesSeq;
            if (self._timesRequest && self._timesRequest.readyState !== 4) {
                try { self._timesRequest.abort(); } catch(e) {}
            }
            container.html('<div class="sbs-loading"><span class="spinner"></span> Loading available times...</div>');
            self._timesRequest = $.ajax({
                url: yatoonData.ajax_url,
                type: 'POST',
                timeout: 15000,
                data: {
                    action:          'yatoon_get_available_times',
                    nonce:           yatoonData.nonce,
                    service_id:      activeSvc.id,
                    staff_id:        activeStaff ? activeStaff.id : 0,
                    // Multi-staff intersection: all staff IDs + per-staff MAIN option IDs (no addons)
                    all_staff_ids:    allStaffIds.length > 1 ? JSON.stringify(allStaffIds) : '',
                    all_option_ids:   allStaffIds.length > 1 ? JSON.stringify(allOptionIdsPerStaff) : '',
                    // Per-staff service IDs - used by backend as fallback when option variation lookup fails
	                    all_service_ids:  allStaffIds.length > 1 ? JSON.stringify(self.pendingServices.map(s => s.service ? s.service.id : 0)) : '',
	                    all_guest_labels: allStaffIds.length > 1 ? JSON.stringify(self.pendingServices.map(s => s.guestLabel || 'Me')) : '',
	                    all_durations:    allStaffIds.length > 1 ? JSON.stringify(self.pendingServices.map(s => self._serviceDuration(s))) : '',
                    // KEY FIX: Pass confirmed cart appointments' staff busy windows to backend.
                    // This lets the backend exclude already-occupied named staff when assigning
                    // "Any staff" slots - prevents booking unavailable staff when they're already booked.
                    // Format: [{staff_id, start_at, end_minutes}, ...]
                    cart_busy_staff: (function() {
                        const windows = [];
                        self.bookingCart.forEach(function(ci) {
                            if (!ci.dateTime || !ci.dateTime.start_at) return;
                            const ciSvcs = ci.services
                                || [{ staff: ci.staff, options: ci.options || [], service: ci.service }];
                            const offsets = self._serviceOffsetsByGuest(ciSvcs);
                            ciSvcs.forEach(function(sv) {
                                const opts    = sv.options || [];
                                const optsDur = opts.reduce(function(d, o) { return d + (parseInt(o.duration) || 0); }, 0);
                                const baseDur = parseInt(sv.service && sv.service.duration) || 0;
                                const dur     = Math.max(1, optsDur > 0 ? optsDur : baseDur);
                                // Compute actual start_at for this service segment
                                const segStartMs = new Date(ci.dateTime.start_at).getTime() + offsets.shift() * 60000;
                                const segStartAt = new Date(segStartMs).toISOString();
                                if (!sv.staff || sv.staff.id === 0) {
                                    // BUGFIX: Any staff cart items - mark all eligible staff as busy.
                                    // Without this, PHP has no way to know these staff are occupied,
                                    // allowing the same time slot to be booked again.
                                    const eligibleIds = (sv.eligibleStaffIds)
                                        || (sv.service && sv.service._eligibleStaffIds)
                                        || [];
                                    eligibleIds.forEach(function(eid) {
                                        windows.push({ staff_id: Number(eid), start_at: segStartAt, duration: dur });
                                    });
                                    return;
                                }
                                windows.push({ staff_id: Number(sv.staff.id), start_at: segStartAt, duration: dur });
                            });
                        });
                        return windows.length > 0 ? JSON.stringify(windows) : '';
                    })(),
                    date:            date,
                    // KEY: only send MAIN option IDs - addons don't have their own Square variation duration
                    option_ids:      mainOptionIds.length ? JSON.stringify(mainOptionIds) : '',
                    // Add-on option IDs - sent separately so backend can do multi-segment Square availability query
                    addon_option_ids: addonOptionIds.length ? JSON.stringify(addonOptionIds) : '',
                    // Add-on extra minutes - backend uses this to filter out slots that don't have
                    // enough buffer after the main service ends (e.g. Fill-in 40min + Remove 20min = 60min needed)
                    addon_duration:  addonOptionIds.length
                        ? activeOptions.filter(o => o.is_addon).reduce((s, o) => s + (parseInt(o.duration) || 0), 0)
                        : 0,
                    // Total duration across ALL pending services (for latest-end-time cutoff on server)
                    total_duration: (function() {
                        if (self.pendingServices.length > 0) {
                            return self._durationByGuest(self.pendingServices).totalDuration;
                        }
                        // Single service
                        const mainDur = activeOptions.filter(o => !o.is_addon).reduce((s, o) => s + (parseInt(o.duration) || 0), 0);
                        const addDur  = activeOptions.filter(o =>  o.is_addon).reduce((s, o) => s + (parseInt(o.duration) || 0), 0);
                        return (mainDur || parseInt(activeSvc.duration) || 0) + addDur;
                    })()
                },
                success: function(response) {
                    if (requestSeq !== self._timesSeq) return;
                    const responseTimes = response.success && response.data.times ? response.data.times : [];
                    const visibleTimes = self._filterPastSlotsForDate(responseTimes, date);
                    if (visibleTimes.length > 0) {
                        self._updateDateAvailabilityMeter(date, visibleTimes.length);
                        // Availability conflict checks
                        // SMART BUSY-WINDOW CALCULATION
                        //
                        // For each service in the NEW appointment (pendingServices):
                        //   - Named staff (id != 0): their time window is [slotStart + offset, + dur].
                        //     If that window overlaps a busy entry in the cart ->show busy warning.
                        //   - Any staff (id == 0): check if AT LEAST ONE eligible staff member is
                        //     free during [slotStart + offset, + dur]. If yes ->slot is available
                        //     (no busy warning). Only show busy if ALL eligible staff are occupied.
                        //
                        // "Busy" in the cart means a named staff member has a confirmed service
                        // in Appointment 1 that overlaps the candidate window.
                        // Availability conflict checks

                        // Step 1: Build a map of every named staff's busy intervals from the cart.
                        // staffBusyMap[staffId] = [{startMs, endMs}, ...]
                        // BUGFIX: Also include "Any staff" cart items - when a slot is booked with
                        // Any staff, ALL eligible staff for that service are considered busy during
                        // that window. Otherwise a second appointment could claim the same slot
                        // because the Any-staff item was skipped when building busyMap.
                        const staffBusyMap = {};
                        self.bookingCart.forEach(function(ci) {
                            if (!ci.dateTime || !ci.dateTime.start_at) return;
                            const ciStartMs = new Date(ci.dateTime.start_at).getTime();
                            const ciSvcs = ci.services
                                || [{ staff: ci.staff, options: ci.options || [], service: ci.service }];
                            const offsets = self._serviceOffsetsByGuest(ciSvcs);
                            ciSvcs.forEach(function(sv) {
                                const opts    = sv.options || [];
                                const optsDur = opts.reduce(function(d,o){ return d+(parseInt(o.duration)||0); }, 0);
                                const baseDur = parseInt(sv.service && sv.service.duration) || 0;
                                const dur     = Math.max(1, optsDur > 0 ? optsDur : baseDur);
                                const sMs     = ciStartMs + offsets.shift() * 60000;
                                const eMs     = sMs + dur * 60000;
                                if (!sv.staff || sv.staff.id === 0) {
                                    // Any staff: mark ALL eligible staff for this service as busy.
                                    // Use eligibleStaffIds stored on the cart item's service, or
                                    // fall back to the service's _eligibleStaffIds if available.
                                    const eligibleIds = (sv.eligibleStaffIds)
                                        || (sv.service && sv.service._eligibleStaffIds)
                                        || [];
                                    eligibleIds.forEach(function(eid) {
                                        const sid = Number(eid);
                                        if (!staffBusyMap[sid]) staffBusyMap[sid] = [];
                                        staffBusyMap[sid].push({ startMs: sMs, endMs: eMs });
                                    });
                                    return;
                                }
                                const sid = Number(sv.staff.id);
                                if (!staffBusyMap[sid]) staffBusyMap[sid] = [];
                                staffBusyMap[sid].push({ startMs: sMs, endMs: eMs });
                            });
                        });

                        // Helper: does a candidate window [wStart, wEnd) overlap any busy interval for staffId?
                        function _isBusy(staffId, wStartMs, wEndMs) {
                            const intervals = staffBusyMap[Number(staffId)] || [];
                            return intervals.some(function(iv) {
                                return wStartMs < iv.endMs && wEndMs > iv.startMs;
                            });
                        }

                        // Step 2: Build per-service window descriptors for the NEW appointment.
                        // newSvcWindows[i] = { staffId, offsetMs, durationMs, eligibleStaffIds, label }
                        const newSvcWindows = [];
                        if (self.pendingServices.length > 0) {
                            const offsets = self._serviceOffsetsByGuest(self.pendingServices);
                            self.pendingServices.forEach(function(ps) {
                                const opts    = ps.options || [];
                                const optsDur = opts.reduce(function(d,o){ return d+(parseInt(o.duration)||0); }, 0);
                                const baseDur = parseInt(ps.service && ps.service.duration) || 0;
                                const dur     = Math.max(1, optsDur > 0 ? optsDur : baseDur);
                                const staffId = (ps.staff && ps.staff.id) ? Number(ps.staff.id) : 0;
                                newSvcWindows.push({
                                    staffId:          staffId,
                                    offsetMs:         offsets.shift() * 60000,
                                    durationMs:       dur * 60000,
                                    eligibleStaffIds: ps.eligibleStaffIds || [],
                                    label:            staffId !== 0 ? self.getFirstName(ps.staff.name) : 'Any Staff Available'
                                });
                            });
                        } else if (self.selectedService) {
                            // Single-service (no pendingServices yet)
                            const opts    = self.selectedOptions || [];
                            const optsDur = opts.reduce(function(d,o){ return d+(parseInt(o.duration)||0); }, 0);
                            const baseDur = parseInt((self.selectedService||{}).duration) || 0;
                            const dur     = Math.max(1, optsDur > 0 ? optsDur : baseDur);
                            const staffId = (self.selectedStaff && self.selectedStaff.id) ? Number(self.selectedStaff.id) : 0;
                            newSvcWindows.push({
                                staffId:          staffId,
                                offsetMs:         0,
                                durationMs:       dur * 60000,
                                eligibleStaffIds: (self.selectedService && self.selectedService._eligibleStaffIds) || [],
                                label:            staffId !== 0 ? self.getFirstName(self.selectedStaff.name) : 'Any Staff Available'
                            });
                        }

                        // Legacy: namedStaffInNew kept for the cart-item time-overlap guard below
                        const namedStaffInNew = newSvcWindows.filter(w => w.staffId !== 0).map(w => w.staffId);
                        // busyWindows kept for compatibility with click handler (data-busy-staff-ids)
                        const busyWindows = []; // populated in slot loop below via _isBusy()

                        const newDurForRender = Math.max(1, (function() {
                            if (self.pendingServices.length > 0) {
                                return self._durationByGuest(self.pendingServices).totalDuration;
                            }
                            const opts = self.selectedOptions || [];
                            return opts.reduce(function(s,o){return s+(parseInt(o.duration)||0);}, 0)
                                || parseInt((self.selectedService||{}).duration||0);
                        })());

                        let html = '<div class="sbs-time-groups">';
                        let currentBucket = '';
                        visibleTimes.forEach(function(time) {
                            const slotStartMs = new Date(time.start_at).getTime();
                            const bucket = self._timeBucket(time.display, time.start_at);
                            if (bucket !== currentBucket) {
                                if (currentBucket) html += '</div>';
                                currentBucket = bucket;
                                html += `<div class="sbs-time-group"><div class="sbs-time-group-title">${bucket}</div>`;
                            }

                            // -- Evaluate each service window in the new appointment ----------
                            // For named staff: conflict if their window overlaps any cart busy interval.
                            // For Any staff:   conflict only if ALL eligible staff are busy ->slot blocked.
                            //                  If some are free ->slot is fine, system assigns a free one.
                            const warningLabels    = []; // visible "staff busy" hints (named staff only)
                            const conflictStaffIds = []; // staff IDs to pass to click handler
                            let   hardBlock        = false; // true = ALL eligible staff busy ->truly unavailable

                            newSvcWindows.forEach(function(sw) {
                                const wStartMs = slotStartMs + sw.offsetMs;
                                const wEndMs   = wStartMs + sw.durationMs;

                                if (sw.staffId !== 0) {
                                    // -- Named staff ----------------------------------------
                                    if (_isBusy(sw.staffId, wStartMs, wEndMs)) {
                                        const label = sw.label + ' busy (Appt 1)';
                                        warningLabels.push(label);
                                        conflictStaffIds.push(sw.staffId);
                                    }
                                } else {
                                    // -- Any staff ------------------------------------------
                                    // Check each eligible staff member.
                                    // If at least one is free ->no problem (slot stays open).
                                    // If none are free ->hard block (show as unavailable).
                                    let eligible = sw.eligibleStaffIds;

                                    // BUGFIX: If eligibleStaffIds is empty (not yet populated),
                                    // fall back to every staff member tracked in staffBusyMap.
                                    // This prevents skipping the conflict check entirely when
                                    // only one staff is working on that day.
                                    if (eligible.length === 0) {
                                        eligible = Object.keys(staffBusyMap).map(Number);
                                    }
                                    if (eligible.length === 0) return; // truly no data ->skip

                                    const freeStaff  = eligible.filter(sid => !_isBusy(sid, wStartMs, wEndMs));
                                    const busyStaff  = eligible.filter(sid =>  _isBusy(sid, wStartMs, wEndMs));

                                    if (freeStaff.length === 0) {
                                        // All eligible staff busy ->genuinely no one available
                                        hardBlock = true;
                                        warningLabels.push('All staff busy (Appt 1)');
                                    }
                                    // Some busy but others free ->no warning needed; system will pick a free one
                                }
                            });

                            if (hardBlock) {
                                html += `<div class="sbs-time-slot sbs-time-slot-unavail" data-time="${time.time}" data-start-at="${time.start_at}" title="No staff available at this time" aria-disabled="true">
                                    <span class="sbs-slot-time">${time.display}</span>
                                    <span class="sbs-slot-tag unavail">Unavailable</span>
                                </div>`;
                            } else if (warningLabels.length > 0) {
                                const combinedLabel = warningLabels.join(', ');
                                const staffIdsAttr  = conflictStaffIds.join(',');
                                html += `<div class="sbs-time-slot sbs-time-slot-busy" data-time="${time.time}" data-start-at="${time.start_at}" data-busy="${combinedLabel}" data-busy-staff-ids="${staffIdsAttr}" title="${combinedLabel} - tap to reassign">
                                    <span class="sbs-slot-time">${time.display}</span>
                                    <span class="sbs-slot-tag busy">Reassign</span>
                                </div>`;
                            } else {
                                const staffMapAttr = time.staff_map ? ` data-staff-map='${JSON.stringify(time.staff_map)}'` : '';
                                html += `<div class="sbs-time-slot" data-time="${time.time}" data-start-at="${time.start_at}"${staffMapAttr}>
                                    <span class="sbs-slot-time">${time.display}</span>
                                    <span class="sbs-slot-check">Yes</span>
                                </div>`;
                            }
                        });
                        if (currentBucket) html += '</div>';
                        html += '</div>';
                        container.html(html);
                        self._renderNextOpenings(visibleTimes, date);
                        $('#sbs-sync-status').html(visibleTimes.length + ' times available from Square');
                    } else {
                        self._updateDateAvailabilityMeter(date, 0);
                        // Server said no availability (could be early fast-return)
                        const msg = responseTimes.length > 0
                            ? 'No remaining available times today.'
                            : ((response.data && response.data.message) ? response.data.message : null);
                        $('#sbs-next-openings').hide().empty();
                        container.html(self._noTimesHtml(msg, date));
                        $('#sbs-sync-status').html('');
                    }
                },
                error: function(xhr, status) {
                    if (requestSeq !== self._timesSeq || status === 'abort' || (xhr && xhr.status === 0)) return;
                    // Check if it's a timeout or a real error with a clean message
                    let isNoAvail = false;
                    let errMsg = null;
                    if (status === 'timeout') {
                        errMsg = 'Network timeout while checking availability.';
                        isNoAvail = true;
                    } else {
                        try {
                            const r = JSON.parse(xhr.responseText);
                            if (r && r.data && r.data.message) {
                                errMsg = r.data.message;
                                isNoAvail = !!(r.data && r.data.no_availability);
                            }
                        } catch(e) {}
                    }
                    container.html(self._noTimesHtml(errMsg, date));
                    self._updateDateAvailabilityMeter(date, 0);
                    $('#sbs-next-openings').hide().empty();
                    $('#sbs-sync-status').html('');
                },
                complete: function() {
                    if (requestSeq === self._timesSeq && self._timesRequest && self._timesRequest.readyState === 4) {
                        self._timesRequest = null;
                    }
                }
            });
        }

        _renderNextOpenings(times, date) {
            const $wrap = $('#sbs-next-openings');
            if (!$wrap.length || !Array.isArray(times) || times.length === 0) return;

            const openTimes = times.filter(function(time) {
                return time && time.display && time.start_at;
            }).slice(0, 3);
            if (openTimes.length === 0) {
                $wrap.hide().empty();
                return;
            }

            const dateParts = String(date || '').split('-');
            let dateLabel = 'Selected date';
            if (dateParts.length === 3) {
                const dateObj = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]);
                dateLabel = dateObj.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' });
            }

            const chips = openTimes.map(function(time, index) {
                const label = index === 0 ? 'Earliest' : 'Open';
                return `<button type="button" class="sbs-next-opening-chip" data-start-at="${time.start_at}" data-time="${time.time || ''}">
                    <span>${label}</span><strong>${time.display}</strong>
                </button>`;
            }).join('');

            $wrap.html(`
                <div class="sbs-next-openings-head">
                    <span>Recommended openings</span>
                    <strong>${dateLabel}</strong>
                </div>
                <div class="sbs-next-openings-list">${chips}</div>
            `).show();
        }

        _availabilityLevel(count) {
            if (count === null || typeof count === 'undefined') return 'checking';
            if (count <= 0) return 'none';
            if (count < 4) return 'low';
            if (count < 8) return 'medium';
            return 'high';
        }

        _updateDateAvailabilityMeter(date, count) {
            if (!date) return;
            this._dateAvailability[date] = count;
            const level = this._availabilityLevel(count);
            const label = count === null || typeof count === 'undefined'
                ? 'Checking availability'
                : (count > 0 ? `${count} times available` : 'No available times');
            const $buttons = $(`.sbs-day-button[data-date="${date}"]`);
            $buttons
                .removeClass('sbs-availability-checking sbs-availability-none sbs-availability-low sbs-availability-medium sbs-availability-high')
                .addClass(`sbs-availability-${level}`)
                .attr('data-availability-count', count === null || typeof count === 'undefined' ? '' : count)
                .attr('aria-label', function(_, oldLabel) {
                    const base = (oldLabel || $(this).text() || 'Date').replace(/,\s*(Checking availability|No available times|\d+ times available)$/i, '').trim();
                    return `${base}, ${label}`;
                });
        }

        _availabilitySelectionKey() {
            const services = this.pendingServices && this.pendingServices.length
                ? this.pendingServices
                : (this.selectedService ? [{ service: this.selectedService, options: this.selectedOptions || [], staff: this.selectedStaff }] : []);
            return services.map((item) => {
                const svc = item.service ? item.service.id : 0;
                const staff = item.staff ? item.staff.id : 0;
                const opts = (item.options || []).map(o => o.id).sort().join(',');
                return [svc, staff, opts].join(':');
            }).join('|');
        }

        _isTodayDate(dateStr) {
            if (!dateStr) return false;
            const now = new Date();
            const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
            return String(dateStr) === today;
        }

        _filterPastSlotsForDate(times, dateStr) {
            if (!Array.isArray(times) || !times.length) return [];
            if (!this._isTodayDate(dateStr)) return times;
            const nowMs = Date.now();
            return times.filter(function(time) {
                if (!time || !time.start_at) return true;
                const slotMs = new Date(time.start_at).getTime();
                return isNaN(slotMs) || slotMs > nowMs;
            });
        }

        _dateLabel(dateStr) {
            if (!dateStr) return 'this date';
            const parts = String(dateStr).split('-').map(Number);
            if (parts.length !== 3 || parts.some(isNaN)) return 'this date';
            const d = new Date(parts[0], parts[1] - 1, parts[2]);
            if (this._isTodayDate(dateStr)) return 'today';
            return d.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' });
        }

        // -- Find next available date and jump straight to it ------------------
        // $btn: the jQuery "Go to next available" button (for loading state).
        _findAndGoNextAvailable($btn) {
            const self = this;

            // Build request params - pendingServices (multi) takes priority
            let serviceId = 0, staffId = 0, optionIds = '';
            let allStaffIds = '', allOptionIdsPerStaff = '';

            if (self.pendingServices && self.pendingServices.length > 0) {
                const pending        = self.pendingServices;
                serviceId            = pending[0].service ? pending[0].service.id : 0;
                staffId              = pending[0].staff   ? pending[0].staff.id   : 0;
                // KEY: only main (non-addon) options for Square availability query
                const mainOpts0      = pending[0].options.filter(o => !o.is_addon);
                optionIds            = mainOpts0.length ? JSON.stringify(mainOpts0.map(o => o.id)) : '';
                allStaffIds          = JSON.stringify(pending.map(s => s.staff ? s.staff.id : 0));
                allOptionIdsPerStaff = JSON.stringify(pending.map(s => s.options.filter(o => !o.is_addon).map(o => o.id)));
                var allGuestLabels   = JSON.stringify(pending.map(s => s.guestLabel || 'Me'));
            } else {
                serviceId = self.selectedService ? self.selectedService.id : 0;
                staffId   = self.selectedStaff   ? self.selectedStaff.id   : 0;
                // KEY: only main (non-addon) options for Square availability query
                const mainOpts = self.selectedOptions ? self.selectedOptions.filter(o => !o.is_addon) : [];
                optionIds = mainOpts.length ? JSON.stringify(mainOpts.map(o => o.id)) : '';
                var allGuestLabels = '';
            }

            if (!serviceId) return;

            // "From" date = currently selected day, or today
            let fromDate = $('.sbs-day-button.selected').data('date') || $('.sbs-day-button.active').data('date');
            if (!fromDate) {
                const t = new Date();
                fromDate = `${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,'0')}-${String(t.getDate()).padStart(2,'0')}`;
            }

            // Show spinner on button
            if ($btn && $btn.length) {
                $btn.prop('disabled', true).html('Searching...');
            }

            $.ajax({
                url:     yatoonData.ajax_url,
                type:    'POST',
                timeout: 45000,
                data: {
                    action:         'yatoon_get_next_available',
                    nonce:          yatoonData.nonce,
                    service_id:     serviceId,
                    staff_id:       staffId,
                    from_date:      fromDate,
                    option_ids:     optionIds,
                    all_staff_ids:  allStaffIds,
                    all_option_ids: allOptionIdsPerStaff,
                    all_guest_labels: allGuestLabels,
                    addon_duration: (function() {
                        const opts = self.pendingServices && self.pendingServices.length > 0
                            ? self.pendingServices[0].options
                            : (self.selectedOptions || []);
                        return opts.filter(o => o.is_addon).reduce((s, o) => s + (parseInt(o.duration) || 0), 0);
                    })(),
                    total_duration: (function() {
                        if (self.pendingServices && self.pendingServices.length > 0) {
                            return self._durationByGuest(self.pendingServices).totalDuration;
                        }
                        const opts = self.selectedOptions || [];
                        return opts.reduce((s,o)=>s+(parseInt(o.duration)||0), 0) || parseInt((self.selectedService||{}).duration||0);
                    })()
                },
                success: function(response) {
                    if ($btn && $btn.length) $btn.prop('disabled', false).html('Find next opening');

                    if (response.success && response.data.date) {
                        const nextDate = response.data.date; // e.g. "2026-03-05"
                        self._showToast('Next opening found. Choose a time below.', 'success');

                        // Directly jump the week view to the week containing nextDate,
                        // then select that day and load its times - no repeated clicking.
                        const parts  = nextDate.split('-');
                        const target = new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]));
                        target.setHours(0, 0, 0, 0);

                        // Set _weekStart directly to the target date so today's/next date is visible first.
                        const targetWeekSunday = new Date(target);

                        if (self._weekStart && self._renderWeekView) {
                            // Direct jump: set week, re-render, then select the day
                            self._weekStart.setTime(targetWeekSunday.getTime());
                            self._renderWeekView();
                            // After render, select the day button and load times
                            const $dayBtn = $(`.sbs-day-button[data-date="${nextDate}"]`);
                            if ($dayBtn.length) {
                                $('.sbs-day-button').removeClass('selected');
                                $dayBtn.addClass('selected');
                                self.loadAvailableTimes(nextDate);
                            } else {
                                // Fallback: load times directly
                                self.loadAvailableTimes(nextDate);
                            }
                        } else {
                            // Fallback: click-based navigation
                            let safetyLimit = 26;
                            const _navigate = function() {
                                const $dayBtn = $(`.sbs-day-button[data-date="${nextDate}"]`);
                                if ($dayBtn.length) {
                                    $('.sbs-day-button').removeClass('selected');
                                    $dayBtn.addClass('selected');
                                    self.loadAvailableTimes(nextDate);
                                } else if (safetyLimit-- > 0) {
                                    const $last = $('.sbs-day-button').last();
                                    ($last.data('date') < nextDate ? $('.sbs-week-next') : $('.sbs-week-prev')).trigger('click');
                                    setTimeout(_navigate, 300);
                                } else {
                                    self.loadAvailableTimes(nextDate);
                                }
                            };
                            setTimeout(_navigate, 100);
                        }
                    } else {
                        // Nothing in the next 60 days
                        if ($btn && $btn.length) $btn.prop('disabled', false).text('No availability found');
                        self._showToast('No upcoming openings found. Join the waitlist and the salon can notify you when a spot opens.', 'info');
                    }
                },
                error: function() {
                    if ($btn && $btn.length) $btn.prop('disabled', false).html('Find next opening');
                }
            });
        }

        _noTimesHtml(msg, dateStr) {
            const rawMsg = String(msg || '').trim();
            const lower = rawMsg.toLowerCase();
            const isNetwork = lower.includes('network') || lower.includes('timeout') || lower.includes('request failed');
            const isConnection = lower.includes('square') || lower.includes('vagaro') || lower.includes('connection') || lower.includes('api');
            const isClosed = lower.includes('closed') || lower.includes('passed') || lower.includes('business hours');
            const staffName = this.selectedStaff && parseInt(this.selectedStaff.id) !== 0 ? this.getFirstName(this.selectedStaff.name) : null;
            const serviceName = this.selectedService && this.selectedService.name ? this.selectedService.name : 'this service';
            const pendingCount = this.pendingServices ? this.pendingServices.length : 0;
            const serviceLabel = pendingCount > 1 ? `your ${pendingCount}-service appointment` : serviceName;
            const dateLabel = this._dateLabel(dateStr);

            let label = 'No times found';
            let headline = rawMsg || `No available times for ${dateLabel}.`;
            let subtext = yatoonData.availabilityHelp || 'Try another date, choose another staff member, edit your services, or text us and we can help.';

            if (isNetwork) {
                label = 'Network error';
                headline = 'We could not load times just now.';
                subtext = 'Please try again. If it keeps happening, text us and we can help you book.';
            } else if (isConnection) {
                label = 'Calendar connection issue';
                headline = 'We could not check the live booking calendar.';
                subtext = 'Square/Vagaro may be temporarily unavailable. Try again or text us for help.';
            } else if (staffName) {
                label = 'Staff unavailable';
                headline = `${staffName} has no availability ${dateLabel}.`;
            } else if (!isClosed) {
                label = 'No availability';
                headline = `No available times for ${serviceLabel} ${dateLabel}.`;
            }

            const chooseStaffBtn = staffName
                ? `<button type="button" class="sbs-try-other-staff">Choose another staff</button>`
                : `<button type="button" class="sbs-availability-choose-staff">Choose staff</button>`;
            const waitlistBtn = yatoonData.waitlistEnabled
                ? `<button type="button" class="sbs-join-waitlist">Join Waitlist</button>`
                : '';
            const textUsBtn = yatoonData.textUsEnabled
                ? `<button type="button" class="sbs-text-us-inline">Text us</button>`
                : '';

            return `
                <div class="sbs-empty-state sbs-availability-empty">
                    <div class="sbs-empty-illustration">
                        <svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
                            <rect x="8" y="12" width="48" height="44" rx="6" fill="#f3f4f6" stroke="#e5e7eb" stroke-width="2"/>
                            <rect x="8" y="12" width="48" height="12" rx="6" fill="#e5e7eb"/>
                            <rect x="18" y="32" width="8" height="8" rx="2" fill="#d1d5db"/>
                            <rect x="30" y="32" width="8" height="8" rx="2" fill="#d1d5db"/>
                            <rect x="42" y="32" width="8" height="8" rx="2" fill="#d1d5db"/>
                            <rect x="18" y="44" width="8" height="8" rx="2" fill="#d1d5db"/>
                            <rect x="30" y="44" width="8" height="8" rx="2" fill="#d1d5db"/>
                            <circle cx="50" cy="50" r="10" fill="#fef3c7" stroke="#fcd34d" stroke-width="2"/>
                            <text x="50" y="54" text-anchor="middle" font-size="12" font-weight="700" fill="#92400e">!</text>
                        </svg>
                    </div>
                    <span class="sbs-availability-label">${this._esc(label)}</span>
                    <p class="sbs-empty-title">${this._esc(headline)}</p>
                    <p class="sbs-empty-subtext">${this._esc(subtext)}</p>
                    <div class="sbs-empty-actions sbs-availability-actions">
                        <button type="button" class="sbs-try-next-date">Find next opening</button>
                        ${chooseStaffBtn}
                        ${textUsBtn}
                        ${waitlistBtn}
                    </div>
                </div>`;
        }

        _calcDurationPrice() {
            if (this.selectedOptions.length > 0) {
                return { duration: this.selectedOptions[0].duration, price: this.selectedOptions[0].price, hasExact: true };
            }
            return { duration: 0, price: this.selectedService ? this.selectedService.price : 0, hasExact: false };
        }

        // FIX: Summary now shows category + service name, with X remove buttons
        updateSummary() {
            const hasPending  = this.pendingServices.length > 0;
            const hasService  = hasPending || !!this.selectedService;
            const $summaryBox = $('.sbs-booking-summary');
            const self        = this;
            // -- Confirmed cart items section (shown at top during steps 1-4) --
            let cartSectionHtml = '';
            if (this.bookingCart.length > 0) {
                let cartRows = '';
                this.bookingCart.forEach(function(item, idx) {
                    const title    = 'Appointment ' + (idx + 1);
                    const services = item.services || [{ service: item.service, options: item.options, staff: item.staff }];
                    const svcSummary = services.map(function(s) {
                        const label     = self._svcDisplayLabelHtml(s.service, s.options);
                        const staffName = s.staff ? self.getFirstName(s.staff.name) : '-';
                        return '<div style="font-size:12px;color:#374151;"><strong>' + label + '</strong><br><span style="color:#6b7280;">' + staffName + '</span></div>';
                    }).join('');
                    const parts = item.dateTime.date.split('-');
                    const dObj  = new Date(parts[0], parts[1]-1, parts[2]);
                    const dStr  = dObj.toLocaleDateString('en-US', { month:'short', day:'numeric' });
                    cartRows += '<div class="sbs-checkout-appt-card" style="border:1px solid #e5e7eb;border-radius:8px;padding:10px 12px;margin-bottom:8px;background:#fff;">'
                        + '<div class="sbs-checkout-appt-head" style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;">'
                        + '<span class="sbs-checkout-appt-title" style="font-size:12px;font-weight:700;color:#111;">' + title + '</span>'
                        + '<div class="sbs-checkout-actions" style="display:flex;gap:4px;">'
                        + '<button type="button" class="sbs-sidebar-cart-edit" data-index="' + idx + '" style="background:#e0e7ff;color:#4338ca;border:none;padding:2px 8px;border-radius:4px;cursor:pointer;font-size:11px;font-weight:600;">Edit</button>'
                        + '<button type="button" class="sbs-sidebar-cart-remove" data-index="' + idx + '" style="background:#fee2e2;color:#dc2626;border:none;padding:2px 8px;border-radius:4px;cursor:pointer;font-size:11px;font-weight:600;">Remove</button>'
                        + '</div></div>'
                        + svcSummary
                        + '<div class="sbs-checkout-time" style="font-size:11px;color:#9ca3af;margin-top:4px;">' + dStr + ' at ' + item.dateTime.display + '</div>'
                        + '</div>';
                });
                const MAX_APPTS  = 2;
                const canAddMore = this.bookingCart.length < MAX_APPTS;
                // On Step 5 (Review & Book), don't show the "In Progress" header -                 // there is no appointment being built, just confirmed ones.
                const isStep5 = this.currentStep === 5;
                cartSectionHtml  = '<div style="margin-bottom:12px;">'
                    + '<div style="font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:#9ca3af;font-weight:600;margin-bottom:8px;">Confirmed (' + this.bookingCart.length + '/' + MAX_APPTS + ')</div>'
                    + cartRows
                    + (!isStep5 && canAddMore ? '<div style="border-top:1px solid #f3f4f6;margin:10px 0;"></div>'
                        + '<div style="font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:#9ca3af;font-weight:600;margin-bottom:8px;">Appointment ' + (this.bookingCart.length + 1) + ' - In Progress</div>'
                        : (!isStep5 ? '<div style="font-size:12px;color:#dc2626;text-align:center;padding:6px;background:#fee2e2;border-radius:6px;">Maximum 2 appointments reached</div>' : ''))
                    + '</div>';
            }

            if (hasPending) {
                // Multi-service mode
                let rowsHtml = '';
                // Sort for display: group by person (Me first, then guests in first-seen order)
                // Keep original idx so edit/remove buttons still point to correct pendingServices slot
                const pendingWithIdx = this.pendingServices.map(function(s, idx) { return { s: s, idx: idx }; });
                const seenLabels = [];
                pendingWithIdx.forEach(function(item) {
                    const lbl = item.s.guestLabel || 'Me';
                    if (seenLabels.indexOf(lbl) === -1) seenLabels.push(lbl);
                });
                seenLabels.sort(function(a, b) {
                    if (a === 'Me') return -1;
                    if (b === 'Me') return 1;
                    return 0;
                });
                const sortedForDisplay = [];
                seenLabels.forEach(function(lbl) {
                    pendingWithIdx.forEach(function(item) {
                        if ((item.s.guestLabel || 'Me') === lbl) sortedForDisplay.push(item);
                    });
                });
                sortedForDisplay.forEach(function(item) {
                    const s = item.s; const idx = item.idx;
                    const svcLabel   = self._svcDisplayLabelHtml(s.service, s.options);
                    const svcPrice   = self._optionsPrice(s.service, s.options);
                    const svcDur     = s.options.length > 0 ? s.options.reduce(function(d,o){ return d+(o.duration||0); }, 0) : (s.service.duration||0);
                    const gLabel     = s.guestLabel || 'Me';
                    const safeGLabel = self._esc(self._displayGuestLabel(gLabel));
                    const hasPeople  = self.pendingServices.some(function(ps){ return ps.guestLabel && ps.guestLabel !== 'Me'; });
                    const personBadge = (hasPeople || gLabel !== 'Me')
                        ? `<span style="display:inline-block;background:${gLabel === 'Me' ? '#dbeafe' : '#dcfce7'};color:${gLabel === 'Me' ? '#1d4ed8' : '#16a34a'};border-radius:4px;font-size:10px;font-weight:700;padding:1px 6px;margin-right:4px;">${safeGLabel}</span>`
                        : '';
                    const personSep = personBadge ? `<span style="color:#9ca3af;margin-right:4px;">-</span>` : '';
                    const staffSelectHtml = self._buildStaffSelect(s, idx);
                    rowsHtml += `
                        <div class="sbs-summary-item sbs-summary-pending-row" data-pending-idx="${idx}"
                             style="flex-direction:column;align-items:stretch;border-bottom:1px solid #f3f4f6;padding-bottom:10px;margin-bottom:6px;">
                            <div style="display:flex;justify-content:space-between;align-items:flex-start;gap:6px;">
                                <div style="flex:1;min-width:0;">
                                    <div style="font-size:13px;font-weight:600;color:#111;line-height:1.3;">${personBadge}${personSep}${svcLabel}</div>
                                    <div style="font-size:12px;color:#6b7280;margin-top:3px;">${staffSelectHtml} &nbsp;&middot;&nbsp; ${svcDur}min &nbsp;&middot;&nbsp; $${svcPrice.toFixed(2)}</div>
                                </div>
                                <div style="display:flex;gap:4px;flex-shrink:0;margin-top:2px;">
                                    <button type="button" class="sbs-pending-edit-btn" data-idx="${idx}"
                                            style="background:#f0fdf4;color:#15803d;border:1px solid #bbf7d0;border-radius:5px;padding:3px 8px;font-size:11px;font-weight:600;cursor:pointer;">Edit</button>
                                </div>
                            </div>
                        </div>`;
                });

                const totalDur   = this._durationByGuest(this.pendingServices).totalDuration;
                const totalPrice = this.pendingServices.reduce(function(sum,s){ return sum + self._optionsPrice(s.service, s.options); }, 0);

                let dtRow = '';
                if (this.selectedDateTime) {
                    const dtParts = this.selectedDateTime.date.split('-');
                    const dtObj   = new Date(dtParts[0], dtParts[1]-1, dtParts[2]);
                    const dtStr   = dtObj.toLocaleDateString('en-US', { weekday:'long', month:'long', day:'numeric' });
                    dtRow = `
                        <div class="sbs-summary-item" style="margin-top:4px;">
                            <div style="flex:1;">
                                <div style="font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:#9ca3af;font-weight:600;margin-bottom:2px;">Date &amp; Time</div>
                                <span style="font-size:13px;color:#111;">${dtStr} at ${this.selectedDateTime.display}</span>
                            </div>
                            <div style="display:flex;gap:4px;">
                                <button type="button" class="sbs-summary-edit-btn" data-goto="4"
                                        style="background:#f3f4f6;color:#374151;border:none;border-radius:5px;padding:3px 8px;font-size:11px;font-weight:600;cursor:pointer;">Edit</button>
                            </div>
                        </div>`;
                } else {
                    dtRow = `
                        <div class="sbs-summary-item" style="margin-top:4px;">
                            <div style="flex:1;">
                                <div style="font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:#9ca3af;font-weight:600;margin-bottom:2px;">Date &amp; Time</div>
                                <span style="font-size:13px;color:#9ca3af;">Not selected</span>
                            </div>
                        </div>`;
                }

                const summaryHtml = `
                    <div style="font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:#9ca3af;font-weight:600;margin-bottom:10px;">
                        ${this._selectionSummaryLabel()}
                    </div>
                    ${rowsHtml}
                    ${dtRow}
                    <div class="sbs-summary-item sbs-summary-total" style="border-top:2px solid #f3f4f6;margin-top:10px;padding-top:12px;">
                        <div>
                            <div style="font-weight:600;font-size:15px;">Total</div>
                            <div style="font-size:12px;color:#9ca3af;">Appointment Length: ${totalDur} min</div>
                        </div>
                        <span id="sbs-summary-total" style="font-size:18px;font-weight:700;color:#ec4899;">$${totalPrice.toFixed(2)}</span>
                    </div>`;

                $summaryBox.html(cartSectionHtml + summaryHtml);
                // Bind inline staff dropdown change
                $summaryBox.off('change.inlineStaff').on('change.inlineStaff', '.sbs-inline-staff-sel', function() {
                    const svcId   = $(this).data('svc-id');
                    const newId   = parseInt($(this).val());
                    const newName = $(this).find('option:selected').data('name');
                    const idx = parseInt($(this).data('pending-idx'));
                    // Prefer the exact pending-service index so duplicate services can each choose different staff.
                    const ps = (!Number.isNaN(idx) && self.pendingServices[idx])
                        ? self.pendingServices[idx]
                        : self.pendingServices.find(function(s) { return String(s.service.id) === String(svcId); });
                    if (ps) {
                        ps.staff = { id: newId, name: newName };
                        self.selectedDateTime = null;
                        $('#sbs-selected-start-at').val('');
                        $('.sbs-time-slot').removeClass('selected');
                        self._saveSession();
                        self.updateSummary();
                        self._showPendingServicesSummary();
                        // Reload available times for the new staff selection
                        const selDate = $('.sbs-day-button.selected').data('date');
                        if (selDate && self.currentStep === 4) {
                            self.loadAvailableTimes(selDate);
                        }
                    }
                });
                $summaryBox.off('click.pending').on('click.pending', '.sbs-pending-edit-btn', function() {
                    self._editPendingService(parseInt($(this).data('idx')));
                }).on('click.pending', '.sbs-pending-remove-btn', function() {
                    self._removePendingService(parseInt($(this).data('idx')));
                });

            } else if (this.currentStep === 5 && this.bookingCart.length > 0) {
                // Step 5 (Review & Book): only show confirmed cart items, no in-progress rows
                $summaryBox.html(cartSectionHtml);
                // updateSummaryForCheckout() will append the total row
            } else {
                // Single service in-progress (steps 1-3)
                const calcResult     = this._calcDurationPrice();
                const duration       = calcResult.duration;
                const price          = calcResult.price;
                const hasExact       = calcResult.hasExact;
                const selectedStaffName = this.selectedStaff ? this.getFirstName(this.selectedStaff.name) : 'Not selected';
                let serviceDisplay   = 'No services added yet';
                if (this.selectedService) {
                    serviceDisplay = this.selectedService.category
                        ? this.selectedService.category + ' - ' + this.selectedService.name
                        : this.selectedService.name;
                }

                let optRow = '';
                if (this.selectedOptions.length > 0) {
                    optRow = `
                        <div class="sbs-summary-item" id="sbs-summary-options-container">
                            <div style="flex:1;">
                                <div style="font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:#9ca3af;font-weight:600;margin-bottom:2px;">Options</div>
                                <span id="sbs-summary-options" style="font-size:14px;">${this.selectedOptions.map(function(o){return o.name;}).join(', ')}</span>
                            </div>
                            <div style="display:flex;gap:4px;align-items:center;">
                                <button type="button" class="sbs-summary-edit-btn" data-goto="2"
                                        style="background:#f3f4f6;color:#374151;border:none;border-radius:5px;padding:3px 8px;font-size:11px;font-weight:600;cursor:pointer;">Edit</button>
                            </div>
                        </div>`;
                }

                const dtColor  = this.selectedDateTime ? '#111' : '#9ca3af';
                let dtText     = 'Not selected';
                let dtEditBtn  = '';
                if (this.selectedDateTime) {
                    const dtParts2 = this.selectedDateTime.date.split('-');
                    const dtObj2   = new Date(dtParts2[0], dtParts2[1]-1, dtParts2[2]);
                    dtText    = dtObj2.toLocaleDateString('en-US', { weekday:'long', month:'long', day:'numeric' }) + ' at ' + this.selectedDateTime.display;
                    dtEditBtn = `
                        <div style="display:flex;gap:4px;align-items:center;">
                            <button type="button" class="sbs-summary-edit-btn" data-goto="4"
                                    style="background:#f3f4f6;color:#374151;border:none;border-radius:5px;padding:3px 8px;font-size:11px;font-weight:600;cursor:pointer;">Edit</button>
                        </div>`;
                }

                $summaryBox.html(cartSectionHtml + `
                    <div class="sbs-summary-item" id="sbs-summary-row-service">
                        <div style="flex:1;">
                            <div style="font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:#9ca3af;font-weight:600;margin-bottom:2px;">Service</div>
                            <span id="sbs-summary-service" style="color:${hasService ? '#1a1a1a' : '#9ca3af'};font-size:14px;">${serviceDisplay}</span>
                        </div>
                        ${hasService ? `
                        <div style="display:flex;gap:4px;align-items:center;">
                            <button type="button" class="sbs-summary-edit-btn" data-goto="1"
                                    style="background:#f3f4f6;color:#374151;border:none;border-radius:5px;padding:3px 8px;font-size:11px;font-weight:600;cursor:pointer;">Edit</button>
                        </div>` : ''}
                    </div>
                    ${optRow}
                    <div class="sbs-summary-item" id="sbs-summary-row-staff">
                        <div style="flex:1;">
                            <div style="font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:#9ca3af;font-weight:600;margin-bottom:2px;">Staff</div>
                            <span id="sbs-summary-staff" style="color:${this.selectedStaff ? '#1a1a1a' : '#9ca3af'};font-size:14px;">${selectedStaffName}</span>
                        </div>
                        ${this.selectedStaff ? `
                        <div style="display:flex;gap:4px;align-items:center;">
                            <button type="button" class="sbs-summary-edit-btn" data-goto="3"
                                    style="background:#f3f4f6;color:#374151;border:none;border-radius:5px;padding:3px 8px;font-size:11px;font-weight:600;cursor:pointer;">Edit</button>
                        </div>` : ''}
                    </div>
                    <div class="sbs-summary-item" id="sbs-summary-row-datetime">
                        <div style="flex:1;">
                            <div style="font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:#9ca3af;font-weight:600;margin-bottom:2px;">Date &amp; Time</div>
                            <span id="sbs-summary-datetime" style="color:${dtColor};font-size:14px;">${dtText}</span>
                        </div>
                        ${dtEditBtn}
                    </div>
                    <div class="sbs-summary-item">
                        <div style="flex:1;">
                            <div style="font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:#9ca3af;font-weight:600;margin-bottom:2px;">Appointment Length</div>
                            <span id="sbs-summary-duration" style="font-size:14px;">${hasExact && duration ? duration + ' minutes' : '-'}</span>
                        </div>
                    </div>
                    <div class="sbs-summary-item sbs-summary-total" style="border-top:2px solid #f3f4f6;margin-top:4px;padding-top:14px;">
                        <strong style="font-size:15px;">Total</strong>
                        <span id="sbs-summary-total" style="font-size:18px;font-weight:700;color:#ec4899;">${hasExact ? '$' + price.toFixed(2) : '-'}</span>
                    </div>`);
            }

            this._renderDrawer();
            this._updateMobileCartBar();

            // Bind sidebar cart edit/remove buttons
            $summaryBox.off('click.sidecart')
                .on('click.sidecart', '.sbs-sidebar-cart-edit', function() {
                    self.editCartItem(parseInt($(this).data('index')));
                })
                .on('click.sidecart', '.sbs-sidebar-cart-remove', function() {
                    const removeIndex = parseInt($(this).data('index'));
                    const $overlay = $('<div style="position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:9999;display:flex;align-items:center;justify-content:center;padding:20px;">'
                        + '<div style="background:#fff;border-radius:16px;padding:28px 24px;max-width:340px;width:100%;text-align:center;box-shadow:0 20px 60px rgba(0,0,0,0.3);">'
                        + '<div style="font-size:36px;margin-bottom:12px;">!</div>'
                        + '<h3 style="margin:0 0 8px;font-size:18px;font-weight:700;color:#111;">Remove Appointment ' + (removeIndex+1) + '?</h3>'
                        + '<p style="color:#6b7280;font-size:14px;margin:0 0 20px;line-height:1.5;">This will remove all services in this appointment. You cannot undo this action.</p>'
                        + '<div style="display:flex;gap:10px;">'
                        + '<button id="sbs-remove-cancel" style="flex:1;background:#f3f4f6;color:#374151;border:none;border-radius:8px;padding:12px;font-size:14px;font-weight:600;cursor:pointer;">Keep it</button>'
                        + '<button id="sbs-remove-confirm" style="flex:1;background:#dc2626;color:#fff;border:none;border-radius:8px;padding:12px;font-size:14px;font-weight:600;cursor:pointer;">Yes, Remove</button>'
                        + '</div></div></div>');
                    $('body').append($overlay);
                    $overlay.on('click', '#sbs-remove-cancel', function() { $overlay.remove(); });
                    $overlay.on('click', '#sbs-remove-confirm', function() {
                        $overlay.remove();
                        self.removeFromCart(removeIndex);
                    });
                });
        }


        // Edit a specific pending service - restore it to selectedService and go back to its options step
        _editPendingService(idx) {
            const s = this.pendingServices[idx];
            if (!s) return;
            this.pendingServices.splice(idx, 1);
            this.selectedService  = { ...s.service };
            this.selectedCategory = s.service.category || null;
            this.selectedOptions  = [...s.options];
            this.selectedStaff    = null;
            this.selectedDateTime = null;
            this.updateSummary();
            this.goToStep(2);
            this.loadServiceOptions();
        }

        // Remove a specific pending service with confirmation
        _removePendingService(idx) {
            const self = this;
            const $overlay = $(`
                <div id="sbs-confirm-overlay" style="position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:99999;display:flex;align-items:center;justify-content:center;padding:16px;">
                  <div style="background:#fff;border-radius:12px;padding:28px 24px;max-width:340px;width:100%;text-align:center;box-shadow:0 8px 32px rgba(0,0,0,.2);">
                    <div style="font-size:28px;margin-bottom:12px;">!</div>
                    <h3 style="margin:0 0 8px;font-size:18px;font-weight:700;">Remove this service?</h3>
                    <p style="margin:0 0 20px;color:#6b7280;font-size:14px;">You'll need to re-add it if you change your mind.</p>
                    <div style="display:flex;gap:12px;">
                      <button id="sbs-confirm-cancel" style="flex:1;padding:12px;background:#f3f4f6;color:#374151;border:none;border-radius:8px;cursor:pointer;font-size:14px;font-weight:600;">Keep it</button>
                      <button id="sbs-confirm-delete" style="flex:1;padding:12px;background:#dc2626;color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:14px;font-weight:600;">Yes, remove</button>
                    </div>
                  </div>
                </div>`);
            $('body').append($overlay);
            $overlay.on('click', '#sbs-confirm-cancel', function() { $overlay.remove(); });
            $overlay.on('click', '#sbs-confirm-delete', function() {
                $overlay.remove();
                self.pendingServices.splice(idx, 1);
                if (self.pendingServices.length === 0) {
                    // No services left - clear all state and go back to step 1
                    self.selectedService  = null;
                    self.selectedCategory = null;
                    self.selectedOptions  = [];
                    self.selectedStaff    = null;
                    self.selectedDateTime = null;
                    // Deselect any highlighted service cards in the list
                    $('.sbs-service-item, .sbs-service-card').removeClass('selected');
                    self.updateSummary();
                    self.goToStep(1);
                } else {
                    self.updateSummary();
                    self._showPendingServicesSummary();
                    // If already on the date/time step, refresh available times immediately
                    if (self.currentStep === 4) {
                        const selectedDate = $('.sbs-day-button.selected').data('date') || $('#sbs-date-picker').val();
                        if (selectedDate) {
                            self.selectedDateTime = null; // clear old selection since services changed
                            self.loadAvailableTimes(selectedDate);
                        }
                    }
                }
            });
        }
        
        updateSummaryForCheckout() {
            // On Step 5, the sidebar is fully rendered by updateSummary() (dynamic cart HTML).
            // Hide ALL static #sbs-summary-* rows so they don't show stale data.
            $('#sbs-summary-row-service, #sbs-summary-options-container, #sbs-summary-row-staff, #sbs-summary-row-datetime, #sbs-summary-row-duration').hide();
            $('#sbs-summary-row-total').hide();
            $('.sbs-booking-summary').addClass('sbs-checkout-card');

            if (this.bookingCart.length === 0) {
                $('#sbs-sidebar-edit-cart').hide();
                $('#sbs-checkout-card-heading').remove();
                return;
            }

            // Remove any previously injected heading (no longer needed - PHP provides it)
            $('#sbs-checkout-card-heading').remove();

            // Show combined total from all cart items
            let totalPrice = 0;
            let totalDuration = 0;
            this.bookingCart.forEach(item => {
                totalPrice    += this.getItemPrice(item);
                totalDuration += this._getItemVisitDuration(item);
            });

            // Inject a step-5-only total row after the cart HTML (or update if exists)
            let $step5Total = $('#sbs-step5-total');
            if (!$step5Total.length) {
                $step5Total = $('<div id="sbs-step5-total" style="border-top:2px solid #f3f4f6;margin-top:4px;padding-top:14px;display:flex;justify-content:space-between;align-items:center;"></div>');
                $('.sbs-booking-summary').append($step5Total);
            }
            $step5Total.html(
                '<strong style="font-size:15px;">Total</strong>'
                + '<span style="font-size:18px;font-weight:700;color:#ec4899;">$' + totalPrice.toFixed(2) + ' &bull; Appointment Length: ' + totalDuration + ' min</span>'
            ).show();

            $('#sbs-sidebar-edit-cart').show();
        }

        _renderDrawer() {
            // No-op: drawer content is rendered fresh in _renderMobileCart when opened
        }

        _handleBrowserBack() {
            // Mirrors the Back button logic - called when browser Back is pressed
            const self = this;
            const prevStep = self.currentStep - 1;

            if (self.currentStep === 5 && self.bookingCart.length > 0) {
                const last = self.bookingCart.pop();
                self.pendingServices  = last.services ? [...last.services] : [];
                self.selectedDateTime = null;
                self._saveSession();
                self._showPendingServicesSummary();
                self.goToStep(4);
                return;
            }
            if (self.currentStep === 4) {
                if (self.pendingServices.length > 0) {
                    self._backFromTimeToStaff();
                    return;
                } else if (false && self.bookingCart.length > 0) {
                    self.selectedDateTime = null;
                    self._saveSession();
                    const $slots = $('.sbs-time-slots');
                    $slots.html('<div class="sbs-loading"><span class="spinner"></span> Loading available times...</div>');
                    const $selectedBtn = $('.sbs-day-button.selected:not(.disabled)');
                    if ($selectedBtn.length) {
                        const date = $selectedBtn.data('date');
                        if (date) self.loadAvailableTimes(date);
                    }
                    self._updateMobileCartBar();
                    return;
                } else if (self.bookingCart.length > 0) {
                    const last = self.bookingCart.pop();
                    self.pendingServices  = last.services ? [...last.services] : [];
                    self.selectedDateTime = null;
                    self._saveSession();
                    self._showPendingServicesSummary();
                    const $slots = $('.sbs-time-slots');
                    $slots.html('<div class="sbs-loading"><span class="spinner"></span> Loading available times...</div>');
                    const $selectedBtn = $('.sbs-day-button.selected:not(.disabled)');
                    if ($selectedBtn.length) {
                        const date = $selectedBtn.data('date');
                        if (date) self.loadAvailableTimes(date);
                    }
                    self._updateMobileCartBar();
                    return;
                }
            }
            if (prevStep >= 1) {
                self.goToStep(prevStep);
            }
        }

        _closeDrawer() {
            const $drawer = $('#sbs-mobile-drawer');
            $drawer.removeClass('open');
            // After transition ends, set display:none so it doesn't peek on iOS Safari
            setTimeout(function() {
                if (!$drawer.hasClass('open')) $drawer.css('display', 'none');
            }, 320);
            $('#sbs-drawer-backdrop').removeClass('open');
            $('#sbs-cart-expand-btn').removeClass('open');
        }

        initMobileCart() {
            const self = this;
            if (!$('#sbs-cart-back-btn').length) {
                $('#sbs-mobile-cart-bar').prepend('<button type="button" id="sbs-cart-back-btn" aria-label="Back">Back</button>');
            }
            $(document).on('click touchend', '#sbs-mobile-cart-bar', function(e) {
                if ($(e.target).closest('#sbs-cart-next-btn, #sbs-cart-back-btn').length) return;
                const lastControlTouch = $(this).data('sbsControlTouch') || 0;
                if (Date.now() - lastControlTouch < 700) return;
                const lastBarTouch = $(this).data('sbsLastTouch') || 0;
                if (e.type === 'touchend') {
                    e.preventDefault();
                    $(this).data('sbsLastTouch', Date.now());
                } else if (Date.now() - lastBarTouch < 700) {
                    return;
                }
                if (self.currentStep === 2) return;
                if ($('#sbs-mobile-drawer').hasClass('open')) {
                    self._closeDrawer();
                } else {
                    const $drawer = $('#sbs-mobile-drawer');
                    $drawer.css('display', 'block');
                    // Force reflow so transition fires
                    $drawer[0].offsetHeight;
                    $drawer.addClass('open');
                    $('#sbs-drawer-backdrop').addClass('open');
                    $('#sbs-cart-expand-btn').addClass('open');
                    self._renderMobileCart();
                }
            });
            $(document).on('click', '#sbs-drawer-backdrop', function() { self._closeDrawer(); });
            $(document).on('click touchend', '#sbs-cart-back-btn', function(e) {
                e.preventDefault();
                e.stopPropagation();
                e.stopImmediatePropagation();
                $('#sbs-mobile-cart-bar').data('sbsControlTouch', Date.now());
                const lastTouch = $(this).data('sbsLastTouch') || 0;
                if (e.type === 'touchend') {
                    $(this).data('sbsLastTouch', Date.now());
                } else if (Date.now() - lastTouch < 700) {
                    return;
                }
                self._closeDrawer();
                const $back = $('.sbs-form-step.active .sbs-btn-back').first();
                if ($back.length) {
                    $back.trigger('click');
                } else if (self.currentStep > 1) {
                    self.goToStep(self.currentStep - 1, true);
                }
            });
            $(document).on('click touchend', '#sbs-cart-next-btn', function(e) {
                e.preventDefault();
                e.stopPropagation(); // don't open drawer
                const lastTouch = $(this).data('sbsLastTouch') || 0;
                if (e.type === 'touchend') {
                    $(this).data('sbsLastTouch', Date.now());
                } else if (Date.now() - lastTouch < 700) {
                    return;
                }
                if ($(this).prop('disabled')) return;
                const btnText = $(this).text();
                const isCheckoutAction = btnText.indexOf('Review') !== -1 || btnText.indexOf('Book') !== -1 || btnText.indexOf('Booking') !== -1 || btnText.indexOf('Checkout') !== -1 || btnText.indexOf('Confirm') !== -1;
                if (isCheckoutAction && self.currentStep === 5) {
                    self._requestCustomerFormSubmit();
                } else if (isCheckoutAction) {
                    self.goToStep(5);
                } else if (self.currentStep === 1 && self.pendingServices.length > 0) {
                    if (self._isGroupAppointmentSelected()) self._showGuestReviewScreen();
                    else self.goToStep(4, true);
                } else if (self.currentStep === 2) {
                    const needsMain = !!self._optionHasMainGroup;
                    const hasMain = (self.selectedOptions || []).some(function(o) { return !o.is_addon; });
                    if (needsMain && !hasMain) {
                        self._showToast('Please choose one main option before continuing.', 'error');
                        self._showMainOptionRequired();
                        return;
                    }
                    self.loadAvailableStaff();
                } else if (self.currentStep === 3) {
                    if (self.selectedService) {
                        self._commitSelectedStaff();
                    } else if (self.pendingServices.length > 0) {
                        self.goToStep(4);
                    }
                }
            });
            $(document).on('click touchend', '.sbs-mobile-cart-remove', function(e) {
                e.preventDefault();
                e.stopPropagation();
                const lastTouch = $(this).data('sbsLastTouch') || 0;
                if (e.type === 'touchend') {
                    $(this).data('sbsLastTouch', Date.now());
                } else if (Date.now() - lastTouch < 700) {
                    return;
                }
                const index = parseInt($(this).data('index'));
                self._confirmRemove(index, true);
            });
            $(document).on('click', '.sbs-mobile-cart-edit', function() {
                const index = parseInt($(this).data('index'));
                self._closeDrawer();
                self.editCartItem(index);
            });
        }
        
        _renderMobileCart() {
            const self = this;
            const container = $('#sbs-mobile-drawer .sbs-booking-summary');

            // -- CASE A: Cart has completed appointments -----------------------
            if (this.bookingCart.length > 0) {
                let html = '<div style="padding:16px 16px 8px;">';
                let totalPrice = 0;
                let totalDuration = 0;

                this.bookingCart.forEach((item, index) => {
                    const price    = this.getItemPrice(item);
                    const duration = this._getItemVisitDuration(item);
                    totalPrice    += price;
                    totalDuration += duration;
                    const dateParts = item.dateTime.date.split('-');
                    const dateObj   = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]);
                    const dateStr   = dateObj.toLocaleDateString('en-US', { month:'short', day:'numeric' });

                    // Multi-service: list all services per cart item
                    const services = item.services || [{ service: item.service, options: item.options, staff: item.staff }];
                    let svcsHtml = '';
                    services.forEach(s => {
                        const svcLabel = this._svcDisplayLabelHtml(s.service, s.options);
                        const staffLabel = s.staff ? this.getFirstName(s.staff.name) : '-';
                        svcsHtml += `
                            <div style="margin-bottom:4px;">
                                <div style="font-weight:600;font-size:14px;color:#111;">${svcLabel}</div>
                                <div style="font-size:12px;color:#6b7280;">${staffLabel}</div>
                            </div>`;
                    });

                    html += `
                        <div class="sbs-drawer-appt-row" style="padding:14px 0;border-bottom:1px solid #f3f4f6;">
                          <div style="display:flex;justify-content:space-between;align-items:flex-start;gap:8px;">
                            <div style="flex:1;min-width:0;">
                                ${svcsHtml}
                                <div style="font-size:12px;color:#6b7280;margin-top:4px;line-height:1.45;">${this._formatItemTimeRange(item, dateStr)}<br><span>Appointment Length: ${duration} min</span></div>
                            </div>
                            <div style="display:flex;flex-direction:column;align-items:flex-end;gap:6px;flex-shrink:0;">
                                <span style="font-weight:700;font-size:15px;color:#ec4899;">$${price.toFixed(2)}</span>
                                <div style="display:flex;gap:6px;">
                                    <button class="sbs-mobile-cart-edit" data-index="${index}"
                                            style="background:#f0f9ff;color:#0369a1;border:1px solid #bae6fd;border-radius:6px;padding:4px 10px;font-size:12px;font-weight:600;cursor:pointer;">Edit</button>
                                    <button class="sbs-mobile-cart-remove" data-index="${index}"
                                            style="background:#fef2f2;color:#dc2626;border:1px solid #fecaca;border-radius:6px;padding:4px 10px;font-size:12px;font-weight:600;cursor:pointer;">Remove</button>
                                </div>
                            </div>
                          </div>
                        </div>`;
                });

                html += `</div>
                    <div style="padding:16px;border-top:1px solid #e5e7eb;">
                      <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:14px;">
                        <span style="font-size:15px;font-weight:600;">Total</span>
                        <div style="text-align:right;">
                          <span style="font-size:18px;font-weight:700;color:#ec4899;">$${totalPrice.toFixed(2)}</span>
                          <span style="font-size:12px;color:#9ca3af;margin-left:6px;">Appointment Length: ${totalDuration} min</span>
                        </div>
                      </div>
                      <button id="sbs-drawer-checkout-btn"
                              style="width:100%;padding:14px;background:#111;color:#fff;border:none;border-radius:10px;font-size:16px;font-weight:700;cursor:pointer;letter-spacing:.02em;">
                          Review &amp; Book
                      </button>
                    </div>`;

                container.html(html);
                container.off('click', '#sbs-drawer-checkout-btn').on('click', '#sbs-drawer-checkout-btn', function() {
                    self._closeDrawer();
                    self.goToStep(5);
                });
                return;
            }

            // -- CASE B: pendingServices (multi-service in progress, no time yet) --
            if (this.pendingServices.length > 0) {
                const all = this.pendingServices;
                const totalDur = this._durationByGuest(all).totalDuration;
                const totalPric = all.reduce((s, sv) => s + this._optionsPrice(sv.service, sv.options), 0);

                let svcsHtml = '';
                all.forEach((s, idx) => {
                    const svcLabel = this._svcDisplayLabelHtml(s.service, s.options);
                    const staffLabel = s.staff ? this.getFirstName(s.staff.name) : '-';
                    const dur = s.options.length > 0
                        ? s.options.reduce((d, o) => d + (o.duration || 0), 0)
                        : (s.service.duration || 0);
                    const pric = this._optionsPrice(s.service, s.options);

                    svcsHtml += `
                        <div style="padding:12px 16px;border-bottom:1px solid #f3f4f6;">
                            <div style="display:flex;justify-content:space-between;align-items:flex-start;gap:8px;">
                                <div style="flex:1;min-width:0;">
                                    <div style="font-weight:600;font-size:13px;color:#111;">${svcLabel}</div>
                                    <div style="font-size:12px;color:#6b7280;">${staffLabel} - ${dur}min</div>
                                </div>
                                <div style="display:flex;flex-direction:column;align-items:flex-end;gap:4px;flex-shrink:0;">
                                    <span style="font-size:13px;font-weight:700;color:#ec4899;">$${pric.toFixed(2)}</span>
                                    <div style="display:flex;gap:5px;">
                                        <button class="sbs-drawer-pending-edit" data-idx="${idx}"
                                                style="background:#f0fdf4;color:#15803d;border:1px solid #bbf7d0;border-radius:5px;padding:3px 8px;font-size:11px;font-weight:600;cursor:pointer;">Edit</button>
                                        <button class="sbs-drawer-pending-remove" data-idx="${idx}"
                                                style="background:#fef2f2;color:#dc2626;border:1px solid #fecaca;border-radius:5px;padding:3px 8px;font-size:11px;font-weight:600;cursor:pointer;">Remove</button>
                                    </div>
                                </div>
                            </div>
                        </div>`;
                });

                // Date & time row
                let dtHtml = '';
                if (this.selectedDateTime) {
                    const dp = this.selectedDateTime.date.split('-');
                    const d  = new Date(dp[0], dp[1]-1, dp[2]);
                    const ds = d.toLocaleDateString('en-US', { weekday:'short', month:'short', day:'numeric' });
                    dtHtml = `
                        <div style="padding:12px 16px;border-bottom:1px solid #f3f4f6;display:flex;justify-content:space-between;align-items:center;">
                            <div>
                                <div style="font-size:11px;text-transform:uppercase;color:#9ca3af;font-weight:600;margin-bottom:1px;">Date &amp; Time</div>
                                <div style="font-size:13px;font-weight:600;color:#111;">${ds} at ${this.selectedDateTime.display}</div>
                            </div>
                            <button class="sbs-drawer-progress-edit" data-step="4"
                                    style="background:none;border:1px solid #e5e7eb;border-radius:6px;padding:5px 10px;font-size:12px;cursor:pointer;color:#374151;">Change</button>
                        </div>`;
                } else {
                    dtHtml = `
                        <div style="padding:12px 16px;border-bottom:1px solid #f3f4f6;">
                            <div style="font-size:11px;text-transform:uppercase;color:#9ca3af;font-weight:600;margin-bottom:1px;">Date &amp; Time</div>
                            <div style="font-size:13px;color:#9ca3af;">Select a time to continue</div>
                        </div>`;
                }

                const hasTime = !!this.selectedDateTime;
                const reviewBtn = hasTime
                    ? `<button id="sbs-drawer-review-btn" style="width:100%;padding:13px;background:#111;color:#fff;border:none;border-radius:10px;font-size:15px;font-weight:700;cursor:pointer;margin-top:12px;">Review &amp; Book</button>`
                    : '';

                container.html(`
                    <div style="padding:14px 16px 6px;">
                        <h3 style="margin:0 0 2px;font-size:16px;font-weight:700;">Your appointment</h3>
                        <div style="font-size:12px;color:#9ca3af;">${all.length} service${all.length > 1 ? 's' : ''} &middot; Appointment Length: ${totalDur} min</div>
                    </div>
                    ${svcsHtml}
                    ${dtHtml}
                    <div style="padding:14px 16px;display:flex;justify-content:space-between;align-items:center;border-top:1px solid #f3f4f6;">
                        <span style="font-size:14px;color:#6b7280;">Appointment Length: ${totalDur} min</span>
                        <span style="font-size:17px;font-weight:700;color:#ec4899;">$${totalPric.toFixed(2)}</span>
                    </div>
                    ${hasTime ? `<div style="padding:0 16px 16px;">${reviewBtn}</div>` : ''}
                `);

                // Bind pending service edit/remove
                container.off('click.pendingDrawer').on('click.pendingDrawer', '.sbs-drawer-pending-edit', function() {
                    const idx = parseInt($(this).data('idx'));
                    self._closeDrawer();
                    self._editPendingService(idx);
                }).on('click.pendingDrawer', '.sbs-drawer-pending-remove', function() {
                    const idx2 = parseInt($(this).data('idx'));
                    self._closeDrawer();
                    self._removePendingService(idx2);
                }).on('click.pendingDrawer', '#sbs-drawer-review-btn', function() {
                    self._closeDrawer();
                    self.goToStep(5);
                }).on('click.pendingDrawer', '.sbs-drawer-progress-edit', function() {
                    const targetStep = parseInt($(this).data('step'));
                    self._closeDrawer();
                    self.goToStep(targetStep, true);
                });
                return;
            }

            // -- CASE C: Single service in-progress (steps 1-3) ---------------
            const priceVal = this._getCurrentPrice();
            const durVal   = this._getCurrentDuration();
            const rows = [];

            rows.push(`<div style="padding:16px 16px 8px;"><h3 style="margin:0 0 4px;font-size:16px;font-weight:700;">Your appointment</h3></div>`);

            if (this.selectedService) {
                const svcLabel = this.selectedService.category
                    ? this.selectedService.category + ' - ' + this.selectedService.name
                    : this.selectedService.name;
                rows.push(`
                    <div style="display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid #f3f4f6;">
                      <div>
                        <div style="font-size:11px;text-transform:uppercase;color:#9ca3af;font-weight:600;letter-spacing:.05em;margin-bottom:2px;">Service</div>
                        <div style="font-weight:600;font-size:14px;">${svcLabel}</div>
                      </div>
                      <div style="display:flex;gap:6px;">
                        <button class="sbs-drawer-progress-edit" data-step="1"
                                style="background:none;border:1px solid #e5e7eb;border-radius:6px;padding:5px 10px;font-size:12px;cursor:pointer;color:#374151;">Edit</button>
                        <button class="sbs-drawer-del-service"
                                style="background:none;border:1px solid #fecaca;border-radius:6px;padding:5px 8px;font-size:12px;cursor:pointer;color:#dc2626;">Remove</button>
                      </div>
                    </div>`);
            } else {
                rows.push(`<div style="padding:12px 16px;border-bottom:1px solid #f3f4f6;color:#9ca3af;font-size:14px;">Select a service to get started</div>`);
            }

            if (this.selectedOptions.length > 0) {
                const optText = this.selectedOptions.map(o => o.name).join(', ');
                rows.push(`
                    <div style="display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid #f3f4f6;">
                      <div>
                        <div style="font-size:11px;text-transform:uppercase;color:#9ca3af;font-weight:600;letter-spacing:.05em;margin-bottom:2px;">Options</div>
                        <div style="font-weight:500;font-size:14px;">${optText}</div>
                      </div>
                      <div style="display:flex;gap:6px;">
                        <button class="sbs-drawer-progress-edit" data-step="2"
                                style="background:none;border:1px solid #e5e7eb;border-radius:6px;padding:5px 10px;font-size:12px;cursor:pointer;color:#374151;">Edit</button>
                        <button class="sbs-drawer-del-options"
                                style="background:none;border:1px solid #fecaca;border-radius:6px;padding:5px 8px;font-size:12px;cursor:pointer;color:#dc2626;">Remove</button>
                      </div>
                    </div>`);
            }

            if (this.selectedStaff) {
                rows.push(`
                    <div style="display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid #f3f4f6;">
                      <div>
                        <div style="font-size:11px;text-transform:uppercase;color:#9ca3af;font-weight:600;letter-spacing:.05em;margin-bottom:2px;">Staff</div>
                        <div style="font-weight:500;font-size:14px;">with ${this.selectedStaff.name}</div>
                      </div>
                      <button class="sbs-drawer-progress-edit" data-step="3"
                              style="background:none;border:1px solid #e5e7eb;border-radius:6px;padding:5px 10px;font-size:12px;cursor:pointer;color:#374151;">Change</button>
                    </div>`);
            }

            if (priceVal > 0 || durVal > 0) {
                rows.push(`
                    <div style="padding:14px 16px;display:flex;justify-content:space-between;align-items:center;border-top:1px solid #f3f4f6;margin-top:4px;">
                      <span style="font-size:13px;color:#6b7280;">${durVal > 0 ? durVal + ' min' : ''}</span>
                      <span style="font-size:17px;font-weight:700;color:#ec4899;">$${priceVal.toFixed(2)}</span>
                    </div>`);
            }

            container.html(rows.join(''));

            container.off('click.drawerC').on('click.drawerC', '.sbs-drawer-progress-edit', function() {
                const targetStep = parseInt($(this).data('step'));
                self._closeDrawer();
                if (targetStep === 2) { self.goToStep(2, true); self.loadServiceOptions(); }
                else if (targetStep === 3) { self.goToStep(3, true); self.loadAvailableStaff(); }
                else { self.goToStep(targetStep, true); }
            }).on('click.drawerC touchend.drawerC', '.sbs-drawer-del-service', function(e) {
                e.preventDefault();
                e.stopPropagation();
                const lastTouch = $(this).data('sbsLastTouch') || 0;
                if (e.type === 'touchend') {
                    $(this).data('sbsLastTouch', Date.now());
                } else if (Date.now() - lastTouch < 700) {
                    return;
                }
                self._confirmDrawerRemoval('service');
            }).on('click.drawerC touchend.drawerC', '.sbs-drawer-del-options', function(e) {
                e.preventDefault();
                e.stopPropagation();
                const lastTouch = $(this).data('sbsLastTouch') || 0;
                if (e.type === 'touchend') {
                    $(this).data('sbsLastTouch', Date.now());
                } else if (Date.now() - lastTouch < 700) {
                    return;
                }
                self._confirmDrawerRemoval('options');
            });
        }

        _confirmDrawerRemoval(action) {
            const self = this;
            const isService = action === 'service';
            $('#sbs-confirm-overlay').remove();
            const title = isService ? 'Remove this service?' : 'Remove these options?';
            const body = isService
                ? 'This will clear the service, options, staff, and time for this appointment.'
                : 'This will clear the selected options for this service.';
            const $overlay = $(`
                <div id="sbs-confirm-overlay" style="
                    position:fixed;inset:0;background:rgba(17,24,39,.52);z-index:99999;
                    display:flex;align-items:center;justify-content:center;padding:18px;">
                  <div style="background:#fff;border-radius:18px;padding:24px;max-width:360px;width:100%;box-shadow:0 24px 70px rgba(17,24,39,.28);">
                    <h3 style="margin:0 0 8px;font-size:20px;font-weight:800;color:#111827;">${title}</h3>
                    <p style="margin:0 0 20px;color:#6b7280;font-size:14px;line-height:1.5;">${body}</p>
                    <div style="display:flex;gap:12px;">
                      <button type="button" id="sbs-confirm-cancel" style="flex:1;min-height:46px;background:#fff;color:#374151;border:1px solid #d8dde6;border-radius:999px;cursor:pointer;font-size:14px;font-weight:800;">Keep it</button>
                      <button type="button" id="sbs-confirm-delete" style="flex:1;min-height:46px;background:#dc2626;color:#fff;border:none;border-radius:999px;cursor:pointer;font-size:14px;font-weight:800;">Remove</button>
                    </div>
                  </div>
                </div>`);
            $('body').append($overlay);
            $overlay.on('click touchend', '#sbs-confirm-cancel', function(e) {
                e.preventDefault();
                e.stopPropagation();
                $overlay.remove();
            });
            $overlay.on('click touchend', '#sbs-confirm-delete', function(e) {
                e.preventDefault();
                e.stopPropagation();
                $overlay.remove();
                if (isService) {
                    self.selectedService = null;
                    self.selectedCategory = null;
                    self.selectedOptions = [];
                    self.selectedStaff = null;
                    self.selectedDateTime = null;
                    $('#sbs-selected-service-id, #sbs-selected-staff-id, #sbs-selected-start-at, #sbs-selected-options').val('');
                    self._closeDrawer();
                    self._saveSession();
                    self.goToStep(1, true);
                } else {
                    self.selectedOptions = [];
                    $('#sbs-selected-options').val('');
                    self._closeDrawer();
                    self.updateSummary();
                    self._updateMobileCartBar();
                    self._saveSession();
                }
            });
        }

        _squarePaymentsReady() {
            return !!(
                typeof yatoonData !== 'undefined' &&
                yatoonData.squarePaymentsEnabled &&
                yatoonData.squareApplicationId &&
                yatoonData.squareLocationId &&
                window.Square &&
                typeof window.Square.payments === 'function'
            );
        }

        _squareMethods() {
            const methods = (yatoonData && Array.isArray(yatoonData.squarePaymentMethods))
                ? yatoonData.squarePaymentMethods
                : ['card'];
            return methods.length ? methods : ['card'];
        }

        _previewSquarePayment(payload) {
            const fd = new FormData();
            Object.keys(payload).forEach(function(key) {
                const val = payload[key];
                if (Array.isArray(val)) {
                    val.forEach(function(itemVal) { fd.append(key + '[]', itemVal); });
                } else {
                    fd.append(key, val == null ? '' : val);
                }
            });
            fd.set('action', 'yatoon_square_preview_payment');
            return fetch((yatoonData.ajax_url || yatoonData.ajaxUrl), {
                method: 'POST',
                body: fd,
                credentials: 'same-origin'
            }).then(function(resp) { return resp.json(); }).then(function(json) {
                if (!json || !json.success || !json.data) {
                    const msg = json && json.data && json.data.message ? json.data.message : 'Could not calculate the Square payment amount.';
                    throw new Error(msg);
                }
                return json.data;
            });
        }

        _buildSquarePaymentRequest(payments, preview) {
            return payments.paymentRequest({
                countryCode: (yatoonData.squareCountryCode || 'US').toUpperCase(),
                currencyCode: (preview.currency || yatoonData.currency || 'USD').toUpperCase(),
                total: {
                    amount: preview.amount || '0.00',
                    label: yatoonData.squarePaymentMode === 'full' ? 'Appointment total' : 'Booking deposit',
                }
            });
        }

        _ensureSquarePaymentCard(payload) {
            const self = this;
            const payloadKey = JSON.stringify({
                services_json: payload.services_json || '',
                service_id: payload.service_id || '',
                selected_options: payload.selected_options || [],
                coupon_code: payload.coupon_code || '',
                customer_email: payload.customer_email || '',
                customer_phone: payload.customer_phone || ''
            });
            if (!this._squareCardPromise || this._squarePaymentPayloadKey !== payloadKey) {
                this._squarePaymentPayloadKey = payloadKey;
                this._squareCardPromise = new Promise(function(resolve, reject) {
                    if (!self._squarePaymentsReady()) {
                        reject(new Error('Square payment form is not ready. Please refresh and try again.'));
                        return;
                    }

                    let $box = $('#sbs-square-payment-box');
                    if (!$box.length) {
                        const modeText = yatoonData.squarePaymentMode === 'full'
                            ? 'Pay securely now to confirm your appointment.'
                            : 'Pay your deposit securely now to confirm your appointment.';
                        $box = $(`
                            <div id="sbs-square-payment-box" class="sbs-square-payment-box" style="margin:16px 0;padding:16px;border:1px solid #dbeafe;border-radius:14px;background:#eff6ff;">
                                <div style="font-size:14px;font-weight:800;color:#1e3a8a;margin-bottom:6px;">Secure Square Payment</div>
                                <div style="font-size:13px;color:#475569;margin-bottom:12px;">${modeText}</div>
                                <div id="sbs-square-payment-amount" style="font-size:13px;font-weight:800;color:#0f172a;margin-bottom:12px;"></div>
                                <div id="sbs-square-method-tabs" style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px;"></div>
                                <button id="sbs-square-apple-pay-button" type="button" class="sbs-square-method-panel" data-method-panel="apple_pay" style="display:none;width:100%;min-height:44px;border-radius:8px;border:0;background:#000;color:#fff;font-weight:800;">Apple Pay</button>
                                <div id="sbs-square-google-pay" class="sbs-square-method-panel" data-method-panel="google_pay" style="display:none;"></div>
                                <div id="sbs-square-cash-app-pay" class="sbs-square-method-panel" data-method-panel="cash_app_pay" style="display:none;"></div>
                                <div id="sbs-square-gift-card-container" class="sbs-square-method-panel" data-method-panel="gift_card" style="display:none;"></div>
                                <div id="sbs-square-card-container"></div>
                                <div id="sbs-square-payment-status" style="font-size:12px;color:#64748b;margin-top:10px;"></div>
                            </div>
                        `);
                        const $form = $('#sbs-booking-form');
                        if ($form.length) $form.prepend($box);
                        else $('.sbs-step-5').prepend($box);
                    }

                    $('#sbs-square-payment-status').text('Loading secure Square card form...');
                    self._previewSquarePayment(payload)
                        .then(function(preview) {
                            $('#sbs-square-payment-amount').text((preview.payment_mode === 'full' ? 'Pay now: ' : 'Deposit due now: ') + preview.amount_display);
                            return Promise.resolve(window.Square.payments(yatoonData.squareApplicationId, yatoonData.squareLocationId))
                                .then(function(payments) {
                                    const enabled = self._squareMethods();
                                    const state = { payments: payments, preview: preview, methods: {}, selected: enabled[0] || 'card' };
                                    const paymentRequest = self._buildSquarePaymentRequest(payments, preview);
                                    const methodLabels = {
                                        card: 'Card',
                                        apple_pay: 'Apple Pay',
                                        google_pay: 'Google Pay',
                                        cash_app_pay: 'Cash App',
                                        gift_card: 'Gift Card'
                                    };
                                    const $tabs = $('#sbs-square-method-tabs').empty();
                                    enabled.forEach(function(method) {
                                        $('<button/>', {
                                            type: 'button',
                                            class: 'sbs-square-method-tab',
                                            'data-method': method,
                                            text: methodLabels[method] || method
                                        }).css({
                                            border: '1px solid #bfdbfe',
                                            background: method === state.selected ? '#1d4ed8' : '#fff',
                                            color: method === state.selected ? '#fff' : '#1e3a8a',
                                            borderRadius: '999px',
                                            padding: '8px 12px',
                                            fontWeight: '800',
                                            cursor: 'pointer'
                                        }).appendTo($tabs);
                                    });

                                    function selectMethod(method) {
                                        state.selected = method;
                                        $('.sbs-square-method-tab').each(function() {
                                            const active = $(this).data('method') === method;
                                            $(this).css({ background: active ? '#1d4ed8' : '#fff', color: active ? '#fff' : '#1e3a8a' });
                                        });
                                        $('.sbs-square-method-panel').hide();
                                        $('#sbs-square-card-container').toggle(method === 'card');
                                        $('[data-method-panel="' + method + '"]').show();
                                        $('#sbs-square-payment-status').text(method === 'apple_pay' || method === 'cash_app_pay'
                                            ? 'Tap the wallet button above to authorize payment, then booking will continue automatically.'
                                            : 'Payment details are encrypted by Square.');
                                    }
                                    $(document).off('click.squareMethod').on('click.squareMethod', '.sbs-square-method-tab', function() {
                                        selectMethod($(this).data('method'));
                                    });

                                    const initTasks = [];
                                    if (enabled.includes('card')) {
                                        initTasks.push(payments.card().then(function(card) {
                                            state.methods.card = card;
                                            return card.attach('#sbs-square-card-container');
                                        }));
                                    }
                                    if (enabled.includes('gift_card') && typeof payments.giftCard === 'function') {
                                        initTasks.push(payments.giftCard().then(function(giftCard) {
                                            state.methods.gift_card = giftCard;
                                            return giftCard.attach('#sbs-square-gift-card-container');
                                        }).catch(function() {}));
                                    }
                                    if (enabled.includes('apple_pay') && typeof payments.applePay === 'function') {
                                        initTasks.push(payments.applePay(paymentRequest).then(function(applePay) {
                                            state.methods.apple_pay = applePay;
                                            $('#sbs-square-apple-pay-button').off('click.squareApple').on('click.squareApple', function(e) {
                                                e.preventDefault();
                                                self._handleSquareWalletButton(payload, applePay, 'apple_pay');
                                            });
                                        }).catch(function() {
                                            $('[data-method="apple_pay"], [data-method-panel="apple_pay"]').hide();
                                        }));
                                    }
                                    if (enabled.includes('google_pay') && typeof payments.googlePay === 'function') {
                                        initTasks.push(payments.googlePay(paymentRequest).then(function(googlePay) {
                                            state.methods.google_pay = googlePay;
                                            return googlePay.attach('#sbs-square-google-pay');
                                        }).catch(function() {
                                            $('[data-method="google_pay"], [data-method-panel="google_pay"]').hide();
                                        }));
                                    }
                                    if (enabled.includes('cash_app_pay') && typeof payments.cashAppPay === 'function') {
                                        initTasks.push(payments.cashAppPay(paymentRequest, {
                                            redirectURL: window.location.href,
                                            referenceId: 'yatoon-' + Date.now()
                                        }).then(function(cashAppPay) {
                                            state.methods.cash_app_pay = cashAppPay;
                                            cashAppPay.addEventListener('ontokenization', function(event) {
                                                const detail = event.detail || {};
                                                if (detail.error) {
                                                    self._showToast('Cash App Pay could not be authorized.', 'error');
                                                    return;
                                                }
                                                if (detail.tokenResult && detail.tokenResult.status === 'OK') {
                                                    self._finishSquareTokenPayment(payload, detail.tokenResult.token, 'cash_app_pay');
                                                }
                                            });
                                            return cashAppPay.attach('#sbs-square-cash-app-pay');
                                        }).catch(function() {
                                            $('[data-method="cash_app_pay"], [data-method-panel="cash_app_pay"]').hide();
                                        }));
                                    }

                                    return Promise.all(initTasks).then(function() {
                                        self._squarePaymentState = state;
                                        selectMethod(state.methods[state.selected] ? state.selected : (state.methods.card ? 'card' : Object.keys(state.methods)[0]));
                                        resolve(state);
                                    });
                                });
                        })
                        .catch(function(err) {
                            self._squareCardPromise = null;
                            reject(err);
                        });
                });
            }
            return this._squareCardPromise;
        }

        _finishSquareTokenPayment(payload, token, method) {
            const self = this;
            $('#sbs-square-payment-status').text('Completing ' + (method || 'Square') + ' payment...');
            return this._sendSquareTokenPayment(payload, token)
                .then(function(paymentId) {
                    self._squarePreparedPaymentId = paymentId;
                    $('#sbs-square-payment-status').text('Payment approved by Square. Confirming your booking...');
                    self._isSubmitting = false;
                    self.submitBooking();
                    return paymentId;
                })
                .catch(function(err) {
                    self._showToast(err && err.message ? err.message : 'Square payment failed. Please try again.', 'error');
                    throw err;
                });
        }

        _handleSquareWalletButton(payload, paymentMethod, method) {
            const self = this;
            $('#sbs-square-payment-status').text('Opening ' + (method === 'apple_pay' ? 'Apple Pay' : 'Square wallet') + '...');
            return paymentMethod.tokenize()
                .then(function(result) {
                    if (!result || result.status !== 'OK' || !result.token) {
                        const detail = result && result.errors && result.errors.length
                            ? result.errors.map(function(e) { return e.message; }).join(' ')
                            : 'The wallet payment was not authorized.';
                        throw new Error(detail);
                    }
                    return self._finishSquareTokenPayment(payload, result.token, method);
                })
                .catch(function(err) {
                    $('#sbs-square-payment-status').text(err && err.message ? err.message : 'Wallet payment failed.');
                    self._showToast(err && err.message ? err.message : 'Wallet payment failed. Please try again.', 'error');
                });
        }

        _sendSquareTokenPayment(payload, token) {
            const fd = new FormData();
            Object.keys(payload).forEach(function(key) {
                const val = payload[key];
                if (Array.isArray(val)) {
                    val.forEach(function(itemVal) { fd.append(key + '[]', itemVal); });
                } else {
                    fd.append(key, val == null ? '' : val);
                }
            });
            fd.set('action', 'yatoon_square_create_payment');
            fd.set('source_id', token);
            return fetch((yatoonData.ajax_url || yatoonData.ajaxUrl), {
                method: 'POST',
                body: fd,
                credentials: 'same-origin'
            }).then(function(resp) { return resp.json(); }).then(function(json) {
                if (!json || !json.success || !json.data || !json.data.payment_id) {
                    const msg = json && json.data && json.data.message ? json.data.message : 'Square payment failed. Please try again.';
                    throw new Error(msg);
                }
                return json.data.payment_id;
            });
        }

        _createSquarePaymentForPayload(payload) {
            const self = this;
            if (!this._squarePaymentsReady()) {
                return Promise.reject(new Error('Square payment is enabled but the secure payment form is unavailable.'));
            }

            return this._ensureSquarePaymentCard(payload)
                .then(function(state) {
                    $('#sbs-square-payment-box').show();
                    const box = document.getElementById('sbs-square-payment-box');
                    if (box) box.scrollIntoView({ behavior: 'smooth', block: 'center' });
                    const method = state.selected || 'card';
                    const paymentMethod = state.methods[method];
                    if (!paymentMethod) {
                        throw new Error('This Square payment method is not available on this device. Please choose another method.');
                    }
                    if (method === 'cash_app_pay') {
                        $('#sbs-square-payment-status').text('Tap the Cash App Pay button above to authorize payment.');
                        return new Promise(function() {});
                    }
                    $('#sbs-square-payment-status').text('Securing your payment with Square...');
                    return paymentMethod.tokenize();
                })
                .then(function(result) {
                    if (!result || result.status !== 'OK' || !result.token) {
                        const detail = result && result.errors && result.errors.length
                            ? result.errors.map(function(e) { return e.message; }).join(' ')
                            : 'Please check your card details and try again.';
                        throw new Error(detail);
                    }
                    return self._sendSquareTokenPayment(payload, result.token);
                })
                .then(function(paymentId) {
                    $('#sbs-square-payment-status').text('Payment approved by Square.');
                    return paymentId;
                })
                .catch(function(err) {
                    $('#sbs-square-payment-status').text(err && err.message ? err.message : 'Square payment failed.');
                    self._showToast(err && err.message ? err.message : 'Square payment failed. Please try again.', 'error');
                    throw err;
                });
        }

        submitBooking() {
            const self = this;
            if (this._isSubmitting) {
                return;
            }

            // -- Turnstile: run the invisible check right now, at the moment of
            // submission, so the token is always fresh and never blocked by a
            // hidden step container. Runs once per submit attempt.
            if (!this._turnstileTokenFetched && typeof window.yatoonGetTurnstileToken === 'function') {
                this._turnstileTokenFetched = true;
                window.yatoonGetTurnstileToken().then(function(token) {
                    window.yatoonTurnstileToken = token;
                    self._turnstileTokenFetched = false;
                    self.submitBooking();
                });
                return;
            }

            // -- OTP Verification Gate ----------------------------------------
            const otpSection   = document.getElementById('sbs-otp-section');
            const verifyEmail  = (typeof yatoonData !== 'undefined') && yatoonData.verifyEmailOtp === '1';
            const verifySms    = (typeof yatoonData !== 'undefined') && yatoonData.verifySmsOtp   === '1';
            if (otpSection && (verifyEmail || verifySms)) {
                const alreadyVerified = otpSection.dataset.verified === '1';
                if (!alreadyVerified) {
                    const email  = (document.getElementById('sbs-customer-email') || {}).value || '';
                    const phone  = (document.getElementById('sbs-customer-phone') || {}).value || '';
                    const name   = (document.getElementById('sbs-customer-name')  || {}).value || '';
                    const channel = verifySms ? 'sms' : 'email';
                    otpSection.style.display = 'block';
                    document.getElementById('sbs-otp-prompt').textContent =
                        channel === 'sms'
                        ? 'We are sending a verification code to your phone. Please enter it below.'
                        : 'We are sending a verification code to ' + email + '. Please enter it below.';
                    // Send OTP automatically if not yet sent
                    if (!otpSection.dataset.sent) {
                        otpSection.dataset.sent = '1';
                        jQuery.post(yatoonData.ajaxUrl, {
                            action:'yatoon_send_otp', nonce: yatoonData.nonce,
                            channel, email, phone, name
                        }, function(resp) {
                            const msg = document.getElementById('sbs-otp-msg');
                            if(msg) msg.textContent = resp.success ? resp.data.message : (resp.data && resp.data.message ? resp.data.message : 'Failed to send code.');
                            const resend = document.getElementById('sbs-otp-resend-btn');
                            if(resend) resend.style.display = '';
                        });
                    }
                    // Verify button handler (one-time bind via data flag)
                    const verifyBtn = document.getElementById('sbs-otp-verify-btn');
                    if(verifyBtn && !verifyBtn.dataset.bound) {
                        verifyBtn.dataset.bound = '1';
                        verifyBtn.addEventListener('click', function() {
                            const code = (document.getElementById('sbs-otp-input') || {}).value || '';
                            const msgEl = document.getElementById('sbs-otp-msg');
                            jQuery.post(yatoonData.ajaxUrl, {
                                action:'yatoon_verify_otp', nonce: yatoonData.nonce,
                                channel, email, phone, code
                            }, function(resp) {
                                if(resp.success) {
                                    otpSection.dataset.verified = '1';
                                    otpSection.dataset.verifiedKey = resp.data && resp.data.verified_key ? resp.data.verified_key : '';
                                    otpSection.style.display = 'none';
                                    self.submitBooking(); // re-trigger now verified
                                } else {
                                    if(msgEl) msgEl.textContent = resp.data && resp.data.message ? resp.data.message : 'Incorrect code.';
                                    if(msgEl) msgEl.style.color = '#dc2626';
                                }
                            });
                        });
                    }
                    // Resend handler
                    const resendBtn = document.getElementById('sbs-otp-resend-btn');
                    if(resendBtn && !resendBtn.dataset.bound) {
                        resendBtn.dataset.bound = '1';
                        resendBtn.addEventListener('click', function() {
                            otpSection.dataset.sent = '';
                            const msgEl = document.getElementById('sbs-otp-msg');
                            if(msgEl) msgEl.textContent = '';
                            self.submitBooking();
                        });
                    }
                    return; // halt - wait for user to verify
                }
            }
            // -- End OTP Gate -------------------------------------------------

            this._isSubmitting = true;
            if (this.bookingCart.length === 0 && this.selectedDateTime && (this.pendingServices.length > 0 || this.selectedService)) {
                this.addToCart();
            }
            if (this.bookingCart.length === 0) {
                this._isSubmitting = false;
                this._showToast('Please add at least one service to your booking.', 'error');
                return;
            }
            const submitBtn = $('.sbs-btn-submit');
            const cartNextBtn = $('#sbs-cart-next-btn');
            const sidebarNextBtn = $('#sbs-sidebar-next-btn');
            const origText  = submitBtn.text();
            const origCartText = cartNextBtn.text();
            const origSidebarText = sidebarNextBtn.text();
            const restoreSubmitButtons = () => {
                this._isSubmitting = false;
                submitBtn.prop('disabled', false).text(origText);
                cartNextBtn.prop('disabled', false).removeClass('sbs-submitting').text(origCartText || 'Confirm Booking');
                sidebarNextBtn.prop('disabled', false).removeClass('sbs-submitting').text(origSidebarText || 'Confirm Booking');
            };
            submitBtn.prop('disabled', true).text('Confirming your booking...');
            cartNextBtn.prop('disabled', true).addClass('sbs-submitting').text('CONFIRMING...');
            sidebarNextBtn.prop('disabled', true).addClass('sbs-submitting').text('CONFIRMING...');
            this._updateLegalBoxes();

            const customerInfo = {
                customer_name:  $('#sbs-customer-name').val(),
                customer_email: $('#sbs-customer-email').val(),
                  customer_phone: $('#sbs-customer-phone').val(),
                  notes:          $('#sbs-customer-notes').val(),
                  coupon_code:    $('#sbs-coupon-code').length ? $('#sbs-coupon-code').val() : '',
                  consent_accepted: $('#sbs-consent-accepted').length ? ($('#sbs-consent-accepted').is(':checked') ? 1 : 0) : 0,
                consent_signature: $('#sbs-consent-signature').length ? $('#sbs-consent-signature').val() : '',
                policy_acknowledged: $('#sbs-policy-acknowledged').length ? ($('#sbs-policy-acknowledged').is(':checked') ? 1 : 0) : 0,
                tip_amount: parseFloat($('#sbs-tip-amount').val()) || 0,
                gift_card_code: $('#sbs-gift-card-code-confirmed').val() || '',
                custom_fields: (function() {
                    const cf = {};
                    $('[name^="cf["]').each(function() {
                        const key = $(this).attr('name').replace(/^cf\[|\]$/g,'');
                        if ($(this).is(':checkbox')) { if ($(this).is(':checked')) cf[key] = $(this).val(); }
                        else if ($(this).is(':radio')) { if ($(this).is(':checked')) cf[key] = $(this).val(); }
                        else cf[key] = $(this).val();
                    });
                    return JSON.stringify(cf);
                })()
            };
            const guestContacts = this._collectGuestContacts();
            // -- Phone validation on submit ---------------------------------
            const phoneMode = (typeof yatoonData !== 'undefined' && yatoonData.phoneMode) ? yatoonData.phoneMode : 'us_only';
            if (phoneMode === 'us_only' && customerInfo.customer_phone) {
                const phoneErr = validateUSPhone(customerInfo.customer_phone);
                if (phoneErr) {
                    restoreSubmitButtons();
                    showPhoneError(phoneErr);
                    document.getElementById('sbs-customer-phone') && document.getElementById('sbs-customer-phone').scrollIntoView({ behavior: 'smooth', block: 'center' });
                    return;
                }
            }

            if (this._needsPolicyAck() && !customerInfo.policy_acknowledged) {
                restoreSubmitButtons();
                this._showToast('Please review and acknowledge the cancellation / no-show policy before booking.', 'error');
                return;
            }

            if (this._needsConsent() && (!customerInfo.consent_accepted || !customerInfo.consent_signature.trim())) {
                restoreSubmitButtons();
                this._showToast('Please read and sign the consent form before booking.', 'error');
                return;
            }

            // Each cart item = one appointment (may contain multiple services).
            // CRITICAL: Submit appointments SEQUENTIALLY (not in parallel) so that the
            // server-side staff-overlap check sees previously committed bookings.
            // Parallel submission causes a race condition where two bookings for the
            // same staff at the same time both pass the DB conflict check simultaneously.
	            const cartItems = this.bookingCart.slice(); // snapshot; each visible cart item is one appointment
            const responses = [];
            const ajaxUrl = (typeof yatoonData !== 'undefined' && (yatoonData.ajax_url || yatoonData.ajaxUrl))
                ? (yatoonData.ajax_url || yatoonData.ajaxUrl)
                : '';
            if (!ajaxUrl) {
                restoreSubmitButtons();
                this._showToast('Booking could not start because the WordPress AJAX URL is missing. Please refresh and try again.', 'error');
                return;
            }
            if (!cartItems.length) {
                restoreSubmitButtons();
                this._showToast('Please choose a service and time before confirming.', 'error');
                return;
            }

            const hasGroupedCartItem = cartItems.some(item => item && item.services && item.services.length > 1);
            const submissionGroupId = (cartItems.length > 1 || hasGroupedCartItem)
                ? ('yatoon-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 10))
                : '';
            let uploadedDesignAttachmentId = 0;
            let uploadedDesignToken = '';

            const submitNext = (idx) => {
                if (idx >= cartItems.length) {
                    // All done
                    const allOk = responses.every(r => r && r.success);
                    if (allOk) {
                        $('.sbs-form-step').removeClass('active');
                        $('.sbs-step-success').show();
                        $('#sbs-success-count').text(cartItems.length);
                        const manageResponses = responses.filter(r => r && r.success && r.data && r.data.manage_url && String(r.data.manage_url).length);
                        const firstManageUrl = manageResponses.length && manageResponses[0].data ? manageResponses[0].data.manage_url : '';
                        const firstCalendarResponse = responses.find(r => r && r.success && r.data && r.data.calendar_url && String(r.data.calendar_url).length);
                        const firstCalendarUrl = firstCalendarResponse && firstCalendarResponse.data ? firstCalendarResponse.data.calendar_url : '';
                        const portalUrl = yatoonData.portalUrl || '';
                        const portalLabel = yatoonData.portalLabel || 'My Appointments';
                        const $successActions = $('#sbs-success-actions').empty();

                        $('<div/>', {
                            class: 'sbs-success-action-note',
                            text: yatoonData.successNote || 'Your booking is confirmed. You can add it to your calendar, manage changes, or book another visit.'
                        }).appendTo($successActions);

                        if (firstManageUrl && manageResponses.length === 1) {
                            // Single booking: one clear action button
                            $('<a/>', {
                                class: 'sbs-success-action-link primary',
                                href: firstManageUrl,
                                target: '_blank',
                                rel: 'noopener',
                                text: 'Reschedule or cancel'
                            }).appendTo($successActions);
                        }
                        if (manageResponses.length > 1) {
                            const $manageList = $('<div/>', { class: 'sbs-success-manage-list' }).appendTo($successActions);
                            const hasGuestLabels = cartItems.some(item => item.guestLabel && item.guestLabel !== 'Me');
                            manageResponses.forEach((r, i) => {
                                const matchingItem = cartItems[i];
                                const guestLabel = (matchingItem && matchingItem.guestLabel && matchingItem.guestLabel !== 'Me') ? matchingItem.guestLabel : null;
                                let btnText;
                                if (guestLabel) {
                                    // Guest group booking: label by guest name
                                    btnText = 'Manage appointment for ' + guestLabel;
                                } else if (hasGuestLabels) {
                                    // Mixed: some guests, this one is "Me"
                                    btnText = 'Manage my appointment';
                                } else {
                                    // Multi-service same person: label by service name
                                    const svcName = (r.data && r.data.service_name) ? r.data.service_name : ('Appointment ' + (i + 1));
                                    btnText = 'Manage: ' + svcName;
                                }
                                $('<a/>', {
                                    class: 'sbs-success-action-link compact',
                                    href: r.data.manage_url,
                                    target: '_blank',
                                    rel: 'noopener',
                                    text: btnText
                                }).appendTo($manageList);
                            });
                        }
                        if (firstCalendarUrl) {
                            $('<a/>', {
                                class: 'sbs-success-action-link',
                                href: firstCalendarUrl,
                                text: 'Add to calendar'
                            }).appendTo($successActions);
                        }
                        if (portalUrl) {
                            $('<a/>', {
                                class: 'sbs-success-action-link',
                                href: portalUrl,
                                text: portalLabel
                            }).appendTo($successActions);
                        }
                        $('<button/>', {
                            type: 'button',
                            class: 'sbs-success-action-link sbs-success-book-again' + (!firstManageUrl ? ' primary' : ''),
                            text: 'Book again'
                        }).appendTo($successActions);
                        $successActions.show();

                        // Google Calendar + ICS calendar buttons
                        if (firstCalendarUrl) {
                            $('#sbs-cal-ics').attr('href', firstCalendarUrl);
                            const firstBookingData = responses.find(r => r && r.success && r.data && r.data.start_time);
                            if (firstBookingData && firstBookingData.data.start_time && firstBookingData.data.end_time) {
                                const fmt = function(t) { return String(t).replace(/[-:]/g,'').split('+')[0].split('.')[0].replace(' ','T'); };
                                const gTitle = encodeURIComponent((yatoonData.businessName || 'Appointment'));
                                const gStart = encodeURIComponent(fmt(firstBookingData.data.start_time));
                                const gEnd   = encodeURIComponent(fmt(firstBookingData.data.end_time));
                                const siteTz = encodeURIComponent((yatoonData && yatoonData.siteTimezone) ? yatoonData.siteTimezone : Intl.DateTimeFormat().resolvedOptions().timeZone);
                                $('#sbs-cal-google').attr('href', 'https://calendar.google.com/calendar/render?action=TEMPLATE&text=' + gTitle + '&dates=' + gStart + '/' + gEnd + '&ctz=' + siteTz);
                            } else {
                                $('#sbs-cal-google').attr('href', firstCalendarUrl);
                            }
                            $('#sbs-success-cal-row').show();
                        }

                        // Rebooking nudge - fade in after a beat, hide if admin left text blank
                        if (yatoonData.rebookingNudgeText) {
                            $('#sbs-rebooking-nudge .sbs-rebooking-text').text(yatoonData.rebookingNudgeText);
                            $('#sbs-rebooking-nudge .sbs-rebooking-btn').text(yatoonData.rebookingNudgeBtn || 'Book next visit');
                            setTimeout(function() { $('#sbs-rebooking-nudge').fadeIn(400); }, 1200);
                        }

                        // Recurring setup - show if Pro and first booking has token
                        const firstBookingData = responses.find(r => r && r.success && r.data && r.data.booking_id);
                        if (firstBookingData && firstBookingData.data.manage_token && $('#sbs-recurring-setup').length) {
                            const recurringBookingId = firstBookingData.data.booking_id;
                            const recurringToken     = firstBookingData.data.manage_token;
                            let selectedWeeks = 2;

                            setTimeout(function() { $('#sbs-recurring-setup').fadeIn(400); }, 2000);

                            // Frequency buttons
                            $(document).on('click', '.sbs-freq-btn', function() {
                                $('.sbs-freq-btn').removeClass('active');
                                $(this).addClass('active');
                                selectedWeeks = parseInt($(this).data('weeks'));
                            });

                            // Confirm button
                            $(document).on('click', '#sbs-recurring-confirm', function() {
                                const $btn = $(this).prop('disabled', true).text('Setting up...');
                                const count = parseInt($('#sbs-recurring-count').val()) || 6;
                                $.post(ajaxurl, {
                                    action:          'yatoon_setup_recurring',
                                    nonce:           yatoonData.nonce,
                                    booking_id:      recurringBookingId,
                                    manage_token:    recurringToken,
                                    frequency_weeks: selectedWeeks,
                                    total_count:     count,
                                }, function(resp) {
                                    const $result = $('#sbs-recurring-result');
                                    if (resp.success) {
                                        $btn.text('Done Yes');
                                        $result.removeClass('error').text(resp.data.message).show();
                                    } else {
                                        $btn.prop('disabled', false).text('Try again');
                                        $result.addClass('error').text((resp.data && resp.data.message) || 'Could not set up recurring.').show();
                                    }
                                }).fail(function() {
                                    $btn.prop('disabled', false).text('Try again');
                                    $('#sbs-recurring-result').addClass('error').text('Network error. Please try again.').show();
                                });
                            });
                        }
                        self.bookingCart     = [];
                        self.pendingServices = [];
                        self._clearSession();
                        // Hide the mobile cart bar - booking is complete
                        $('#sbs-mobile-cart-bar').removeClass('sbs-bar-visible').css('display', 'none');
                        self._clearCartBarSpacing();
                        self._closeDrawer();
                        self._isSubmitting = false;
                        restoreSubmitButtons();
                        cartNextBtn.hide();
                        sidebarNextBtn.hide();
                    } else {
                        const failed  = responses.filter(r => !r || !r.success);
                        const errMsg  = failed.map(r => r && r.data && r.data.message ? r.data.message : '').filter(Boolean).join(' ');
                        const hint    = errMsg ? `\n\nDetails: ${errMsg}` : '';
                        const okCount = responses.length - failed.length;
                        self._showToast(`${okCount} booking(s) confirmed, but ${failed.length} failed. Please contact us.${hint}`, 'error');
                        restoreSubmitButtons();
                    }
                    return;
                }

                const item     = cartItems[idx];
                const services = item.services || [{ service: item.service, options: item.options, staff: item.staff }];
                const staffMapRaw = item.dateTime && item.dateTime.staff_map ? item.dateTime.staff_map : null;
                const staffMap = (typeof staffMapRaw === 'string') ? (function() {
                    try { return JSON.parse(staffMapRaw); } catch(e) { return null; }
                })() : staffMapRaw;
                const remappedStaffMap = {};
                services.forEach(function(s, newIdx) {
                    const originalIdx = (typeof s._originalIndex !== 'undefined') ? s._originalIndex : newIdx;
                    if (staffMap && typeof staffMap[originalIdx] !== 'undefined') {
                        remappedStaffMap[newIdx] = staffMap[originalIdx];
                    }
                });
                const staffMapJson = Object.keys(remappedStaffMap).length
                    ? JSON.stringify(remappedStaffMap)
                    : (staffMapRaw ? (typeof staffMapRaw === 'string' ? staffMapRaw : JSON.stringify(staffMapRaw)) : '');
                const itemGuestLabel = item.guestLabel || (services[0] && services[0].guestLabel) || 'Me';
                const guestContact = guestContacts[itemGuestLabel] || {};
                let itemNotes = customerInfo.notes || '';
                if (itemGuestLabel !== 'Me') {
                    const guestBits = [`Guest: ${itemGuestLabel}`];
                    if (guestContact.name) guestBits.push(`Name: ${guestContact.name}`);
                    if (guestContact.phone) guestBits.push(`Phone: ${guestContact.phone}`);
                    itemNotes = `${itemNotes}${itemNotes ? '\n\n' : ''}${guestBits.join(' - ')}`;
                }

                const payload = {
                    action:          'yatoon_create_booking',
                    nonce:           yatoonData.nonce,
                    start_at:        item.dateTime.start_at,
                    // staff_map: Square real-time staff assignment captured at Step 4.
                    // Key = service position index (0,1,2...), value = Square team_member_id.
                    // Passed to PHP so Pass2 can assign staff without re-querying Square.
                    staff_map_json:  staffMapJson,
                    services_json:   JSON.stringify(services.map(s => ({
                        service_id: s.service.id,
                        staff_id:   s.staff ? s.staff.id : 0,
                        option_ids: s.options.map(o => o.id),
                        guest_label: s.guestLabel || item.guestLabel || 'Me'
                    }))),
                    // Legacy single-service fields (fallback for PHP)
                    service_id:       services[0].service.id,
                    staff_id:         services[0].staff ? services[0].staff.id : 0,
                    selected_options: services[0].options.map(o => o.id),
                    notes:            itemNotes,
                    guest_label:      itemGuestLabel,
                    guest_name:       guestContact.name || '',
                    guest_phone:      guestContact.phone || '',
                    booking_group_id: submissionGroupId,
                    coupon_code:      customerInfo.coupon_code,
                    customer_name:    customerInfo.customer_name,
                    customer_email:   customerInfo.customer_email,
                    customer_phone:   customerInfo.customer_phone,
                    otp_channel:      ((typeof yatoonData !== 'undefined') && yatoonData.verifySmsOtp === '1') ? 'sms' : 'email',
                    otp_verified_key: (document.getElementById('sbs-otp-section') && document.getElementById('sbs-otp-section').dataset.verifiedKey) ? document.getElementById('sbs-otp-section').dataset.verifiedKey : '',
                    consent_accepted: customerInfo.consent_accepted,
                    consent_signature: customerInfo.consent_signature,
                    policy_acknowledged: customerInfo.policy_acknowledged,
                    design_attachment_id: uploadedDesignAttachmentId || '',
                    design_upload_token: uploadedDesignToken || '',
                    website:          $('#sbs-website').val() || '',
                    form_rendered_at: $('#sbs-form-rendered-at').val() || '',
                    cf_turnstile_response: (typeof window.yatoonTurnstileToken !== 'undefined') ? window.yatoonTurnstileToken : '',
                    yatoon_test_booking: $('.sbs-booking-container').hasClass('yatoon-test-mode') ? '1' : ''
                };
                const designInput = document.getElementById('sbs-design-reference');
                const designFile = designInput && designInput.files && designInput.files[0] ? designInput.files[0] : null;
                let ajaxData = payload;
                let ajaxProcessData = true;
                let ajaxContentType = 'application/x-www-form-urlencoded; charset=UTF-8';
                if (designFile && !uploadedDesignAttachmentId) {
                    ajaxData = new FormData();
                    Object.keys(payload).forEach(function(key) {
                        const val = payload[key];
                        if (Array.isArray(val)) {
                            val.forEach(function(itemVal) {
                                ajaxData.append(key + '[]', itemVal);
                            });
                        } else {
                            ajaxData.append(key, val);
                        }
                    });
                    ajaxData.append('design_reference', designFile);
                    ajaxProcessData = false;
                    ajaxContentType = false;
                }

                const sendBookingRequest = function() {
                $.ajax({
                    url:  ajaxUrl,
                    type: 'POST',
                    timeout: 45000,
                    data: ajaxData,
                    processData: ajaxProcessData,
                    contentType: ajaxContentType,
                    success: function(resp) {
                        try {
                            if (typeof resp === 'string') {
                                const jsonStart = resp.indexOf('{');
                                const jsonText = jsonStart >= 0 ? resp.slice(jsonStart) : resp;
                                resp = JSON.parse(jsonText);
                            }
                            responses.push(resp);
                            if (!resp || !resp.success) {
                                // Stop on first failure - don't submit remaining appointments
                                const errMsg = resp && resp.data && resp.data.message ? resp.data.message : '';
                                const hint   = errMsg ? `\n\nDetails: ${errMsg}` : '';
                                const okCount = responses.length - 1;
                                self._showToast(`${okCount} booking(s) confirmed, but appointment ${idx + 1} failed. Please contact us.${hint}`, 'error');
                                restoreSubmitButtons();
                                return;
                            }
                            if (resp.data && resp.data.design_attachment_id) {
                                uploadedDesignAttachmentId = parseInt(resp.data.design_attachment_id, 10) || uploadedDesignAttachmentId;
                            }
                            if (resp.data && resp.data.design_upload_token) {
                                uploadedDesignToken = String(resp.data.design_upload_token || '');
                            }
                            submitNext(idx + 1);
                        } catch (err) {
                            console.error('Yatoon booking submit failed after server response:', err);
                            self._showToast('The booking response could not be displayed. Please check the booking list or contact the business before trying again.', 'error');
                            restoreSubmitButtons();
                        }
                    },
                    error: function(xhr, status) {
                        responses.push({ success: false });
                        const okCount = responses.length - 1;
                        const reason = status === 'timeout'
                            ? 'the request timed out'
                            : 'a network error';
                        self._showToast(`${okCount} booking(s) confirmed, but appointment ${idx + 1} failed due to ${reason}. Please contact us.`, 'error');
                        restoreSubmitButtons();
                    }
                });
                };

                if (self._squarePreparedPaymentId) {
                    if (ajaxData instanceof FormData) {
                        ajaxData.set('square_payment_id', self._squarePreparedPaymentId);
                    } else {
                        ajaxData.square_payment_id = self._squarePreparedPaymentId;
                    }
                    self._squarePreparedPaymentId = '';
                    sendBookingRequest();
                } else if (self._squarePaymentsReady()) {
                    self._createSquarePaymentForPayload(payload)
                        .then(function(paymentId) {
                            if (ajaxData instanceof FormData) {
                                ajaxData.set('square_payment_id', paymentId);
                            } else {
                                ajaxData.square_payment_id = paymentId;
                            }
                            sendBookingRequest();
                        })
                        .catch(function() {
                            restoreSubmitButtons();
                        });
                } else {
                    sendBookingRequest();
                }
            };

            try {
                submitNext(0);
            } catch (err) {
                console.error('Yatoon booking submit error:', err);
                this._showToast('Something went wrong before the booking could be sent. Please refresh and try again.', 'error');
                restoreSubmitButtons();
            }
        }
        
        addToCart() {
            if (this._pendingAdd) return;
            this._pendingAdd = true;

            // Build one cart item from ALL pendingServices + shared dateTime
            const services = this.pendingServices.length > 0
                ? [...this.pendingServices]
                : [{ service: { ...this.selectedService },
                     options: [...this.selectedOptions],
                     staff: this.selectedStaff,
                     eligibleStaffIds: (this.selectedService && this.selectedService._eligibleStaffIds) || [] }];

            const cartItem = {
                services: services,      // array of {service, options, staff}
                dateTime: this.selectedDateTime,
                // Convenience aliases for single-service backward compat
                service:  services[0].service,
                options:  services[0].options,
                staff:    services[0].staff,
            };
            this.bookingCart.push(cartItem);

            // Reset everything for next appointment
            this.pendingServices  = [];
            this._nextGuestLabel  = null;
            this._guestCount      = 0;
            this.selectedService  = null;
            this.selectedCategory = null;
            this.selectedOptions  = [];
            this.selectedStaff    = null;
            this.selectedDateTime = null;

            this.renderCart();
            this._renderGuestContactFields();
            this._saveSession();
            setTimeout(() => { this._pendingAdd = false; }, 300);
        }
        
        renderCart() {
            const container = $('#sbs-booking-cart-list');
            if (!container.length) return;
            
            const MAX_APPOINTMENTS = 2;
            // Show/hide the Add Another Appointment button based on limit and mode
            const $addAnotherBtn = $('#sbs-add-another-appt-btn');
            if (!yatoonData.multiApptEnabled || this.bookingCart.length >= MAX_APPOINTMENTS) {
                $addAnotherBtn.hide();
            } else {
                $addAnotherBtn.show();
            }
            
            if (this.bookingCart.length === 0) {
                container.html(`<div style="text-align:center;padding:28px 16px;">
                    <svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin:0 auto 12px;display:block;">
                        <rect x="4" y="8" width="40" height="32" rx="5" fill="#f3f4f6" stroke="#e5e7eb" stroke-width="1.5"/>
                        <rect x="10" y="16" width="18" height="3" rx="1.5" fill="#d1d5db"/>
                        <rect x="10" y="22" width="12" height="3" rx="1.5" fill="#e5e7eb"/>
                        <circle cx="36" cy="24" r="7" fill="var(--primary-color, #ec4899)" opacity="0.15"/>
                        <path d="M33 24h6M36 21v6" stroke="var(--primary-color, #ec4899)" stroke-width="2" stroke-linecap="round"/>
                    </svg>
                    <p style="font-size:14px;font-weight:600;color:#374151;margin:0 0 4px;">Pick a service above</p>
                    <p style="font-size:12px;color:#9ca3af;margin:0;">Tap any service card to add it to your booking</p>
                </div>`);
                this._applyConfirmBtn();
                return;
            }
            
            let html = '';
            let totalPrice = 0;
            let totalDuration = 0;
            
            this.bookingCart.forEach((item, index) => {
                const price    = this.getItemPrice(item);
                const duration = this._getItemVisitDuration(item);
                totalPrice    += price;
                totalDuration += duration;
                const dateParts = item.dateTime.date.split('-');
                const dateObj   = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]);
                const dateStr   = dateObj.toLocaleDateString('en-US', { month:'short', day:'numeric' });
                const title = this.bookingCart.length === 1 ? 'Appointment details' : `Appointment ${index + 1}`;

                // Build services rows with guest labels + read-only staff confirmation.
                const services = item.services || [{ service: item.service, options: item.options, staff: item.staff }];
                const hasPeople = services.some(s => s.guestLabel && s.guestLabel !== 'Me');
                let servicesHtml = '';
                const self2 = this;
                services.forEach((s, si) => {
                    const svcLabel = self2._svcDisplayLabelHtml(s.service, s.options);
                    const gLabel   = s.guestLabel || '';
                    const safeGLabel = self2._esc(self2._displayGuestLabel(gLabel));
                    const badge    = (hasPeople || gLabel) && gLabel
                        ? `<span style="display:inline-block;background:${gLabel === 'Me' ? '#dbeafe' : '#dcfce7'};color:${gLabel === 'Me' ? '#1d4ed8' : '#16a34a'};border-radius:4px;font-size:10px;font-weight:700;padding:1px 5px;margin-right:4px;">${safeGLabel}</span>`
                        : '';
                    const badgeSep = badge ? `<span style="color:#9ca3af;margin-right:4px;">-</span>` : '';
                    const staffName = self2._staffDisplayName(s.staff && s.staff.name);
                    servicesHtml += `<div style="margin-bottom:8px;font-size:13px;color:#374151;" data-cart-idx="${index}" data-svc-idx="${si}">
                        <div class="sbs-review-service-row">${badge}${badgeSep}<strong class="sbs-review-service-name">${svcLabel}</strong></div>
                        <div style="margin-top:3px;"><span class="sbs-review-staff-name">${self2._esc(staffName)}</span></div>
                    </div>`;
                });

                html += `
                    <div class="sbs-cart-item" style="border:1px solid #e5e7eb;border-radius:8px;padding:16px;margin-bottom:12px;background:#fff;">
                        <div style="display:flex;justify-content:space-between;align-items:start;margin-bottom:10px;">
                            <h4 style="margin:0;font-size:16px;font-weight:600;">${title}</h4>
                            <div style="display:flex;gap:8px;">
                                <button type="button" class="sbs-cart-edit" data-index="${index}" style="background:#e0e7ff;color:#4338ca;border:none;padding:4px 12px;border-radius:4px;cursor:pointer;font-size:12px;font-weight:500;">EDIT</button>
                                <button type="button" class="sbs-cart-remove" data-index="${index}" style="background:#fee;color:#dc2626;border:none;padding:4px 12px;border-radius:4px;cursor:pointer;font-size:12px;">REMOVE</button>
                            </div>
                        </div>
                        ${servicesHtml}
                        <p style="margin:8px 0 0;font-size:13px;color:#6b7280;line-height:1.45;">${this._formatItemTimeRange(item, dateStr)}<br><span>Appointment Length: ${duration} min</span></p>
                        <div style="font-size:18px;font-weight:700;color:#ec4899;margin-top:8px;">$${price.toFixed(2)}</div>
                    </div>`;
            });
            
            html += `<div style="border-top:2px solid #e5e7eb;margin-top:16px;padding-top:16px;">
                <div style="display:flex;justify-content:space-between;font-size:16px;font-weight:600;">
                    <span>Total:</span>
                    <span style="color:#ec4899;">$${totalPrice.toFixed(2)} &bull; Appointment Length: ${totalDuration} min</span>
                </div>
            </div>`;
            
            container.html(html);
            container.off('change.cartStaff');

            this._applyConfirmBtn();
            this._updateMobileCartBar();
        }
        
        _updateMobileCartBar() {
            // -- Determine if we have anything worth showing ------------------
            const hasPending  = this.pendingServices.length > 0;
            const hasService  = !!this.selectedService;
            const hasCart     = this.bookingCart.length > 0;
            const hasAnything = hasPending || hasService || hasCart;

            // Hide bar completely when nothing is selected yet.
            if (!hasAnything) {
                $('#sbs-mobile-cart-bar').removeClass('sbs-bar-visible').css('display', 'none');
                this._clearCartBarSpacing();
                return;
            }

            // -- Calculate totals ---------------------------------------------
            let totalPrice = 0, totalDuration = 0, nameText = '', metaText = '';
            const stepLabel = 'Step ' + this.currentStep + '/5';

            if (hasCart) {
                // Completed cart items
                let serviceCount = 0;
                this.bookingCart.forEach(item => {
                    totalPrice    += this.getItemPrice(item);
                    totalDuration += this._getItemVisitDuration(item);
                    serviceCount  += (item.services && item.services.length) ? item.services.length : 1;
                });
                nameText = 'Selected services';
                metaText = 'Tap to view cart';

            } else if (hasPending) {
                // Multi-service in progress
                this.pendingServices.forEach(s => {
                    totalPrice += this._optionsPrice(s.service, s.options);
                });
                totalDuration = this._durationByGuest(this.pendingServices).totalDuration;
                const pendingCount = this.pendingServices.length;
                nameText = 'Selected services';
                const pendingParts = [];
                if (totalPrice > 0)    pendingParts.push('$' + totalPrice.toFixed(2));
                if (totalDuration > 0) pendingParts.push('Appointment Length: ' + totalDuration + ' min');
                pendingParts.push(stepLabel);
                metaText = 'Tap to view cart';

            } else if (hasService) {
                // Single service in progress
                const { duration, price } = this._calcDurationPrice();
                const svc = this.selectedService;
                nameText = 'Selected service';
                const parts = [];
                if (price > 0)    parts.push('$' + price.toFixed(2));
                if (duration > 0) parts.push(duration + ' min');
                parts.push(stepLabel);
                metaText = 'Tap to view cart';
                if (this.currentStep === 2) {
                    const needsMain = !!this._optionHasMainGroup;
                    const hasMain = (this.selectedOptions || []).some(function(o) { return !o.is_addon; });
                    if (needsMain && !hasMain) {
                        nameText = 'Choose an option';
                        metaText = 'Add-ons are optional after the main choice';
                    }
                }
            }

            // -- Show "Review & Book" button if time is selected --------------
            const hasTime = !!this.selectedDateTime;
            const $nextBtn = $('#sbs-cart-next-btn');
            const $sidebarNextBtn = $('#sbs-sidebar-next-btn');
            $nextBtn.add($sidebarNextBtn)
                .attr('aria-disabled', 'false')
                .removeClass('is-disabled');
            const needsMainOption = this.currentStep === 2 && !!this._optionHasMainGroup;
            const hasMainOption = (this.selectedOptions || []).some(function(o) { return !o.is_addon; });
            const itemCount = hasCart
                ? this.bookingCart.reduce((count, item) => count + ((item.services && item.services.length) ? item.services.length : 1), 0)
                : (hasPending ? this.pendingServices.length : 1);
            if (itemCount > 0 && this.currentStep >= 2 && !(this.currentStep === 2 && nameText === 'Choose an option')) {
                nameText = itemCount + (itemCount === 1 ? ' service' : ' services');
                const barServices = hasCart
                    ? this.bookingCart.flatMap((item) => item.services && item.services.length ? item.services : [{ service: item.service, guestLabel: item.guestLabel || 'Me' }])
                    : (hasPending ? this.pendingServices : [{ service: this.selectedService, guestLabel: this._nextGuestLabel || 'Me' }]);
                const peopleCount = this._peopleCount(barServices);
                metaText = this.currentStep === 5
                    ? 'Review details'
                    : (peopleCount > 1 ? (peopleCount + ' people · Tap cart') : 'Tap cart');
            }
            $('#sbs-cart-back-btn').toggle(this.currentStep > 1);
            $('#sbs-cart-expand-btn').toggle(this.currentStep !== 2 && this.currentStep !== 5);
            $('#sbs-mobile-cart-bar')
                .removeClass('sbs-unified-step-1 sbs-unified-step-2 sbs-unified-step-3 sbs-unified-step-4 sbs-unified-step-5 sbs-needs-choice')
                .addClass('sbs-unified-step-' + this.currentStep)
                .toggleClass('sbs-needs-choice', needsMainOption && !hasMainOption);
            if ((hasPending || hasService) && hasTime) {
                $nextBtn.prop('disabled', false).text('Review & Book').show();
                $sidebarNextBtn.prop('disabled', false).text('Review & Book').show();
            } else if (hasCart) {
                $nextBtn.prop('disabled', false).text(this.currentStep === 5 ? 'Book Now' : 'Checkout').show();
                $sidebarNextBtn.prop('disabled', false).text(this.currentStep === 5 ? 'Book Now' : 'Checkout').show();
            } else if (this.currentStep === 1 && hasPending) {
                $nextBtn.prop('disabled', false).text('Continue').show();
                $sidebarNextBtn.prop('disabled', false).text('Continue').show();
            } else if (this.currentStep === 2 && hasService) {
                $nextBtn
                    .prop('disabled', false)
                    .attr('aria-disabled', needsMainOption && !hasMainOption ? 'true' : 'false')
                    .toggleClass('is-disabled', needsMainOption && !hasMainOption)
                    .text(needsMainOption && !hasMainOption ? 'Continue' : 'Continue')
                    .show();
                $sidebarNextBtn
                    .prop('disabled', false)
                    .attr('aria-disabled', needsMainOption && !hasMainOption ? 'true' : 'false')
                    .toggleClass('is-disabled', needsMainOption && !hasMainOption)
                    .text(needsMainOption && !hasMainOption ? 'Continue' : 'Continue')
                    .show();
            } else if (this.currentStep === 3 && hasService) {
                $nextBtn
                    .prop('disabled', !this.selectedStaff)
                    .text('Continue')
                    .show();
                $sidebarNextBtn
                    .prop('disabled', !this.selectedStaff)
                    .text('Continue')
                    .show();
            } else if (this.currentStep === 3 && hasPending) {
                $nextBtn.prop('disabled', false).text('Choose Time').show();
                $sidebarNextBtn.prop('disabled', false).text('Choose Time').show();
            } else if (this.currentStep === 4 && (hasPending || hasService)) {
                $nextBtn.prop('disabled', true).text('Pick a Time').show();
                $sidebarNextBtn.prop('disabled', true).text('Pick a Time').show();
            } else {
                $nextBtn.prop('disabled', false).hide();
                if (this.currentStep !== 2) {
                    $sidebarNextBtn.prop('disabled', false).hide();
                }
            }

            const unifiedLabel = this.currentStep === 4
                ? (hasTime ? 'Review booking' : 'Pick a time')
                : (this.currentStep === 5 || hasTime || hasCart ? 'Confirm Booking' : 'Continue');
            if ($nextBtn.is(':visible')) $nextBtn.text(unifiedLabel);
            if ($sidebarNextBtn.is(':visible')) $sidebarNextBtn.text(unifiedLabel);

            $('#sbs-cart-name').text(nameText || 'Booking in progress');
            $('#sbs-cart-meta').text(metaText);
            $('#sbs-cart-avatar').html('<span class="sbs-cart-icon" aria-hidden="true"></span><span class="sbs-cart-count">' + itemCount + '</span>');
            $('#sbs-mobile-cart-bar').css('display', this.currentStep === 2 ? 'grid' : 'flex').addClass('sbs-bar-visible');

            // Push the widget's own content up so the fixed bar never covers it.
            this._syncCartBarSpacing();
        }

        // FIX (mobile Chrome overscroll blank-space bug): keeps the widget
        // container's bottom padding in sync with the *actual* rendered height
        // of the fixed mobile cart bar via ResizeObserver, instead of guessing
        // with a fixed setTimeout(300). Scoped to .sbs-booking-container
        // (not <body>), so it never inflates the surrounding page/theme and
        // never leaves stray, oversized blank space when mobile Chrome's
        // address bar hides/shows mid-scroll.
        _syncCartBarSpacing() {
            const barEl = document.getElementById('sbs-mobile-cart-bar');
            const $container = $('.sbs-booking-container');
            if (!barEl || !$container.length) return;

            const apply = () => {
                const barH = barEl.offsetHeight || 70;
                // CSS floats the bar ~10px off the bottom edge on mobile;
                // include that gap so nothing is under- or over-reserved.
                const bottomGap = 10;
                $container.css('padding-bottom', (barH + bottomGap) + 'px');
                document.documentElement.style.setProperty('--sbs-mobile-bar-height', barH + 'px');
            };

            apply();

            if (typeof ResizeObserver !== 'undefined') {
                if (this._cartBarRO) this._cartBarRO.disconnect();
                this._cartBarRO = new ResizeObserver(apply);
                this._cartBarRO.observe(barEl);
            } else {
                // Fallback for very old browsers without ResizeObserver
                setTimeout(apply, 300);
            }
        }

        // Undo _syncCartBarSpacing(): stop observing and remove any reserved
        // space (also cleans up stale `body` padding left by the old code).
        _clearCartBarSpacing() {
            if (this._cartBarRO) { this._cartBarRO.disconnect(); this._cartBarRO = null; }
            $('.sbs-booking-container').css('padding-bottom', '');
            $('body').css('padding-bottom', '');
        }
        
        // Confirm before removing - avoids accidental deletion
        _confirmRemove(index, isMobile) {
            const self = this;
            // Build a simple inline confirmation overlay
            const $overlay = $(`
                <div id="sbs-confirm-overlay" style="
                    position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:99999;
                    display:flex;align-items:center;justify-content:center;padding:16px;">
                  <div style="background:#fff;border-radius:12px;padding:28px 24px;max-width:340px;width:100%;text-align:center;box-shadow:0 8px 32px rgba(0,0,0,.2);">
                    <div style="font-size:28px;margin-bottom:12px;">!</div>
                    <h3 style="margin:0 0 8px;font-size:18px;font-weight:700;">Remove this appointment?</h3>
                    <p style="margin:0 0 20px;color:#6b7280;font-size:14px;">This can't be undone. You'll need to re-add the service if you change your mind.</p>
                    <div style="display:flex;gap:12px;">
                      <button id="sbs-confirm-cancel" style="flex:1;padding:12px;background:#f3f4f6;color:#374151;border:none;border-radius:8px;cursor:pointer;font-size:14px;font-weight:600;">Keep it</button>
                      <button id="sbs-confirm-delete" style="flex:1;padding:12px;background:#dc2626;color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:14px;font-weight:600;">Yes, remove</button>
                    </div>
                  </div>
                </div>`);
            $('body').append($overlay);
            $overlay.on('click', '#sbs-confirm-cancel', function() { $overlay.remove(); });
            $overlay.on('click', '#sbs-confirm-delete', function() {
                $overlay.remove();
                self.removeFromCart(index);
                if (isMobile) self._renderMobileCart();
            });
        }

        // FIX: removeFromCart - proper cleanup, no localStorage persistence
        removeFromCart(index) {
            this.bookingCart.splice(index, 1);
            this._saveSession();
            this.renderCart();              // updates step-5 Your Appointments list
            this._updateMobileCartBar();
            this.updateSummary();           // ALWAYS sync the right sidebar (all steps)

            // If the user is on step 4 (date/time picker), reload the time slots
            // so busy-staff indicators reflect the updated cart immediately.
            if (this.currentStep === 4) {
                const selDate = $('.sbs-day-button.selected').data('date')
                    || $('.sbs-day-button.active').data('date');
                if (selDate) {
                    this.loadAvailableTimes(selDate);
                }
            }

            if (this.currentStep === 5) {
                if (this.bookingCart.length === 0) {
                    this._closeDrawer();
                    this.selectedService = null;
                    this.selectedCategory = null;
                    this.selectedOptions = [];
                    this.selectedStaff = null;
                    this.selectedDateTime = null;
                    this.pendingServices = [];
                    this.goToStep(1, true);
                    return;
                }
                this.updateSummaryForCheckout(); // also refresh checkout sidebar view
                // Reset submit button in case it was left disabled from a failed attempt
                const $btn = $('.sbs-btn-submit');
                if ($btn.prop('disabled')) {
                    $btn.prop('disabled', false);
                    this._applyConfirmBtn();
                }
            }
        }
        
        editCartItem(index) {
            const item = this.bookingCart[index];
            if (!item) return;
            // Restore item to "current selection" and let user re-book
            this.bookingCart.splice(index, 1);
            this.selectedService  = { ...item.service };
            this.selectedCategory = item.service.category || null;
            this.selectedOptions  = [...item.options];
            this.selectedStaff    = item.staff;
            this.selectedDateTime = null;
            this.renderCart();
            this.updateSummary();
            this.goToStep(2);
            this.loadServiceOptions();
        }

        initWeekView() {
            const self = this;
            // Expose on instance so external code (e.g. _findAndGoNextAvailable) can jump weeks
            self._weekStart = new Date();
            self._weekStart.setHours(0, 0, 0, 0);
            // Keep local alias for the closure below
            const getWeekStart = () => self._weekStart;

            // -- Month View --------------------------------------------------
            self._monthDate = new Date();
            self._monthDate.setDate(1);
            self._monthDate.setHours(0, 0, 0, 0);

            function renderMonthView() {
                const today = new Date(); today.setHours(0,0,0,0);
                const maxDate = new Date(); maxDate.setDate(maxDate.getDate() + 60);
                const year  = self._monthDate.getFullYear();
                const month = self._monthDate.getMonth();
                const monthNames = ['January','February','March','April','May','June',
                                    'July','August','September','October','November','December'];
                const dayNames = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
                const firstDay = new Date(year, month, 1).getDay();
                const daysInMonth = new Date(year, month + 1, 0).getDate();

                let html = `<div class="sbs-month-header">
                    <button type="button" class="sbs-month-prev">&lt;</button>
                    <span class="sbs-month-title">${monthNames[month]} ${year}</span>
                    <button type="button" class="sbs-month-next">&gt;</button>
                </div>
                <div class="sbs-month-grid">`;

                // Day name headers
                dayNames.forEach(d => {
                    html += `<div class="sbs-month-dayname">${d}</div>`;
                });

                // Empty cells before first day
                for (let i = 0; i < firstDay; i++) {
                    html += `<div class="sbs-month-cell empty"></div>`;
                }

                // Day cells
                for (let d = 1; d <= daysInMonth; d++) {
                    const date   = new Date(year, month, d);
                    const isPast = date < today;
                    const isFuture = date > maxDate;
                    const isToday = date.getTime() === today.getTime();
                    const y = date.getFullYear();
                    const m = String(date.getMonth() + 1).padStart(2,'0');
                    const ds = String(d).padStart(2,'0');
                    const dateStr = `${y}-${m}-${ds}`;
                    const disabled = isPast || isFuture;
                    const availability = self._dateAvailability[dateStr];
                    const availabilityClass = typeof availability !== 'undefined'
                        ? 'sbs-availability-' + self._availabilityLevel(availability)
                        : '';
                    const classes = ['sbs-month-cell sbs-day-button',
                        disabled ? 'disabled' : '',
                        availabilityClass,
                        isToday  ? 'today' : ''].filter(Boolean).join(' ');
                    html += `<button type="button" class="${classes}" data-date="${dateStr}" ${disabled ? 'disabled' : ''}>
                        <span class="sbs-month-day-number">${d}</span>
                        <span class="sbs-date-availability-meter" aria-hidden="true"><span></span></span>
                    </button>`;
                }

                html += `</div>`;
                $('.sbs-month-view').html(html);
            }

            $(document).on('click', '.sbs-month-prev', function() {
                self._monthDate.setMonth(self._monthDate.getMonth() - 1);
                renderMonthView();
            });
            $(document).on('click', '.sbs-month-next', function() {
                self._monthDate.setMonth(self._monthDate.getMonth() + 1);
                renderMonthView();
            });
            // ----------------------------------------------------------------

            function renderWeekView() {
                const currentWeekStart = self._weekStart;
                const weekStart = new Date(currentWeekStart);
                const weekEnd   = new Date(currentWeekStart);
                weekEnd.setDate(weekEnd.getDate() + 6);
                const rangeOpts = { month:'short', day:'numeric' };
                $('.sbs-week-range').text(
                    weekStart.toLocaleDateString('en-US', rangeOpts) + ' - ' +
                    weekEnd.toLocaleDateString('en-US', rangeOpts)
                );
                const today    = new Date(); today.setHours(0,0,0,0);
                const dayNames = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
                let html = '';
                for (let i = 0; i < 7; i++) {
                    const date   = new Date(weekStart);
                    date.setDate(date.getDate() + i);
                    const isPast  = date < today;
                    const isToday = date.getTime() === today.getTime();
                    const y = date.getFullYear();
                    const m = String(date.getMonth() + 1).padStart(2,'0');
                    const d = String(date.getDate()).padStart(2,'0');
                    const dateStr = `${y}-${m}-${d}`;
                    const availability = self._dateAvailability[dateStr];
                    const availabilityClass = typeof availability !== 'undefined'
                        ? 'sbs-availability-' + self._availabilityLevel(availability)
                        : '';
                    const classes = ['sbs-day-button', isPast ? 'disabled' : '', availabilityClass, isToday ? 'today' : ''].filter(Boolean).join(' ');
                    html += `<button type="button" class="${classes}" data-date="${dateStr}" ${isPast ? 'disabled' : ''}>
                        <span class="sbs-day-month">${date.toLocaleDateString('en-US', { month: 'short' })}</span>
                        <span class="sbs-day-name">${dayNames[date.getDay()]}</span>
                        <span class="sbs-day-number">${date.getDate()}</span>
                        <span class="sbs-date-availability-meter" aria-hidden="true"><span></span></span>
                    </button>`;
                }
                $('.sbs-week-days').html(html);
            }
            
            $(document).on('click', '.sbs-view-toggle', function() {
                const view = $(this).data('view');
                $('.sbs-view-toggle').removeClass('active');
                $(this).addClass('active');
                if (view === 'week') {
                    $('.sbs-week-view').addClass('active');
                    $('.sbs-month-view').removeClass('active');
                    renderWeekView();
                } else {
                    $('.sbs-week-view').removeClass('active');
                    $('.sbs-month-view').addClass('active');
                    renderMonthView();
                }
            });
            $(document).on('click', '.sbs-week-prev', function() {
                self._weekStart.setDate(self._weekStart.getDate() - 7);
                renderWeekView();
            });
            $(document).on('click', '.sbs-week-next', function() {
                self._weekStart.setDate(self._weekStart.getDate() + 7);
                renderWeekView();
            });
            $(document).on('click', '.sbs-day-button', function() {
                if ($(this).hasClass('disabled')) return;
                $('.sbs-day-button').removeClass('selected');
                $(this).addClass('selected');
                const selectedDate = $(this).data('date');
                self.loadAvailableTimes(selectedDate);
            });
            // Expose render functions so external code can refresh views
            self._renderWeekView  = renderWeekView;
            self._renderMonthView = renderMonthView;
            renderWeekView();
        }

        reset() {
            this.currentStep    = 1;
            this.selectedService  = null;
            this.selectedCategory = null;
            this.selectedOptions  = [];
            this.selectedStaff    = null;
            this.selectedDateTime = null;
            this.bookingCart      = [];
            this._pendingAdd      = false;
            
            $('#sbs-customer-form')[0].reset();
            $('#sbs-selected-service-id, #sbs-selected-staff-id, #sbs-selected-start-at, #sbs-selected-options').val('');
            this._closeDrawer();
            $('#sbs-mobile-cart-bar').removeClass('sbs-bar-visible').css('display', 'none');
            this._clearCartBarSpacing();
            $('#sbs-cart-name').text('Selected services');
            $('#sbs-cart-meta').text('Tap to view cart');
            $('.sbs-step-success').hide();
            this._clearSession(); // user is resetting - wipe saved state
            this.goToStep(1);
        }
        
        getNextDayName() {
            const tomorrow = new Date();
            tomorrow.setDate(tomorrow.getDate() + 1);
            return tomorrow.toLocaleDateString('en-US', { weekday:'long', month:'long', day:'numeric' });
        }
    }

    $(document).ready(function() {
        if ($('.sbs-booking-container').length) {
            window._sbsFormInstance = new SquareBookingForm();
            // Initial empty guide state - hide if cart/service already loaded from session
            setTimeout(function() {
                const inst = window._sbsFormInstance;
                if (inst && (inst.bookingCart.length > 0 || inst.selectedService || inst.pendingServices.length > 0)) {
                    $('#sbs-sidebar-empty-guide').hide();
                    $('#sbs-booking-summary-panel').show();
                }
            }, 100);

            // -- Load "next available opening" for each service card ----------
            const $openingPlaceholders = $('.ybs-next-opening');
            if ($openingPlaceholders.length) {
                const svcIds = [];
                $openingPlaceholders.each(function() {
                    const id = parseInt($(this).data('svc-id'), 10);
                    if (id && svcIds.indexOf(id) === -1) svcIds.push(id);
                });
                if (svcIds.length) {
                    $.post(yatoonData.ajaxUrl || yatoonData.ajax_url, {
                        action: 'yatoon_get_next_openings',
                        nonce:  yatoonData.nonce,
                        service_ids: svcIds
                    }).done(function(resp) {
                        if (!resp || !resp.success || !resp.data || !resp.data.openings) {
                            $openingPlaceholders.hide();
                            return;
                        }
                        const openings = resp.data.openings;
                        $openingPlaceholders.each(function() {
                            const $el = $(this);
                            const id  = parseInt($el.data('svc-id'), 10);
                            const op  = openings[id];
                            if (!op || !op.label) {
                                $el.hide();
                                return;
                            }
                            $el.find('.ybs-next-opening-text').text(op.label);
                            if (op.urgent) $el.addClass('is-urgent');
                            $el.addClass('is-loaded');
                        });
                    }).fail(function() {
                        $openingPlaceholders.hide();
                    });
                }
            }

            $(document).on('click', '.sbs-popular-pick[data-scroll-service]', function() {
                const serviceId = $(this).data('scroll-service');
                const $target = $('.sbs-service-item[data-service-id="' + serviceId + '"]').first();
                if (!$target.length) return;
                const $cat = $target.prevAll('.sbs-category-title').first();
                if ($cat.length) {
                    const catId = ($cat.attr('id') || '').replace(/^cat-/, '');
                    if (catId) {
                        $('.sbs-cat-tab, .sbs-cat-dropdown-item').removeClass('active');
                        $('.sbs-cat-tab[data-cat="' + catId + '"], .sbs-cat-dropdown-item[data-cat="' + catId + '"]').addClass('active');
                    }
                }
                $target[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
                $target.addClass('sbs-attention-pulse');
                setTimeout(function() { $target.removeClass('sbs-attention-pulse'); }, 900);
            });

            $(document).on('click', '.sbs-date-shortcut', function() {
                const inst = window._sbsFormInstance;
                if (!inst) return;
                const offset = parseInt($(this).data('offset'), 10) || 0;
                const d = new Date();
                d.setDate(d.getDate() + offset);
                d.setHours(0, 0, 0, 0);
                const y = d.getFullYear();
                const m = String(d.getMonth() + 1).padStart(2, '0');
                const day = String(d.getDate()).padStart(2, '0');
                const dateStr = y + '-' + m + '-' + day;
                $('.sbs-view-toggle[data-view="week"]').trigger('click');
                if (inst._weekStart) {
                    const weekStart = new Date(d);
                    weekStart.setDate(d.getDate() - d.getDay());
                    inst._weekStart = weekStart;
                    if (inst._renderWeekView) inst._renderWeekView();
                }
                $('.sbs-day-button').removeClass('selected');
                const $day = $('.sbs-day-button[data-date="' + dateStr + '"]').first();
                if ($day.length && !$day.hasClass('disabled')) {
                    $day.addClass('selected');
                    inst.loadAvailableTimes(dateStr);
                } else {
                    inst.loadAvailableTimes(dateStr);
                }
                $('.sbs-date-shortcut').removeClass('active');
                $(this).addClass('active');
            });
        }

        // -- US Phone validation handlers (functions are defined at IIFE scope above) --

        // Validate on blur
        $(document).on('blur', '#sbs-customer-phone', function() {
            const val = $(this).val().trim();
            if (!val) { showPhoneError(''); return; }
            const phoneMode = (typeof yatoonData !== 'undefined' && yatoonData.phoneMode) ? yatoonData.phoneMode : 'us_only';
            if (phoneMode === 'us_only') {
                showPhoneError(validateUSPhone(val));
            }
        });

        // Clear error while typing
        $(document).on('input', '#sbs-customer-phone', function() {
            showPhoneError('');
            $(this).css('border-color', '');
        });

        // -- Gift card apply button -------------------------------------------
        $(document).on('click', '#sbs-gift-card-apply', function() {
            const code = $('#sbs-gift-card-code').val().trim().toUpperCase();
            if (!code) return;
            const $btn = $(this).prop('disabled', true).text('Checking...');
            const $result = $('#sbs-gift-card-result');
            $.post(ajaxurl, {
                action: 'yatoon_validate_gift_card',
                nonce:  yatoonData.nonce,
                code,
            }, function(resp) {
                $btn.prop('disabled', false).text('Apply');
                $result.show();
                if (resp.success) {
                    $result.removeClass('error').addClass('success')
                        .html('Yes ' + resp.data.message);
                    $('#sbs-gift-card-code-confirmed').val(code);
                    $('#sbs-gift-card-code').prop('readonly', true);
                    $btn.text('Applied Yes').prop('disabled', true);
                } else {
                    $result.removeClass('success').addClass('error')
                        .text(resp.data?.message || 'Invalid gift card.');
                    $('#sbs-gift-card-code-confirmed').val('');
                }
            }).fail(function() {
                $btn.prop('disabled', false).text('Apply');
                $result.show().removeClass('success').addClass('error').text('Network error. Please try again.');
            });
        });

        // Auto-uppercase gift card input
        $(document).on('input', '#sbs-gift-card-code', function() {
            this.value = this.value.toUpperCase();
        });

        // -- Coupon code Apply button ------------------------------------------
        $(document).on('click', '#sbs-coupon-apply', function() {
            const code = $('#sbs-coupon-code').val().trim().toUpperCase();
            if (!code) return;
            const $btn = $(this).prop('disabled', true).text('Checking...');
            const $result = $('#sbs-coupon-result');
            const inst = window._sbsFormInstance;
            const total = inst ? (inst.cartTotal || 0) : 0;
            $.post(ajaxurl, {
                action: 'yatoon_validate_coupon',
                nonce:  yatoonData.nonce,
                coupon_code: code,
                total:  total,
            }, function(resp) {
                $btn.prop('disabled', false).text('Apply');
                $result.show();
                if (resp.success) {
                    $result.removeClass('error').addClass('success')
                        .html('Yes ' + resp.data.message);
                    $('#sbs-coupon-code').prop('readonly', true);
                    $('#sbs-coupon-discount').val(resp.data.discount || 0);
                    $btn.text('Applied Yes').prop('disabled', true);
                } else {
                    $result.removeClass('success').addClass('error')
                        .text(resp.data?.message || 'Invalid coupon code.');
                    $('#sbs-coupon-discount').val(0);
                }
            }).fail(function() {
                $btn.prop('disabled', false).text('Apply');
                $result.show().removeClass('success').addClass('error').text('Network error. Please try again.');
            });
        });

        $(document).on('input', '#sbs-coupon-code', function() {
            this.value = this.value.toUpperCase();
        });

        // -- Tip preset buttons -----------------------------------------------
        $(document).on('click', '.sbs-tip-btn', function() {
            $('.sbs-tip-btn').removeClass('active');
            $(this).addClass('active');
            const pct = $(this).data('pct');
            if (pct === 'custom') {
                $('#sbs-tip-custom-wrap').show();
                $('#sbs-tip-amount').val($('#sbs-tip-custom-amount').val() || 0);
                $('#sbs-tip-display').hide();
            } else if (pct === 0 || pct === '0') {
                $('#sbs-tip-custom-wrap').hide();
                $('#sbs-tip-amount').val(0);
                $('#sbs-tip-display').hide();
            } else {
                $('#sbs-tip-custom-wrap').hide();
                // Calculate tip from current cart total
                const inst = window._sbsFormInstance;
                let subtotal = 0;
                if (inst) {
                    (inst.bookingCart || []).forEach(function(item) { subtotal += inst.getItemPrice(item); });
                    if (!subtotal && inst.selectedService) subtotal = inst.selectedService.price || 0;
                }
                const tip = Math.round(subtotal * parseInt(pct) / 100 * 100) / 100;
                $('#sbs-tip-amount').val(tip);
                $('#sbs-tip-display').text('Tip: $' + tip.toFixed(2)).show();
            }
        });

        $(document).on('input', '#sbs-tip-custom-amount', function() {
            const val = parseFloat($(this).val()) || 0;
            $('#sbs-tip-amount').val(val);
            if (val > 0) {
                $('#sbs-tip-display').text('Tip: $' + val.toFixed(2)).show();
            } else {
                $('#sbs-tip-display').hide();
            }
        });
        
        // "Go to next available" button - auto-triggered or manual click
        $(document).on('click', '.sbs-try-next-date', function(e) {
            e.preventDefault();
            const self = window._sbsFormInstance;
            if (self) self._findAndGoNextAvailable($(this));
        });
        
        // "Try another staff member" button - go back to step 3, clear staff + datetime
        $(document).on('click', '.sbs-try-other-staff', function(e) {
            e.preventDefault();
            const self = window._sbsFormInstance;
            if (!self) return;
            self.selectedStaff    = null;
            self.selectedDateTime = null;
            self.updateSummary();
            self.goToStep(3);
            self.loadAvailableStaff();
            self._scrollToFormTop();
        });


        $(document).on('click', '.sbs-availability-choose-staff', function(e) {
            e.preventDefault();
            const self = window._sbsFormInstance;
            if (!self) return;
            self.selectedStaff = null;
            self.selectedDateTime = null;
            self.updateSummary();
            self.goToStep(3);
            self.loadAvailableStaff();
            self._scrollToFormTop();
        });

        $(document).on('click', '.sbs-text-us-inline', function(e) {
            e.preventDefault();
            if ($('#sbs-text-us-btn').length) {
                $('#sbs-text-us-btn').trigger('click');
            }
        });
        // Waitlist button
        $(document).on('click', '.sbs-join-waitlist', function() {
            $('#sbs-waitlist-form')[0].reset();
            const currentServiceId = $('#sbs-selected-service-id').val();
            if (currentServiceId) {
                $('#waitlist-service-checkboxes input[value="' + currentServiceId + '"]').prop('checked', true)
                    .closest('.waitlist-service-label').css({ 'border-color':'var(--primary-color,#ec4899)', 'background':'#fdf2f8' });
            }
            const currentDate = $('.sbs-day-button.selected').data('date') || '';
            if (currentDate) $('#waitlist-date').val(currentDate);
            $('#sbs-waitlist-form').show();
            $('#sbs-waitlist-success').hide();
            $('#sbs-waitlist-modal').fadeIn(200);
        });
        
        $(document).on('change', '.waitlist-svc-chk', function() {
            const $label = $(this).closest('.waitlist-service-label');
            if ($(this).is(':checked')) {
                $label.css({ 'border-color':'var(--primary-color,#ec4899)', 'background':'#fdf2f8' });
            } else {
                $label.css({ 'border-color':'#e5e7eb', 'background':'' });
            }
        });
        $(document).on('change', '.waitlist-time-label input[type="radio"]', function() {
            $('.waitlist-time-label').css({ 'border-color':'#e5e7eb', 'background':'' });
            $(this).closest('.waitlist-time-label').css({ 'border-color':'var(--primary-color,#ec4899)', 'background':'#fdf2f8' });
        });
        $(document).on('click', '#sbs-waitlist-modal .sbs-text-modal-close, #sbs-waitlist-modal', function(e) {
            if (e.target === this) $('#sbs-waitlist-modal').fadeOut(200);
        });
        $(document).on('submit', '#sbs-waitlist-form', function(e) {
            e.preventDefault();
            const checkedServices = $('.waitlist-svc-chk:checked');
            if (checkedServices.length === 0) {
                $('#waitlist-service-checkboxes').css('border','1.5px solid #ef4444');
                setTimeout(function(){ $('#waitlist-service-checkboxes').css('border',''); }, 2000);
                if (window._sbsFormInstance) window._sbsFormInstance._showToast('Please select at least one service.', 'error');
                return;
            }
            const $btn = $(this).find('button[type="submit"]');
            const origText = $btn.text();
            $btn.prop('disabled', true).text('Submitting...');
            $.ajax({
                url:  yatoonData.ajax_url,
                type: 'POST',
                data: $(this).serialize() + '&action=yatoon_join_waitlist&nonce=' + yatoonData.nonce,
                success: function(response) {
                    $btn.prop('disabled', false).text(origText);
                    if (response.success) {
                        $('#sbs-waitlist-form').hide();
                        $('#sbs-waitlist-success').fadeIn(200);
                        setTimeout(function() { $('#sbs-waitlist-modal').fadeOut(200); }, 3500);
                    } else {
                        if (window._sbsFormInstance) window._sbsFormInstance._showToast(response.data.message || 'Failed to join waitlist. Please try again.', 'error');
                    }
                },
                error: function() {
                    $btn.prop('disabled', false).text(origText);
                    if (window._sbsFormInstance) window._sbsFormInstance._showToast('Network error. Please try again.', 'error');
                }
            });
        });
    });


    /* -- 2.3.0 Appointment type selector --------------------------- */
    /* Hide Group card if max guests = 0 */
    $(document).ready(function () {
        // Use yatoonData.maxGuestsTogether (set via wp_localize_script) as the
        // authoritative value - avoids get_option cache issues on some hosts.
        var maxG = (typeof yatoonData !== 'undefined' && yatoonData.maxGuestsTogether)
            ? parseInt(yatoonData.maxGuestsTogether, 10)
            : parseInt($('#sbs-appt-type-wrap').data('max-guests') || 0, 10);

        if (maxG <= 0) {
            $('#sbs-appt-group').hide();
            $('#sbs-appt-type-wrap').hide();
        } else {
            // Overwrite the subtitle with the correct JS-sourced value
            var guestWord = maxG === 1
                ? '1 guest'
                : 'up to ' + maxG + ' guests';
            $('#sbs-appt-group-sub').text('Add ' + guestWord + '; everyone starts together');
            // Also update the data attribute so the rest of the JS stays consistent
            $('#sbs-appt-type-wrap').data('max-guests', maxG);
        }
    });

    $(document).on('click', '.sbs-appt-type-card', function () {
        var type = $(this).data('type');
        $('.sbs-appt-type-card').removeClass('active');
        $(this).addClass('active');

        var $hint = $('#sbs-appt-group-hint');
        if (type === 'group') {
            $hint.slideDown(180);
            // Store intent so the first ADD triggers _addGuestTogether after adding own service
            window._sbsGroupIntent = true;
        } else {
            $hint.slideUp(180);
            window._sbsGroupIntent = false;
        }
        if (window._sbsFormInstance) window._sbsFormInstance._saveSession();
    });

    /* Group intent is now handled inline inside the time-slot click handler (addToCart path).
       No separate listener needed here - the old .sbs-book-btn listener was firing too early
       (before the user had finished selecting staff/time for themselves), causing a reset. */

})(jQuery);
