﻿{"id":4731,"date":"2026-05-18T10:08:40","date_gmt":"2026-05-18T10:08:40","guid":{"rendered":"https:\/\/liautoyerevan.am\/?page_id=4731"},"modified":"2026-07-17T11:38:26","modified_gmt":"2026-07-17T11:38:26","slug":"configurator-l9-livis","status":"publish","type":"page","link":"https:\/\/liautoyerevan.am\/am\/configurator-l9-livis\/","title":{"rendered":"configurator-l9-Livis"},"content":{"rendered":"<div data-elementor-type=\"wp-page\" data-elementor-id=\"4731\" class=\"elementor elementor-4731\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-0ba3889 e-con-full e-flex e-con e-parent\" data-id=\"0ba3889\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-117fce9 elementor-widget elementor-widget-html\" data-id=\"117fce9\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">\r\n    <title>2026 Li Auto Configurator<\/title>\r\n    <style>\r\n        * {\r\n            margin: 0;\r\n            padding: 0;\r\n            box-sizing: border-box;\r\n        }\r\n\r\n        body {\r\n            font-family: 'Jost', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;\r\n            background: #fafafa;\r\n            color: #1a1a1a;\r\n            line-height: 1.5;\r\n            overflow-x: hidden;\r\n            -webkit-font-smoothing: antialiased;\r\n            -moz-osx-font-smoothing: grayscale;\r\n            touch-action: manipulation;\r\n        }\r\n\r\n        .configurator {\r\n            width: 100%;\r\n            max-width: none;\r\n            margin: 0;\r\n            padding: 0;\r\n            height: 95vh;\r\n            display: flex;\r\n            background: white;\r\n        }\r\n\r\n        .car-studio {\r\n            flex: 0 0 60%;\r\n            height: 95vh;\r\n            position: relative;\r\n            background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);\r\n            display: flex;\r\n            align-items: center;\r\n            justify-content: center;\r\n            overflow: hidden;\r\n        }\r\n\r\n        .car-display {\r\n            width: 100%;\r\n            height: 100%;\r\n            position: relative;\r\n            display: flex;\r\n            align-items: center;\r\n            justify-content: center;\r\n            overflow: hidden;\r\n        }\r\n\r\n        .car-view {\r\n            position: absolute;\r\n            top: 0;\r\n            left: 0;\r\n            width: 100%;\r\n            height: 100%;\r\n            display: flex;\r\n            align-items: center;\r\n            justify-content: center;\r\n            opacity: 0;\r\n            transition: opacity 0.6s ease;\r\n            pointer-events: none;\r\n        }\r\n\r\n            .car-view.active {\r\n                opacity: 1;\r\n                pointer-events: auto;\r\n            }\r\n\r\n        \/* blurred copy of the current image, filling behind the sharp car.\r\n           two layers (front\/back) crossfade in lockstep with the car pictures *\/\r\n        .car-backdrop {\r\n            position: absolute;\r\n            inset: 0;\r\n            width: 100%;\r\n            height: 100%;\r\n            object-fit: cover;\r\n            transform: scale(1.15);\r\n            filter: blur(30px) saturate(1.15) brightness(0.96);\r\n            z-index: 0;\r\n            pointer-events: none;\r\n            user-select: none;\r\n            transition: opacity 0.8s ease;\r\n        }\r\n        .car-backdrop.front { opacity: 1; }\r\n        .car-backdrop.back { opacity: 0; }\r\n        \/* transparent-cutout models: no backdrop *\/\r\n        .car-display.no-backdrop .car-backdrop { display: none; }\r\n\r\n\r\n        div#exteriorView {\r\n            top: 0;\r\n        }\r\n\r\n\r\n\r\n        .car-image {\r\n            max-width: 110% !important;\r\n            max-height: 110% !important;\r\n            height: auto !important;\r\n            object-fit: contain;\r\n            border-radius: 20px;\r\n            transition: transform 0.3s ease, opacity 0.3s ease;\r\n            user-select: none;\r\n            -webkit-user-select: none;\r\n            -moz-user-select: none;\r\n            -ms-user-select: none;\r\n            -webkit-user-drag: none;\r\n            -webkit-touch-callout: none;\r\n        }\r\n\r\n        \/* Interior fills the viewport (cover) *\/\r\n        .interior-view .car-picture {\r\n            align-items: stretch !important;\r\n            justify-content: stretch !important;\r\n        }\r\n        .interior-view .car-image {\r\n            width: 100% !important;\r\n            height: 100% !important;\r\n            max-width: none !important;\r\n            max-height: none !important;\r\n            object-fit: cover !important;\r\n            object-position: center center !important;\r\n        }\r\n\r\n        \/* Exterior: never clip the car. The <img> shrink-wraps the displayed image\r\n           (width\/height auto, capped at 100%) instead of being a full-container box,\r\n           so its border-radius rounds the actual picture rectangle, not empty letterbox.\r\n           Picture is centered so the content-sized image sits in the middle. *\/\r\n        .exterior-view .car-picture {\r\n            align-items: center !important;\r\n            justify-content: center !important;\r\n        }\r\n        .exterior-view .car-image {\r\n            width: auto !important;\r\n            height: auto !important;\r\n            max-width: 100% !important;\r\n            max-height: 100% !important;\r\n            object-fit: contain !important;\r\n        }\r\n\r\n        \/* Crossfade support - stack picture elements *\/\r\n        .car-picture {\r\n            position: absolute;\r\n            top: 0;\r\n            left: 0;\r\n            width: 100%;\r\n            height: 100%;\r\n            display: flex;\r\n            align-items: center;\r\n            justify-content: center;\r\n            transition: opacity 0.8s ease;\r\n        }\r\n\r\n        .car-picture.front {\r\n            z-index: 2;\r\n            opacity: 1;\r\n        }\r\n\r\n        .car-picture.back {\r\n            z-index: 1;\r\n            opacity: 0;\r\n        }\r\n\r\n        \/* Base scale for views - handled by JS zoom system *\/\r\n        @media (min-width: 769px) {\r\n            \/* Scales set by updateZoom(): both views 0.9 *\/\r\n        }\r\n\r\n        \/* Mobile scales handled by JS zoom system *\/\r\n        @media (max-width: 768px) {\r\n            .car-image {\r\n                transition: opacity 0.3s ease;\r\n            }\r\n\r\n            \/* Interior fills (cover). Exterior keeps the desktop shrink-wrap+contain\r\n               so the car isn't cropped and border-radius rounds the actual picture. *\/\r\n            .interior-view .car-image {\r\n                width: 100% !important;\r\n                height: 100% !important;\r\n                max-width: none !important;\r\n                max-height: none !important;\r\n                object-fit: cover;\r\n            }\r\n        }\r\n\r\n        .image-skeleton {\r\n            position: absolute;\r\n            top: 50%;\r\n            left: 50%;\r\n            transform: translate(-50%, -50%);\r\n            width: 80%;\r\n            height: 60%;\r\n            background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);\r\n            background-size: 200% 100%;\r\n            animation: skeleton-loading 1.5s infinite;\r\n            border-radius: 8px;\r\n            opacity: 0;\r\n            transition: opacity 0.3s ease;\r\n        }\r\n\r\n            .image-skeleton.show {\r\n                opacity: 1;\r\n            }\r\n\r\n        @keyframes skeleton-loading {\r\n            0% {\r\n                background-position: 200% 0;\r\n            }\r\n\r\n            100% {\r\n                background-position: -200% 0;\r\n            }\r\n        }\r\n\r\n        .car-image-overlay {\r\n            position: absolute;\r\n            top: 0;\r\n            left: 0;\r\n            width: 100%;\r\n            height: 100%;\r\n            display: flex;\r\n            align-items: center;\r\n            justify-content: center;\r\n            opacity: 0;\r\n            transition: opacity 0.4s ease;\r\n            pointer-events: none;\r\n        }\r\n\r\n            .car-image-overlay.active {\r\n                opacity: 1;\r\n            }\r\n\r\n        .car-info-overlay {\r\n            position: absolute;\r\n            top: 40px;\r\n            left: 40px;\r\n            background: rgba(255,255,255,0.95);\r\n            backdrop-filter: blur(10px);\r\n            padding: 16px 24px;\r\n            border-radius: 12px;\r\n            box-shadow: 0 8px 32px rgba(0,0,0,0.12);\r\n            z-index: 10;\r\n            max-width: 350px;\r\n            transition: opacity 0.3s ease, transform 0.3s ease,\r\n                        width 0.35s cubic-bezier(0.4, 0, 0.2, 1),\r\n                        height 0.35s cubic-bezier(0.4, 0, 0.2, 1);\r\n        }\r\n\r\n        .car-info-title {\r\n            font-size: 28px;\r\n            font-weight: 300;\r\n            color: #1a1a1a;\r\n            margin-bottom: 4px;\r\n            letter-spacing: -0.5px;\r\n        }\r\n\r\n        .car-info-subtitle {\r\n            color: #6b7280;\r\n            font-size: 14px;\r\n            margin-bottom: 12px;\r\n        }\r\n\r\n        .detail-text {\r\n            color: #374151;\r\n            font-size: 13px;\r\n            line-height: 1.4;\r\n            font-weight: 500;\r\n            \/* Allow wrapping for better layout *\/\r\n            word-wrap: break-word;\r\n        }\r\n\r\n        \/* Enter animation for config items (colors\/wheels\/options) when a panel renders.\r\n           Staggered so they cascade in instead of popping all at once. *\/\r\n        @keyframes cardEnter {\r\n            from { opacity: 0; transform: translateY(10px); }\r\n            to   { opacity: 1; transform: none; }\r\n        }\r\n        @keyframes cardExit {\r\n            from { opacity: 1; transform: none; }\r\n            to   { opacity: 0; transform: translateY(-8px); }\r\n        }\r\n        .color-option, .wheel-option, .audio-option {\r\n            animation: cardEnter 0.35s cubic-bezier(0.22, 1, 0.36, 1) both;\r\n        }\r\n        .color-option:nth-child(1), .wheel-option:nth-child(1), .audio-option:nth-child(1) { animation-delay: 0ms; }\r\n        .color-option:nth-child(2), .wheel-option:nth-child(2), .audio-option:nth-child(2) { animation-delay: 45ms; }\r\n        .color-option:nth-child(3), .wheel-option:nth-child(3), .audio-option:nth-child(3) { animation-delay: 90ms; }\r\n        .color-option:nth-child(4), .wheel-option:nth-child(4), .audio-option:nth-child(4) { animation-delay: 135ms; }\r\n        .color-option:nth-child(5), .wheel-option:nth-child(5), .audio-option:nth-child(5) { animation-delay: 180ms; }\r\n        .color-option:nth-child(6), .wheel-option:nth-child(6), .audio-option:nth-child(6) { animation-delay: 225ms; }\r\n        .color-option:nth-child(7), .wheel-option:nth-child(7), .audio-option:nth-child(7) { animation-delay: 270ms; }\r\n        .color-option:nth-child(n+8), .wheel-option:nth-child(n+8), .audio-option:nth-child(n+8) { animation-delay: 315ms; }\r\n        @media (prefers-reduced-motion: reduce) {\r\n            .color-option, .wheel-option, .audio-option { animation: none; }\r\n        }\r\n\r\n        .studio-controls {\r\n            position: absolute;\r\n            bottom: 40px;\r\n            left: 50%;\r\n            transform: translateX(-50%);\r\n            display: flex;\r\n            gap: 12px;\r\n            background: rgba(255,255,255,0.95);\r\n            backdrop-filter: blur(10px);\r\n            padding: 12px 20px;\r\n            border-radius: 50px;\r\n            box-shadow: 0 8px 32px rgba(0,0,0,0.12);\r\n            z-index: 10;\r\n        }\r\n\r\n            .studio-controls::before {\r\n                content: '';\r\n                position: absolute;\r\n                background: #1a1a1a;\r\n                border-radius: 25px;\r\n                transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);\r\n                z-index: 0;\r\n                top: 12px;\r\n                bottom: 12px;\r\n                left: var(--indicator-left, 0);\r\n                width: var(--indicator-width, 0);\r\n            }\r\n\r\n        .view-tab {\r\n            padding: 8px 16px;\r\n            border: none;\r\n            background: transparent !important;\r\n            color: #666 !important;\r\n            font-size: 14px;\r\n            font-weight: 500;\r\n            cursor: pointer;\r\n            border-radius: 20px;\r\n            transition: color 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n            text-transform: uppercase;\r\n            letter-spacing: 0.5px;\r\n            min-height: 44px;\r\n            display: flex;\r\n            align-items: center;\r\n            justify-content: center;\r\n            position: relative;\r\n            z-index: 1;\r\n        }\r\n\r\n            .view-tab:hover {\r\n                color: #333 !important;\r\n            }\r\n\r\n            .view-tab.active {\r\n                color: white !important;\r\n                font-weight: 600;\r\n            }\r\n\r\n            .view-tab.active:hover {\r\n                color: white !important;\r\n            }\r\n\r\n            .view-tab.hidden {\r\n                display: none;\r\n            }\r\n\r\n        .zoom-controls {\r\n            position: absolute;\r\n            top: 40px;\r\n            right: 40px;\r\n            display: flex;\r\n            gap: 8px;\r\n            background: rgba(255,255,255,0.95);\r\n            backdrop-filter: blur(10px);\r\n            padding: 8px;\r\n            border-radius: 50px;\r\n            box-shadow: 0 4px 20px rgba(0,0,0,0.1);\r\n            z-index: 10;\r\n            transition: opacity 0.3s ease, transform 0.3s ease;\r\n        }\r\n\r\n        \/* Hide +\/- zoom buttons on mobile, keep reset button *\/\r\n        @media (max-width: 768px) {\r\n            .zoom-controls .zoom-btn:nth-child(1),\r\n            .zoom-controls .zoom-btn:nth-child(2) {\r\n                display: none;\r\n            }\r\n\r\n            \/* Hide entire zoom controls on mobile when at base scale *\/\r\n            .zoom-controls.hide-on-mobile {\r\n                opacity: 0;\r\n                pointer-events: none;\r\n                transform: scale(0.95) translateX(10px);\r\n            }\r\n        }\r\n\r\n        .zoom-btn {\r\n            width: 44px;\r\n            height: 44px;\r\n            border: none;\r\n            background: transparent;\r\n            border-radius: 50%;\r\n            cursor: pointer;\r\n            display: flex;\r\n            align-items: center;\r\n            justify-content: center;\r\n            font-size: 16px;\r\n            color: #666;\r\n            transition: all 0.2s ease;\r\n            touch-action: manipulation;\r\n        }\r\n\r\n            .zoom-btn:hover {\r\n                background: #1a1a1a;\r\n                color: white;\r\n            }\r\n\r\n        .config-panel {\r\n            flex: 0 0 40%;\r\n            height: 95vh;\r\n            background: white;\r\n            border-left: 1px solid #e5e7eb;\r\n            display: flex;\r\n            flex-direction: column;\r\n            overflow: hidden;\r\n        }\r\n\r\n        .config-header {\r\n            padding: 10px 32px 32px 32px;\r\n            border-bottom: 1px solid #e5e7eb;\r\n            flex-shrink: 0;\r\n        }\r\n\r\n        .config-content {\r\n            flex: 1;\r\n            overflow-y: auto;\r\n            padding: 0 32px;\r\n            scrollbar-width: none;\r\n            -ms-overflow-style: none;\r\n        }\r\n\r\n            .config-content::-webkit-scrollbar {\r\n                display: none;\r\n            }\r\n\r\n        .config-section {\r\n            margin-bottom: 25px;\r\n            margin-top: 25px;\r\n        }\r\n\r\n        .section-title {\r\n            font-size: 20px;\r\n            font-weight: 500;\r\n            color: #1a1a1a;\r\n            margin-bottom: 24px;\r\n            letter-spacing: -0.2px;\r\n        }\r\n\r\n        .wheel-grid {\r\n            display: grid;\r\n            grid-template-columns: repeat(2, 1fr);\r\n            gap: 16px;\r\n        }\r\n\r\n        .wheel-option, .audio-option {\r\n            border: 2px solid #e5e7eb;\r\n            border-radius: 12px;\r\n            padding: 20px;\r\n            cursor: pointer;\r\n            transition: all 0.3s ease;\r\n            text-align: center;\r\n            background: white;\r\n            min-height: 44px;\r\n            touch-action: manipulation;\r\n        }\r\n\r\n            .wheel-option:hover, .audio-option:hover {\r\n                border-color: #9ca3af;\r\n                transform: translateY(-2px);\r\n                box-shadow: 0 8px 24px rgba(0,0,0,0.08);\r\n            }\r\n\r\n            .wheel-option.selected, .audio-option.selected {\r\n                border-color: #1a1a1a;\r\n                background: #f9fafb;\r\n            }\r\n\r\n        .wheel-image {\r\n            width: 100px;\r\n            height: 100px;\r\n            margin: 0 auto 12px;\r\n            border-radius: 50%;\r\n            overflow: hidden;\r\n            background: #f3f4f6;\r\n            display: flex;\r\n            align-items: center;\r\n            justify-content: center;\r\n        }\r\n\r\n            .wheel-image img,\r\n            .wheel-image picture {\r\n                width: 100% !important;\r\n                height: 100% !important;\r\n                object-fit: cover !important;       \/* fill the circle *\/\r\n                object-position: center !important; \/* crop excess from the sides, centered *\/\r\n                max-width: none !important;\r\n            }\r\n\r\n        \/* Larger image for rear entertainment screen *\/\r\n        #option-screen {\r\n            display: flex;\r\n            flex-direction: row;\r\n            align-items: center;\r\n            gap: 20px;\r\n            text-align: left;\r\n        }\r\n\r\n        #option-screen .wheel-image {\r\n            width: 240px;\r\n            height: 240px;\r\n            border-radius: 12px;\r\n            margin: 0;\r\n            flex-shrink: 0;\r\n            background: transparent;\r\n        }\r\n\r\n        #option-screen .wheel-image img,\r\n        #option-screen .wheel-image picture {\r\n            height: 80px !important;\r\n            width: auto !important;\r\n            max-width: none !important;\r\n        }\r\n\r\n        #option-screen .option-text-content {\r\n            display: flex;\r\n            flex-direction: column;\r\n            gap: 8px;\r\n            flex: 1;\r\n        }\r\n\r\n        #option-screen .audio-name {\r\n            text-align: left;\r\n        }\r\n\r\n        #option-screen .price-tag {\r\n            text-align: left;\r\n        }\r\n\r\n        .wheel-name, .audio-name {\r\n            font-weight: 600;\r\n            color: #1a1a1a;\r\n            margin-bottom: 4px;\r\n            font-size: 14px;\r\n        }\r\n\r\n        .wheel-desc, .audio-desc {\r\n            color: #6b7280;\r\n            font-size: 12px;\r\n            line-height: 1.6;\r\n        }\r\n\r\n        .spec-label {\r\n            color: #374151;\r\n            font-weight: 600;\r\n        }\r\n\r\n        .color-grid {\r\n            display: grid;\r\n            grid-template-columns: repeat(3, 1fr);\r\n            gap: 16px;\r\n        }\r\n\r\n        .interior-color-grid {\r\n            display: grid;\r\n            grid-template-columns: repeat(3, 1fr);\r\n            gap: 16px;\r\n        }\r\n\r\n        .color-option {\r\n            aspect-ratio: 16\/9;\r\n            border: 3px solid #e5e7eb;\r\n            border-radius: 12px;\r\n            cursor: pointer;\r\n            transition: all 0.3s ease;\r\n            position: relative;\r\n            overflow: hidden;\r\n            background-size: cover;\r\n            background-position: center;\r\n            background-repeat: no-repeat;\r\n            min-height: 44px;\r\n            touch-action: manipulation;\r\n        }\r\n\r\n            .color-option:hover {\r\n                transform: scale(1.02);\r\n                box-shadow: 0 8px 24px rgba(0,0,0,0.12);\r\n            }\r\n\r\n            .color-option.selected {\r\n                border-color: #1a1a1a;\r\n                transform: scale(1.05);\r\n                box-shadow: 0 12px 32px rgba(0,0,0,0.15);\r\n            }\r\n\r\n                .color-option.selected::after {\r\n                    content: '\u2713';\r\n                    position: absolute;\r\n                    top: 50%;\r\n                    left: 50%;\r\n                    transform: translate(-50%, -50%);\r\n                    color: white;\r\n                    font-size: 20px;\r\n                    font-weight: bold;\r\n                    background: rgba(0,0,0,0.8);\r\n                    width: 36px;\r\n                    height: 36px;\r\n                    border-radius: 50%;\r\n                    display: flex;\r\n                    align-items: center;\r\n                    justify-content: center;\r\n                }\r\n\r\n        .color-label {\r\n            position: absolute;\r\n            bottom: 0;\r\n            left: 0;\r\n            right: 0;\r\n            background: linear-gradient(transparent, rgba(0,0,0,0.8));\r\n            color: white;\r\n            padding: 16px 12px 8px;\r\n            font-size: 12px;\r\n            font-weight: 500;\r\n            line-height: 1.25;\r\n            text-align: center;\r\n        }\r\n        .color-label small { display: block; }\r\n\r\n        .config-footer {\r\n            padding: 32px;\r\n            border-top: 1px solid #e5e7eb;\r\n            flex-shrink: 0;\r\n            background: #f9fafb;\r\n            box-shadow: 0px -10px 20px -20px #000000;\r\n        }\r\n\r\n        .status-row {\r\n            display: flex;\r\n            justify-content: space-between;\r\n            align-items: flex-start;\r\n            margin-bottom: 12px;\r\n        }\r\n\r\n        .status-label {\r\n            font-size: 14px;\r\n            font-weight: 500;\r\n            color: #6b7280;\r\n            \/* Allow wrapping for long option lists *\/\r\n            word-wrap: break-word;\r\n            max-width: 70%;\r\n        }\r\n\r\n        .status-value {\r\n            font-size: 14px;\r\n            font-weight: 600;\r\n            color: #1a1a1a;\r\n            transition: opacity 0.3s ease;\r\n        }\r\n\r\n        .total-row {\r\n            padding-top: 12px;\r\n            border-top: 2px solid #1a1a1a;\r\n            margin-bottom: 24px;\r\n        }\r\n\r\n            .total-row .status-label {\r\n                font-size: 18px;\r\n                font-weight: 600;\r\n                color: #1a1a1a;\r\n            }\r\n\r\n            .total-row .status-value {\r\n                font-size: 18px;\r\n                font-weight: 700;\r\n                color: #1a1a1a;\r\n            }\r\n\r\n        .cta-button {\r\n            width: 100%;\r\n            padding: 16px 24px;\r\n            background: #1a1a1a;\r\n            color: white;\r\n            border: none;\r\n            border-radius: 12px;\r\n            font-size: 16px;\r\n            font-weight: 600;\r\n            cursor: pointer;\r\n            transition: all 0.3s ease;\r\n            text-transform: uppercase;\r\n            letter-spacing: 0.5px;\r\n            min-height: 56px;\r\n            touch-action: manipulation;\r\n        }\r\n\r\n            .cta-button:hover {\r\n                background: #374151;\r\n                transform: translateY(-2px);\r\n                box-shadow: 0 8px 24px rgba(0,0,0,0.2);\r\n            }\r\n\r\n        .contact-form {\r\n            position: fixed;\r\n            top: 0;\r\n            left: 0;\r\n            right: 0;\r\n            bottom: 0;\r\n            background: white;\r\n            transform: translateY(100%);\r\n            transition: transform 0.4s cubic-bezier(0.4, 0.0, 0.2, 1);\r\n            overflow-y: auto;\r\n            z-index: 1000;\r\n            visibility: hidden;\r\n        }\r\n\r\n            .contact-form.show {\r\n                visibility: visible;\r\n                transform: translateY(0);\r\n            }\r\n\r\n        body.form-open {\r\n            overflow: hidden;\r\n        }\r\n\r\n        .form-header {\r\n            padding: 32px;\r\n            border-bottom: 1px solid #e5e7eb;\r\n            background: #f9fafb;\r\n        }\r\n\r\n        .form-title {\r\n            font-size: 24px;\r\n            font-weight: 500;\r\n            color: #1a1a1a;\r\n            margin-bottom: 8px;\r\n        }\r\n\r\n        .form-subtitle {\r\n            color: #6b7280;\r\n            font-size: 14px;\r\n        }\r\n\r\n        .form-content {\r\n            padding: 32px;\r\n        }\r\n\r\n        .form-grid {\r\n            display: grid;\r\n            grid-template-columns: 1fr 1fr;\r\n            gap: 20px;\r\n            margin-bottom: 20px;\r\n        }\r\n\r\n        .form-group {\r\n            display: flex;\r\n            flex-direction: column;\r\n            gap: 8px;\r\n        }\r\n\r\n            .form-group.full-width {\r\n                grid-column: 1 \/ -1;\r\n            }\r\n\r\n        .form-label {\r\n            font-size: 14px;\r\n            font-weight: 500;\r\n            color: #374151;\r\n        }\r\n\r\n        .form-input {\r\n            padding: 12px 16px;\r\n            border: 2px solid #e5e7eb;\r\n            border-radius: 8px;\r\n            font-size: 16px;\r\n            transition: border-color 0.2s ease;\r\n            background: white;\r\n            min-height: 48px;\r\n        }\r\n\r\n            .form-input:focus {\r\n                outline: none;\r\n                border-color: #1a1a1a;\r\n            }\r\n\r\n            .form-input.error {\r\n                border-color: #ef4444;\r\n            }\r\n\r\n        .form-error {\r\n            color: #ef4444;\r\n            font-size: 12px;\r\n        }\r\n\r\n        .form-buttons {\r\n            display: flex;\r\n            gap: 16px;\r\n            margin-top: 32px;\r\n        }\r\n\r\n        .cancel-btn {\r\n            flex: 1;\r\n            padding: 14px;\r\n            border: 2px solid #e5e7eb;\r\n            background: white;\r\n            color: #6b7280;\r\n            border-radius: 8px;\r\n            cursor: pointer;\r\n            font-weight: 500;\r\n            text-transform: uppercase;\r\n            letter-spacing: 0.5px;\r\n            transition: all 0.2s ease;\r\n            min-height: 56px;\r\n            touch-action: manipulation;\r\n        }\r\n\r\n            .cancel-btn:hover {\r\n                border-color: #9ca3af;\r\n                color: #374151;\r\n            }\r\n\r\n        .submit-btn {\r\n            flex: 2;\r\n            padding: 14px;\r\n            background: #1a1a1a;\r\n            color: white;\r\n            border: none;\r\n            border-radius: 8px;\r\n            cursor: pointer;\r\n            font-weight: 600;\r\n            text-transform: uppercase;\r\n            letter-spacing: 0.5px;\r\n            transition: all 0.2s ease;\r\n            min-height: 56px;\r\n            touch-action: manipulation;\r\n        }\r\n\r\n            .submit-btn:hover:not(:disabled) {\r\n                background: #374151;\r\n            }\r\n\r\n            .submit-btn:disabled {\r\n                background: #9ca3af;\r\n                cursor: not-allowed;\r\n            }\r\n\r\n        .message {\r\n            padding: 16px;\r\n            border-radius: 8px;\r\n            margin-bottom: 20px;\r\n            text-align: center;\r\n            font-weight: 500;\r\n        }\r\n\r\n            .message.success {\r\n                background: #d1fae5;\r\n                color: #065f46;\r\n                border: 1px solid #a7f3d0;\r\n            }\r\n\r\n            .message.error {\r\n                background: #fee2e2;\r\n                color: #991b1b;\r\n                border: 1px solid #fecaca;\r\n            }\r\n\r\n        .price-tag {\r\n            font-size: 11px;\r\n            color: #059669;\r\n            font-weight: 600;\r\n            margin-top: 4px;\r\n        }\r\n\r\n            .price-tag.paid {\r\n                color: #dc2626;\r\n            }\r\n\r\n        .note-text {\r\n            font-size: 12px;\r\n            color: #6b7280;\r\n            font-style: italic;\r\n            margin-top: 8px;\r\n        }\r\n\r\n        \/* L9 Livis Preorder Banner - Clean Minimal Design *\/\r\n        .livis-preorder-banner {\r\n            background: white;\r\n            border: 2px solid #C9A962;\r\n            border-radius: 12px;\r\n            padding: 32px 24px;\r\n            margin-top: 32px;\r\n            margin-bottom: 32px;\r\n            text-align: center;\r\n            box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);\r\n            position: relative;\r\n            animation: fadeInBanner 0.6s ease-out;\r\n        }\r\n\r\n        @keyframes fadeInBanner {\r\n            from {\r\n                opacity: 0;\r\n                transform: translateY(-10px);\r\n            }\r\n            to {\r\n                opacity: 1;\r\n                transform: translateY(0);\r\n            }\r\n        }\r\n\r\n        .livis-preorder-title {\r\n            font-size: 28px;\r\n            font-weight: 600;\r\n            color: #1a1a1a;\r\n            margin-bottom: 8px;\r\n            letter-spacing: -0.5px;\r\n        }\r\n\r\n        .livis-preorder-subtitle {\r\n            font-size: 14px;\r\n            color: #6b7280;\r\n            margin-bottom: 24px;\r\n            font-weight: 500;\r\n        }\r\n\r\n        .livis-waiting-count-container {\r\n            margin-top: 8px;\r\n        }\r\n\r\n        .livis-waiting-count {\r\n            font-size: 56px;\r\n            font-weight: 700;\r\n            color: #C9A962;\r\n            line-height: 1;\r\n            margin-bottom: 8px;\r\n        }\r\n\r\n        .livis-waiting-label {\r\n            font-size: 13px;\r\n            color: #6b7280;\r\n            font-weight: 500;\r\n            text-transform: uppercase;\r\n            letter-spacing: 1px;\r\n        }\r\n\r\n        \/* Color backgrounds dynamically generated by generateColorPreviewCSS() *\/\r\n\r\n        \/* Mobile Optimizations *\/\r\n        @media (max-width: 1024px) {\r\n            .configurator {\r\n                flex-direction: column;\r\n                height: auto;\r\n                padding: 0;\r\n                margin-top: 8vh;\r\n            }\r\n\r\n            .car-studio {\r\n                flex: none;\r\n                height: 41vh;\r\n                min-height: 400px;\r\n                position: sticky;\r\n                top: 75px;\r\n                z-index: 1;\r\n            }\r\n\r\n            .car-info-overlay.interior-hidden {\r\n                opacity: 0;\r\n                pointer-events: none;\r\n                transform: scale(0.95) translateY(-10px);\r\n            }\r\n\r\n            .config-panel {\r\n                flex: none;\r\n                border-left: none;\r\n                height: auto;\r\n            }\r\n\r\n            .config-header {\r\n                padding: 24px 20px;\r\n            }\r\n\r\n            .config-content {\r\n                padding: 0 20px;\r\n                max-height: none;\r\n            }\r\n\r\n            .config-footer {\r\n                padding: 20px;\r\n            }\r\n\r\n            .studio-controls {\r\n                bottom: 20px;\r\n                padding: 10px 16px;\r\n            }\r\n\r\n            .car-info-overlay {\r\n                top: 20px;\r\n                left: 20px;\r\n                padding: 12px 16px;\r\n                max-width: 295px;\r\n                z-index: 10; \/* above car pictures (z 1\/2), else the card hides behind the car *\/\r\n            }\r\n\r\n            .car-info-title {\r\n                font-size: 24px !important;\r\n                margin-bottom: 3px;\r\n            }\r\n\r\n            .car-info-subtitle {\r\n                font-size: 12px;\r\n                margin-bottom: 8px;\r\n            }\r\n\r\n            .color-grid {\r\n                grid-template-columns: repeat(2, 1fr);\r\n                gap: 12px;\r\n            }\r\n\r\n            .wheel-grid {\r\n                grid-template-columns: 1fr;\r\n                gap: 12px;\r\n            }\r\n\r\n            .form-grid {\r\n                grid-template-columns: 1fr;\r\n            }\r\n\r\n            .section-title {\r\n                font-size: 18px;\r\n                margin-bottom: 20px;\r\n            }\r\n        }\r\n\r\n        @media (max-width: 768px) {\r\n            .car-studio {\r\n                height: 45vh;\r\n                min-height: 350px;\r\n            }\r\n\r\n            .config-header {\r\n                padding: 20px 16px;\r\n            }\r\n\r\n            .config-content {\r\n                padding: 0 16px;\r\n            }\r\n\r\n            .config-footer {\r\n                padding: 16px;\r\n            }\r\n\r\n            .form-content, .form-header {\r\n                padding: 20px 16px;\r\n            }\r\n\r\n            .section-title {\r\n                font-size: 16px;\r\n                margin-bottom: 16px;\r\n            }\r\n\r\n            .livis-preorder-banner {\r\n                padding: 28px 20px;\r\n                margin-top: 28px;\r\n                margin-bottom: 28px;\r\n            }\r\n\r\n            .livis-preorder-title {\r\n                font-size: 24px;\r\n            }\r\n\r\n            .livis-preorder-subtitle {\r\n                font-size: 13px;\r\n            }\r\n\r\n            .livis-waiting-count {\r\n                font-size: 48px;\r\n            }\r\n\r\n            .livis-waiting-label {\r\n                font-size: 12px;\r\n            }\r\n\r\n            .color-grid, .interior-color-grid {\r\n                grid-template-columns: 1fr;\r\n                gap: 32px !important;\r\n            }\r\n\r\n            .color-option {\r\n                aspect-ratio: 20\/9;\r\n                min-height: 60px;\r\n            }\r\n\r\n            .wheel-option, .audio-option {\r\n                padding: 16px;\r\n            }\r\n\r\n            .wheel-image {\r\n                width: 60px;\r\n                height: 60px;\r\n                margin-bottom: 8px;\r\n            }\r\n\r\n            #option-screen .wheel-image {\r\n                width: 200px;\r\n                height: 200px;\r\n                margin: 0;\r\n            }\r\n\r\n            #option-screen {\r\n                gap: 16px;\r\n            }\r\n\r\n            .view-tab {\r\n                padding: 8px 12px;\r\n                font-size: 12px;\r\n                min-height: 40px;\r\n            }\r\n\r\n            .studio-controls {\r\n                gap: 8px;\r\n                padding: 8px 12px;\r\n            }\r\n\r\n            .studio-controls::before {\r\n                top: 8px;\r\n                bottom: 8px;\r\n            }\r\n\r\n            .status-label, .status-value {\r\n                font-size: 12px;\r\n            }\r\n\r\n            .total-row .status-label, .total-row .status-value {\r\n                font-size: 16px;\r\n            }\r\n\r\n            .cta-button {\r\n                font-size: 15px;\r\n                padding: 14px 20px;\r\n            }\r\n\r\n            .form-buttons {\r\n                flex-direction: column;\r\n                gap: 12px;\r\n            }\r\n\r\n            .cancel-btn, .submit-btn {\r\n                flex: none;\r\n                width: 100%;\r\n            }\r\n\r\n            .config-section {\r\n                margin-bottom: 72px;\r\n            }\r\n        }\r\n\r\n        @media (max-width: 480px) {\r\n            .car-studio {\r\n                height: 40vh;\r\n                min-height: 300px;\r\n                border-bottom: 2px solid #dddddd;\r\n                border-radius: 0px 0px 12px 12px;\r\n                box-shadow: 0px 10px 20px -20px #000000;\r\n            }\r\n\r\n            .config-header {\r\n                padding: 16px 12px;\r\n            }\r\n\r\n            .config-content {\r\n                padding: 0 12px;\r\n            }\r\n\r\n            .config-footer {\r\n                padding: 12px;\r\n                z-index: 2;\r\n            }\r\n\r\n            .form-content, .form-header {\r\n                padding: 16px 12px;\r\n            }\r\n\r\n            .detail-text {\r\n                font-size: 11px;\r\n            }\r\n\r\n            .section-title {\r\n                font-size: 14px;\r\n                margin-bottom: 12px;\r\n            }\r\n\r\n            .livis-preorder-banner {\r\n                padding: 24px 16px;\r\n                margin-top: 24px;\r\n                margin-bottom: 24px;\r\n            }\r\n\r\n            .livis-preorder-title {\r\n                font-size: 22px;\r\n            }\r\n\r\n            .livis-preorder-subtitle {\r\n                font-size: 12px;\r\n                margin-bottom: 20px;\r\n            }\r\n\r\n            .livis-waiting-count {\r\n                font-size: 42px;\r\n            }\r\n\r\n            .livis-waiting-label {\r\n                font-size: 11px;\r\n            }\r\n\r\n            .color-option {\r\n                aspect-ratio: 18\/9;\r\n                min-height: 50px;\r\n            }\r\n\r\n            .color-label {\r\n                font-size: 11px;\r\n                padding: 12px 8px 6px;\r\n            }\r\n\r\n            .wheel-option, .audio-option {\r\n                padding: 12px;\r\n            }\r\n\r\n            .wheel-image {\r\n                width: 50px;\r\n                height: 50px;\r\n            }\r\n\r\n            #option-screen .wheel-image {\r\n                width: 180px;\r\n                height: 180px;\r\n                margin: 0;\r\n            }\r\n\r\n            #option-screen {\r\n                gap: 12px;\r\n            }\r\n\r\n            .wheel-name, .audio-name {\r\n                font-size: 12px;\r\n            }\r\n\r\n            .wheel-desc, .audio-desc {\r\n                font-size: 10px;\r\n            }\r\n\r\n            .view-tab {\r\n                padding: 6px 10px;\r\n                font-size: 11px;\r\n                min-height: 36px;\r\n            }\r\n\r\n            .studio-controls {\r\n                bottom: 16px;\r\n                gap: 6px;\r\n                padding: 6px 10px;\r\n            }\r\n\r\n            .studio-controls::before {\r\n                top: 6px;\r\n                bottom: 6px;\r\n            }\r\n\r\n            .car-info-overlay {\r\n                top: 16px;\r\n                padding: 8px 12px;\r\n                z-index: 10; \/* above car pictures, else the card hides behind the car *\/\r\n            }\r\n\r\n            .car-info-title {\r\n                font-size: 20px;\r\n            }\r\n\r\n            .car-info-subtitle {\r\n                font-size: 11px;\r\n            }\r\n\r\n            .detail-text {\r\n                font-size: 10px;\r\n            }\r\n\r\n            .cta-button {\r\n                font-size: 14px;\r\n                padding: 12px 16px;\r\n                min-height: 48px;\r\n            }\r\n\r\n            .form-input {\r\n                font-size: 16px;\r\n                padding: 10px 12px;\r\n                min-height: 44px;\r\n            }\r\n\r\n            .cancel-btn, .submit-btn {\r\n                min-height: 48px;\r\n                font-size: 14px;\r\n            }\r\n\r\n            .config-section {\r\n                margin-bottom: 60px;\r\n            }\r\n\r\n            .color-grid, .interior-color-grid {\r\n                gap: 48px !important;\r\n            }\r\n\r\n            .form-title {\r\n                font-size: 20px;\r\n            }\r\n\r\n            .form-subtitle {\r\n                font-size: 13px;\r\n            }\r\n        }\r\n\r\n        \/* Prevent zoom on input focus *\/\r\n        @media screen and (max-width: 768px) {\r\n            .form-input {\r\n                font-size: 16px !important;\r\n            }\r\n        }\r\n\r\n        \/* Multi-angle navigation *\/\r\n        .angle-arrow {\r\n            position: absolute; top: 50%; transform: translateY(-50%);\r\n            width: 44px; height: 44px; border-radius: 50%;\r\n            border: none; background: rgba(255,255,255,0.95);\r\n            backdrop-filter: blur(10px); box-shadow: 0 4px 20px rgba(0,0,0,0.1);\r\n            color: #666; font-size: 24px; line-height: 1; cursor: pointer;\r\n            display: none; align-items: center; justify-content: center;\r\n            z-index: 10; transition: all 0.2s ease; touch-action: manipulation;\r\n        }\r\n        .angle-arrow:hover { background: #1a1a1a; color: #fff; }\r\n        .angle-arrow.angle-prev { left: 16px; }\r\n        .angle-arrow.angle-next { right: 16px; }\r\n        .angle-arrow.show { display: flex; }\r\n        .angle-dots {\r\n            display: none !important;\r\n        }\r\n        .angle-dots-orig {\r\n            position: absolute; bottom: 14px; left: 0; right: 0; z-index: 6;\r\n            display: flex; gap: 8px; justify-content: center; pointer-events: none;\r\n        }\r\n        .angle-dots .dot {\r\n            width: 8px; height: 8px; border-radius: 50%;\r\n            background: rgba(0,0,0,0.25); cursor: pointer; pointer-events: auto;\r\n            transition: background .15s, transform .15s;\r\n        }\r\n        .angle-dots .dot.active { background: #222; transform: scale(1.3); }\r\n\r\n        \/* =====================================================================\r\n           Embed\/responsive tweaks (client). Appended last so these win over the\r\n           earlier same-specificity rules. Ordered 1024 -> 768 -> 480 so the\r\n           smaller breakpoint wins at small widths.\r\n           ===================================================================== *\/\r\n        \/* Hide the configurator document's own scrollbar so when embedded in an\r\n           iframe there's only ONE visible bar (the host page's). Scrolling by\r\n           touch\/wheel still works - this only hides the bar, not the scroll. *\/\r\n        html, body { scrollbar-width: none; -ms-overflow-style: none; }\r\n        html::-webkit-scrollbar, body::-webkit-scrollbar { width: 0; height: 0; display: none; }\r\n\r\n        @media (max-width: 1024px) {\r\n            .car-info-title { font-size: 16px !important; margin-bottom: -2px; }\r\n            .car-info-subtitle { font-size: 12px; margin-bottom: 0px; }\r\n            .studio-controls { bottom: 20px; padding: 10px 16px; }\r\n        }\r\n        @media (max-width: 768px) {\r\n            .car-studio { height: 50vh; min-height: 50vh; border-radius: 0px 0px 38px 38px; }\r\n            .view-tab { padding: 8px 10px; font-size: 12px; min-height: 40px; }\r\n            .studio-controls { gap: 8px; padding: 8px 12px; }\r\n        }\r\n        @media (max-width: 480px) {\r\n            .car-studio {\r\n                height: 40vh;\r\n                min-height: 300px;\r\n                border-bottom: 2px solid #dddddd;\r\n                border-radius: 0px 0px 38px 38px;\r\n                box-shadow: 0px 10px 20px -20px #000000;\r\n                margin-bottom: 6vh;\r\n            }\r\n            .car-info-subtitle { font-size: 11px; }\r\n            .view-tab { font-size: 8px; min-height: 31px; }\r\n            .studio-controls { bottom: 16px; gap: 6px; padding: 6px 10px; }\r\n        }\r\n    <\/style>\r\n<\/head>\r\n<body>\r\n    <div class=\"configurator\">\r\n        <!-- Left Side - Car Studio -->\r\n        <div class=\"car-studio\">\r\n            <div class=\"car-info-overlay\">\r\n                <h1 class=\"car-info-title\" id=\"carTitle\">Loading... 2026<\/h1>\r\n                <p class=\"car-info-subtitle\" id=\"carSubtitle\">\u054a\u0580\u0565\u0574\u056b\u0578\u0582\u0574 \u0567\u056c\u0565\u056f\u057f\u0580\u0561\u056f\u0561\u0576 \u0561\u057e\u057f\u0578\u0574\u0565\u0584\u0565\u0576\u0561<\/p>\r\n                <p class=\"detail-text\" id=\"configDetails\">Loading configuration...<\/p>\r\n            <\/div>\r\n\r\n            <div class=\"car-display\">\r\n                <div class=\"car-view exterior-view active\" id=\"exteriorView\">\r\n                    <img class=\"car-backdrop front\" id=\"exteriorBackdropFront\" alt=\"\" aria-hidden=\"true\">\r\n                    <img class=\"car-backdrop back\" id=\"exteriorBackdropBack\" alt=\"\" aria-hidden=\"true\">\r\n                    <div class=\"image-skeleton\" id=\"exteriorSkeleton\"><\/div>\r\n                    <picture id=\"exteriorPicture\" class=\"car-picture front\">\r\n                        <source id=\"exteriorSourceAvif\" type=\"image\/avif\">\r\n                        <source id=\"exteriorSourceWebp\" type=\"image\/webp\">\r\n                        <img class=\"car-image\" id=\"exteriorImage\" alt=\"Li i8 Exterior\">\r\n                    <\/picture>\r\n                    <picture id=\"exteriorPictureBack\" class=\"car-picture back\">\r\n                        <source id=\"exteriorSourceAvifBack\" type=\"image\/avif\">\r\n                        <source id=\"exteriorSourceWebpBack\" type=\"image\/webp\">\r\n                        <img class=\"car-image\" id=\"exteriorImageBack\" alt=\"Li i8 Exterior\">\r\n                    <\/picture>\r\n                <\/div>\r\n                <div class=\"car-view interior-view\" id=\"interiorView\">\r\n                    <img class=\"car-backdrop front\" id=\"interiorBackdropFront\" alt=\"\" aria-hidden=\"true\">\r\n                    <img class=\"car-backdrop back\" id=\"interiorBackdropBack\" alt=\"\" aria-hidden=\"true\">\r\n                    <div class=\"image-skeleton\" id=\"interiorSkeleton\"><\/div>\r\n                    <picture id=\"interiorPicture\" class=\"car-picture front\">\r\n                        <source id=\"interiorSourceAvif\" type=\"image\/avif\">\r\n                        <source id=\"interiorSourceWebp\" type=\"image\/webp\">\r\n                        <img class=\"car-image\" id=\"interiorImage\" alt=\"Li i8 Interior\">\r\n                    <\/picture>\r\n                    <picture id=\"interiorPictureBack\" class=\"car-picture back\">\r\n                        <source id=\"interiorSourceAvifBack\" type=\"image\/avif\">\r\n                        <source id=\"interiorSourceWebpBack\" type=\"image\/webp\">\r\n                        <img class=\"car-image\" id=\"interiorImageBack\" alt=\"Li i8 Interior\">\r\n                    <\/picture>\r\n                <\/div>\r\n                <button class=\"angle-arrow angle-prev\" id=\"anglePrev\" onclick=\"changeAngle(-1)\" aria-label=\"Previous angle\">&#8249;<\/button>\r\n                <button class=\"angle-arrow angle-next\" id=\"angleNext\" onclick=\"changeAngle(1)\" aria-label=\"Next angle\">&#8250;<\/button>\r\n                <div class=\"angle-dots\" id=\"angleDots\"><\/div>\r\n            <\/div>\r\n\r\n            <div class=\"studio-controls\">\r\n                <button class=\"view-tab active\" data-view=\"exterior\" onclick=\"switchView('exterior', event)\">\u0537\u0584\u057d\u057f\u0565\u0580\u056b\u0565\u0580<\/button>\r\n                <button class=\"view-tab\" data-view=\"interior\" onclick=\"switchView('interior', event)\">\u053b\u0576\u057f\u0565\u0580\u056b\u0565\u0580<\/button>\r\n            <\/div>\r\n\r\n            <div class=\"zoom-controls\">\r\n                <button class=\"zoom-btn\" onclick=\"zoomIn()\">+<\/button>\r\n                <button class=\"zoom-btn\" onclick=\"zoomOut()\">\u2212<\/button>\r\n                <button class=\"zoom-btn\" onclick=\"resetZoom()\">\u21ba<\/button>\r\n            <\/div>\r\n        <\/div>\r\n\r\n        <!-- Right Side - Configuration Panel -->\r\n        <div class=\"config-panel\">\r\n\r\n            <div class=\"config-content\" id=\"configContent\">\r\n                <!-- Dynamically populated by JavaScript -->\r\n            <\/div>\r\n\r\n            <div class=\"config-footer\">\r\n                <div class=\"status-row\">\r\n                    <span class=\"status-label\">\u0540\u056b\u0574\u0576\u0561\u056f\u0561\u0576 \u0561\u0580\u056a\u0565\u0584<\/span>\r\n                    <span class=\"status-value\" id=\"basePrice\">22,750,000 AMD<\/span>\r\n                <\/div>\r\n                <div class=\"status-row\">\r\n                    <span class=\"status-label\">\u0555\u057a\u0581\u056b\u0561<\/span>\r\n                    <span class=\"status-value\" id=\"optionsPrice\">0 AMD<\/span>\r\n                <\/div>\r\n                <div class=\"status-row total-row\">\r\n                    <span class=\"status-label\">\u054e\u0565\u0580\u057b\u0576\u0561\u056f\u0561\u0576 \u0561\u0580\u056a\u0565\u0584<\/span>\r\n                    <span class=\"status-value\" id=\"totalPrice\">22,750,000 AMD<\/span>\r\n                <\/div>\r\n                <button class=\"cta-button\" onclick=\"showContactForm()\">\u054d\u057f\u0561\u0581\u056b\u0580 \u0570\u0561\u057f\u0578\u0582\u056f \u0561\u057c\u0561\u057b\u0561\u0580\u056f<\/button>\r\n            <\/div>\r\n        <\/div>\r\n    <\/div>\r\n\r\n    <!-- Contact Form Modal -->\r\n    <div class=\"contact-form\" id=\"contactForm\">\r\n        <div class=\"form-header\">\r\n            <h3 class=\"form-title\">\u054d\u057f\u0561\u0581\u056b\u0580 \u0570\u0561\u057f\u0578\u0582\u056f \u0561\u057c\u0561\u057b\u0561\u0580\u056f<\/h3>\r\n            <p class=\"form-subtitle\" id=\"formSubtitle\">Configure your perfect car and get a special price.<\/p>\r\n        <\/div>\r\n        <div class=\"form-content\">\r\n            <div id=\"messageContainer\"><\/div>\r\n            <form id=\"notificationForm\">\r\n                <div class=\"form-grid\">\r\n                    <div class=\"form-group\">\r\n                        <label class=\"form-label\" for=\"firstName\">First Name *<\/label>\r\n                        <input type=\"text\" id=\"firstName\" name=\"firstName\" class=\"form-input\" required>\r\n                        <div class=\"form-error\" id=\"firstNameError\"><\/div>\r\n                    <\/div>\r\n                    <div class=\"form-group\">\r\n                        <label class=\"form-label\" for=\"lastName\">Last Name *<\/label>\r\n                        <input type=\"text\" id=\"lastName\" name=\"lastName\" class=\"form-input\" required>\r\n                        <div class=\"form-error\" id=\"lastNameError\"><\/div>\r\n                    <\/div>\r\n                    <div class=\"form-group full-width\">\r\n                        <label class=\"form-label\" for=\"email\">Email Address<\/label>\r\n                        <input type=\"email\" id=\"email\" name=\"email\" class=\"form-input\">\r\n                        <div class=\"form-error\" id=\"emailError\"><\/div>\r\n                    <\/div>\r\n                    <div class=\"form-group full-width\">\r\n                        <label class=\"form-label\" for=\"phone\">Phone Number *<\/label>\r\n                        <input type=\"tel\" id=\"phone\" name=\"phone\" class=\"form-input\" required>\r\n                        <div class=\"form-error\" id=\"phoneError\"><\/div>\r\n                    <\/div>\r\n                <\/div>\r\n                <div class=\"form-buttons\">\r\n                    <button type=\"button\" class=\"cancel-btn\" onclick=\"hideContactForm()\">Cancel<\/button>\r\n                    <button type=\"submit\" class=\"submit-btn\" id=\"submitButton\">\r\n                        <span id=\"submitText\">\u0548\u0582\u0572\u0561\u0580\u056f\u0565\u056c<\/span>\r\n                    <\/button>\r\n                <\/div>\r\n            <input type=\"hidden\" name=\"trp-form-language\" value=\"am\"\/><\/form>\r\n        <\/div>\r\n    <\/div>\r\n\r\n    <script>\r\n        \/\/ Universal configurator - loads config based on URL parameter\r\n        let currentView = 'exterior';\r\n        let currentZoom = 1;\r\n        const zoomStep = 0.1;\r\n        const maxZoom = 2;\r\n        const minZoom = 0.8;\r\n        let isResettingZoom = false; \/\/ Flag to prevent UI flicker during reset animation\r\n\r\n        \/\/ Pan state\r\n        let panX = 0;\r\n        let panY = 0;\r\n        let isDragging = false;\r\n        let startX = 0;\r\n        let startY = 0;\r\n\r\n        \/\/ Touch state for pinch-to-zoom\r\n        let initialDistance = 0;\r\n        let initialZoom = 1;\r\n\r\n        \/\/ Crossfade state to prevent overlapping transitions\r\n        let isTransitioning = {\r\n            exterior: false,\r\n            interior: false\r\n        };\r\n        \/\/ when a change is requested mid-transition we can't start a second one, so\r\n        \/\/ we flag it and re-run once the current transition finishes (coalesces rapid\r\n        \/\/ changes to the latest selection instead of dropping them -> stale image)\r\n        let pendingUpdate = { exterior: false, interior: false };\r\n\r\n        \/\/ Global config storage - loaded from JSON\r\n        let MODEL_CONFIG = null;\r\n        let currentModelKey = '';\r\n        let chineseNames = {};\r\n        let pricing = {};\r\n\r\n        \/\/ Base path for local images\r\n        const imagePath = 'https:\/\/liautoyerevan.am\/configurator_images\/downloaded-images-';\r\n\r\n        \/\/ \u2500\u2500 Catalog-driven multi-angle images \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        \/\/ Images are content-addressed by UUID; catalog.json maps a config to\r\n        \/\/ its angle UUIDs. Replaces the old chinese-name\/angle-0 path scheme\r\n        \/\/ (kept as getImagePathsLegacy fallback). Hostname-aware so the same\r\n        \/\/ file works against local test images and the production server.\r\n        const LI_LOCAL = location.hostname === 'localhost' || location.hostname === '127.0.0.1';\r\n        const LI_IMG_BASE = LI_LOCAL ? 'out\/webp' : 'https:\/\/liautoyerevan.am\/li-images\/webp';\r\n        const LI_CATALOG_URL = LI_LOCAL ? 'catalog.json' : 'https:\/\/liautoyerevan.am\/li-images\/catalog.json';\r\n        let CATALOG = null;\r\n        let currentAngle = { exterior: 0, interior: 0 };\r\n\r\n        \/\/ model + trim -> Lixiang productId (from scraper\/bind.js against the live catalog)\r\n        const MODEL_PRODUCT_IDS = {\r\n          \"i8\": { \"i8\": \"100626685699850565\" },\r\n          \"i6\": { \"i6\": \"100866705652204076\" },\r\n          \"l6\": { \"pro\": \"100177724178416049\", \"max\": \"100161935878677128\" },\r\n          \"l7\": { \"pro\": \"100242372026212380\", \"max\": \"100196999991665603\", \"ultra\": \"100197670006595588\" },\r\n          \"l9\": { \"pro\": \"100203803219869909\", \"ultra\": \"100202342931013895\" },\r\n          \"mega\": { \"ultra\": \"100591277989274312\", \"home\": \"100161557921481917\" },\r\n          \"l9-livis\": { \"mono\": \"100521922857648007\", \"duo\": \"100521922857648007\" }\r\n        };\r\n\r\n        \/\/ models whose renders are NOT transparent (full studio background) -> show\r\n        \/\/ the blurred backdrop. Transparent-cutout models don't get one.\r\n        const BACKDROP_MODELS = new Set(['i6', 'i8', 'l9-livis']);\r\n        const useBackdrop = () => BACKDROP_MODELS.has(currentModelKey);\r\n\r\n        \/\/ chinese item-name -> itemId, per option group, for a catalog product\r\n        function liIdMaps(prod) {\r\n            const m = {};\r\n            for (const [g, o] of Object.entries(prod.options || {})) {\r\n                m[g] = {};\r\n                for (const it of o.items) m[g][it.name] = it.id;\r\n            }\r\n            return m;\r\n        }\r\n\r\n        \/\/ Resolve the current selectedConfig to a catalog variant (or null).\r\n        \/\/ Builds the visualKey the same way the crawler did: affecting groups,\r\n        \/\/ each \"group-item\", sorted, joined by \"_\".\r\n        \/\/ sel (optional) overrides parts of the current selection by English id:\r\n        \/\/ {exterior, wheels, interior, options:[ids]} - used for preloading neighbors.\r\n        function liVariant(sel) {\r\n            if (!CATALOG || !MODEL_CONFIG) return null;\r\n            sel = sel || {};\r\n            const pid = (MODEL_PRODUCT_IDS[currentModelKey] || {})[selectedConfig.model.id];\r\n            const prod = pid && CATALOG.products[pid];\r\n            if (!prod || !prod.variants) return null;\r\n            const idOf = liIdMaps(prod), def = prod.defaultAttrs || {}, attrs = {};\r\n            const ext = chineseNames.exteriorColors[sel.exterior || selectedConfig.exterior.id];\r\n            const wheel = chineseNames.wheels[sel.wheels || selectedConfig.wheels.id];\r\n            const int = chineseNames.interiorColors[sel.interior || selectedConfig.interior.id];\r\n            \/\/ option chinese names (override list, else current multi\/single selection)\r\n            const optIds = sel.options || (selectedConfig.options && selectedConfig.options.length\r\n                ? selectedConfig.options.map(o => o.id)\r\n                : (selectedConfig.option && selectedConfig.option.id !== 'none' ? [selectedConfig.option.id] : []));\r\n            const optChinese = optIds.map(id => chineseNames.options[id]).filter(Boolean);\r\n            for (const g of (prod.affectingGroups || []).map(String)) {\r\n                if (g === '20') attrs[g] = idOf['20'] && idOf['20'][ext];\r\n                else if (g === '19') attrs[g] = idOf['19'] && idOf['19'][wheel];\r\n                else if (g === '18') attrs[g] = idOf['18'] && idOf['18'][int];\r\n                else { \/\/ option group: selected option's item, else the default (off) item\r\n                    let on = null;\r\n                    for (const ch of optChinese) if (idOf[g] && ch in idOf[g]) on = idOf[g][ch];\r\n                    attrs[g] = on != null ? on : def[g];\r\n                }\r\n            }\r\n            const key = (prod.affectingGroups || []).map(g => `${g}-${attrs[g]}`).sort().join('_');\r\n            return prod.variants[key] || null;\r\n        }\r\n\r\n        \/\/ ordered angle UUIDs for a view (exterior=group 20, interior=group 18)\r\n        function liAngles(view, sel) {\r\n            const v = liVariant(sel);\r\n            if (!v) return null;\r\n            return v.images[view === 'interior' ? '18' : '20'] || null;\r\n        }\r\n        \/\/ bump LI_IMG_VER whenever the image set changes (forces browser refetch\r\n        \/\/ past cache - same UUID filenames otherwise get served from cache)\r\n        const LI_IMG_VER = '5';\r\n        const liUrl = (uuid) => `${LI_IMG_BASE}\/${uuid}.webp?v=${LI_IMG_VER}`;\r\n\r\n        \/\/ \u2500\u2500 angle navigation (arrows + dots) \u2500\u2500\r\n        function renderAngleControls() {\r\n            const dots = document.getElementById('angleDots');\r\n            const prev = document.getElementById('anglePrev');\r\n            const next = document.getElementById('angleNext');\r\n            if (!dots || !prev || !next) return;\r\n            const angles = liAngles(currentView);\r\n            const n = angles ? angles.length : 0;\r\n            let i = currentAngle[currentView] || 0;\r\n            if (i >= n) { i = 0; currentAngle[currentView] = 0; }\r\n            if (n <= 1) { dots.innerHTML = ''; prev.classList.remove('show'); next.classList.remove('show'); return; }\r\n            prev.classList.add('show'); next.classList.add('show');\r\n            dots.innerHTML = '';\r\n            for (let k = 0; k < n; k++) {\r\n                const d = document.createElement('div');\r\n                d.className = 'dot' + (k === i ? ' active' : '');\r\n                d.onclick = () => setAngle(k);\r\n                dots.appendChild(d);\r\n            }\r\n        }\r\n        function setAngle(idx, dir) {\r\n            const angles = liAngles(currentView);\r\n            if (!angles || !angles.length) return;\r\n            const n = angles.length;\r\n            const cur = currentAngle[currentView] || 0;\r\n            const next = ((idx % n) + n) % n; \/\/ wrap around\r\n            if (next === cur) return;\r\n            if (dir === undefined) dir = next > cur ? 1 : -1; \/\/ dot clicks: infer direction\r\n            currentAngle[currentView] = next;\r\n            \/\/ changing angle resets any zoom\/pan - a zoomed image sliding looks wrong\r\n            const baseScale = 0.9;\r\n            if (currentZoom !== baseScale || panX !== 0 || panY !== 0) {\r\n                currentZoom = baseScale; panX = 0; panY = 0;\r\n                updateZoom();\r\n            }\r\n            updateImage(currentView, false, dir); \/\/ dir != 0 -> directional slide\r\n        }\r\n        \/\/ arrows pass an explicit direction so wrap-around (last->first) still slides correctly\r\n        function changeAngle(delta) { setAngle((currentAngle[currentView] || 0) + delta, delta > 0 ? 1 : -1); }\r\n\r\n        let selectedConfig = {\r\n            model: { id: '', name: '', price: 0 },\r\n            wheels: { id: '', name: '', desc: '', price: 0 },\r\n            exterior: { id: '', name: '', price: 0 },\r\n            interior: { id: '', name: '', price: 0 },\r\n            option: { id: 'none', name: 'N\/A', price: 0 },\r\n            options: [] \/\/ For multi-option models like i6\r\n        };\r\n\r\n        \/\/ Catalog-driven image path for the current view + angle. Falls back to\r\n        \/\/ the legacy folder scheme if the catalog\/variant isn't available (e.g.\r\n        \/\/ catalog not yet uploaded, or a model not in the catalog). webp-only\r\n        \/\/ (avif dropped), so avif is returned empty and crossfade skips it.\r\n        function getImagePaths(viewType = 'exterior') {\r\n            if (!MODEL_CONFIG) return { avif: '', webp: '' };\r\n            const angles = liAngles(viewType);\r\n            if (angles && angles.length) {\r\n                let i = currentAngle[viewType] || 0;\r\n                if (i >= angles.length) i = 0;\r\n                return { avif: '', webp: liUrl(angles[i]) };\r\n            }\r\n            return getImagePathsLegacy(viewType);\r\n        }\r\n\r\n        \/\/ Generate image path based on current configuration (LEGACY fallback)\r\n        function getImagePathsLegacy(viewType = 'exterior') {\r\n            if (!MODEL_CONFIG) return { avif: '', webp: '' };\r\n\r\n            const ext = chineseNames.exteriorColors[selectedConfig.exterior.id];\r\n            const wheel = chineseNames.wheels[selectedConfig.wheels.id];\r\n            const int = chineseNames.interiorColors[selectedConfig.interior.id];\r\n            const chineseFolder = MODEL_CONFIG.chineseFolder;\r\n\r\n            console.log('[DEBUG getImagePaths] currentModelKey:', currentModelKey, 'viewType:', viewType);\r\n\r\n            \/\/ Special case: L9 Livis has unique folder structure\r\n            if (currentModelKey === 'l9-livis') {\r\n                console.log('[DEBUG] Using L9 Livis special path structure');\r\n                let filename;\r\n                if (viewType === 'exterior') {\r\n                    \/\/ Exterior pattern: {color}-Livis\u4e13\u5c5e22\u82f1\u5bf8\u6676\u7070\u8f6e\u6bc2-{interior}.webp\r\n                    filename = `${ext}-${wheel}-${int}`;\r\n                    return {\r\n                        avif: `${imagePath}avif\/${chineseFolder}\/output-avif\/exterior\/${filename}.avif`,\r\n                        webp: `${imagePath}webp\/${chineseFolder}\/output-webp\/exterior\/${filename}.webp`\r\n                    };\r\n                } else {\r\n                    \/\/ Interior pattern: {interior}-{exterior}.webp (no wheel!)\r\n                    filename = `${int}-${ext}`;\r\n                    return {\r\n                        avif: `${imagePath}avif\/${chineseFolder}\/output-avif\/interior\/${filename}.avif`,\r\n                        webp: `${imagePath}webp\/${chineseFolder}\/output-webp\/interior\/${filename}.webp`\r\n                    };\r\n                }\r\n            }\r\n\r\n            \/\/ Get variant folder from selected model\r\n            \/\/ Special case: L6 Pro with platinum speakers uses Max interior images\r\n            let modelId = selectedConfig.model.id;\r\n            if (viewType === 'interior' &&\r\n                selectedConfig.option.id === 'platinum' &&\r\n                selectedConfig.model.id === 'pro' &&\r\n                MODEL_CONFIG.models['max']) {\r\n                modelId = 'max'; \/\/ Use Max variant folder for interior when Pro has platinum speakers\r\n            }\r\n            const currentModel = MODEL_CONFIG.models[modelId];\r\n            const variantFolder = currentModel.variantFolder;\r\n\r\n            \/\/ Check if wheels should be included in filename (MEGA doesn't include wheels)\r\n            const includeWheels = MODEL_CONFIG.includeWheelsInFilename !== false;\r\n\r\n            \/\/ Check filename patterns (different models have different orders)\r\n            const exteriorPattern = MODEL_CONFIG.exteriorPattern || 'default'; \/\/ 'default' or 'interior-before-wheels'\r\n            const interiorPattern = MODEL_CONFIG.interiorPattern || 'default'; \/\/ 'default' or 'exterior-first'\r\n\r\n            let filename, basePath;\r\n\r\n            if (viewType === 'exterior') {\r\n                \/\/ Exterior patterns:\r\n                \/\/ Default (i8, L6, L7): {exteriorColor}-{wheelType}-{interiorColor}[-{option}]\r\n                \/\/ L9: {exteriorColor}-{interiorColor}-{wheelType}[-{option}]\r\n                \/\/ MEGA: {exteriorColor}-{interiorColor} (no wheels)\r\n                const parts = [ext];\r\n\r\n                if (exteriorPattern === 'interior-before-wheels') {\r\n                    \/\/ L9 pattern: color -> interior -> wheel\r\n                    parts.push(int);\r\n                    if (includeWheels) {\r\n                        parts.push(wheel);\r\n                    }\r\n                } else {\r\n                    \/\/ Default pattern: color -> wheel -> interior\r\n                    if (includeWheels) {\r\n                        parts.push(wheel);\r\n                    }\r\n                    parts.push(int);\r\n                }\r\n\r\n                \/\/ Handle options in filename\r\n                \/\/ i6 multi-option model: rear screen affects exterior\r\n                if (currentModelKey === 'i6' && MODEL_CONFIG.optionMode === 'multiple') {\r\n                    \/\/ Check if rear-screen option is selected\r\n                    const hasRearScreen = selectedConfig.options.some(opt => opt.id === 'rear-screen');\r\n                    const rearScreenOption = MODEL_CONFIG.options['rear-screen'];\r\n\r\n                    if (hasRearScreen && rearScreenOption) {\r\n                        parts.push(rearScreenOption.chinese); \/\/ \u540e\u8231\u5a31\u4e50\u5c4f+\u6d41\u5a92\u4f53\u540e\u89c6\u955c\r\n                    } else if (rearScreenOption && rearScreenOption.chineseBase) {\r\n                        parts.push(rearScreenOption.chineseBase); \/\/ \u65e0\u540e\u8231\u5a31\u4e50\u5c4f\r\n                    }\r\n                } else {\r\n                    \/\/ Single-option models: include option if it affects exterior\r\n                    if (selectedConfig.option.id !== 'none') {\r\n                        const optionData = MODEL_CONFIG.options[selectedConfig.option.id];\r\n                        \/\/ Check view-specific flag (affectsExterior) or fall back to legacy affectsVisuals flag\r\n                        const affectsThisView = optionData && optionData.affectsExterior !== undefined\r\n                            ? optionData.affectsExterior\r\n                            : (optionData && optionData.affectsVisuals !== false);\r\n                        if (affectsThisView) {\r\n                            parts.push(chineseNames.options[selectedConfig.option.id]);\r\n                        }\r\n                    }\r\n                }\r\n                filename = parts.join('-');\r\n                basePath = `${chineseFolder}\/${variantFolder}\/variant-attribute\/exterior\/angle-0\/`;\r\n            } else {\r\n                \/\/ Interior patterns:\r\n                \/\/ Default (i8, L6): {interiorColor}-{exteriorColor}[-{option}]\r\n                \/\/ L7, L9, MEGA: {exteriorColor}-{interiorColor}[-{option}]\r\n                let parts;\r\n                if (interiorPattern === 'exterior-first') {\r\n                    \/\/ L7\/L9\/MEGA pattern: exterior -> interior\r\n                    parts = [ext, int];\r\n                } else {\r\n                    \/\/ Default pattern: interior -> exterior\r\n                    parts = [int, ext];\r\n                }\r\n\r\n                \/\/ Handle options in filename\r\n                \/\/ i6 multi-option model: audio, rear screen, and drivetrain affect interior\r\n                if (currentModelKey === 'i6' && MODEL_CONFIG.optionMode === 'multiple') {\r\n                    \/\/ i6 interior pattern: {interior}-{color}-{audio}-{rear_screen}-{drivetrain}\r\n                    \/\/ Audio: platinum or base (always present)\r\n                    const hasPlatinumAudio = selectedConfig.options.some(opt => opt.id === 'platinum-audio');\r\n                    const audioOption = MODEL_CONFIG.options['platinum-audio'];\r\n                    if (hasPlatinumAudio && audioOption) {\r\n                        parts.push(audioOption.chinese); \/\/ \u94c2\u91d1\u97f3\u54cd\r\n                    } else if (audioOption && audioOption.chineseBase) {\r\n                        parts.push(audioOption.chineseBase); \/\/ \u9ad8\u7ea7\u97f3\u54cd\r\n                    }\r\n\r\n                    \/\/ Rear screen: with or without (always present)\r\n                    const hasRearScreen = selectedConfig.options.some(opt => opt.id === 'rear-screen');\r\n                    const rearScreenOption = MODEL_CONFIG.options['rear-screen'];\r\n                    if (hasRearScreen && rearScreenOption) {\r\n                        parts.push(rearScreenOption.chinese); \/\/ \u540e\u8231\u5a31\u4e50\u5c4f+\u6d41\u5a92\u4f53\u540e\u89c6\u955c\r\n                    } else if (rearScreenOption && rearScreenOption.chineseBase) {\r\n                        parts.push(rearScreenOption.chineseBase); \/\/ \u65e0\u540e\u8231\u5a31\u4e50\u5c4f\r\n                    }\r\n\r\n                    \/\/ Drivetrain: AWD or base RWD (always present)\r\n                    const hasAWD = selectedConfig.options.some(opt => opt.id === 'awd-system');\r\n                    const awdOption = MODEL_CONFIG.options['awd-system'];\r\n                    if (hasAWD && awdOption) {\r\n                        parts.push(awdOption.chinese); \/\/ \u53cc\u7535\u673a\u56db\u9a71\r\n                    } else if (awdOption && awdOption.chineseBase) {\r\n                        parts.push(awdOption.chineseBase); \/\/ \u540e\u9a71\r\n                    }\r\n                } else {\r\n                    \/\/ Single-option models\r\n                    \/\/ Don't add suffix if we switched variant folders (folder switch IS the visual change)\r\n                    if (selectedConfig.option.id !== 'none') {\r\n                        const optionData = MODEL_CONFIG.options[selectedConfig.option.id];\r\n                        \/\/ Check if we switched variant folders for this option\r\n                        const didVariantSwitch = (selectedConfig.option.id === 'platinum' &&\r\n                                                  selectedConfig.model.id === 'pro' &&\r\n                                                  MODEL_CONFIG.models['max']);\r\n\r\n                        if (!didVariantSwitch) {\r\n                            \/\/ Check view-specific flag (affectsInterior) or fall back to legacy affectsVisuals flag\r\n                            const affectsThisView = optionData && optionData.affectsInterior !== undefined\r\n                                ? optionData.affectsInterior\r\n                                : (optionData && optionData.affectsVisuals !== false);\r\n                            if (affectsThisView) {\r\n                                parts.push(chineseNames.options[selectedConfig.option.id]);\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n                filename = parts.join('-');\r\n                basePath = `${chineseFolder}\/${variantFolder}\/variant-attribute\/interior\/angle-0\/`;\r\n            }\r\n\r\n            return {\r\n                avif: `${imagePath}avif\/${basePath}${filename}.avif`,\r\n                webp: `${imagePath}webp\/${basePath}${filename}.webp`\r\n            };\r\n        }\r\n\r\n        \/\/ Update image. slideDir != 0 -> directional slide (angle change);\r\n        \/\/ otherwise crossfade (color \/ feature \/ model \/ view change).\r\n        function updateImage(viewType, skipCrossfade = false, slideDir = 0) {\r\n            const paths = getImagePaths(viewType);\r\n            if (paths.webp) preloadUrl(paths.webp, 8); \/\/ retain the image we're about to show -> no refetch next time\r\n            const v = viewType === 'interior' ? 'interior' : 'exterior';\r\n            const ids = [v + 'Picture', v + 'PictureBack', v + 'SourceAvif', v + 'SourceWebp',\r\n                v + 'Image', v + 'SourceAvifBack', v + 'SourceWebpBack', v + 'ImageBack'];\r\n            if (slideDir && !skipCrossfade) {\r\n                slideImage(ids[0], ids[1], paths, viewType, slideDir);\r\n            } else {\r\n                crossfadeImage(ids[0], ids[1], ids[2], ids[3], ids[4], ids[5], ids[6], ids[7], paths, skipCrossfade, viewType);\r\n            }\r\n            \/\/ refresh angle arrows\/dots for the (possibly new) variant + view\r\n            if (viewType === currentView) renderAngleControls();\r\n        }\r\n\r\n        \/\/ Directional slide for ANGLE changes: new angle slides in from the side\r\n        \/\/ (next from the right, prev from the left); old slides out the opposite way.\r\n        \/\/ Pictures slide via translateX; the blurred backdrop slides with them.\r\n        function slideImage(frontPicId, backPicId, paths, viewType, dir) {\r\n            const pic1 = document.getElementById(frontPicId);\r\n            const pic2 = document.getElementById(backPicId);\r\n            if (isTransitioning[viewType]) { pendingUpdate[viewType] = true; return; }\r\n            isTransitioning[viewType] = true;\r\n            const view = pic1.parentElement;\r\n            const front = pic1.classList.contains('front') ? pic1 : pic2;\r\n            const back = pic1.classList.contains('back') ? pic1 : pic2;\r\n            const backAvif = back.querySelector('source[type=\"image\/avif\"]');\r\n            const backWebp = back.querySelector('source[type=\"image\/webp\"]');\r\n            const backImg = back.querySelector('img');\r\n            const bdFront = useBackdrop() ? view.querySelector('.car-backdrop.front') : null;\r\n            const bdBack = useBackdrop() ? view.querySelector('.car-backdrop.back') : null;\r\n            const inFrom = dir > 0 ? '100%' : '-100%';  \/\/ new enters from this side\r\n            const outTo = dir > 0 ? '-100%' : '100%';   \/\/ old exits the other side\r\n            const DUR = 300;\r\n            const EASE = 'cubic-bezier(0.65, 0, 0.35, 1)'; \/\/ ease-in-out: speeds up then slows to a stop\r\n            const bdTf = (x) => `translateX(${x}) scale(1.15)`; \/\/ keep backdrop's base scale\r\n\r\n            backAvif.srcset = paths.avif || '';\r\n            backWebp.srcset = paths.webp;\r\n            backImg.src = paths.webp;\r\n            if (bdBack) bdBack.src = paths.webp;\r\n\r\n            backImg.onload = () => {\r\n                \/\/ place the incoming layers offscreen, fully opaque, no transition\r\n                back.style.transition = 'none'; back.style.opacity = '1'; back.style.transform = `translateX(${inFrom})`;\r\n                if (bdBack) { bdBack.style.transition = 'none'; bdBack.style.opacity = '1'; bdBack.style.transform = bdTf(inFrom); }\r\n                back.offsetHeight; \/\/ reflow\r\n                \/\/ animate the slide (both layers move together)\r\n                const t = `transform ${DUR}ms ${EASE}`;\r\n                back.style.transition = t; front.style.transition = t;\r\n                if (bdBack) bdBack.style.transition = t;\r\n                if (bdFront) bdFront.style.transition = t;\r\n                requestAnimationFrame(() => {\r\n                    back.style.transform = 'translateX(0)';\r\n                    front.style.transform = `translateX(${outTo})`;\r\n                    if (bdBack) bdBack.style.transform = bdTf('0');\r\n                    if (bdFront) bdFront.style.transform = bdTf(outTo);\r\n                });\r\n                setTimeout(() => {\r\n                    \/\/ hide the outgoing layers INSTANTLY (transition off) before resetting\r\n                    \/\/ their transform, otherwise the old image jumps back to center and\r\n                    \/\/ flashes while its opacity is still fading.\r\n                    front.style.transition = 'none'; front.style.opacity = '0'; front.style.transform = '';\r\n                    back.style.transition = 'none'; back.style.opacity = '1'; back.style.transform = '';\r\n                    if (bdFront) { bdFront.style.transition = 'none'; bdFront.style.opacity = '0'; bdFront.style.transform = ''; }\r\n                    if (bdBack) { bdBack.style.transition = 'none'; bdBack.style.opacity = '1'; bdBack.style.transform = ''; }\r\n                    back.offsetHeight; \/\/ commit instantly\r\n                    \/\/ swap front\/back classes\r\n                    front.classList.remove('front'); front.classList.add('back');\r\n                    back.classList.remove('back'); back.classList.add('front');\r\n                    if (bdFront && bdBack) {\r\n                        bdFront.classList.remove('front'); bdFront.classList.add('back');\r\n                        bdBack.classList.remove('back'); bdBack.classList.add('front');\r\n                    }\r\n                    \/\/ restore transitions for subsequent crossfades\r\n                    front.style.transition = ''; back.style.transition = '';\r\n                    if (bdFront) bdFront.style.transition = '';\r\n                    if (bdBack) bdBack.style.transition = '';\r\n                    isTransitioning[viewType] = false;\r\n                    if (pendingUpdate[viewType]) { pendingUpdate[viewType] = false; updateImage(viewType); }\r\n                }, DUR);\r\n            };\r\n            backImg.onerror = () => {\r\n                isTransitioning[viewType] = false;\r\n                if (pendingUpdate[viewType]) { pendingUpdate[viewType] = false; updateImage(viewType); }\r\n            };\r\n        }\r\n\r\n        \/\/ Crossfade between two picture elements\r\n        \/\/ New image loads underneath, old image fades out on top to reveal it\r\n        function crossfadeImage(frontPicId, backPicId, frontAvifId, frontWebpId, frontImgId, backAvifId, backWebpId, backImgId, paths, skipCrossfade, viewType) {\r\n            const pic1 = document.getElementById(frontPicId);\r\n            const pic2 = document.getElementById(backPicId);\r\n            const view = pic1.parentElement; \/\/ car-view (also holds the backdrops)\r\n            \/\/ only non-transparent (with-bg) models use the blurred backdrop\r\n            const bdFront = useBackdrop() ? view.querySelector('.car-backdrop.front') : null;\r\n            const bdBack = useBackdrop() ? view.querySelector('.car-backdrop.back') : null;\r\n\r\n            if (skipCrossfade) {\r\n                \/\/ Direct update without crossfade (for initial load)\r\n                \/\/ Load into whichever has front class\r\n                const frontPic = pic1.classList.contains('front') ? pic1 : pic2;\r\n                const frontAvif = frontPic.querySelector('source[type=\"image\/avif\"]');\r\n                const frontWebp = frontPic.querySelector('source[type=\"image\/webp\"]');\r\n                const frontImg = frontPic.querySelector('img');\r\n                frontAvif.srcset = paths.avif || ''; \/\/ empty -> browser skips avif, uses webp\r\n                frontWebp.srcset = paths.webp;\r\n                frontImg.src = paths.webp;\r\n                if (bdFront) bdFront.src = paths.webp;\r\n                return;\r\n            }\r\n\r\n            \/\/ Same image already showing -> nothing visually changes, so don't animate.\r\n            \/\/ (lots of options map to the identical variant render; a fade there is just a pointless flash)\r\n            \/\/ BUT only treat it as a no-op when nothing is in flight: mid-transition the\r\n            \/\/ 'front' layer still holds the OLD image and the running crossfade will land\r\n            \/\/ on a DIFFERENT image, so we must queue a refresh to end on the latest pick.\r\n            const frontNow = pic1.classList.contains('front') ? pic1 : pic2;\r\n            const frontWebpNow = frontNow.querySelector('source[type=\"image\/webp\"]');\r\n            if (frontWebpNow && frontWebpNow.srcset === paths.webp && !isTransitioning[viewType]) return;\r\n\r\n            \/\/ Transition already running: remember to refresh when it finishes,\r\n            \/\/ then bail (can't overlap two crossfades on the same layers)\r\n            if (isTransitioning[viewType]) {\r\n                pendingUpdate[viewType] = true;\r\n                return;\r\n            }\r\n\r\n            \/\/ Lock transitions for this view\r\n            isTransitioning[viewType] = true;\r\n\r\n            \/\/ Determine which element currently has which class\r\n            const currentFront = pic1.classList.contains('front') ? pic1 : pic2;\r\n            const currentBack = pic1.classList.contains('back') ? pic1 : pic2;\r\n\r\n            \/\/ Get sources for the back layer (where we'll load new image)\r\n            const backAvif = currentBack.querySelector('source[type=\"image\/avif\"]');\r\n            const backWebp = currentBack.querySelector('source[type=\"image\/webp\"]');\r\n            const backImg = currentBack.querySelector('img');\r\n\r\n            \/\/ Load new image into back picture (underneath) + back backdrop\r\n            backAvif.srcset = paths.avif || ''; \/\/ empty -> browser skips avif, uses webp\r\n            backWebp.srcset = paths.webp;\r\n            backImg.src = paths.webp;\r\n            if (bdBack) bdBack.src = paths.webp; \/\/ same (cached) url -> ready with the picture\r\n\r\n            \/\/ Crossfade dials tuned in crossfade-tuner.html:\r\n            \/\/ new (back) leads the fade-in, old (front) waits FRONT_DELAY then fades out.\r\n            \/\/ The negative overlap keeps coverage high so semi-transparent shadow\/glass\r\n            \/\/ regions never stack at a midpoint (kills the additive flash).\r\n            const CF_DUR = 400, CF_EASE = 'ease-in-out', CF_FRONT_DELAY = 200; \/\/ -50% overlap (new leads, on top)\r\n            backImg.onload = () => {\r\n                \/\/ Raise the INCOMING layer above the old one so its fade-in is actually\r\n                \/\/ visible. Without this the new image fades in underneath the opaque old\r\n                \/\/ image (front=z2, back=z1) and the whole \"new leads\" overlap is occluded.\r\n                currentBack.style.zIndex = '3';\r\n                if (bdBack) bdBack.style.zIndex = '1';\r\n                \/\/ back starts hidden, no transition\r\n                currentBack.style.transition = 'none';\r\n                if (bdBack) bdBack.style.transition = 'none';\r\n                currentBack.style.opacity = '0';\r\n                if (bdBack) bdBack.style.opacity = '0';\r\n                currentBack.offsetHeight; \/\/ reflow so the 0 sticks before we animate\r\n\r\n                requestAnimationFrame(() => {\r\n                    \/\/ new fades IN immediately\r\n                    currentBack.style.transition = `opacity ${CF_DUR}ms ${CF_EASE}`;\r\n                    currentBack.style.opacity = '1';\r\n                    if (bdBack) { bdBack.style.transition = `opacity ${CF_DUR}ms ${CF_EASE}`; bdBack.style.opacity = '1'; }\r\n                    \/\/ old fades OUT after the lead delay\r\n                    setTimeout(() => {\r\n                        currentFront.style.transition = `opacity ${CF_DUR}ms ${CF_EASE}`;\r\n                        currentFront.style.opacity = '0';\r\n                        if (bdFront) { bdFront.style.transition = `opacity ${CF_DUR}ms ${CF_EASE}`; bdFront.style.opacity = '0'; }\r\n                    }, CF_FRONT_DELAY);\r\n                });\r\n\r\n                \/\/ After crossfade completes, swap layers for next transition\r\n                setTimeout(() => {\r\n                    \/\/ Swap z-index classes\r\n                    currentFront.classList.remove('front');\r\n                    currentFront.classList.add('back');\r\n                    currentBack.classList.remove('back');\r\n                    currentBack.classList.add('front');\r\n\r\n                    \/\/ Reset inline transitions + z-index (back to CSS default) + opacity:\r\n                    \/\/ old image (now back) hidden, new image (now front) visible\r\n                    currentFront.style.transition = ''; currentBack.style.transition = '';\r\n                    currentFront.style.zIndex = ''; currentBack.style.zIndex = '';\r\n                    currentFront.style.opacity = '0';\r\n                    currentBack.style.opacity = '1';\r\n\r\n                    \/\/ swap backdrop layers to match\r\n                    if (bdFront && bdBack) {\r\n                        bdFront.classList.remove('front'); bdFront.classList.add('back');\r\n                        bdBack.classList.remove('back'); bdBack.classList.add('front');\r\n                        bdFront.style.transition = ''; bdBack.style.transition = '';\r\n                        bdFront.style.zIndex = ''; bdBack.style.zIndex = '';\r\n                        bdFront.style.opacity = '0';\r\n                        bdBack.style.opacity = '1';\r\n                    }\r\n\r\n                    \/\/ Unlock transitions for this view\r\n                    isTransitioning[viewType] = false;\r\n                    \/\/ if a change was requested mid-transition, apply the latest now\r\n                    if (pendingUpdate[viewType]) { pendingUpdate[viewType] = false; updateImage(viewType); }\r\n                }, CF_DUR + CF_FRONT_DELAY + 40); \/\/ full fade incl. the old-image lead delay\r\n            };\r\n\r\n            \/\/ Handle image load error - unlock transitions so user can try again\r\n            backImg.onerror = () => {\r\n                isTransitioning[viewType] = false;\r\n                if (pendingUpdate[viewType]) { pendingUpdate[viewType] = false; updateImage(viewType); }\r\n            };\r\n        }\r\n\r\n        \/\/ Image cache + predictive preload queue.\r\n        \/\/ We RETAIN the Image objects (not just the url string) for the whole session: the\r\n        \/\/ dev server (and possibly prod) sends no Cache-Control, so the browser's HTTP cache\r\n        \/\/ is unreliable - a live HTMLImageElement keeps the resource warm in memory so any\r\n        \/\/ later <img src=sameURL> reuses it with no network. This is what makes preload stick.\r\n        \/\/\r\n        \/\/ Background prefetch runs through a small priority queue with a concurrency cap so we\r\n        \/\/ don't fire dozens of requests at once. Priority tiers:\r\n        \/\/   10 = hover (user is about to click this) \u00b7 5 = current variant's other angles\r\n        \/\/    1 = adjacent options' first angle (likely-next). Higher prio loads first.\r\n        const imageCache = new Map();   \/\/ url -> HTMLImageElement (kept alive on purpose)\r\n        const preloadQueue = [];        \/\/ [{ url, prio }] pending background loads\r\n        let activePreloads = 0;\r\n        const MAX_CONCURRENT_PRELOADS = 6; \/\/ ~browser per-host cap\r\n\r\n        function preloadUrl(url, prio = 1) {\r\n            if (!url || imageCache.has(url)) return;\r\n            const queued = preloadQueue.find(q => q.url === url);\r\n            if (queued) { if (prio > queued.prio) queued.prio = prio; return; } \/\/ bump priority\r\n            preloadQueue.push({ url, prio });\r\n            pumpPreloadQueue();\r\n        }\r\n        function pumpPreloadQueue() {\r\n            while (activePreloads < MAX_CONCURRENT_PRELOADS && preloadQueue.length) {\r\n                preloadQueue.sort((a, b) => b.prio - a.prio); \/\/ highest priority first\r\n                const { url } = preloadQueue.shift();\r\n                if (imageCache.has(url)) continue;\r\n                const img = new Image();\r\n                imageCache.set(url, img); \/\/ retain + dedupe immediately\r\n                activePreloads++;\r\n                const done = () => { activePreloads--; pumpPreloadQueue(); };\r\n                img.onload = done;\r\n                img.onerror = done;\r\n                img.src = url;\r\n            }\r\n        }\r\n        function preloadImage(avifPath, webpPath) { preloadUrl(webpPath); } \/\/ legacy shim (webp-only now)\r\n\r\n        \/\/ Hover prefetch: hovering a color\/wheel card is the strongest \"about to click\" signal,\r\n        \/\/ so pull ALL angles (both views) of that variant at top priority. Delegated on document\r\n        \/\/ so it survives panel re-renders; each card is prefetched at most once (WeakSet guard).\r\n        const _hoverPrefetched = new WeakSet();\r\n        function initHoverPrefetch() {\r\n            if (window._hoverPrefetchWired) return;\r\n            window._hoverPrefetchWired = true;\r\n            document.addEventListener('mouseover', (e) => {\r\n                const card = e.target.closest && e.target.closest('.color-option, .wheel-option');\r\n                if (!card || _hoverPrefetched.has(card) || !MODEL_CONFIG) return;\r\n                _hoverPrefetched.add(card);\r\n                let sel = null;\r\n                if (card.dataset.ext) sel = { exterior: card.dataset.ext };\r\n                else if (card.dataset.int) sel = { interior: card.dataset.int };\r\n                else if (card.dataset.wheelId) sel = { wheels: card.dataset.wheelId };\r\n                if (!sel) return;\r\n                const v = liVariant(sel);\r\n                if (!v) return;\r\n                for (const arr of Object.values(v.images)) for (const u of arr) preloadUrl(liUrl(u), 10);\r\n            });\r\n        }\r\n\r\n        \/\/ Catalog-aware preload: the current variant's ALL angles (so angle nav +\r\n        \/\/ view toggle are instant) plus the first angle of every adjacent color \/\r\n        \/\/ wheel \/ interior (likely next clicks). Falls back to the legacy folder\r\n        \/\/ scheme only if the catalog isn't loaded.\r\n        function preloadAdjacentOptions() {\r\n            if (!MODEL_CONFIG) return;\r\n            const cur = liVariant();\r\n            if (!cur) { preloadAdjacentLegacy(); return; }\r\n\r\n            \/\/ current variant: every angle of both views (instant angle-nav + view toggle)\r\n            for (const arr of Object.values(cur.images)) for (const u of arr) preloadUrl(liUrl(u), 5);\r\n            \/\/ adjacent options: first angle only (likely-next click), lower priority\r\n            for (const cid of Object.keys(MODEL_CONFIG.exteriorColors)) {\r\n                if (cid === selectedConfig.exterior.id) continue;\r\n                const v = liVariant({ exterior: cid });\r\n                if (v && v.images['20']) preloadUrl(liUrl(v.images['20'][0]), 1);\r\n            }\r\n            for (const iid of Object.keys(MODEL_CONFIG.interiorColors)) {\r\n                if (iid === selectedConfig.interior.id) continue;\r\n                const v = liVariant({ interior: iid });\r\n                if (v && v.images['18']) preloadUrl(liUrl(v.images['18'][0]), 1);\r\n            }\r\n            for (const wid of Object.keys(MODEL_CONFIG.wheels)) {\r\n                if (wid === selectedConfig.wheels.id) continue;\r\n                const v = liVariant({ wheels: wid });\r\n                if (v && v.images['20']) preloadUrl(liUrl(v.images['20'][0]), 1);\r\n            }\r\n        }\r\n\r\n        \/\/ legacy folder-scheme preload (fallback when catalog isn't loaded)\r\n        function preloadAdjacentLegacy() {\r\n            if (!MODEL_CONFIG) return;\r\n\r\n            const exteriorColors = Object.keys(MODEL_CONFIG.exteriorColors);\r\n            const interiorColors = Object.keys(MODEL_CONFIG.interiorColors);\r\n\r\n            \/\/ Filter out disabled wheels for current model\r\n            const wheels = Object.keys(MODEL_CONFIG.wheels).filter(wheelId => {\r\n                const wheelData = MODEL_CONFIG.wheels[wheelId];\r\n                return !wheelData.disabledFor || !wheelData.disabledFor.includes(selectedConfig.model.id);\r\n            });\r\n\r\n            \/\/ Find current indices\r\n            const extIdx = exteriorColors.indexOf(selectedConfig.exterior.id);\r\n            const wheelIdx = wheels.indexOf(selectedConfig.wheels.id);\r\n            const intIdx = interiorColors.indexOf(selectedConfig.interior.id);\r\n\r\n            \/\/ Create temporary configs for adjacent options\r\n            const tempConfig = { ...selectedConfig };\r\n\r\n            \/\/ Preload previous\/next exterior color\r\n            if (extIdx > 0) {\r\n                tempConfig.exterior = { ...selectedConfig.exterior, id: exteriorColors[extIdx - 1] };\r\n                const paths = getImagePathsFromConfig(tempConfig, currentView);\r\n                preloadImage(paths.avif, paths.webp);\r\n            }\r\n            if (extIdx < exteriorColors.length - 1) {\r\n                tempConfig.exterior = { ...selectedConfig.exterior, id: exteriorColors[extIdx + 1] };\r\n                const paths = getImagePathsFromConfig(tempConfig, currentView);\r\n                preloadImage(paths.avif, paths.webp);\r\n            }\r\n\r\n            \/\/ Preload previous\/next wheel (only available wheels)\r\n            tempConfig.exterior = selectedConfig.exterior;\r\n            if (wheelIdx > 0) {\r\n                tempConfig.wheels = { ...selectedConfig.wheels, id: wheels[wheelIdx - 1] };\r\n                const paths = getImagePathsFromConfig(tempConfig, currentView);\r\n                preloadImage(paths.avif, paths.webp);\r\n            }\r\n            if (wheelIdx < wheels.length - 1) {\r\n                tempConfig.wheels = { ...selectedConfig.wheels, id: wheels[wheelIdx + 1] };\r\n                const paths = getImagePathsFromConfig(tempConfig, currentView);\r\n                preloadImage(paths.avif, paths.webp);\r\n            }\r\n\r\n            \/\/ Preload previous\/next interior color\r\n            tempConfig.wheels = selectedConfig.wheels;\r\n            if (intIdx > 0) {\r\n                tempConfig.interior = { ...selectedConfig.interior, id: interiorColors[intIdx - 1] };\r\n                const paths = getImagePathsFromConfig(tempConfig, currentView);\r\n                preloadImage(paths.avif, paths.webp);\r\n            }\r\n            if (intIdx < interiorColors.length - 1) {\r\n                tempConfig.interior = { ...selectedConfig.interior, id: interiorColors[intIdx + 1] };\r\n                const paths = getImagePathsFromConfig(tempConfig, currentView);\r\n                preloadImage(paths.avif, paths.webp);\r\n            }\r\n\r\n            \/\/ Preload alternate view (exterior <-> interior)\r\n            const alternateView = currentView === 'exterior' ? 'interior' : 'exterior';\r\n            const alternatePaths = getImagePaths(alternateView);\r\n            preloadImage(alternatePaths.avif, alternatePaths.webp);\r\n        }\r\n\r\n        function getImagePathsFromConfig(config, viewType = 'exterior') {\r\n            if (!MODEL_CONFIG) return { avif: '', webp: '' };\r\n\r\n            const ext = chineseNames.exteriorColors[config.exterior.id];\r\n            const wheel = chineseNames.wheels[config.wheels.id];\r\n            const int = chineseNames.interiorColors[config.interior.id];\r\n            const chineseFolder = MODEL_CONFIG.chineseFolder;\r\n\r\n            \/\/ Special case: L9 Livis has unique folder structure\r\n            if (currentModelKey === 'l9-livis') {\r\n                let filename;\r\n                if (viewType === 'exterior') {\r\n                    \/\/ Exterior pattern: {color}-Livis\u4e13\u5c5e22\u82f1\u5bf8\u6676\u7070\u8f6e\u6bc2-{interior}.webp\r\n                    filename = `${ext}-${wheel}-${int}`;\r\n                    return {\r\n                        avif: `${imagePath}avif\/${chineseFolder}\/output-avif\/exterior\/${filename}.avif`,\r\n                        webp: `${imagePath}webp\/${chineseFolder}\/output-webp\/exterior\/${filename}.webp`\r\n                    };\r\n                } else {\r\n                    \/\/ Interior pattern: {interior}-{exterior}.webp (no wheel!)\r\n                    filename = `${int}-${ext}`;\r\n                    return {\r\n                        avif: `${imagePath}avif\/${chineseFolder}\/output-avif\/interior\/${filename}.avif`,\r\n                        webp: `${imagePath}webp\/${chineseFolder}\/output-webp\/interior\/${filename}.webp`\r\n                    };\r\n                }\r\n            }\r\n\r\n            \/\/ Get variant folder from config's model\r\n            \/\/ Special case: L6 Pro with platinum speakers uses Max interior images\r\n            let modelId = config.model.id;\r\n            if (viewType === 'interior' &&\r\n                config.option.id === 'platinum' &&\r\n                config.model.id === 'pro' &&\r\n                MODEL_CONFIG.models['max']) {\r\n                modelId = 'max'; \/\/ Use Max variant folder for interior when Pro has platinum speakers\r\n            }\r\n            const currentModel = MODEL_CONFIG.models[modelId];\r\n            const variantFolder = currentModel.variantFolder;\r\n\r\n            let filename, basePath;\r\n\r\n            if (viewType === 'exterior') {\r\n                const parts = [ext, wheel, int];\r\n                if (config.option.id !== 'none') {\r\n                    const optionData = MODEL_CONFIG.options[config.option.id];\r\n                    \/\/ Check if option affects exterior view\r\n                    const affectsThisView = optionData && optionData.affectsExterior !== undefined\r\n                        ? optionData.affectsExterior\r\n                        : (optionData && optionData.affectsVisuals !== false);\r\n                    if (affectsThisView) {\r\n                        parts.push(chineseNames.options[config.option.id]);\r\n                    }\r\n                }\r\n                filename = parts.join('-');\r\n                basePath = `${chineseFolder}\/${variantFolder}\/variant-attribute\/exterior\/angle-0\/`;\r\n            } else {\r\n                const parts = [int, ext];\r\n                if (config.option.id !== 'none') {\r\n                    const optionData = MODEL_CONFIG.options[config.option.id];\r\n                    \/\/ Check if we switched variant folders for this option\r\n                    const didVariantSwitch = (config.option.id === 'platinum' &&\r\n                                              config.model.id === 'pro' &&\r\n                                              MODEL_CONFIG.models['max']);\r\n\r\n                    if (!didVariantSwitch) {\r\n                        \/\/ Check if option affects interior view\r\n                        const affectsThisView = optionData && optionData.affectsInterior !== undefined\r\n                            ? optionData.affectsInterior\r\n                            : (optionData && optionData.affectsVisuals !== false);\r\n                        if (affectsThisView) {\r\n                            parts.push(chineseNames.options[config.option.id]);\r\n                        }\r\n                    }\r\n                }\r\n                filename = parts.join('-');\r\n                basePath = `${chineseFolder}\/${variantFolder}\/variant-attribute\/interior\/angle-0\/`;\r\n            }\r\n\r\n            return {\r\n                avif: `${imagePath}avif\/${basePath}${filename}.avif`,\r\n                webp: `${imagePath}webp\/${basePath}${filename}.webp`\r\n            };\r\n        }\r\n\r\n        function isMobile() {\r\n            return window.innerWidth <= 768;\r\n        }\r\n\r\n        function formatPrice(price) {\r\n            return price.toLocaleString('en-US') + ' AMD';\r\n        }\r\n\r\n        function calculateTotalPrice() {\r\n            \/\/ Calculate sum of multi-options for models with optionMode: 'multiple'\r\n            let multiOptionsSum = 0;\r\n            if (MODEL_CONFIG && MODEL_CONFIG.optionMode === 'multiple') {\r\n                multiOptionsSum = selectedConfig.options.reduce((sum, opt) => sum + opt.price, 0);\r\n            }\r\n\r\n            return selectedConfig.model.price +\r\n                selectedConfig.exterior.price +\r\n                selectedConfig.wheels.price +\r\n                selectedConfig.interior.price +\r\n                selectedConfig.option.price +\r\n                multiOptionsSum;\r\n        }\r\n\r\n        function updatePricing() {\r\n            const basePrice = selectedConfig.model.price;\r\n\r\n            \/\/ Calculate options price including multi-options\r\n            let multiOptionsSum = 0;\r\n            if (MODEL_CONFIG && MODEL_CONFIG.optionMode === 'multiple') {\r\n                multiOptionsSum = selectedConfig.options.reduce((sum, opt) => sum + opt.price, 0);\r\n            }\r\n\r\n            const optionsPrice = selectedConfig.exterior.price + selectedConfig.wheels.price + selectedConfig.interior.price + selectedConfig.option.price + multiOptionsSum;\r\n            const totalPrice = calculateTotalPrice();\r\n\r\n            const basePriceEl = document.getElementById('basePrice');\r\n            const optionsPriceEl = document.getElementById('optionsPrice');\r\n            const totalPriceEl = document.getElementById('totalPrice');\r\n\r\n            const basePriceText = formatPrice(basePrice);\r\n            const optionsPriceText = formatPrice(optionsPrice);\r\n            const totalPriceText = formatPrice(totalPrice);\r\n\r\n            \/\/ Collect names of options that add to the price\r\n            const paidOptionNames = [];\r\n            if (selectedConfig.exterior.price > 0) {\r\n                paidOptionNames.push(selectedConfig.exterior.name);\r\n            }\r\n            if (selectedConfig.wheels.price > 0) {\r\n                paidOptionNames.push(selectedConfig.wheels.name);\r\n            }\r\n            if (selectedConfig.interior.price > 0) {\r\n                paidOptionNames.push(selectedConfig.interior.name);\r\n            }\r\n            if (selectedConfig.option.price > 0 && selectedConfig.option.id !== 'none') {\r\n                paidOptionNames.push(selectedConfig.option.name);\r\n            }\r\n            \/\/ Add multi-options with price > 0\r\n            if (MODEL_CONFIG && MODEL_CONFIG.optionMode === 'multiple') {\r\n                selectedConfig.options.forEach(opt => {\r\n                    if (opt.price > 0) {\r\n                        paidOptionNames.push(opt.name);\r\n                    }\r\n                });\r\n            }\r\n\r\n            \/\/ Update the Options label to include option names\r\n            const optionsLabel = optionsPriceEl.previousElementSibling;\r\n            if (optionsLabel && optionsLabel.classList.contains('status-label')) {\r\n                if (paidOptionNames.length > 0) {\r\n                    optionsLabel.textContent = 'Options: ' + paidOptionNames.join(' - ');\r\n                } else {\r\n                    optionsLabel.textContent = 'Options:';\r\n                }\r\n            }\r\n\r\n            \/\/ Fade out, update, fade in animation\r\n            function updateWithFade(element, newText) {\r\n                if (element.textContent !== newText) {\r\n                    element.style.opacity = '0.4';\r\n                    setTimeout(() => {\r\n                        element.textContent = newText;\r\n                        element.style.opacity = '1';\r\n                    }, 150);\r\n                }\r\n            }\r\n\r\n            updateWithFade(basePriceEl, basePriceText);\r\n            updateWithFade(optionsPriceEl, optionsPriceText);\r\n            updateWithFade(totalPriceEl, totalPriceText);\r\n        }\r\n\r\n        function selectModel(id, name, price) {\r\n            selectedConfig.model = { id, name, price };\r\n\r\n            document.querySelectorAll('.config-section:first-child .wheel-option').forEach(option => {\r\n                option.classList.remove('selected');\r\n            });\r\n            event.currentTarget.classList.add('selected');\r\n\r\n            \/\/ Update wheel pricing if dependent on model\r\n            if (MODEL_CONFIG) {\r\n                const currentWheelData = MODEL_CONFIG.wheels[selectedConfig.wheels.id];\r\n                if (currentWheelData && typeof currentWheelData.price === 'object') {\r\n                    selectedConfig.wheels.price = currentWheelData.price[id] || 0;\r\n                }\r\n\r\n                \/\/ Check if current wheel is disabled for this model, switch to first available\r\n                if (currentWheelData && currentWheelData.disabledFor && currentWheelData.disabledFor.includes(id)) {\r\n                    const availableWheelId = Object.keys(MODEL_CONFIG.wheels).find(wheelId => {\r\n                        const wheelData = MODEL_CONFIG.wheels[wheelId];\r\n                        return !wheelData.disabledFor || !wheelData.disabledFor.includes(id);\r\n                    });\r\n                    if (availableWheelId) {\r\n                        const wheelData = MODEL_CONFIG.wheels[availableWheelId];\r\n                        let wheelPrice = wheelData.price;\r\n                        if (typeof wheelPrice === 'object') {\r\n                            wheelPrice = wheelPrice[id] || 0;\r\n                        }\r\n                        selectedConfig.wheels = {\r\n                            id: availableWheelId,\r\n                            name: wheelData.name,\r\n                            desc: wheelData.desc || '',\r\n                            price: wheelPrice\r\n                        };\r\n                    }\r\n                }\r\n\r\n                \/\/ Check if current exterior color is restricted to a different trim, switch to first available\r\n                const currentExteriorData = MODEL_CONFIG.exteriorColors[selectedConfig.exterior.id];\r\n                if (currentExteriorData && currentExteriorData.forTrim && currentExteriorData.forTrim !== id) {\r\n                    const availableColorId = Object.keys(MODEL_CONFIG.exteriorColors).find(colorId => {\r\n                        const colorData = MODEL_CONFIG.exteriorColors[colorId];\r\n                        return !colorData.forTrim || colorData.forTrim === id;\r\n                    });\r\n                    if (availableColorId) {\r\n                        const colorData = MODEL_CONFIG.exteriorColors[availableColorId];\r\n                        selectedConfig.exterior = {\r\n                            id: availableColorId,\r\n                            name: colorData.name,\r\n                            price: colorData.price\r\n                        };\r\n                    }\r\n                }\r\n\r\n                \/\/ Update option pricing if dependent on model\r\n                if (selectedConfig.option.id !== 'none') {\r\n                    const optionData = MODEL_CONFIG.options[selectedConfig.option.id];\r\n                    if (optionData && typeof optionData.price === 'object') {\r\n                        selectedConfig.option.price = optionData.price[id] || 0;\r\n                    }\r\n                }\r\n\r\n                \/\/ Re-render UI to update prices (animated out->in so colors\/options\r\n                \/\/ that appear\/disappear for this trim transition instead of popping)\r\n                animatedRerender(updateWaitingListDisplay);\r\n            }\r\n\r\n            updatePricing();\r\n            updateConfigDetails();\r\n            updateImage(currentView); \/\/ Update current view (exterior or interior)\r\n        }\r\n\r\n        function selectOption(id, name) {\r\n            if (!MODEL_CONFIG) return;\r\n            const optionType = MODEL_CONFIG.optionType;\r\n            const optionMode = MODEL_CONFIG.optionMode;\r\n            const optionData = MODEL_CONFIG.options[id];\r\n\r\n            \/\/ Get option price (can be object or number)\r\n            let optionPrice = 0;\r\n            if (optionData) {\r\n                if (typeof optionData.price === 'number') {\r\n                    optionPrice = optionData.price;\r\n                } else if (typeof optionData.price === 'object') {\r\n                    optionPrice = optionData.price[selectedConfig.model.id] || 0;\r\n                }\r\n            }\r\n\r\n            \/\/ Handle multi-option mode (i6 model)\r\n            if (optionMode === 'multiple') {\r\n                \/\/ Check if option is already selected\r\n                const index = selectedConfig.options.findIndex(opt => opt.id === id);\r\n\r\n                if (index >= 0) {\r\n                    \/\/ Deselect option\r\n                    selectedConfig.options.splice(index, 1);\r\n                } else {\r\n                    \/\/ Select option\r\n                    selectedConfig.options.push({ id, name, price: optionPrice });\r\n                }\r\n\r\n                \/\/ Handle AWD + 21\" wheels bundle (i6 specific)\r\n                \/\/ When AWD is selected, check if user has 21\" wheels and make them free\r\n                if (currentModelKey === 'i6') {\r\n                    const hasAWD = selectedConfig.options.some(opt => opt.id === 'awd-system');\r\n                    const has21Wheels = selectedConfig.wheels.id === '21-7-spoke';\r\n\r\n                    if (hasAWD && has21Wheels) {\r\n                        \/\/ AWD bundles with 21\" wheels - make wheels free\r\n                        selectedConfig.wheels.price = 0;\r\n                    } else if (!hasAWD && has21Wheels) {\r\n                        \/\/ No AWD but has 21\" wheels - restore original price\r\n                        const wheelData = MODEL_CONFIG.wheels['21-7-spoke'];\r\n                        selectedConfig.wheels.price = wheelData.price || 0;\r\n                    }\r\n                }\r\n\r\n                \/\/ Update only the affected buttons in place. A full renderConfigUI()\r\n                \/\/ here tore down + rebuilt the whole panel (innerHTML), causing a\r\n                \/\/ visible flicker\/layout shift on every multi-option toggle.\r\n                const btn = document.querySelector(`.audio-option[data-option-id=\"${id}\"]`);\r\n                if (btn) btn.classList.toggle('selected', selectedConfig.options.some(o => o.id === id));\r\n\r\n                \/\/ i6 AWD<->21\" bundle: only the 21\" wheel price tag changes -> patch it in place\r\n                if (currentModelKey === 'i6') {\r\n                    const tag = document.querySelector('.wheel-option[data-wheel-id=\"21-7-spoke\"] .price-tag');\r\n                    if (tag) {\r\n                        const hasAWD = selectedConfig.options.some(o => o.id === 'awd-system');\r\n                        const wd = MODEL_CONFIG.wheels['21-7-spoke'];\r\n                        let base = (typeof wd.price === 'object') ? (wd.price[selectedConfig.model.id] || 0) : (wd.price || 0);\r\n                        const shown = hasAWD ? 0 : base;\r\n                        tag.innerHTML = shown === 0 ? '0 AMD' : `<span style=\"color: #fbbf24;\">+${formatPrice(shown)}<\/span>`;\r\n                    }\r\n                }\r\n            } else {\r\n                \/\/ Single-option mode (existing behavior)\r\n                const optionEl = document.querySelector('[data-option-id]');\r\n\r\n                \/\/ Toggle functionality\r\n                if (selectedConfig.option.id === id) {\r\n                    if (optionEl) optionEl.classList.remove('selected');\r\n                    selectedConfig.option = { id: 'none', name: 'N\/A', price: 0 };\r\n                } else {\r\n                    selectedConfig.option = { id, name, price: optionPrice };\r\n                    if (optionEl) optionEl.classList.add('selected');\r\n                }\r\n            }\r\n\r\n            updatePricing();\r\n\r\n            \/\/ Only reload image if option affects any view's visuals\r\n            if (optionData) {\r\n                \/\/ Check if option affects exterior or interior using view-specific flags\r\n                const affectsExterior = optionData.affectsExterior !== undefined\r\n                    ? optionData.affectsExterior\r\n                    : (optionData.affectsVisuals !== false);\r\n                const affectsInterior = optionData.affectsInterior !== undefined\r\n                    ? optionData.affectsInterior\r\n                    : (optionData.affectsVisuals !== false);\r\n\r\n                \/\/ Manual feature->view+angle map: when an option is turned ON and\r\n                \/\/ has a switchTo target, jump there. Otherwise just refresh the\r\n                \/\/ current view. (Toggling OFF never force-switches.)\r\n                const turnedOn = (optionMode === 'multiple')\r\n                    ? selectedConfig.options.some(o => o.id === id)\r\n                    : selectedConfig.option.id === id;\r\n                const st = optionData.switchTo;\r\n                if (turnedOn && st) {\r\n                    currentAngle[st.view] = st.angle || 0;\r\n                    changeViewThenImage(st.view); \/\/ view first, then image (handles card animation)\r\n                } else if (affectsExterior || affectsInterior) {\r\n                    updateImage(currentView);\r\n                    updateConfigDetails(); \/\/ animated card update\r\n                    preloadAdjacentOptions();\r\n                } else {\r\n                    updateConfigDetails(); \/\/ no visual change, just refresh the card (count\/price)\r\n                }\r\n            } else {\r\n                updateConfigDetails();\r\n            }\r\n        }\r\n\t\t\r\n\t\t\/\/ Embedded model configurations (avoids CORS issues with file:\/\/ protocol)\r\n\t\tconst ALL_MODEL_CONFIGS = {\r\n\t\t\r\n  \"i8\": {\r\n    \"displayName\": \"Li i8\",\r\n    \"subtitle\": \"Premium Electric Vehicle\",\r\n    \"chineseFolder\": \"\u7406\u60f3i8\",\r\n    \"optionType\": \"option\",\r\n    \"includeWheelsInFilename\": true,\r\n    \"models\": {\r\n      \"i8\": {\r\n        \"name\": \"i8\",\r\n        \"variantFolder\": \"\u7406\u60f3i8\",\r\n        \"price\": 22750000,\r\n        \"specs\": \"5C Fast Charging: up to 500 km range in just 10 minutes<br>CLTC Range: 720 km<br>Battery Capacity: 97.8 kWh<br>Drivetrain: 4\u00d74 (All-Wheel Drive)<br>System: NVIDIA DRIVE Thor-U<br>Air Suspension: height-adjustable pneumatic system\"\r\n      }\r\n    },\r\n    \"exteriorColors\": {\r\n      \"white-pearl\": { \"name\": \"White Pearl\", \"chinese\": \"\u767d\u8272\u73cd\u73e0\u6f06\", \"price\": 0 },\r\n      \"silver-metallic\": { \"name\": \"Silver Metallic\", \"chinese\": \"\u94f6\u8272\u91d1\u5c5e\u6f06\", \"price\": 0 },\r\n      \"black-metallic\": { \"name\": \"Black Metallic\", \"chinese\": \"\u9ed1\u8272\u91d1\u5c5e\u6f06\", \"price\": 0 },\r\n      \"elephant-gray\": { \"name\": \"Elephant Gray\", \"chinese\": \"\u5c0f\u8c61\u7070\u7279\u522b\u7248\u51b7\u5149\u6f06\", \"price\": 420000 },\r\n      \"sky-blue\": { \"name\": \"Sky Blue\", \"chinese\": \"\u5929\u7a7a\u84dd\u7279\u522b\u7248\u51b7\u5149\u6f06\", \"price\": 420000 }\r\n    },\r\n    \"wheels\": {\r\n      \"20-aero\": { \"name\": \"20\u2033 Aero Wheels\", \"desc\": \"255\/50 R20 Silent Tires\", \"chinese\": \"20\u82f1\u5bf8\u4f4e\u98ce\u963b\u8f6e\u6bc2\", \"price\": 0 },\r\n      \"21-aero\": { \"name\": \"21\u2033 Aero Wheels\", \"desc\": \"265\/45 R21 Silent Tires\", \"chinese\": \"21\u82f1\u5bf8\u4f4e\u98ce\u963b\u8f6e\u6bc2\", \"price\": 700000 },\r\n      \"21-7-spoke\": { \"name\": \"21\u2033 7-Spoke Sport Wheels\", \"desc\": \"265\/45 R21 Silent Tires\", \"chinese\": \"21\u82f1\u5bf8\u4e03\u8f90\u8fd0\u52a8\u8f6e\u6bc2\", \"price\": 700000 }\r\n    },\r\n    \"interiorColors\": {\r\n      \"cream\": { \"name\": \"Cream\", \"chinese\": \"\u767d\u8272\u5185\u9970\", \"price\": 0 },\r\n      \"brown\": { \"name\": \"Brown\", \"chinese\": \"\u7070\u68d5\u8272\u5185\u9970\", \"price\": 0 },\r\n      \"orange\": { \"name\": \"Orange\", \"chinese\": \"\u6a59\u9ec4\u8272\u5185\u9970\", \"price\": 0 }\r\n    },\r\n    \"options\": {\r\n      \"rear-screen\": { \"name\": \"Rear Entertainment Screen\", \"chinese\": \"\u540e\u8231\u5a31\u4e50\u5c4f\u5957\u88c5\", \"price\": 700000, \"switchTo\": { \"view\": \"interior\", \"angle\": 1 } }\r\n    },\r\n    \"defaultConfig\": {\r\n      \"model\": \"i8\",\r\n      \"wheels\": \"20-aero\",\r\n      \"exterior\": \"white-pearl\",\r\n      \"interior\": \"cream\",\r\n      \"option\": \"none\"\r\n    }\r\n  },\r\n  \"i6\": {\r\n    \"displayName\": \"Li i6\",\r\n    \"subtitle\": \"Premium Electric Vehicle\",\r\n    \"chineseFolder\": \"\u7406\u60f3i6\",\r\n    \"optionMode\": \"multiple\",\r\n    \"includeWheelsInFilename\": true,\r\n    \"models\": {\r\n      \"i6\": {\r\n        \"name\": \"i6\",\r\n        \"variantFolder\": \"\u7406\u60f3i6\",\r\n        \"price\": 17900000,\r\n        \"specs\": \"Extended Range Electric Vehicle<br>Premium Interior<br>Advanced Driver Assistance<br>Intelligent Cockpit System\"\r\n      }\r\n    },\r\n    \"exteriorColors\": {\r\n      \"white-pearl\": { \"name\": \"White Pearl\", \"chinese\": \"\u73cd\u73e0\u767d\", \"price\": 0 },\r\n      \"alien-silver\": { \"name\": \"Alien Silver\", \"chinese\": \"\u5916\u661f\u94f6\", \"price\": 0 },\r\n      \"darth-vader-black\": { \"name\": \"Darth Vader Black\", \"chinese\": \"\u9ed1\u6b66\u58eb\", \"price\": 0 },\r\n      \"shark-gray\": { \"name\": \"Shark Gray\", \"chinese\": \"\u5c0f\u7070\u9ca8\", \"price\": 420000 },\r\n      \"clouds-yellow\": { \"name\": \"Clouds Yellow\", \"chinese\": \"\u4e91\u6735\u9ec4\", \"price\": 420000 }\r\n    },\r\n    \"wheels\": {\r\n      \"20-low-drag\": { \"name\": \"20\u2033 Low-drag Wheels\", \"desc\": \"255\/50 R20 Silent Tires\", \"chinese\": \"20\u82f1\u5bf8\u4f4e\u98ce\u963b\u8f6e\u6bc2\", \"price\": 0 },\r\n      \"21-7-spoke\": { \"name\": \"21\u2033 7-Spoke Sport Wheels\", \"desc\": \"265\/45 R21 Silent Tires\", \"chinese\": \"21\u82f1\u5bf8\u4e03\u8f90\u8fd0\u52a8\u8f6e\u6bc2\", \"price\": 700000, \"bundledWith\": \"awd-system\" }\r\n    },\r\n    \"interiorColors\": {\r\n      \"white\": { \"name\": \"White\", \"chinese\": \"\u767d\u8272\u5185\u9970\", \"price\": 0 },\r\n      \"brown\": { \"name\": \"Brown\", \"chinese\": \"\u7070\u68d5\u8272\u5185\u9970\", \"price\": 0 },\r\n      \"orange\": { \"name\": \"Orange\", \"chinese\": \"\u6a59\u9ec4\u8272\u5185\u9970\", \"price\": 0 }\r\n    },\r\n    \"options\": {\r\n      \"awd-system\": {\r\n        \"name\": \"Dual Motor AWD System\",\r\n        \"chinese\": \"\u53cc\u7535\u673a\u56db\u9a71\",\r\n        \"chineseBase\": \"\u540e\u9a71\",\r\n        \"price\": 1400000,\r\n        \"affectsExterior\": false,\r\n        \"affectsInterior\": true,\r\n        \"category\": \"drivetrain\"\r\n      },\r\n      \"platinum-audio\": {\r\n        \"name\": \"Platinum Audio\",\r\n        \"chinese\": \"\u94c2\u91d1\u97f3\u54cd\",\r\n        \"chineseBase\": \"\u9ad8\u7ea7\u97f3\u54cd\",\r\n        \"price\": 350000,\r\n        \"affectsExterior\": false,\r\n        \"affectsInterior\": true,\r\n        \"switchTo\": { \"view\": \"interior\", \"angle\": 1 },\r\n        \"category\": \"audio\"\r\n      },\r\n      \"rear-screen\": {\r\n        \"name\": \"Rear Entertainment Screen\",\r\n        \"chinese\": \"\u540e\u8231\u5a31\u4e50\u5c4f+\u6d41\u5a92\u4f53\u540e\u89c6\u955c\",\r\n        \"chineseBase\": \"\u65e0\u540e\u8231\u5a31\u4e50\u5c4f\",\r\n        \"price\": 700000,\r\n        \"affectsExterior\": true,\r\n        \"affectsInterior\": true,\r\n        \"switchTo\": { \"view\": \"interior\", \"angle\": 1 },\r\n        \"category\": \"screen\"\r\n      },\r\n      \"soft-close\": {\r\n        \"name\": \"Soft Close Doors\",\r\n        \"price\": 350000,\r\n        \"affectsExterior\": false,\r\n        \"affectsInterior\": false,\r\n        \"category\": \"comfort\"\r\n      },\r\n      \"refrigerator\": {\r\n        \"name\": \"Smart Refrigerator\",\r\n        \"price\": 350000,\r\n        \"affectsExterior\": false,\r\n        \"affectsInterior\": false,\r\n        \"category\": \"comfort\"\r\n      },\r\n      \"air-suspension\": {\r\n        \"name\": \"Magic Carpet Air Suspension\",\r\n        \"price\": 1100000,\r\n        \"affectsExterior\": false,\r\n        \"affectsInterior\": false,\r\n        \"category\": \"performance\"\r\n      },\r\n      \"trailer-hitch\": {\r\n        \"name\": \"Trailer Hitch\",\r\n        \"price\": 210000,\r\n        \"affectsExterior\": false,\r\n        \"affectsInterior\": false,\r\n        \"category\": \"utility\"\r\n      }\r\n    },\r\n    \"defaultConfig\": {\r\n      \"model\": \"i6\",\r\n      \"wheels\": \"20-low-drag\",\r\n      \"exterior\": \"white-pearl\",\r\n      \"interior\": \"white\",\r\n      \"options\": []\r\n    }\r\n  },\r\n  \"l6\": {\r\n    \"displayName\": \"Li L6\",\r\n    \"subtitle\": \"Premium Electric Vehicle\",\r\n    \"chineseFolder\": \"\u7406\u60f3L6\",\r\n    \"optionType\": \"audio\",\r\n    \"includeWheelsInFilename\": true,\r\n    \"models\": {\r\n      \"pro\": { \"name\": \"L6 Pro\", \"variantFolder\": \"\u7406\u60f3L6 Pro\", \"price\": 17790000,\r\n                \"specs\": \"Range: 212km<br>Intelligent Driving: High-speed<br>Cockpit: Quad-screen<br>Audio: 19-speaker\"\r\n\r\n       },\r\n      \"max\": { \"name\": \"L6 Max\", \"variantFolder\": \"\u7406\u60f3L6 Max\", \"price\": 19630000, \"specs\": \"Range: 212km<br>Intelligent Driving: City+Highway<br>Audio: Platinum<br>Features: Smart refrigerator\" }\r\n    },\r\n    \"exteriorColors\": {\r\n      \"white-pearl\": { \"name\": \"White Pearl\", \"chinese\": \"\u767d\u8272\u73cd\u73e0\u6f06\", \"price\": 0 },\r\n      \"silver-metallic\": { \"name\": \"Silver Metallic\", \"chinese\": \"\u94f6\u8272\u91d1\u5c5e\u6f06\", \"price\": 0 },\r\n      \"gray-metallic\": { \"name\": \"Gray Metallic\", \"chinese\": \"\u7070\u8272\u91d1\u5c5e\u6f06\", \"price\": 0 },\r\n      \"black-metallic\": { \"name\": \"Black Metallic\", \"chinese\": \"\u9ed1\u8272\u91d1\u5c5e\u6f06\", \"price\": 0 },\r\n      \"elephant-grey\": { \"name\": \"Elephant Grey\", \"chinese\": \"\u5c0f\u8c61\u7070\u7279\u522b\u7248\u51b7\u5149\u6f06\", \"price\": 350000 },\r\n      \"sky-cyan\": { \"name\": \"Mint Blue\", \"chinese\": \"\u5929\u9752\u8272\u7279\u522b\u7248\u73cd\u73e0\u6f06\", \"price\": 350000 },\r\n      \"deep-green\": { \"name\": \"Deep Green\", \"chinese\": \"\u7eff\u8272\u7279\u522b\u7248\u73cd\u73e0\u6f06\", \"price\": 350000 }\r\n    },\r\n    \"wheels\": {\r\n      \"20-silver-grey\": { \"name\": \"20\u2033 Silver-Grey\", \"desc\": \"255\/50 R20 Silent Tires\", \"chinese\": \"\u5168\u65b020\u82f1\u5bf8\u94f6\u7070\u53cc\u8272\u8f6e\u6bc2\", \"price\": 0 },\r\n      \"20-black-grey\": { \"name\": \"20\u2033 Black-Grey\", \"desc\": \"255\/50 R20 Silent Tires\", \"chinese\": \"\u5168\u65b020\u82f1\u5bf8\u9ed1\u7070\u53cc\u8272\u8f6e\u6bc2\", \"price\": 0 },\r\n      \"21-silver-grey\": { \"name\": \"21\u2033 Silver-Grey\", \"desc\": \"265\/45 R21 Silent Tires\", \"chinese\": \"\u5168\u65b021\u82f1\u5bf8\u94f6\u7070\u53cc\u8272\u8f6e\u6bc2\", \"price\": 350000 },\r\n      \"21-black-grey\": { \"name\": \"21\u2033 Black-Grey\", \"desc\": \"265\/45 R21 Silent Tires\", \"chinese\": \"\u5168\u65b021\u82f1\u5bf8\u9ed1\u7070\u53cc\u8272\u8f6e\u6bc2\", \"price\": 350000 }\r\n    },\r\n    \"interiorColors\": {\r\n      \"black-brown\": { \"name\": \"Black-Brown\", \"chinese\": \"\u9ed1\u68d5\u53cc\u8272\u5185\u9970\", \"price\": 0 },\r\n      \"black-orange\": { \"name\": \"Black-Orange\", \"chinese\": \"\u9ed1\u6a59\u53cc\u8272\u5185\u9970\", \"price\": 0 },\r\n      \"black-white\": { \"name\": \"Black-White\", \"chinese\": \"\u9ed1\u767d\u53cc\u8272\u5185\u9970\", \"price\": 0 },\r\n      \"black-sport\": { \"name\": \"Black Sport\", \"chinese\": \"\u9ed1\u8272\u8fd0\u52a8\u5185\u9970\", \"price\": 0 }\r\n    },\r\n    \"options\": {\r\n      \"platinum\": { \"name\": \"Platinum 19-speaker\", \"chinese\": \"19\u626c\u58f0\u5668\u94c2\u91d1\u97f3\u54cd\", \"price\": { \"pro\": 350000, \"max\": 0 }, \"disabledFor\": [\"max\", \"pro\"], \"affectsExterior\": false, \"affectsInterior\": true }\r\n    },\r\n    \"defaultConfig\": {\r\n      \"model\": \"pro\",\r\n      \"wheels\": \"20-silver-grey\",\r\n      \"exterior\": \"white-pearl\",\r\n      \"interior\": \"black-brown\",\r\n      \"option\": \"none\"\r\n    }\r\n  },\r\n  \"l7\": {\r\n    \"displayName\": \"Li L7\",\r\n    \"subtitle\": \"Premium Electric Vehicle\",\r\n    \"chineseFolder\": \"\u7406\u60f3L7\",\r\n    \"optionType\": \"footboards\",\r\n    \"includeWheelsInFilename\": true,\r\n    \"interiorPattern\": \"exterior-first\",\r\n    \"models\": {\r\n      \"pro\": { \"name\": \"L7 Pro\", \"variantFolder\": \"\u7406\u60f3L7 Pro\", \"price\": 20540000, \"specs\" : \"CLTC Range: 225 kilometers<br>Cockpit: Quad-screen interactive<br>Intelligent Driving: High-speed<br>Audio: 19-speaker premium\", \"disabled\": true },\r\n      \"max\": { \"name\": \"L7 Max\", \"variantFolder\": \"\u7406\u60f3L7 Max\", \"price\": 22870000, \"specs\" : \"CLTC Range: 225 kilometers<br>Cockpit: Quad-screen interactive<br>Intelligent Driving: City+Highway<br>Audio: Platinum 19-speaker<br>Refrigerator: Intelligent cold and warm dual-purpose\", \"disabled\": true },\r\n      \"ultra\": { \"name\": \"L7 Ultra\", \"variantFolder\": \"\u7406\u60f3L7 Ultra\", \"price\": 24510000, \"specs\" : \"CLTC Range: 286 kilometers<br>Cockpit: Five-screen interactive<br>Intelligent Driving: City+Highway<br>Audio: 21-speaker platinum<br>Refrigerator: Intelligent cold and warm dual-purpose\" }\r\n    },\r\n    \"exteriorColors\": {\r\n      \"white-pearl\": { \"name\": \"White Pearl\", \"chinese\": \"\u767d\u8272\u73cd\u73e0\u6f06\", \"price\": 0 },\r\n      \"silver-metallic\": { \"name\": \"Silver Metallic\", \"chinese\": \"\u94f6\u8272\u91d1\u5c5e\u6f06\", \"price\": 0 },\r\n      \"gray-metallic\": { \"name\": \"Gray Metallic\", \"chinese\": \"\u7070\u8272\u91d1\u5c5e\u6f06\", \"price\": 0 },\r\n      \"black-metallic\": { \"name\": \"Black Metallic\", \"chinese\": \"\u9ed1\u8272\u91d1\u5c5e\u6f06\", \"price\": 0 },\r\n      \"deep-green\": { \"name\": \"Deep Green\", \"chinese\": \"\u7eff\u8272\u7279\u522b\u7248\u73cd\u73e0\u6f06\", \"price\": 350000 },\r\n      \"blue-gray\": { \"name\": \"Blue Gray\", \"chinese\": \"\u84dd\u7070\u7279\u522b\u7248\u51b7\u5149\u6f06\", \"price\": 350000 },\r\n      \"baby-blue\": { \"name\": \"Baby Blue\", \"chinese\": \"Baby Blue\u200b\u7279\u522b\u7248\u73cd\u73e0\u6f06\", \"price\": 350000 }\r\n    },\r\n    \"wheels\": {\r\n      \"20-silver-grey\": { \"name\": \"20\u2033 Silver-Grey\", \"desc\": \"255\/50 R20 Silent Tires\", \"chinese\": \"\u5168\u65b020\u82f1\u5bf8\u94f6\u7070\u53cc\u8272\u8f6e\u6bc2\", \"price\": 0, \"disabledFor\": [\"ultra\"] },\r\n      \"20-black-grey\": { \"name\": \"20\u2033 Black-Grey\", \"desc\": \"255\/50 R20 Silent Tires\", \"chinese\": \"\u5168\u65b020\u82f1\u5bf8\u9ed1\u7070\u53cc\u8272\u8f6e\u6bc2\", \"price\": 0, \"disabledFor\": [\"ultra\"] },\r\n      \"21-silver-grey\": { \"name\": \"21\u2033 Silver-Grey\", \"desc\": \"265\/45 R21 Silent Tires\", \"chinese\": \"\u5168\u65b021\u82f1\u5bf8\u94f6\u7070\u53cc\u8272\u8f6e\u6bc2\", \"price\": { \"pro\": 350000, \"max\": 350000, \"ultra\": 0 } },\r\n      \"21-black-grey\": { \"name\": \"21\u2033 Black-Grey\", \"desc\": \"265\/45 R21 Silent Tires\", \"chinese\": \"\u5168\u65b021\u82f1\u5bf8\u9ed1\u7070\u53cc\u8272\u8f6e\u6bc2\", \"price\": { \"pro\": 350000, \"max\": 350000, \"ultra\": 0 } }\r\n    },\r\n    \"interiorColors\": {\r\n      \"black-brown\": { \"name\": \"Black-Brown\", \"chinese\": \"\u9ed1\u68d5\u53cc\u8272\u5185\u9970\", \"price\": 0 },\r\n      \"black-orange\": { \"name\": \"Black-Orange\", \"chinese\": \"\u9ed1\u6a59\u53cc\u8272\u5185\u9970\", \"price\": 0 },\r\n      \"black-white\": { \"name\": \"Black-White\", \"chinese\": \"\u9ed1\u767d\u53cc\u8272\u5185\u9970\", \"price\": 0 },\r\n      \"black-sport\": { \"name\": \"Black Sport\", \"chinese\": \"\u9ed1\u8272\u8fd0\u52a8\u5185\u9970\", \"price\": 0 }\r\n    },\r\n    \"options\": {\r\n      \"footboards\": { \"name\": \"Electric Running Boards\", \"chinese\": \"\u7535\u52a8\u8e0f\u677f\", \"price\": 350000, \"affectsExterior\": false, \"affectsInterior\": false }\r\n    },\r\n    \"defaultConfig\": {\r\n      \"model\": \"ultra\",\r\n      \"wheels\": \"21-silver-grey\",\r\n      \"exterior\": \"white-pearl\",\r\n      \"interior\": \"black-brown\",\r\n      \"option\": \"none\"\r\n    }\r\n  },\r\n  \"l9\": {\r\n    \"displayName\": \"Li L9\",\r\n    \"subtitle\": \"Premium Electric Vehicle\",\r\n    \"chineseFolder\": \"\u7406\u60f3L9\",\r\n    \"optionType\": \"footboards\",\r\n    \"includeWheelsInFilename\": true,\r\n    \"exteriorPattern\": \"interior-before-wheels\",\r\n    \"interiorPattern\": \"exterior-first\",\r\n    \"models\": {\r\n      \"pro\": { \"name\": \"L9 Pro\", \"variantFolder\": \"\u7406\u60f3L9 Pro\", \"price\": 27620000, \"specs\" : \"CLTC Range: 280 kilometers<br>Cockpit: Five-screen interactive<br>Intelligent Driving: High-speed<br>Audio: 21-speaker platinum<br>Seating: Zero-gravity queen seat\", \"disabled\": true },\r\n      \"ultra\": { \"name\": \"L9 Ultra\", \"variantFolder\": \"\u7406\u60f3L9 Ultra\", \"price\": 29350000, \"specs\" : \"CLTC Range: 280 kilometers<br>Cockpit: Five-screen interactive<br>Intelligent Driving: City + highway<br>Audio: 21-speaker platinum<br>Seating: Zero-gravity queen seat\" }\r\n    },\r\n    \"exteriorColors\": {\r\n      \"white-pearl\": { \"name\": \"White Pearl\", \"chinese\": \"\u767d\u8272\u73cd\u73e0\u6f06\", \"price\": 0 },\r\n      \"silver-metallic\": { \"name\": \"Silver Metallic\", \"chinese\": \"\u94f6\u8272\u91d1\u5c5e\u6f06\", \"price\": 0 },\r\n      \"gray-metallic\": { \"name\": \"Gray Metallic\", \"chinese\": \"\u7070\u8272\u91d1\u5c5e\u6f06\", \"price\": 0 },\r\n      \"black-metallic\": { \"name\": \"Black Metallic\", \"chinese\": \"\u9ed1\u8272\u91d1\u5c5e\u6f06\", \"price\": 0 },\r\n      \"elegant-gray\": { \"name\": \"Elegant Gray\", \"chinese\": \"\u96c5\u7070\u7279\u522b\u7248\u51b7\u5149\u6f06\", \"price\": 350000 },\r\n      \"deep-green\": { \"name\": \"Deep Green\", \"chinese\": \"\u7eff\u8272\u7279\u522b\u7248\u73cd\u73e0\u6f06\", \"price\": 350000 },\r\n      \"purple\": { \"name\": \"Purple\", \"chinese\": \"\u7d2b\u8272\u7279\u522b\u7248\u73cd\u73e0\u6f06\", \"price\": 350000 }\r\n    },\r\n    \"wheels\": {\r\n      \"21-silver-grey\": { \"name\": \"21\u2033 Silver-Gray Two-tone\", \"desc\": \"265\/45 R21 Silent Tires\", \"chinese\": \"\u5168\u65b021\u82f1\u5bf8\u94f6\u7070\u53cc\u8272\u8f6e\u6bc2\", \"price\": 0 },\r\n      \"21-black-grey\": { \"name\": \"21\u2033 Black-Gray Two-tone\", \"desc\": \"265\/45 R21 Silent Tires\", \"chinese\": \"\u5168\u65b021\u82f1\u5bf8\u9ed1\u7070\u53cc\u8272\u8f6e\u6bc2\", \"price\": 0 }\r\n    },\r\n    \"interiorColors\": {\r\n      \"black-brown\": { \"name\": \"Black-Brown\", \"chinese\": \"\u9ed1\u68d5\u53cc\u8272\u5185\u9970\", \"price\": 0 },\r\n      \"black-orange\": { \"name\": \"Black-Orange\", \"chinese\": \"\u9ed1\u6a59\u53cc\u8272\u5185\u9970\", \"price\": 0 },\r\n      \"black-white\": { \"name\": \"Black-White\", \"chinese\": \"\u9ed1\u767d\u53cc\u8272\u5185\u9970\", \"price\": 0 },\r\n      \"black-sport\": { \"name\": \"Black Sport\", \"chinese\": \"\u9ed1\u8272\u8fd0\u52a8\u5185\u9970\", \"price\": 0 }\r\n    },\r\n    \"options\": {\r\n      \"footboards\": { \"name\": \"Electric Running Boards\", \"chinese\": \"\u7535\u52a8\u8e0f\u677f\", \"price\": { \"pro\": 350000, \"ultra\": 0 }, \"disabledFor\": [\"ultra\"], \"affectsExterior\": false, \"affectsInterior\": false }\r\n    },\r\n    \"defaultConfig\": {\r\n      \"model\": \"ultra\",\r\n      \"wheels\": \"21-silver-grey\",\r\n      \"exterior\": \"white-pearl\",\r\n      \"interior\": \"black-brown\",\r\n      \"option\": \"none\"\r\n    }\r\n  },\r\n  \"mega\": {\r\n    \"displayName\": \"Li MEGA\",\r\n    \"subtitle\": \"Premium Electric Vehicle\",\r\n    \"chineseFolder\": \"\u7406\u60f3MEGA\",\r\n    \"optionType\": \"footboards\",\r\n    \"includeWheelsInFilename\": false,\r\n    \"interiorPattern\": \"exterior-first\",\r\n    \"models\": {\r\n      \"ultra\": { \"name\": \"Mega Ultra\", \"variantFolder\": \"\u7406\u60f3MEGA Ultra\", \"price\": 36070000, \"specs\" : \"5C Fast Charging: 500km in 10min<br>Battery Capacity: 102.7kWh<br>CLTC Range: 710km<br>Domain Controller: NVIDIA DRIVE Thor-U (700 TOPS)<br>Massage: Spa-grade 16-point (10 back + 6 cushion)\" },\r\n      \"home\": { \"name\": \"Mega Home\", \"variantFolder\": \"\u7406\u60f3MEGA Home\", \"price\": 38150000, \"specs\" : \"5C Fast Charging: 500km in 10min<br>Battery Capacity: 102.7kWh<br>CLTC Range: 710km<br>Domain Controller: NVIDIA DRIVE Thor-U (700 TOPS)<br>Massage: Hot Stone 18-point kneading (12 back + 6 cushion)\" }\r\n    },\r\n    \"exteriorColors\": {\r\n      \"panda-pearl\": { \"name\": \"Panda Pearl\", \"chinese\": \"\u718a\u732b\u767d\u73cd\u73e0\u6f06\", \"price\": 0 },\r\n      \"silver-metallic\": { \"name\": \"Silver Metallic\", \"chinese\": \"\u94f6\u8272\u91d1\u5c5e\u6f06\", \"price\": 0 },\r\n      \"gray-metallic\": { \"name\": \"Gray Metallic\", \"chinese\": \"\u7070\u8272\u91d1\u5c5e\u6f06\", \"price\": 0 },\r\n      \"elephant-gray\": { \"name\": \"Elephant Gray\", \"chinese\": \"\u5927\u8c61\u7070\u7279\u522b\u7248\u73cd\u73e0\u6f06\", \"price\": 350000 }\r\n    },\r\n    \"wheels\": {\r\n      \"18-black-gray\": { \"name\": \"18\u2033 Black-gray Low-drag\", \"desc\": \"245\/60 R18 Silent Tires\", \"chinese\": \"18\u82f1\u5bf8\u9ed1\u7070\u53cc\u8272\u4f4e\u98ce\u963b\u8f6e\u6bc2\", \"price\": 0 }\r\n    },\r\n    \"interiorColors\": {\r\n      \"cream\": { \"name\": \"Cream\", \"chinese\": \"\u767d\u8272\u5185\u9970\", \"price\": 0 },\r\n      \"brown\": { \"name\": \"Brown\", \"chinese\": \"\u68d5\u8272\u5185\u9970\", \"price\": 0 }\r\n    },\r\n    \"options\": {\r\n      \/\/\"footboards\": { \"name\": \"Electric Running Boards\", \"chinese\": \"\u7535\u52a8\u8e0f\u677f\", \"price\": 0, \"disabledFor\": [\"ultra\", \"home\"], \"alwaysIncluded\": true, \"affectsExterior\": false, \"affectsInterior\": false }\r\n    },\r\n    \"defaultConfig\": {\r\n      \"model\": \"ultra\",\r\n      \"wheels\": \"18-black-gray\",\r\n      \"exterior\": \"panda-pearl\",\r\n      \"interior\": \"cream\",\r\n      \"option\": \"none\"\r\n    }\r\n  },\r\n  \"l9-livis\": {\r\n    \"displayName\": \"Li L9 Livis\",\r\n    \"subtitle\": \"Flagship Electric Vehicle\",\r\n    \"chineseFolder\": \"\u7406\u60f3L9 Livis\",\r\n    \"optionMode\": \"multiple\",\r\n    \"includeWheelsInFilename\": true,\r\n    \"models\": {\r\n      \"mono\": {\r\n        \"name\": \"L9 Livis Mono-Tone\",\r\n        \"variantFolder\": \"\u7406\u60f3L9 Livis Mono\",\r\n        \"price\": 41670000,\r\n        \"specs\": \"CLTC Range: 280 kilometers<br>Cockpit: Five-screen interactive<br>Intelligent Driving: City + highway<br>Audio: 21-speaker platinum<br>Seating: Comfort seat with zero-gravity effect<br>Paint: Multi-tone premium finish with dual-tone interior option\"\r\n      },\r\n      \"duo\": {\r\n        \"name\": \"L9 Livis Duo-Tone\",\r\n        \"variantFolder\": \"\u7406\u60f3L9 Livis Duo\",\r\n        \"price\": 43070000,\r\n        \"specs\": \"CLTC Range: 280 kilometers<br>Cockpit: Five-screen interactive<br>Intelligent Driving: City + highway<br>Audio: 21-speaker platinum<br>Seating: Comfort seat with zero-gravity effect<br>Paint: Exclusive two-tone custom edition with dual-tone interior option\"\r\n      }\r\n    },\r\n    \"exteriorColors\": {\r\n      \"pearl-white\": { \"name\": \"Pearl White\", \"chinese\": \"\u73cd\u73e0\u767d\u91d1\u5c5e\u6f06\", \"price\": 0, \"forTrim\": \"mono\" },\r\n      \"polar-gray\": { \"name\": \"Polar Gray Cool\", \"chinese\": \"\u6781\u5730\u7070\u51b7\u5149\u6f06\", \"price\": 0, \"forTrim\": \"mono\" },\r\n      \"diamond-black\": { \"name\": \"Diamond Black\", \"chinese\": \"\u94bb\u77f3\u9ed1\u91d1\u5c5e\u6f06\", \"price\": 0, \"forTrim\": \"mono\" },\r\n      \"titanium-gold\": { \"name\": \"Titanium Gold\", \"chinese\": \"\u949b\u91d1\u8272\u91d1\u5c5e\u6f06\", \"price\": 0, \"forTrim\": \"mono\" },\r\n      \"emerald-green\": { \"name\": \"Emerald Green Special Edition\", \"chinese\": \"\u7956\u6bcd\u7eff\u7279\u522b\u7248\u73cd\u73e0\u6f06\", \"price\": 700000, \"forTrim\": \"mono\" },\r\n      \"truffle-gold\": { \"name\": \"Truffle Gold Special Edition\", \"chinese\": \"\u677e\u9732\u91d1\u7279\u522b\u7248\u73cd\u73e0\u6f06\", \"price\": 700000, \"forTrim\": \"mono\" },\r\n      \"dark-purple\": { \"name\": \"Dark Purple Special Edition\", \"chinese\": \"\u6697\u591c\u7d2b\u7279\u522b\u7248\u73cd\u73e0\u6f06\", \"price\": 700000, \"forTrim\": \"mono\" },\r\n      \"brown-gold\": { \"name\": \"Brown and Gold Custom Edition\", \"chinese\": \"\u68d5\u91d1\u5b9a\u5236\u7248\u53cc\u8272\u8f66\u8eab\", \"price\": 0, \"forTrim\": \"duo\" },\r\n      \"black-gold\": { \"name\": \"Black and Gold Custom Edition\", \"chinese\": \"\u9ed1\u91d1\u5b9a\u5236\u7248\u53cc\u8272\u8f66\u8eab\", \"price\": 0, \"forTrim\": \"duo\" },\r\n      \"green-gold\": { \"name\": \"Green and Gold Custom Edition\", \"chinese\": \"\u7eff\u91d1\u5b9a\u5236\u7248\u53cc\u8272\u8f66\u8eab\", \"price\": 0, \"forTrim\": \"duo\" },\r\n      \"purple-gold\": { \"name\": \"Purple and Gold Custom Edition\", \"chinese\": \"\u7d2b\u91d1\u5b9a\u5236\u7248\u53cc\u8272\u8f66\u8eab\", \"price\": 0, \"forTrim\": \"duo\" }\r\n    },\r\n    \"wheels\": {\r\n      \"22-crystal-gray\": { \"name\": \"Livis Exclusive 22-inch Crystal Gray\", \"desc\": \"22-inch Crystal Gray Wheels\", \"chinese\": \"Livis\u4e13\u5c5e22\u82f1\u5bf8\u6676\u7070\u8f6e\u6bc2\", \"price\": 0 }\r\n    },\r\n    \"interiorColors\": {\r\n      \"green-white\": { \"name\": \"Green and White\", \"chinese\": \"\u7eff\u767d\u53cc\u8272\u5185\u9970\", \"price\": 0 },\r\n      \"brown-coffee\": { \"name\": \"Brown and Coffee\", \"chinese\": \"\u68d5\u5496\u53cc\u8272\u5185\u9970\", \"price\": 0 },\r\n      \"gray-purple\": { \"name\": \"Gray-Purple\", \"chinese\": \"\u7070\u7d2b\u53cc\u8272\u5185\u9970\", \"price\": 0 }\r\n    },\r\n    \"options\": {\r\n      \"electric-glass\": {\r\n        \"name\": \"Second and Third Rows Electrically Adjustable Glass\",\r\n        \"chinese\": \"\u7b2c\u4e8c\u4e09\u6392\u7535\u52a8\u8c03\u8282\u73bb\u7483\",\r\n        \"price\": 700000,\r\n        \"affectsExterior\": false,\r\n        \"affectsInterior\": true,\r\n        \"category\": \"comfort\"\r\n      },\r\n      \"hidden-pedal\": {\r\n        \"name\": \"Hidden Electric Pedal\",\r\n        \"chinese\": \"\u9690\u85cf\u5f0f\u7535\u52a8\u8e0f\u677f\",\r\n        \"price\": 700000,\r\n        \"affectsExterior\": false,\r\n        \"affectsInterior\": false,\r\n        \"category\": \"utility\"\r\n      },\r\n      \"trailer-hitch\": {\r\n        \"name\": \"Electric Trailer Hitch\",\r\n        \"chinese\": \"\u7535\u52a8\u62d6\u8f66\u94a9\",\r\n        \"price\": 420000,\r\n        \"affectsExterior\": true,\r\n        \"affectsInterior\": false,\r\n        \"switchTo\": { \"view\": \"exterior\", \"angle\": 2 },\r\n        \"category\": \"utility\"\r\n      }\r\n    },\r\n    \"defaultConfig\": {\r\n      \"model\": \"mono\",\r\n      \"wheels\": \"22-crystal-gray\",\r\n      \"exterior\": \"pearl-white\",\r\n      \"interior\": \"green-white\",\r\n      \"options\": []\r\n    }\r\n  }\r\n\r\n\r\n\t\t};\r\n        \/\/ Load configuration from URL path or query parameter\r\n        \/\/ Tries path first (e.g., \/en\/configurator-l7\/), then query (?model=l7)\r\n        function getModelFromURL() {\r\n            const pathname = window.location.pathname;\r\n            const match = pathname.match(\/configurator-([^\\\/]+)\/);\r\n            if (match) return match[1];\r\n\r\n            const params = new URLSearchParams(window.location.search);\r\n            return params.get('model') || 'i8'; \/\/ Default to i8\r\n        }\r\n\r\n        \/\/ Load model config from embedded data\r\n        async function loadModelConfig() {\r\n            try {\r\n                \/\/ load the image catalog once (non-fatal: falls back to legacy images)\r\n                if (!CATALOG) {\r\n                    try { CATALOG = await (await fetch(LI_CATALOG_URL)).json(); }\r\n                    catch (e) { console.warn('[catalog] load failed, using legacy images:', e); }\r\n                }\r\n                const allConfigs = ALL_MODEL_CONFIGS;\r\n\r\n                currentModelKey = getModelFromURL();\r\n                console.log('[DEBUG] currentModelKey from URL:', currentModelKey);\r\n                MODEL_CONFIG = allConfigs[currentModelKey];\r\n\r\n                if (!MODEL_CONFIG) {\r\n                    console.error(`Model ${currentModelKey} not found in config`);\r\n                    currentModelKey = 'i8';\r\n                    MODEL_CONFIG = allConfigs['i8'];\r\n                }\r\n                console.log('[DEBUG] Loaded config for:', currentModelKey, MODEL_CONFIG.displayName);\r\n\r\n                \/\/ Build chinese names lookup\r\n                chineseNames = {\r\n                    exteriorColors: {},\r\n                    wheels: {},\r\n                    interiorColors: {},\r\n                    options: {}\r\n                };\r\n\r\n                Object.keys(MODEL_CONFIG.exteriorColors).forEach(key => {\r\n                    chineseNames.exteriorColors[key] = MODEL_CONFIG.exteriorColors[key].chinese;\r\n                });\r\n\r\n                Object.keys(MODEL_CONFIG.wheels).forEach(key => {\r\n                    chineseNames.wheels[key] = MODEL_CONFIG.wheels[key].chinese;\r\n                });\r\n\r\n                Object.keys(MODEL_CONFIG.interiorColors).forEach(key => {\r\n                    chineseNames.interiorColors[key] = MODEL_CONFIG.interiorColors[key].chinese;\r\n                });\r\n\r\n                Object.keys(MODEL_CONFIG.options).forEach(key => {\r\n                    chineseNames.options[key] = MODEL_CONFIG.options[key].chinese;\r\n                });\r\n\r\n                \/\/ Set initial configuration from defaults\r\n                const defaultCfg = MODEL_CONFIG.defaultConfig;\r\n                const defaultModel = MODEL_CONFIG.models[defaultCfg.model];\r\n                selectedConfig.model = {\r\n                    id: defaultCfg.model,\r\n                    name: defaultModel.name,\r\n                    price: defaultModel.price\r\n                };\r\n\r\n                const defaultWheel = MODEL_CONFIG.wheels[defaultCfg.wheels];\r\n                let wheelPrice = defaultWheel.price;\r\n                if (typeof wheelPrice === 'object') {\r\n                    wheelPrice = wheelPrice[defaultCfg.model] || 0;\r\n                }\r\n\r\n                \/\/ Check if default wheel is disabled for default model, use first available wheel\r\n                let finalWheelId = defaultCfg.wheels;\r\n                let finalWheelData = defaultWheel;\r\n                let finalWheelPrice = wheelPrice;\r\n\r\n                if (defaultWheel.disabledFor && defaultWheel.disabledFor.includes(defaultCfg.model)) {\r\n                    const availableWheelId = Object.keys(MODEL_CONFIG.wheels).find(wheelId => {\r\n                        const wheelData = MODEL_CONFIG.wheels[wheelId];\r\n                        return !wheelData.disabledFor || !wheelData.disabledFor.includes(defaultCfg.model);\r\n                    });\r\n                    if (availableWheelId) {\r\n                        finalWheelId = availableWheelId;\r\n                        finalWheelData = MODEL_CONFIG.wheels[availableWheelId];\r\n                        finalWheelPrice = finalWheelData.price;\r\n                        if (typeof finalWheelPrice === 'object') {\r\n                            finalWheelPrice = finalWheelPrice[defaultCfg.model] || 0;\r\n                        }\r\n                    }\r\n                }\r\n\r\n                selectedConfig.wheels = {\r\n                    id: finalWheelId,\r\n                    name: finalWheelData.name,\r\n                    desc: finalWheelData.desc,\r\n                    price: finalWheelPrice\r\n                };\r\n\r\n                selectedConfig.exterior = {\r\n                    id: defaultCfg.exterior,\r\n                    name: MODEL_CONFIG.exteriorColors[defaultCfg.exterior].name,\r\n                    price: MODEL_CONFIG.exteriorColors[defaultCfg.exterior].price\r\n                };\r\n\r\n                selectedConfig.interior = {\r\n                    id: defaultCfg.interior,\r\n                    name: MODEL_CONFIG.interiorColors[defaultCfg.interior].name,\r\n                    price: MODEL_CONFIG.interiorColors[defaultCfg.interior].price\r\n                };\r\n\r\n                \/\/ Initialize options based on model mode\r\n                if (MODEL_CONFIG.optionMode === 'multiple') {\r\n                    \/\/ Multi-option mode: use options array from defaultConfig\r\n                    selectedConfig.options = defaultCfg.options || [];\r\n                    selectedConfig.option = { id: 'none', name: 'N\/A', price: 0 }; \/\/ Keep for compatibility\r\n                } else {\r\n                    \/\/ Single-option mode: use option field\r\n                    selectedConfig.option = { id: 'none', name: 'N\/A', price: 0 };\r\n                    selectedConfig.options = []; \/\/ Keep empty for compatibility\r\n                }\r\n\r\n                \/\/ Update page titles\r\n                document.title = `2026 ${MODEL_CONFIG.displayName} Configurator`;\r\n                document.getElementById('carTitle').textContent = `${MODEL_CONFIG.displayName} 2026`;\r\n                document.getElementById('carSubtitle').textContent = MODEL_CONFIG.subtitle;\r\n                document.getElementById('formSubtitle').textContent = `The wait is over. The ${MODEL_CONFIG.displayName} is now available near you.`;\r\n\r\n                \/\/ Render UI\r\n                currentAngle = { exterior: 0, interior: 0 };\r\n                document.querySelector('.car-display').classList.toggle('no-backdrop', !useBackdrop());\r\n                renderConfigUI();\r\n                renderAngleControls();\r\n\r\n            } catch (error) {\r\n                console.error('Error loading model config:', error);\r\n                document.getElementById('configContent').innerHTML = '<p style=\"padding: 20px; text-align: center; color: red;\">Error loading configuration. Please refresh the page.<\/p>';\r\n            }\r\n        }\r\n\r\n        \/\/ Helper function to generate picture element with AVIF and WebP sources\r\n        \/\/ Strip the \"Custom\/Special Edition\" suffix from color names - just show the color.\r\n        function colorLabelHTML(name) {\r\n            const m = name.match(\/^(.*?)\\s+((?:Custom|Special) Edition)$\/i);\r\n            return m ? m[1] : name;\r\n        }\r\n\r\n        function generatePictureElement(basePath, alt, cssClass = '') {\r\n            \/\/ Check if this is a JPG source file (from sources\/ folder)\r\n            if (basePath.includes('\/sources\/') && basePath.endsWith('.jpg')) {\r\n                \/\/ For JPG sources, just use a simple img tag\r\n                return `<img decoding=\"async\" src=\"${basePath}\" alt=\"${alt}\" ${cssClass ? `class=\"${cssClass}\"` : ''} onerror=\"this.parentElement.style.display='none'\">`;\r\n            }\r\n\r\n            \/\/ Generate AVIF and WebP sources for preview images\r\n            const avifSrc = basePath.replace('\/webp\/', '\/avif\/').replace('.webp', '.avif');\r\n            const webpSrc = basePath;\r\n\r\n            return `\r\n                <picture>\r\n                    <source srcset=\"${avifSrc}\" type=\"image\/avif\">\r\n                    <source srcset=\"${webpSrc}\" type=\"image\/webp\">\r\n                    <img decoding=\"async\" src=\"${webpSrc}\" alt=\"${alt}\" ${cssClass ? `class=\"${cssClass}\"` : ''} onerror=\"this.parentElement.style.display='none'\">\r\n                <\/picture>\r\n            `;\r\n        }\r\n\r\n        \/\/ Helper function to style spec labels (text before colons)\r\n        function formatSpecs(specs) {\r\n            if (!specs) return '';\r\n            \/\/ Replace \"Label:\" with \"<span class='spec-label'>Label:<\/span>\"\r\n            return specs.replace(\/([^<>\\n]+?):\/g, '<span class=\"spec-label\">$1:<\/span>');\r\n        }\r\n\r\n        \/\/ Render configuration UI dynamically\r\n        \/\/ Animated panel re-render: animate the existing CARDS out individually, then\r\n        \/\/ swap content so the new cards cascade in via their cardEnter animation. Used on\r\n        \/\/ trim changes so options\/colors transition instead of popping. `after` runs once\r\n        \/\/ the new DOM exists.\r\n        let _rerenderTimer = null;\r\n        function animatedRerender(after) {\r\n            const container = document.getElementById('configContent');\r\n            const cards = container ? container.querySelectorAll('.color-option, .wheel-option, .audio-option') : [];\r\n            if (!cards.length) {\r\n                renderConfigUI();\r\n                if (after) after();\r\n                return;\r\n            }\r\n            cards.forEach((c, i) => {\r\n                c.style.animation = `cardExit 0.2s cubic-bezier(0.4, 0, 1, 1) both`;\r\n                c.style.animationDelay = Math.min(i * 18, 110) + 'ms';\r\n            });\r\n            clearTimeout(_rerenderTimer);\r\n            _rerenderTimer = setTimeout(() => {\r\n                renderConfigUI(); \/\/ new cards carry cardEnter -> they cascade in\r\n                if (after) after();\r\n            }, 230);\r\n        }\r\n\r\n        function renderConfigUI() {\r\n            const container = document.getElementById('configContent');\r\n            let html = '';\r\n\r\n            \/\/ L9 Livis Preorder Banner\r\n            if (currentModelKey === 'l9-livis') {\r\n                html += `\r\n                    <div class=\"livis-preorder-banner\">\r\n                        <div class=\"livis-preorder-title\">Li L9 Livis Preorder<\/div>\r\n                        <div class=\"livis-preorder-subtitle\">Join the exclusive flagship waiting list<\/div>\r\n                        <div class=\"livis-waiting-count-container\">\r\n                            <div class=\"livis-waiting-count\" id=\"waitingCount\">--\/45<\/div>\r\n                            <div class=\"livis-waiting-label\">Spots Remaining<\/div>\r\n                        <\/div>\r\n                    <\/div>\r\n                `;\r\n            }\r\n\r\n            \/\/ Models section\r\n            html += '<div class=\"config-section\"><h3 class=\"section-title\">Model<\/h3>';\r\n            html += '<div style=\"display: grid; grid-template-columns: 1fr; gap: 16px;\">';\r\n            Object.keys(MODEL_CONFIG.models).forEach(modelId => {\r\n                const model = MODEL_CONFIG.models[modelId];\r\n                const selected = modelId === selectedConfig.model.id ? 'selected' : '';\r\n                const specs = model.specs || '';\r\n                const formattedSpecs = formatSpecs(specs);\r\n                const disabled = model.disabled === true;\r\n                const disabledClass = disabled ? 'disabled' : '';\r\n                const disabledStyle = disabled ? 'opacity: 0.5; cursor: not-allowed; pointer-events: none;' : '';\r\n                const onclickAttr = disabled ? '' : `onclick=\"selectModel('${modelId}', '${model.name}', ${model.price})\"`;\r\n                html += `\r\n                    <div class=\"wheel-option ${selected} ${disabledClass}\" style=\"${disabledStyle}\" ${onclickAttr}>\r\n                        <div class=\"wheel-name\">${model.name}<\/div>\r\n                        ${formattedSpecs ? `<div class=\"wheel-desc\">${formattedSpecs}<\/div>` : ''}\r\n                        <div class=\"price-tag\">${formatPrice(model.price)}<\/div>\r\n                    <\/div>\r\n                `;\r\n            });\r\n            html += '<\/div><\/div>';\r\n\r\n            \/\/ Exterior colors\r\n            html += '<div class=\"config-section\"><h3 class=\"section-title\">Exterior Color<\/h3>';\r\n            html += '<div class=\"color-grid\">';\r\n            Object.keys(MODEL_CONFIG.exteriorColors).forEach(colorId => {\r\n                const color = MODEL_CONFIG.exteriorColors[colorId];\r\n\r\n                \/\/ Filter colors by trim if forTrim property exists\r\n                if (color.forTrim && color.forTrim !== selectedConfig.model.id) {\r\n                    return; \/\/ Skip this color if it doesn't match the selected trim\r\n                }\r\n\r\n                const selected = colorId === selectedConfig.exterior.id ? 'selected' : '';\r\n                const priceText = color.price === 0 ? '0 AMD' : `<span style=\"color: #fbbf24;\">+${formatPrice(color.price)}<\/span>`;\r\n                html += `\r\n                    <div class=\"color-option ext-${colorId} ${selected}\" data-ext=\"${colorId}\" onclick=\"selectExteriorColor('${colorId}', '${color.name}', ${color.price})\">\r\n                        <div class=\"color-label\">${colorLabelHTML(color.name)}<small>${priceText}<\/small><\/div>\r\n                    <\/div>\r\n                `;\r\n            });\r\n            html += '<\/div><\/div>';\r\n\r\n            \/\/ Interior colors\r\n            html += '<div class=\"config-section\"><h3 class=\"section-title\">Interior Color<\/h3>';\r\n            html += '<div class=\"interior-color-grid\">';\r\n            Object.keys(MODEL_CONFIG.interiorColors).forEach(colorId => {\r\n                const color = MODEL_CONFIG.interiorColors[colorId];\r\n                const selected = colorId === selectedConfig.interior.id ? 'selected' : '';\r\n                html += `\r\n                    <div class=\"color-option int-${colorId} ${selected}\" data-int=\"${colorId}\" onclick=\"selectInteriorColor('${colorId}', '${color.name}', ${color.price})\">\r\n                        <div class=\"color-label\">${colorLabelHTML(color.name)}<\/div>\r\n                    <\/div>\r\n                `;\r\n            });\r\n            html += '<\/div><\/div>';\r\n\r\n            \/\/ Wheels\r\n            html += '<div class=\"config-section\" id=\"wheelsSection\"><h3 class=\"section-title\">Wheels<\/h3>';\r\n            html += '<div class=\"wheel-grid\">';\r\n            Object.keys(MODEL_CONFIG.wheels).forEach(wheelId => {\r\n                const wheel = MODEL_CONFIG.wheels[wheelId];\r\n                const selected = wheelId === selectedConfig.wheels.id ? 'selected' : '';\r\n                let wheelPrice = wheel.price;\r\n                if (typeof wheelPrice === 'object') {\r\n                    wheelPrice = wheelPrice[selectedConfig.model.id] || 0;\r\n                }\r\n\r\n                \/\/ Check for i6 AWD + 21\" wheels bundle\r\n                if (currentModelKey === 'i6' && wheelId === '21-7-spoke') {\r\n                    const hasAWD = selectedConfig.options.some(opt => opt.id === 'awd-system');\r\n                    if (hasAWD) {\r\n                        wheelPrice = 0; \/\/ Free with AWD\r\n                    }\r\n                }\r\n\r\n                const disabled = wheel.disabledFor && wheel.disabledFor.includes(selectedConfig.model.id);\r\n                const disabledStyle = disabled ? 'opacity: 0.5; cursor: not-allowed; pointer-events: none;' : '';\r\n                const disabledAttr = disabled ? '' : `onclick=\"selectWheel('${wheelId}', '${wheel.name}', '${wheel.desc}', ${wheelPrice})\"`;\r\n                const priceText = wheelPrice === 0 ? '0 AMD' : `<span style=\"color: #fbbf24;\">+${formatPrice(wheelPrice)}<\/span>`;\r\n                const wheelImagePath = `https:\/\/liautoyerevan.am\/preview-images\/sources\/${currentModelKey}-${wheelId}.jpg`;\r\n                if (!disabled) {\r\n                    html += `\r\n                        <div class=\"wheel-option ${selected}\" data-wheel-id=\"${wheelId}\" ${disabledAttr}>\r\n                            <div class=\"wheel-image\">\r\n                                ${generatePictureElement(wheelImagePath, wheel.name)}\r\n                            <\/div>\r\n                            <div class=\"wheel-name\">${wheel.name}<\/div>\r\n                            <div class=\"wheel-desc\">${wheel.desc}<\/div>\r\n                            <div class=\"price-tag\">${priceText}<\/div>\r\n                        <\/div>\r\n                    `;\r\n                }\r\n            });\r\n            html += '<\/div><\/div>';\r\n\r\n            \/\/ Options - only show section if there are available options for current model\r\n            const optionType = MODEL_CONFIG.optionType;\r\n            const availableOptions = Object.keys(MODEL_CONFIG.options).filter(optionId => {\r\n                const option = MODEL_CONFIG.options[optionId];\r\n                return !option.disabledFor || !option.disabledFor.includes(selectedConfig.model.id);\r\n            });\r\n\r\n            if (availableOptions.length > 0) {\r\n                html += '<div class=\"config-section\"><h3 class=\"section-title\">Options<\/h3>';\r\n                html += '<div style=\"display: grid; grid-template-columns: 1fr; gap: 16px;\">';\r\n                Object.keys(MODEL_CONFIG.options).forEach(optionId => {\r\n                    const option = MODEL_CONFIG.options[optionId];\r\n                    let optionPrice = option.price;\r\n                    if (typeof optionPrice === 'object') {\r\n                        optionPrice = optionPrice[selectedConfig.model.id] || 0;\r\n                    }\r\n                    const disabled = option.disabledFor && option.disabledFor.includes(selectedConfig.model.id);\r\n                    const disabledStyle = disabled ? 'opacity: 0.5; cursor: not-allowed;' : '';\r\n                    const disabledAttr = disabled ? 'onclick=\"return false;\"' : `onclick=\"selectOption('${optionId}', '${option.name}')\"`;\r\n\r\n                    \/\/ Handle free wheels when bundled with AWD (i6 specific)\r\n                    let effectivePrice = optionPrice;\r\n                    if (currentModelKey === 'i6' && optionId === 'awd-system') {\r\n                        \/\/ Check if 21\" wheels are selected - if so, they become free with AWD\r\n                        if (selectedConfig.wheels.id === '21-7-spoke') {\r\n                            const wheelData = MODEL_CONFIG.wheels['21-7-spoke'];\r\n                            const wheelBasePrice = wheelData.price || 0;\r\n                            \/\/ Show AWD price includes free wheels\r\n                            effectivePrice = optionPrice; \/\/ AWD price stays same, but wheels become free\r\n                        }\r\n                    }\r\n\r\n                    const priceText = effectivePrice === 0 ? (option.alwaysIncluded ? 'Included' : '0 AMD') : `+${formatPrice(effectivePrice)}`;\r\n                    const optionImagePath = `https:\/\/liautoyerevan.am\/preview-images\/sources\/${currentModelKey}-${optionId}.jpg`;\r\n\r\n                    \/\/ Check if option is selected (multi-option or single-option mode)\r\n                    let isSelected = false;\r\n                    if (MODEL_CONFIG.optionMode === 'multiple') {\r\n                        isSelected = selectedConfig.options.some(opt => opt.id === optionId);\r\n                    } else {\r\n                        isSelected = selectedConfig.option.id === optionId;\r\n                    }\r\n                    const selectedClass = isSelected ? ' selected' : '';\r\n\r\n                    html += `\r\n                        <div class=\"audio-option${selectedClass}\" data-option-id=\"${optionId}\" style=\"${disabledStyle}\" ${disabledAttr}>\r\n                            <div class=\"wheel-image\">\r\n                                ${generatePictureElement(optionImagePath, option.name)}\r\n                            <\/div>\r\n                            <div class=\"option-text-content\">\r\n                                <div class=\"audio-name\">${option.name}<\/div>\r\n                                <div class=\"price-tag\">${priceText}<\/div>\r\n                                ${disabled ? '<div class=\"note-text\">Included with this model<\/div>' : ''}\r\n                            <\/div>\r\n                        <\/div>\r\n                    `;\r\n                });\r\n                html += '<\/div><\/div>';\r\n            }\r\n\r\n            container.innerHTML = html;\r\n\r\n            \/\/ Generate CSS for color previews\r\n            generateColorPreviewCSS();\r\n        }\r\n\r\n        \/\/ Generate CSS for color preview backgrounds\r\n        function generateColorPreviewCSS() {\r\n            let css = '';\r\n            const chineseFolder = MODEL_CONFIG.chineseFolder;\r\n\r\n            \/\/ Get current variant folder (for multi-variant models like L6 Pro\/Max)\r\n            const currentModel = MODEL_CONFIG.models[selectedConfig.model.id];\r\n            const variantFolder = currentModel.variantFolder;\r\n\r\n            \/\/ Exterior colors - use full car images\r\n            Object.keys(MODEL_CONFIG.exteriorColors).forEach(colorId => {\r\n                const color = MODEL_CONFIG.exteriorColors[colorId];\r\n\r\n                \/\/ Filter colors by trim if forTrim property exists\r\n                if (color.forTrim && color.forTrim !== selectedConfig.model.id) {\r\n                    return; \/\/ Skip this color if it doesn't match the selected trim\r\n                }\r\n\r\n                const colorChinese = chineseNames.exteriorColors[colorId];\r\n                const defaultInterior = Object.keys(MODEL_CONFIG.interiorColors)[0];\r\n                const interiorChinese = chineseNames.interiorColors[defaultInterior];\r\n                const defaultWheel = Object.keys(MODEL_CONFIG.wheels)[0];\r\n                const wheelChinese = chineseNames.wheels[defaultWheel];\r\n\r\n                let imagePath;\r\n\r\n                \/\/ Special case: L9 Livis has unique folder structure\r\n                if (currentModelKey === 'l9-livis') {\r\n                    const filename = `${colorChinese}-${wheelChinese}-${interiorChinese}.webp`;\r\n                    imagePath = `https:\/\/liautoyerevan.am\/configurator_images\/downloaded-images-webp\/${chineseFolder}\/output-webp\/exterior\/${filename}`;\r\n                    css += `.ext-${colorId} { background-image: url('${imagePath}'); }\\n`;\r\n                    return; \/\/ Skip standard logic\r\n                }\r\n\r\n                let filename;\r\n                const includeWheels = MODEL_CONFIG.includeWheelsInFilename !== false;\r\n                const exteriorPattern = MODEL_CONFIG.exteriorPattern || 'default';\r\n\r\n                if (!includeWheels) {\r\n                    \/\/ MEGA: no wheels in filename\r\n                    filename = `${colorChinese}-${interiorChinese}.webp`;\r\n                } else if (exteriorPattern === 'interior-before-wheels') {\r\n                    \/\/ L9: color -> interior -> wheel\r\n                    \/\/ Get first available wheel (not disabled for current model)\r\n                    const defaultWheel = Object.keys(MODEL_CONFIG.wheels).find(wheelId => {\r\n                        const wheelData = MODEL_CONFIG.wheels[wheelId];\r\n                        return !wheelData.disabledFor || !wheelData.disabledFor.includes(selectedConfig.model.id);\r\n                    }) || Object.keys(MODEL_CONFIG.wheels)[0];\r\n                    const wheelChinese = chineseNames.wheels[defaultWheel];\r\n                    filename = `${colorChinese}-${interiorChinese}-${wheelChinese}.webp`;\r\n                } else {\r\n                    \/\/ Default (i8, L6, L7, i6): color -> wheel -> interior\r\n                    \/\/ Get first available wheel (not disabled for current model)\r\n                    const defaultWheel = Object.keys(MODEL_CONFIG.wheels).find(wheelId => {\r\n                        const wheelData = MODEL_CONFIG.wheels[wheelId];\r\n                        return !wheelData.disabledFor || !wheelData.disabledFor.includes(selectedConfig.model.id);\r\n                    }) || Object.keys(MODEL_CONFIG.wheels)[0];\r\n                    const wheelChinese = chineseNames.wheels[defaultWheel];\r\n                    filename = `${colorChinese}-${wheelChinese}-${interiorChinese}.webp`;\r\n\r\n                    \/\/ i6 multi-option: exterior includes rear screen option\r\n                    if (MODEL_CONFIG.optionMode === 'multiple') {\r\n                        \/\/ Get rear screen base value (when not selected)\r\n                        const rearScreenOption = MODEL_CONFIG.options['rear-screen'];\r\n                        if (rearScreenOption && rearScreenOption.chineseBase) {\r\n                            filename = `${colorChinese}-${wheelChinese}-${interiorChinese}-${rearScreenOption.chineseBase}.webp`;\r\n                        }\r\n                    }\r\n                }\r\n\r\n                imagePath = `https:\/\/liautoyerevan.am\/configurator_images\/downloaded-images-webp\/${chineseFolder}\/${variantFolder}\/variant-attribute\/exterior\/angle-0\/${filename}`;\r\n                css += `.ext-${colorId} { background-image: url('${imagePath}'); }\\n`;\r\n            });\r\n\r\n            \/\/ Interior colors - use full car images\r\n            Object.keys(MODEL_CONFIG.interiorColors).forEach(colorId => {\r\n                const interiorChinese = chineseNames.interiorColors[colorId];\r\n                const defaultExterior = Object.keys(MODEL_CONFIG.exteriorColors)[0];\r\n                const exteriorChinese = chineseNames.exteriorColors[defaultExterior];\r\n\r\n                \/\/ Special case: L9 Livis has unique folder structure\r\n                if (currentModelKey === 'l9-livis') {\r\n                    const filename = `${interiorChinese}-${exteriorChinese}.webp`;\r\n                    const imagePath = `https:\/\/liautoyerevan.am\/configurator_images\/downloaded-images-webp\/${chineseFolder}\/output-webp\/interior\/${filename}`;\r\n                    css += `.int-${colorId} { background-image: url('${imagePath}'); }\\n`;\r\n                    return; \/\/ Skip standard logic\r\n                }\r\n\r\n                \/\/ Use correct pattern based on model config\r\n                const interiorPattern = MODEL_CONFIG.interiorPattern || 'default';\r\n                let filename;\r\n                if (interiorPattern === 'exterior-first') {\r\n                    \/\/ L7\/L9\/MEGA pattern\r\n                    filename = `${exteriorChinese}-${interiorChinese}.webp`;\r\n\r\n                    \/\/ L7 Ultra Baby Blue has special pattern: exterior-interior-exterior\r\n                    if (variantFolder === '\u7406\u60f3L7 Ultra' && exteriorChinese === 'Baby Blue\u200b\u7279\u522b\u7248\u73cd\u73e0\u6f06') {\r\n                        filename = `${exteriorChinese}-${interiorChinese}-${exteriorChinese}.webp`;\r\n                    }\r\n                } else {\r\n                    \/\/ Default (i8\/L6\/i6) pattern\r\n                    filename = `${interiorChinese}-${exteriorChinese}.webp`;\r\n\r\n                    \/\/ i6 multi-option: interior includes audio, rear screen, and drivetrain options\r\n                    if (MODEL_CONFIG.optionMode === 'multiple') {\r\n                        \/\/ Pattern with base options: {interior}-{color}-{audio}-{rearScreen}-{drivetrain}\r\n                        const rearScreenOption = MODEL_CONFIG.options['rear-screen'];\r\n                        const audioOption = MODEL_CONFIG.options['platinum-audio'];\r\n                        const awdOption = MODEL_CONFIG.options['awd-system'];\r\n\r\n                        const rearScreenBase = rearScreenOption ? rearScreenOption.chineseBase : '';\r\n                        const audioBase = audioOption ? audioOption.chineseBase : '';\r\n                        const drivetrainBase = awdOption ? awdOption.chineseBase : '';\r\n\r\n                        filename = `${interiorChinese}-${exteriorChinese}-${audioBase}-${rearScreenBase}-${drivetrainBase}.webp`;\r\n                    }\r\n                }\r\n\r\n                const imagePath = `https:\/\/liautoyerevan.am\/configurator_images\/downloaded-images-webp\/${chineseFolder}\/${variantFolder}\/variant-attribute\/interior\/angle-0\/${filename}`;\r\n                css += `.int-${colorId} { background-image: url('${imagePath}'); }\\n`;\r\n            });\r\n\r\n            \/\/ Inject CSS\r\n            const styleEl = document.createElement('style');\r\n            styleEl.textContent = css;\r\n            document.head.appendChild(styleEl);\r\n        }\r\n\r\n        async function initConfigurator() {\r\n            await loadModelConfig();\r\n            updateConfigDetails();\r\n            updatePricing();\r\n            updateWaitingListDisplay(); \/\/ Show L9 Livis waiting list counter if applicable\r\n            updateImage('exterior', true); \/\/ Load initial image without crossfade\r\n            currentZoom = 0.9; updateZoom(); \/\/ apply base zoom on load (matches resetZoom target) - else first view-switch jumps 1.0 -> 1.2\r\n            initHoverPrefetch(); \/\/ wire hover-to-prefetch (once)\r\n            preloadAdjacentOptions(); \/\/ Preload adjacent options on init\r\n\r\n            if ('ontouchstart' in window) {\r\n                addTouchSupport();\r\n            }\r\n\r\n            \/\/ Add zoom and pan event listeners\r\n            const carDisplay = document.querySelector('.car-display');\r\n            if (carDisplay) {\r\n                \/\/ Mouse events\r\n                carDisplay.addEventListener('wheel', handleWheel, { passive: false });\r\n                carDisplay.addEventListener('mousedown', handleMouseDown);\r\n                document.addEventListener('mousemove', handleMouseMove);\r\n                document.addEventListener('mouseup', handleMouseUp);\r\n\r\n                \/\/ Touch events\r\n                carDisplay.addEventListener('touchstart', handleTouchStart, { passive: false });\r\n                carDisplay.addEventListener('touchmove', handleTouchMove, { passive: false });\r\n                carDisplay.addEventListener('touchend', handleTouchEnd);\r\n            }\r\n\r\n            \/\/ Initialize tab indicator position after DOM is fully rendered\r\n            setTimeout(updateTabIndicator, 0);\r\n\r\n            \/\/ Hide zoom controls on mobile initially (at base scale)\r\n            if (isMobile()) {\r\n                const zoomControls = document.querySelector('.zoom-controls');\r\n                if (zoomControls) {\r\n                    zoomControls.classList.add('hide-on-mobile');\r\n                }\r\n            }\r\n\r\n            \/\/ Prevent page-level pinch-to-zoom globally (for Elementor compatibility)\r\n            \/\/ This works even when viewport meta tags are overridden\r\n            document.addEventListener('touchstart', function(e) {\r\n                if (e.touches.length > 1) {\r\n                    \/\/ Allow pinch on car display (handled by our custom zoom)\r\n                    const target = e.target;\r\n                    const carDisplay = target.closest('.car-display');\r\n                    if (!carDisplay) {\r\n                        \/\/ Prevent pinch-to-zoom on the rest of the page\r\n                        e.preventDefault();\r\n                    }\r\n                }\r\n            }, { passive: false });\r\n\r\n            document.addEventListener('touchmove', function(e) {\r\n                if (e.touches.length > 1) {\r\n                    \/\/ Allow pinch on car display (handled by our custom zoom)\r\n                    const target = e.target;\r\n                    const carDisplay = target.closest('.car-display');\r\n                    if (!carDisplay) {\r\n                        \/\/ Prevent pinch-to-zoom on the rest of the page\r\n                        e.preventDefault();\r\n                    }\r\n                }\r\n            }, { passive: false });\r\n        }\r\n\r\n        function addTouchSupport() {\r\n            document.querySelectorAll('.wheel-option, .color-option, .audio-option, .view-tab, .cta-button, .cancel-btn, .submit-btn').forEach(element => {\r\n                element.addEventListener('touchstart', function () {\r\n                    this.style.opacity = '0.8';\r\n                }, { passive: true });\r\n\r\n                element.addEventListener('touchend', function () {\r\n                    this.style.opacity = '';\r\n                }, { passive: true });\r\n\r\n                element.addEventListener('touchcancel', function () {\r\n                    this.style.opacity = '';\r\n                }, { passive: true });\r\n            });\r\n        }\r\n\r\n        \/\/ L9 Livis Waiting List Counter\r\n        async function fetchWaitingListCount() {\r\n            try {\r\n                const response = await fetch('https:\/\/liautoyerevan.am\/wp-json\/key-counter\/v1\/increment?key=livis');\r\n                const data = await response.json();\r\n                const count = data.count || 0;\r\n\r\n                const countEl = document.getElementById('waitingCount');\r\n                const subtitleEl = document.querySelector('.livis-preorder-subtitle');\r\n                const labelEl = document.querySelector('.livis-waiting-label');\r\n\r\n                if (count >= 45) {\r\n                    \/\/ Preorders closed\r\n                    if (countEl) countEl.textContent = '45\/45';\r\n                    if (subtitleEl) subtitleEl.textContent = 'Preorders Closed';\r\n                    if (labelEl) labelEl.textContent = 'ALL SPOTS FILLED';\r\n\r\n                    \/\/ Hide CTA button\r\n                    const ctaButton = document.querySelector('.cta-button');\r\n                    if (ctaButton) {\r\n                        ctaButton.style.display = 'none';\r\n                    }\r\n                } else {\r\n                    \/\/ Preorders still open\r\n                    if (countEl) countEl.textContent = count + '\/45';\r\n                    if (subtitleEl) subtitleEl.textContent = 'Join the exclusive flagship waiting list';\r\n                    if (labelEl) labelEl.textContent = 'SPOTS REMAINING';\r\n\r\n                    \/\/ Show CTA button\r\n                    const ctaButton = document.querySelector('.cta-button');\r\n                    if (ctaButton) {\r\n                        ctaButton.style.display = 'block';\r\n                    }\r\n                }\r\n\r\n                return count;\r\n            } catch (error) {\r\n                console.error('Error fetching waiting list count:', error);\r\n                const countEl = document.getElementById('waitingCount');\r\n                if (countEl) {\r\n                    countEl.textContent = '--\/45';\r\n                }\r\n                return 0;\r\n            }\r\n        }\r\n\r\n        async function incrementWaitingListCount() {\r\n            try {\r\n                const response = await fetch('https:\/\/liautoyerevan.am\/wp-json\/key-counter\/v1\/increment?key=livis', {\r\n                    method: 'POST'\r\n                });\r\n                const data = await response.json();\r\n                console.log('Waiting list count incremented:', data.count);\r\n                return data.count;\r\n            } catch (error) {\r\n                console.error('Error incrementing waiting list count:', error);\r\n                return 0;\r\n            }\r\n        }\r\n\r\n        function updateWaitingListDisplay() {\r\n            \/\/ For L9 Livis, fetch and display the waiting list count\r\n            \/\/ Banner is rendered by renderConfigUI(), just need to fetch count\r\n            if (currentModelKey === 'l9-livis') {\r\n                fetchWaitingListCount();\r\n            }\r\n        }\r\n\r\n        function updateTabIndicator() {\r\n            const studioControls = document.querySelector('.studio-controls');\r\n            const activeTab = document.querySelector('.view-tab.active');\r\n\r\n            if (!studioControls || !activeTab) return;\r\n\r\n            \/\/ Get the position and size of the active tab relative to the container\r\n            const containerRect = studioControls.getBoundingClientRect();\r\n            const tabRect = activeTab.getBoundingClientRect();\r\n\r\n            \/\/ Calculate the left position and width for the indicator\r\n            const left = tabRect.left - containerRect.left;\r\n            const width = tabRect.width;\r\n\r\n            \/\/ Update the ::before pseudo-element via CSS custom properties\r\n            studioControls.style.setProperty('--indicator-left', left + 'px');\r\n            studioControls.style.setProperty('--indicator-width', width + 'px');\r\n        }\r\n\r\n        function switchView(view, clickEvent, skipImage) {\r\n\r\n            currentView = view;\r\n\r\n            \/\/ Update tab highlighting\r\n            document.querySelectorAll('.view-tab').forEach(tab => tab.classList.remove('active'));\r\n\r\n            \/\/ If called from tab click, use the clicked element\r\n            if (clickEvent && clickEvent.target && clickEvent.target.classList.contains('view-tab')) {\r\n                clickEvent.target.classList.add('active');\r\n            } else {\r\n                \/\/ Programmatic call - find and highlight the correct tab by data-view attribute\r\n                const targetTab = document.querySelector(`.view-tab[data-view=\"${view}\"]`);\r\n                if (targetTab) {\r\n                    targetTab.classList.add('active');\r\n                }\r\n            }\r\n\r\n            \/\/ Update sliding indicator position\r\n            updateTabIndicator();\r\n\r\n            document.querySelectorAll('.car-view').forEach(view => view.classList.remove('active'));\r\n            document.getElementById(view + 'View').classList.add('active');\r\n\r\n            const overlay = document.querySelector('.car-info-overlay');\r\n            if (isMobile() && (view === 'interior')) {\r\n                overlay.classList.add('interior-hidden');\r\n            } else {\r\n                overlay.classList.remove('interior-hidden');\r\n            }\r\n\r\n            if (!skipImage) updateImage(view); \/\/ Load correct image for the view\r\n            resetZoom();\r\n            \/\/ Manual tab switch: snap the card (just a reorder). Selection-driven switches\r\n            \/\/ (skipImage) leave the card to the caller so it animates the real change.\r\n            if (!skipImage) updateConfigDetails(true);\r\n        }\r\n\r\n        \/\/ When a selection lives in another view, change the VIEW first and only animate\r\n        \/\/ the image once the view transition has settled (feels like \"look over there,\r\n        \/\/ then here's the change\" instead of both happening at once).\r\n        let _viewImgTimer = null;\r\n        function changeViewThenImage(view) {\r\n            if (currentView !== view) {\r\n                \/\/ Only do the \"look here, then change\" beat if the target view already\r\n                \/\/ shows an image to hold during the switch. If it's empty (first visit),\r\n                \/\/ load immediately - otherwise we'd stare at a blank box for 750ms.\r\n                const frontImg = document.querySelector(`#${view}View .car-picture.front img`);\r\n                const hasImage = !!(frontImg && frontImg.getAttribute('src'));\r\n                switchView(view, null, true); \/\/ switch view now, hold the image\r\n                updateConfigDetails(); \/\/ animated card update, now in the new view's order\r\n                clearTimeout(_viewImgTimer);\r\n                \/\/ wait until the 0.6s view fade fully lands + a beat, so it reads as\r\n                \/\/ \"look here\" ... then \"notice the change\"\r\n                _viewImgTimer = setTimeout(() => updateImage(view), hasImage ? 750 : 0);\r\n            } else {\r\n                updateImage(view);\r\n                updateConfigDetails(); \/\/ animated card update\r\n            }\r\n        }\r\n\r\n        function selectWheel(id, name, desc, price) {\r\n            \/\/ Handle AWD + 21\" wheels bundle for i6\r\n            if (currentModelKey === 'i6' && id === '21-7-spoke') {\r\n                const hasAWD = selectedConfig.options.some(opt => opt.id === 'awd-system');\r\n                if (hasAWD) {\r\n                    \/\/ AWD bundles with 21\" wheels - make them free\r\n                    price = 0;\r\n                }\r\n            }\r\n\r\n            selectedConfig.wheels = { id, name, desc, price };\r\n\r\n            document.querySelectorAll('#wheelsSection .wheel-option').forEach(option => {\r\n                option.classList.remove('selected');\r\n            });\r\n            event.currentTarget.classList.add('selected');\r\n\r\n            updatePricing();\r\n            changeViewThenImage('exterior'); \/\/ view first, then image (handles card animation)\r\n            preloadAdjacentOptions(); \/\/ Preload adjacent options\r\n        }\r\n\r\n        function selectExteriorColor(id, name, price) {\r\n            selectedConfig.exterior = { id, name, price };\r\n\r\n            document.querySelectorAll('.color-grid .color-option').forEach(option => {\r\n                option.classList.remove('selected');\r\n            });\r\n            event.currentTarget.classList.add('selected');\r\n\r\n            updatePricing();\r\n            changeViewThenImage('exterior'); \/\/ view first, then image (handles card animation)\r\n            preloadAdjacentOptions(); \/\/ Preload adjacent options\r\n        }\r\n\r\n        function selectInteriorColor(id, name, price) {\r\n            selectedConfig.interior = { id, name, price };\r\n\r\n            document.querySelectorAll('.interior-color-grid .color-option').forEach(option => {\r\n                option.classList.remove('selected');\r\n            });\r\n            event.currentTarget.classList.add('selected');\r\n\r\n            updatePricing();\r\n            changeViewThenImage('interior'); \/\/ view first, then image (handles card animation)\r\n            preloadAdjacentOptions(); \/\/ Preload adjacent options\r\n        }\r\n\r\n        function updateConfigDetails(instant) {\r\n            const details = document.getElementById('configDetails');\r\n            const formattedPrice = formatPrice(calculateTotalPrice());\r\n\r\n            \/\/ Build options string (single or multiple)\r\n            let optionsText = '';\r\n            if (MODEL_CONFIG && MODEL_CONFIG.optionMode === 'multiple') {\r\n                \/\/ Multi-option mode: show count instead of names (more compact)\r\n                if (selectedConfig.options.length > 0) {\r\n                    const count = selectedConfig.options.length;\r\n                    optionsText = `${count} option${count > 1 ? 's' : ''} selected`;\r\n                }\r\n            } else {\r\n                \/\/ Single-option mode: show single option if selected\r\n                if (selectedConfig.option.id !== 'none') {\r\n                    optionsText = selectedConfig.option.name;\r\n                }\r\n            }\r\n\r\n            \/\/ Strip \"Custom\/Special Edition\" suffix here too - same as the color swatches.\r\n            const exteriorName = colorLabelHTML(selectedConfig.exterior.name);\r\n            const interiorName = colorLabelHTML(selectedConfig.interior.name);\r\n\r\n            let newContent;\r\n            if (currentView === 'exterior') {\r\n                newContent = `${selectedConfig.model.name} - ${exteriorName} &bull; ${interiorName}<br>`;\r\n                if (optionsText) {\r\n                    newContent += `${selectedConfig.wheels.name} &bull; ${optionsText}<br>`;\r\n                } else {\r\n                    newContent += `${selectedConfig.wheels.name}<br>`;\r\n                }\r\n                newContent += `${formattedPrice}`;\r\n            } else if (currentView === 'interior') {\r\n                newContent = `${selectedConfig.model.name} - ${interiorName} &bull; ${exteriorName}<br>`;\r\n                if (optionsText) {\r\n                    newContent += `${selectedConfig.wheels.name} &bull; ${optionsText}<br>`;\r\n                } else {\r\n                    newContent += `${selectedConfig.wheels.name}<br>`;\r\n                }\r\n                newContent += `${formattedPrice}`;\r\n            }\r\n\r\n            \/\/ Sequence: fade the text OUT, swap it (while invisible), animate the CARD\r\n            \/\/ from its old size to the new size, and only AFTER the resize finishes fade\r\n            \/\/ the text back IN. Text stays at opacity 0 through the whole resize, so its\r\n            \/\/ reflow is never visible (no clipping needed).\r\n            if (details.innerHTML !== newContent) {\r\n                const card = details.closest('.car-info-overlay');\r\n\r\n                \/\/ Instant update (e.g. exterior<->interior view switch just reorders the\r\n                \/\/ same info) - snap it, no fade\/resize animation.\r\n                if (instant) {\r\n                    card.style.transition = 'none';\r\n                    card.style.width = ''; card.style.height = '';\r\n                    details.style.transition = 'none';\r\n                    details.innerHTML = newContent;\r\n                    details.style.opacity = '1';\r\n                    card.offsetHeight;\r\n                    card.style.transition = ''; details.style.transition = '';\r\n                    return;\r\n                }\r\n\r\n                details.style.transition = 'opacity 0.15s ease';\r\n                details.style.opacity = '0';\r\n\r\n                const fadeIn = () => {\r\n                    details.style.transition = 'opacity 0.25s ease';\r\n                    details.style.opacity = '1';\r\n                };\r\n\r\n                setTimeout(() => {\r\n                    \/\/ measure old box, swap content (invisible), measure new box\r\n                    const startW = card.offsetWidth, startH = card.offsetHeight;\r\n                    card.style.transition = 'none';\r\n                    card.style.width = ''; card.style.height = '';\r\n                    details.innerHTML = newContent;\r\n                    const endW = card.offsetWidth, endH = card.offsetHeight;\r\n\r\n                    \/\/ no size change -> just bring the text back\r\n                    if (startW === endW && startH === endH) {\r\n                        card.style.width = ''; card.style.height = ''; card.style.transition = '';\r\n                        fadeIn();\r\n                        return;\r\n                    }\r\n\r\n                    \/\/ pin to old size, reflow, then animate to new size\r\n                    card.style.width = startW + 'px';\r\n                    card.style.height = startH + 'px';\r\n                    card.offsetHeight; \/\/ force reflow so the start size sticks\r\n                    card.style.transition = '';\r\n                    card.style.width = endW + 'px';\r\n                    card.style.height = endH + 'px';\r\n\r\n                    \/\/ after the size animation completes: release inline sizes, THEN fade text in\r\n                    clearTimeout(card._resizeTimer);\r\n                    card._resizeTimer = setTimeout(() => {\r\n                        card.style.transition = 'none';\r\n                        card.style.width = ''; card.style.height = '';\r\n                        card.offsetHeight;\r\n                        card.style.transition = '';\r\n                        fadeIn();\r\n                    }, 370); \/\/ ~ size anim (350ms) + small buffer\r\n                }, 150);\r\n            }\r\n        }\r\n\r\n        \/\/ Bounce animation for zoom limit feedback\r\n        function bounceZoom(amount) {\r\n            const originalZoom = currentZoom;\r\n            currentZoom += amount;\r\n            updateZoom();\r\n\r\n            \/\/ Bounce back after 100ms\r\n            setTimeout(() => {\r\n                currentZoom = originalZoom;\r\n                updateZoom();\r\n            }, 100);\r\n        }\r\n\r\n        function zoomIn() {\r\n            if (currentZoom < maxZoom) {\r\n                currentZoom += zoomStep;\r\n                currentZoom = Math.min(currentZoom, maxZoom);\r\n                updateZoom();\r\n            } else {\r\n                \/\/ Bounce feedback when hitting max zoom\r\n                bounceZoom(0.02);\r\n            }\r\n        }\r\n\r\n        function zoomOut() {\r\n            if (currentZoom > minZoom) {\r\n                currentZoom -= zoomStep;\r\n                currentZoom = Math.max(currentZoom, minZoom);\r\n\r\n                \/\/ Auto-center when zooming below base scale to prevent softlock\r\n                const baseScale = 0.9;\r\n                if (currentZoom <= baseScale) {\r\n                    panX = 0;\r\n                    panY = 0;\r\n                }\r\n\r\n                updateZoom();\r\n            } else {\r\n                \/\/ Bounce feedback when hitting min zoom\r\n                bounceZoom(-0.015);\r\n            }\r\n        }\r\n\r\n        function resetZoom() {\r\n            const targetZoom = 0.9;\r\n            const startZoom = currentZoom;\r\n            const startPanX = panX;\r\n            const startPanY = panY;\r\n\r\n            \/\/ Set flag to prevent UI flicker during animation\r\n            isResettingZoom = true;\r\n\r\n            \/\/ Animate zoom reset over 300ms\r\n            const duration = 300;\r\n            const startTime = performance.now();\r\n\r\n            function animate(currentTime) {\r\n                const elapsed = currentTime - startTime;\r\n                const progress = Math.min(elapsed \/ duration, 1);\r\n\r\n                \/\/ Ease-out cubic for smooth deceleration\r\n                const easeProgress = 1 - Math.pow(1 - progress, 3);\r\n\r\n                \/\/ Interpolate values\r\n                currentZoom = startZoom + (targetZoom - startZoom) * easeProgress;\r\n                panX = startPanX + (0 - startPanX) * easeProgress;\r\n                panY = startPanY + (0 - startPanY) * easeProgress;\r\n\r\n                updateZoom();\r\n\r\n                if (progress < 1) {\r\n                    requestAnimationFrame(animate);\r\n                } else {\r\n                    \/\/ Ensure final values are exact\r\n                    currentZoom = targetZoom;\r\n                    panX = 0;\r\n                    panY = 0;\r\n                    updateZoom();\r\n\r\n                    \/\/ Clear flag after animation completes\r\n                    isResettingZoom = false;\r\n\r\n                    \/\/ Update mobile UI now that flag is cleared\r\n                    updateZoom();\r\n                }\r\n            }\r\n\r\n            requestAnimationFrame(animate);\r\n        }\r\n\r\n        function updateZoom() {\r\n            const activeImages = document.querySelectorAll('.car-view.active .car-image');\r\n            if (activeImages.length) {\r\n                \/\/ Apply pan boundaries to prevent image from being dragged completely outside viewport\r\n                const img = activeImages[0];\r\n                const container = img.closest('.car-display');\r\n\r\n                \/\/ Guard: before the image decodes, naturalWidth\/height are 0 -> imgAspect NaN\r\n                \/\/ -> pan math yields NaN -> transform string invalid -> browser drops it and\r\n                \/\/ the image shows at scale(1). Skip pan calc until measured; scale still applies.\r\n                if (container && (img.naturalWidth || img.width) && (img.naturalHeight || img.height)) {\r\n                    const containerRect = container.getBoundingClientRect();\r\n                    const containerWidth = containerRect.width;\r\n                    const containerHeight = containerRect.height;\r\n\r\n                    \/\/ Get natural image dimensions (before any transform)\r\n                    const naturalWidth = img.naturalWidth || img.width;\r\n                    const naturalHeight = img.naturalHeight || img.height;\r\n\r\n                    \/\/ Calculate actual displayed dimensions considering object-fit: contain\r\n                    const imgAspect = naturalWidth \/ naturalHeight;\r\n                    const containerAspect = containerWidth \/ containerHeight;\r\n\r\n                    let baseWidth, baseHeight;\r\n                    if (imgAspect > containerAspect) {\r\n                        \/\/ Image is wider - width constrained\r\n                        baseWidth = containerWidth;\r\n                        baseHeight = containerWidth \/ imgAspect;\r\n                    } else {\r\n                        \/\/ Image is taller - height constrained\r\n                        baseHeight = containerHeight;\r\n                        baseWidth = containerHeight * imgAspect;\r\n                    }\r\n\r\n                    \/\/ Apply current zoom to get actual scaled dimensions\r\n                    const scaledWidth = baseWidth * currentZoom;\r\n                    const scaledHeight = baseHeight * currentZoom;\r\n\r\n                    \/\/ Calculate zoom-dependent minimum visible percentage\r\n                    \/\/ At base scale: 20% minimum, at max zoom (2x): 40% minimum\r\n                    const baseScale = 0.9;\r\n                    const zoomRatio = (currentZoom - baseScale) \/ (maxZoom - baseScale);\r\n                    const minVisiblePercent = 0.2 + (zoomRatio * 0.2); \/\/ 20% to 40% based on zoom\r\n\r\n                    \/\/ Calculate how much the scaled image exceeds the viewport\r\n                    const excessWidth = Math.max(0, scaledWidth - containerWidth);\r\n                    const excessHeight = Math.max(0, scaledHeight - containerHeight);\r\n\r\n                    \/\/ Calculate max pan: half the excess, minus the minimum visible margin\r\n                    const minVisibleMarginX = scaledWidth * minVisiblePercent;\r\n                    const minVisibleMarginY = scaledHeight * minVisiblePercent;\r\n\r\n                    const maxPanX = Math.max(0, (excessWidth \/ 2) + (scaledWidth \/ 2) - minVisibleMarginX);\r\n                    const maxPanY = Math.max(0, (excessHeight \/ 2) + (scaledHeight \/ 2) - minVisibleMarginY);\r\n\r\n                    \/\/ Clamp pan values to boundaries\r\n                    panX = Math.max(-maxPanX, Math.min(maxPanX, panX));\r\n                    panY = Math.max(-maxPanY, Math.min(maxPanY, panY));\r\n                }\r\n\r\n                const baseScale = 0.9;\r\n\r\n                activeImages.forEach(img => {\r\n                    img.style.transform = `translate(${panX}px, ${panY}px) scale(${currentZoom})`;\r\n                    \/\/ Show grab cursor only when zoom is above base scale (when panning is enabled)\r\n                    img.style.cursor = currentZoom > baseScale ? 'grab' : 'default';\r\n                });\r\n            }\r\n\r\n            \/\/ Hide info overlay and show reset button when zoomed away from default.\r\n            \/\/ Use the STACKED-LAYOUT breakpoint (1024), not isMobile() (768): the overlay\r\n            \/\/ sits over the car and its hide rule live at max-width:1024, so the 769-1024\r\n            \/\/ range needs this too - else the card stays visible at every zoom there.\r\n            \/\/ Skip during reset animation to prevent flicker.\r\n            if (window.innerWidth <= 1024 && !isResettingZoom) {\r\n                const overlay = document.querySelector('.car-info-overlay');\r\n                const zoomControls = document.querySelector('.zoom-controls');\r\n                let baseScale = 0.9;\r\n\r\n                \/\/ Show reset button when zoom is not at base scale (either above or below)\r\n                const isAtBaseScale = Math.abs(currentZoom - baseScale) < 0.01;\r\n\r\n                if (!isAtBaseScale) {\r\n                    \/\/ Zoomed away from default (either in or out)\r\n                    overlay.classList.add('interior-hidden');\r\n                    zoomControls.classList.remove('hide-on-mobile');\r\n                } else {\r\n                    \/\/ At base scale\r\n                    \/\/ Only show overlay if not in interior view (which has its own hide logic)\r\n                    if (currentView !== 'interior') {\r\n                        overlay.classList.remove('interior-hidden');\r\n                    }\r\n                    zoomControls.classList.add('hide-on-mobile');\r\n                }\r\n            }\r\n        }\r\n\r\n        \/\/ Mouse wheel zoom\r\n        function handleWheel(e) {\r\n            \/\/ Allow zooming from anywhere in car-display, not just on the image\r\n            e.preventDefault();\r\n\r\n            if (e.deltaY < 0) {\r\n                zoomIn();\r\n            } else {\r\n                zoomOut();\r\n            }\r\n        }\r\n\r\n        \/\/ Mouse drag to pan\r\n        function handleMouseDown(e) {\r\n            \/\/ Allow dragging from anywhere in car-display, not just on the image\r\n            const carDisplay = e.target.closest('.car-display');\r\n            if (!carDisplay) return;\r\n\r\n            \/\/ Disable panning when at base scale (default zoom)\r\n            const baseScale = 0.9;\r\n            if (currentZoom <= baseScale) return;\r\n\r\n            isDragging = true;\r\n            startX = e.clientX - panX;\r\n            startY = e.clientY - panY;\r\n\r\n            \/\/ Update cursor on all active images\r\n            const activeImages = document.querySelectorAll('.car-view.active .car-image');\r\n            activeImages.forEach(img => img.style.cursor = 'grabbing');\r\n        }\r\n\r\n        function handleMouseMove(e) {\r\n            if (!isDragging) return;\r\n            e.preventDefault();\r\n\r\n            panX = e.clientX - startX;\r\n            panY = e.clientY - startY;\r\n            updateZoom();\r\n        }\r\n\r\n        function handleMouseUp(e) {\r\n            if (isDragging) {\r\n                isDragging = false;\r\n                \/\/ Reset cursor on all active images\r\n                const activeImages = document.querySelectorAll('.car-view.active .car-image');\r\n                activeImages.forEach(img => img.style.cursor = 'grab');\r\n            }\r\n        }\r\n\r\n        \/\/ Touch pinch to zoom and drag to pan\r\n        function getTouchDistance(touches) {\r\n            const dx = touches[0].clientX - touches[1].clientX;\r\n            const dy = touches[0].clientY - touches[1].clientY;\r\n            return Math.sqrt(dx * dx + dy * dy);\r\n        }\r\n\r\n        \/\/ single-finger horizontal swipe at default zoom -> change angle\r\n        let swipeStartX = 0, swipeStartY = 0, swipeStartTime = 0, swipeTracking = false;\r\n\r\n        function handleTouchStart(e) {\r\n            \/\/ Allow touch from anywhere in car-display, not just on the image\r\n            const carDisplay = e.target.closest('.car-display');\r\n            if (!carDisplay) return;\r\n\r\n            const baseScale = 0.9;\r\n\r\n            if (e.touches.length === 2) {\r\n                e.preventDefault();\r\n                initialDistance = getTouchDistance(e.touches);\r\n                initialZoom = currentZoom;\r\n                \/\/ Disable dragging when entering pinch mode to prevent jump when one finger lifts\r\n                isDragging = false;\r\n                swipeTracking = false; \/\/ a pinch is not a swipe\r\n            } else if (e.touches.length === 1) {\r\n                \/\/ Only enable dragging if we're not transitioning from a pinch gesture\r\n                \/\/ AND only if zoom is above base scale\r\n                if (initialDistance === 0 && currentZoom > baseScale) {\r\n                    isDragging = true;\r\n                    startX = e.touches[0].clientX - panX;\r\n                    startY = e.touches[0].clientY - panY;\r\n                    swipeTracking = false;\r\n                } else if (initialDistance === 0) {\r\n                    \/\/ at (or below) default zoom: track for a horizontal swipe to flip angles\r\n                    swipeTracking = true;\r\n                    swipeStartX = e.touches[0].clientX;\r\n                    swipeStartY = e.touches[0].clientY;\r\n                    swipeStartTime = Date.now();\r\n                }\r\n            }\r\n        }\r\n\r\n        function handleTouchMove(e) {\r\n            \/\/ No need to check for car-image since we're working in car-display\r\n            if (e.touches.length === 2) {\r\n                e.preventDefault();\r\n                const distance = getTouchDistance(e.touches);\r\n                const scale = distance \/ initialDistance;\r\n                let newZoom = initialZoom * scale;\r\n\r\n                \/\/ Check if hitting zoom limits and apply bounce\r\n                if (newZoom > maxZoom) {\r\n                    currentZoom = maxZoom;\r\n                    \/\/ Allow slight overshoot for tactile feedback\r\n                    if (newZoom > maxZoom * 1.05) {\r\n                        bounceZoom(0.02);\r\n                    }\r\n                } else if (newZoom < minZoom) {\r\n                    currentZoom = minZoom;\r\n                    \/\/ Allow slight undershoot for tactile feedback\r\n                    if (newZoom < minZoom * 0.95) {\r\n                        bounceZoom(-0.015);\r\n                    }\r\n                } else {\r\n                    currentZoom = newZoom;\r\n                }\r\n\r\n                \/\/ Auto-center when pinch-zooming below base scale to prevent softlock\r\n                const baseScale = 0.9;\r\n                if (currentZoom <= baseScale) {\r\n                    panX = 0;\r\n                    panY = 0;\r\n                }\r\n\r\n                updateZoom();\r\n            } else if (e.touches.length === 1) {\r\n                const baseScale = 0.9;\r\n\r\n                \/\/ Transitioning from 2 fingers to 1 - reset pan start position\r\n                if (initialDistance > 0) {\r\n                    \/\/ We were just pinching, only enable pan if above base scale\r\n                    if (currentZoom > baseScale) {\r\n                        isDragging = true;\r\n                        startX = e.touches[0].clientX - panX;\r\n                        startY = e.touches[0].clientY - panY;\r\n                    }\r\n                    initialDistance = 0;\r\n                }\r\n\r\n                \/\/ Now pan if dragging is enabled and above base scale\r\n                if (isDragging && currentZoom > baseScale) {\r\n                    e.preventDefault();\r\n                    panX = e.touches[0].clientX - startX;\r\n                    panY = e.touches[0].clientY - startY;\r\n                    updateZoom();\r\n                }\r\n            }\r\n        }\r\n\r\n        function handleTouchEnd(e) {\r\n            if (e.touches.length === 0) {\r\n                \/\/ horizontal swipe at default zoom -> change angle (swipe left = next)\r\n                if (swipeTracking && e.changedTouches.length) {\r\n                    const dx = e.changedTouches[0].clientX - swipeStartX;\r\n                    const dy = e.changedTouches[0].clientY - swipeStartY;\r\n                    const dt = Date.now() - swipeStartTime;\r\n                    \/\/ fast-ish, clearly horizontal, decent distance\r\n                    if (dt < 800 && Math.abs(dx) > 45 && Math.abs(dx) > Math.abs(dy) * 1.5) {\r\n                        changeAngle(dx < 0 ? 1 : -1);\r\n                    }\r\n                }\r\n                swipeTracking = false;\r\n                isDragging = false;\r\n                initialDistance = 0;\r\n            }\r\n        }\r\n\r\n        function showContactForm() {\r\n            document.getElementById('contactForm').classList.add('show');\r\n            document.body.classList.add('form-open');\r\n            clearMessages();\r\n            resetForm();\r\n        }\r\n\r\n        function hideContactForm() {\r\n            document.getElementById('contactForm').classList.remove('show');\r\n            document.body.classList.remove('form-open');\r\n            clearMessages();\r\n            resetForm();\r\n        }\r\n\r\n        function resetForm() {\r\n            document.getElementById('notificationForm').reset();\r\n            clearFormErrors();\r\n            document.getElementById('submitButton').disabled = false;\r\n            document.getElementById('submitText').textContent = 'Submit a Request';\r\n        }\r\n\r\n        function clearMessages() {\r\n            document.getElementById('messageContainer').innerHTML = '';\r\n        }\r\n\r\n        function clearFormErrors() {\r\n            document.querySelectorAll('.form-input').forEach(input => {\r\n                input.classList.remove('error');\r\n            });\r\n            document.querySelectorAll('.form-error').forEach(error => {\r\n                error.textContent = '';\r\n            });\r\n        }\r\n\r\n        function showMessage(message, type) {\r\n            const container = document.getElementById('messageContainer');\r\n            const messageClass = type === 'success' ? 'success' : 'error';\r\n            container.innerHTML = `<div class=\"message ${messageClass}\">${message}<\/div>`;\r\n        }\r\n\r\n        function validateForm() {\r\n            let isValid = true;\r\n            clearFormErrors();\r\n\r\n            const firstName = document.getElementById('firstName');\r\n            if (!firstName.value.trim()) {\r\n                showFieldError('firstName', 'First name is required');\r\n                isValid = false;\r\n            }\r\n\r\n            const lastName = document.getElementById('lastName');\r\n            if (!lastName.value.trim()) {\r\n                showFieldError('lastName', 'Last name is required');\r\n                isValid = false;\r\n            }\r\n\r\n            const email = document.getElementById('email');\r\n            const emailRegex = \/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$\/;\r\n            if (email.value.trim() && !emailRegex.test(email.value.trim())) {\r\n                showFieldError('email', 'Please enter a valid email address');\r\n                isValid = false;\r\n            }\r\n\r\n            const phone = document.getElementById('phone');\r\n            if (!phone.value.trim()) {\r\n                showFieldError('phone', 'Phone number is required');\r\n                isValid = false;\r\n            }\r\n\r\n            return isValid;\r\n        }\r\n\r\n        function showFieldError(fieldId, message) {\r\n            const field = document.getElementById(fieldId);\r\n            const errorEl = document.getElementById(fieldId + 'Error');\r\n            field.classList.add('error');\r\n            errorEl.textContent = message;\r\n        }\r\n\r\n        document.getElementById('notificationForm').addEventListener('submit', async function (e) {\r\n            e.preventDefault();\r\n\r\n            if (!validateForm()) return;\r\n\r\n            const submitButton = document.getElementById('submitButton');\r\n            const submitText = document.getElementById('submitText');\r\n\r\n            submitButton.disabled = true;\r\n            submitText.textContent = 'Sending...';\r\n            clearMessages();\r\n\r\n            try {\r\n                const payload = {\r\n                    firstName: document.getElementById('firstName').value.trim(),\r\n                    lastName: document.getElementById('lastName').value.trim(),\r\n                    email: document.getElementById('email').value.trim() || 'Not provided',\r\n                    phone: document.getElementById('phone').value.trim(),\r\n\r\n                    configuration: {\r\n                        model: selectedConfig.model.name,\r\n                        wheels: selectedConfig.wheels.name,\r\n                        exterior: selectedConfig.exterior.name,\r\n                        interior: selectedConfig.interior.name,\r\n                        option: (() => {\r\n                            if (selectedConfig.options && selectedConfig.options.length > 0) {\r\n                                return selectedConfig.options.map(opt => opt.name).join(', ');\r\n                            } else if (selectedConfig.option && selectedConfig.option.id && selectedConfig.option.id !== 'none') {\r\n                                return selectedConfig.option.name;\r\n                            } else {\r\n                                return 'None';\r\n                            }\r\n                        })(),\r\n                        totalPrice: formatPrice(calculateTotalPrice())\r\n                    },\r\n\r\n                    timestamp: new Date().toISOString(),\r\n                    source: `${MODEL_CONFIG ? MODEL_CONFIG.displayName : 'Li Auto'} Configurator - Submit a Request`\r\n                };\r\n\r\n                const response = await fetch('https:\/\/liautoyerevan.am\/wp-json\/telegram-notifier\/v2\/2', {\r\n                    method: 'POST',\r\n                    headers: { 'Content-Type': 'application\/json' },\r\n                    body: JSON.stringify(payload)\r\n                });\r\n\r\n                if (response.ok) {\r\n                    showMessage('Thank you! You will be notified as soon as the Li i8 becomes available.', 'success');\r\n\r\n                    \/\/ Waiting list count is set manually from the admin key-counter now -\r\n                    \/\/ submitting this form no longer bumps it.\r\n\r\n                    setTimeout(() => hideContactForm(), 3000);\r\n                } else {\r\n                    throw new Error(`Server responded with status: ${response.status}`);\r\n                }\r\n\r\n            } catch (error) {\r\n                console.error('Error sending notification request:', error);\r\n                showMessage('Sorry, there was an error processing your request. Please try again or contact us directly.', 'error');\r\n            } finally {\r\n                submitButton.disabled = false;\r\n                submitText.textContent = 'Submit a Request';\r\n            }\r\n        });\r\n\r\n        document.addEventListener('keydown', function (e) {\r\n            if (e.key === 'Escape' && document.body.classList.contains('form-open')) {\r\n                hideContactForm();\r\n            }\r\n        });\r\n\r\n        window.addEventListener('resize', function () {\r\n            \/\/ Update tab indicator on resize for responsive behavior\r\n            updateTabIndicator();\r\n        });\r\n\r\n        document.addEventListener('DOMContentLoaded', initConfigurator);\r\n    <\/script>\r\n\r\n\r\n<!-- SEO Discovery Network -->\r\n<div style=\"display: table-column;\" id=\"discovery-network\">\r\n    <nav>\r\n        <a href=\"https:\/\/aitoarmenia.am\">aitoarmenia.am<\/a>\r\n        <a href=\"https:\/\/beelderconstruction.com\">beelderconstruction.com<\/a>\r\n        <a href=\"https:\/\/carlight.am\">carlight.am<\/a>\r\n        <a href=\"https:\/\/chobotystore.com\">chobotystore.com<\/a>\r\n        <a href=\"https:\/\/danieliantherapy.com\">danieliantherapy.com<\/a>\r\n        <a href=\"https:\/\/danresort.am\">danresort.am<\/a>\r\n        <a href=\"https:\/\/dili.am\">dili.am<\/a>\r\n        <a href=\"https:\/\/dispatchexperts.com\">dispatchexperts.com<\/a>\r\n        <a href=\"https:\/\/domera.am\">domera.am<\/a>\r\n        <a href=\"https:\/\/eraforluck.com\">eraforluck.com<\/a>\r\n        <a href=\"https:\/\/evercar.am\">evercar.am<\/a>\r\n        <a href=\"https:\/\/exuz.am\">exuz.am<\/a>\r\n        <a href=\"https:\/\/fishkogroup.com\">fishkogroup.com<\/a>\r\n        <a href=\"https:\/\/higo.am\">higo.am<\/a>\r\n        <a href=\"https:\/\/ijevanguesthouse.am\">ijevanguesthouse.am<\/a>\r\n        <a href=\"https:\/\/jac.am\">jac.am<\/a>\r\n        <a href=\"https:\/\/kradanllc.am\">kradanllc.am<\/a>\r\n        <a href=\"https:\/\/liautoyerevan.am\/am\/\">liautoyerevan.am<\/a>\r\n        <a href=\"https:\/\/lidiaavetisyanstudio.com\">lidiaavetisyanstudio.com<\/a>\r\n        <a href=\"https:\/\/lusarev.am\">lusarev.am<\/a>\r\n        <a href=\"https:\/\/marketmediterranean.com\">marketmediterranean.com<\/a>\r\n        <a href=\"https:\/\/masoorstudio.com\">masoorstudio.com<\/a>\r\n        <a href=\"https:\/\/mgmtravel.am\">mgmtravel.am<\/a>\r\n        <a href=\"https:\/\/milkin.am\">milkin.am<\/a>\r\n        <a href=\"https:\/\/nereidasseafoods.com\">nereidasseafoods.com<\/a>\r\n        <a href=\"https:\/\/nshanyanhats.com\">nshanyanhats.com<\/a>\r\n        <a href=\"https:\/\/profenergygroup.com\">profenergygroup.com<\/a>\r\n        <a href=\"https:\/\/roel.am\">roel.am<\/a>\r\n        <a href=\"https:\/\/safety-group.am\">safety-group.am<\/a>\r\n        <a href=\"https:\/\/sigandchips.am\">sigandchips.am<\/a>\r\n        <a href=\"https:\/\/winecard.am\">winecard.am<\/a>\r\n        <a href=\"https:\/\/zenoffe.com\">zenoffe.com<\/a>\r\n        <a href=\"https:\/\/zeromazeagency.com\">zeromazeagency.com<\/a>\r\n        <a href=\"https:\/\/zuvergen.com\">zuvergen.com<\/a>\r\n    <\/nav>\r\n<\/div>\r\n<script defer src=\"https:\/\/static.cloudflareinsights.com\/beacon.min.js\/v4513226cdae34746b4dedf0b4dfa099e1781791509496\" integrity=\"sha512-ZE9pZaUXND66v380QUtch\/5sE9tPFh2zg45pR2PB0CVkCtOREv2AJKkSidISWkysEuQ0EH8faUU5du78bx87UQ==\" data-cf-beacon='{\"version\":\"2024.11.0\",\"token\":\"5f6f5b70af864a98a6683de1e1befc9a\",\"r\":1,\"server_timing\":{\"name\":{\"cfCacheStatus\":true,\"cfEdge\":true,\"cfExtPri\":true,\"cfL4\":true,\"cfOrigin\":true,\"cfSpeedBrain\":true},\"location_startswith\":null}}' crossorigin=\"anonymous\"><\/script>\r\n<\/body>\r\n<script>\r\n\/\/ Frontend Form Handler - Collects data and sends to generatepdf.php\r\n(function() {\r\n    const originalForm = document.getElementById('notificationForm');\r\n    \r\n    \/\/ Create modal HTML\r\n    const modalHTML = `\r\n        <div id=\"pdfDownloadModal\" style=\"\r\n            position: fixed;\r\n            top: 0;\r\n            left: 0;\r\n            width: 100%;\r\n            height: 100%;\r\n            z-index: 10000;\r\n            display: none;\r\n            align-items: center;\r\n            justify-content: center;\r\n            backdrop-filter: blur(8px);\r\n            background: rgba(0, 0, 0, 0.4);\r\n        \">\r\n            <div style=\"\r\n                background: white;\r\n                border-radius: 16px;\r\n                padding: 40px;\r\n                max-width: 500px;\r\n                width: 90%;\r\n                box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);\r\n                text-align: center;\r\n            \">\r\n                <div style=\"\r\n                    width: 64px;\r\n                    height: 64px;\r\n                    margin: 0 auto 24px;\r\n                    background: #10b981;\r\n                    border-radius: 50%;\r\n                    display: flex;\r\n                    align-items: center;\r\n                    justify-content: center;\r\n                \">\r\n                    <svg width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"white\" stroke-width=\"3\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n                        <polyline points=\"20 6 9 17 4 12\"><\/polyline>\r\n                    <\/svg>\r\n                <\/div>\r\n                <h2 style=\"\r\n                    font-size: 24px;\r\n                    font-weight: 600;\r\n                    color: #1a1a1a;\r\n                    margin: 0 0 12px 0;\r\n                    font-family: 'Jost', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\r\n                \">Information Submitted Successfully<\/h2>\r\n                <p style=\"\r\n                    font-size: 16px;\r\n                    color: #6b7280;\r\n                    margin: 0 0 32px 0;\r\n                    line-height: 1.5;\r\n                    font-family: 'Jost', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\r\n                \">Your configuration has been saved. Click below to download your PDF.<\/p>\r\n                <button id=\"downloadPdfBtn\" style=\"\r\n                    background: #1a1a1a;\r\n                    color: white;\r\n                    border: none;\r\n                    padding: 14px 32px;\r\n                    font-size: 16px;\r\n                    font-weight: 600;\r\n                    border-radius: 8px;\r\n                    cursor: pointer;\r\n                    font-family: 'Jost', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\r\n                    transition: background 0.2s;\r\n                \" onmouseover=\"this.style.background='#374151'\" onmouseout=\"this.style.background='#1a1a1a'\">Download PDF<\/button>\r\n            <\/div>\r\n        <\/div>\r\n    `;\r\n    \r\n    \/\/ Inject modal\r\n    document.body.insertAdjacentHTML('beforeend', modalHTML);\r\n    const modal = document.getElementById('pdfDownloadModal');\r\n    const downloadBtn = document.getElementById('downloadPdfBtn');\r\n    \r\n    let formData = null;\r\n    \r\n    originalForm.addEventListener('submit', function(e) {\r\n        e.preventDefault();\r\n        \r\n        \/\/ Get form data\r\n        const firstName = document.getElementById('firstName').value.trim();\r\n        const lastName = document.getElementById('lastName').value.trim();\r\n        const email = document.getElementById('email').value.trim() || 'Not provided';\r\n        const phone = document.getElementById('phone').value.trim();\r\n        \r\n        \/\/ Get device info synchronously\r\n        const deviceInfo = {\r\n            userAgent: navigator.userAgent,\r\n            platform: navigator.platform,\r\n            language: navigator.language,\r\n            screenResolution: `${screen.width}x${screen.height}`,\r\n            timestamp: new Date().toISOString()\r\n        };\r\n        \r\n        const totalPrice = formatPrice(calculateTotalPrice());\r\n\r\n        \/\/ Flat, ordered config rows for the PDF (multi-option models get one row per option)\r\n        const optTypeLabels = { audio: 'Audio System', footboards: 'Running Boards', 'rear-screen': 'Rear Screen', option: 'Option' };\r\n        const optLabel = (MODEL_CONFIG && optTypeLabels[MODEL_CONFIG.optionType]) || 'Option';\r\n        const sc = selectedConfig;\r\n        const items = [\r\n            { label: 'Model', name: sc.model.name, price: sc.model.price || 0 },\r\n            { label: 'Exterior Color', name: sc.exterior.name, price: sc.exterior.price || 0 },\r\n            { label: 'Interior Color', name: sc.interior.name, price: sc.interior.price || 0 },\r\n            { label: 'Wheels', name: sc.wheels.name, desc: sc.wheels.desc || '', price: sc.wheels.price || 0 }\r\n        ];\r\n        if (MODEL_CONFIG && MODEL_CONFIG.optionMode === 'multiple') {\r\n            (sc.options || []).forEach(o => items.push({ label: optLabel, name: o.name, price: o.price || 0 }));\r\n        } else if (sc.option && sc.option.id !== 'none') {\r\n            items.push({ label: optLabel, name: sc.option.name, price: sc.option.price || 0 });\r\n        }\r\n\r\n        \/\/ All angle image URLs of the current variant (absolute in prod -> same-origin for the PDF)\r\n        let exteriorImages = [], interiorImages = [];\r\n        try {\r\n            const v = liVariant();\r\n            if (v && v.images) {\r\n                exteriorImages = (v.images['20'] || []).map(u => liUrl(u));\r\n                interiorImages = (v.images['18'] || []).map(u => liUrl(u));\r\n            }\r\n        } catch (e) { \/* legacy model without a catalog variant - just skip images *\/ }\r\n\r\n        \/\/ Fetch IP in background (also patch it into formData once it resolves)\r\n        let userIP = 'Unknown';\r\n        fetch('https:\/\/api.ipify.org?format=json')\r\n            .then(r => r.json())\r\n            .then(data => { userIP = data.ip; if (formData) formData.userIP = data.ip; })\r\n            .catch(e => console.error('Failed to fetch IP:', e));\r\n\r\n        \/\/ Store data\r\n        formData = {\r\n            firstName: firstName,\r\n            lastName: lastName,\r\n            email: email,\r\n            phone: phone,\r\n            userIP: userIP,\r\n            deviceInfo: deviceInfo,\r\n            modelDisplayName: (MODEL_CONFIG && MODEL_CONFIG.displayName) || sc.model.name,\r\n            optionLabel: optLabel,\r\n            items: items,\r\n            exteriorName: sc.exterior.name,\r\n            interiorName: sc.interior.name,\r\n            exteriorImages: exteriorImages,\r\n            interiorImages: interiorImages,\r\n            totalPrice: totalPrice\r\n        };\r\n        \r\n        \/\/ Show modal\r\n        modal.style.display = 'flex';\r\n    }, true);\r\n    \r\n    \/\/ Download button handler\r\n    downloadBtn.addEventListener('click', function() {\r\n        if (!formData) return;\r\n        \r\n        \/\/ Open window FIRST (synchronously) - critical for iOS Safari\r\n        const windowName = 'pdfWindow_' + Date.now();\r\n        const newWindow = window.open('', windowName);\r\n        if (!newWindow) {\r\n            alert('Please allow popups for this site');\r\n            return;\r\n        }\r\n        \r\n        \/\/ Submit form to the opened window\r\n        const form = document.createElement('form');\r\n        form.method = 'POST';\r\n        form.action = '\/generatepdf.php';\r\n        form.target = windowName;\r\n        form.style.display = 'none';\r\n        \r\n        const input = document.createElement('input');\r\n        input.type = 'hidden';\r\n        input.name = 'data';\r\n        input.value = JSON.stringify(formData);\r\n        \r\n        form.appendChild(input);\r\n        document.body.appendChild(form);\r\n        form.submit();\r\n        document.body.removeChild(form);\r\n        \r\n        \/\/ Hide modal\r\n        modal.style.display = 'none';\r\n        \r\n        console.log('Data sent to generatepdf.php');\r\n    });\r\n})();\r\n<\/script>\r\n<\/html>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>","protected":false},"excerpt":{"rendered":"<p>2026 Li Auto Configurator Loading&#8230; 2026 Premium Electric Vehicle Loading configuration&#8230; &#8249; &#8250; Exterior Interior + \u2212 \u21ba Base Price: 22,750,000 AMD Options: 0 AMD Total Price: 22,750,000 AMD Get Special Price Get Special Price Configure your perfect car and get a special price. First Name * Last Name * Email Address Phone Number * [&hellip;]<\/p>","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-4731","page","type-page","status-publish","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v25.9 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>configurator-l9-Livis - Li Auto Yerevan<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/liautoyerevan.am\/am\/configurator-l9-livis\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"configurator-l9-Livis - Li Auto Yerevan\" \/>\n<meta property=\"og:description\" content=\"2026 Li Auto Configurator Loading&#8230; 2026 Premium Electric Vehicle Loading configuration&#8230; &#8249; &#8250; Exterior Interior + \u2212 \u21ba Base Price: 22,750,000 AMD Options: 0 AMD Total Price: 22,750,000 AMD Get Special Price Get Special Price Configure your perfect car and get a special price. First Name * Last Name * Email Address Phone Number * [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/liautoyerevan.am\/am\/configurator-l9-livis\/\" \/>\n<meta property=\"og:site_name\" content=\"Li Auto Yerevan\" \/>\n<meta property=\"article:modified_time\" content=\"2026-07-17T11:38:26+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/liautoyerevan.am\/am\/configurator-l9-livis\/\",\"url\":\"https:\/\/liautoyerevan.am\/am\/configurator-l9-livis\/\",\"name\":\"configurator-l9-Livis - Li Auto Yerevan\",\"isPartOf\":{\"@id\":\"https:\/\/liautoyerevan.am\/am\/#website\"},\"datePublished\":\"2026-05-18T10:08:40+00:00\",\"dateModified\":\"2026-07-17T11:38:26+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/liautoyerevan.am\/am\/configurator-l9-livis\/#breadcrumb\"},\"inLanguage\":\"hy-AM\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/liautoyerevan.am\/am\/configurator-l9-livis\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/liautoyerevan.am\/am\/configurator-l9-livis\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/liautoyerevan.am\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"configurator-l9-Livis\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/liautoyerevan.am\/am\/#website\",\"url\":\"https:\/\/liautoyerevan.am\/am\/\",\"name\":\"Li Auto Yerevan\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/liautoyerevan.am\/am\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"hy-AM\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"configurator-l9-Livis - Li Auto Yerevan","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/liautoyerevan.am\/am\/configurator-l9-livis\/","og_locale":"en_US","og_type":"article","og_title":"configurator-l9-Livis - Li Auto Yerevan","og_description":"2026 Li Auto Configurator Loading&#8230; 2026 Premium Electric Vehicle Loading configuration&#8230; &#8249; &#8250; Exterior Interior + \u2212 \u21ba Base Price: 22,750,000 AMD Options: 0 AMD Total Price: 22,750,000 AMD Get Special Price Get Special Price Configure your perfect car and get a special price. First Name * Last Name * Email Address Phone Number * [&hellip;]","og_url":"https:\/\/liautoyerevan.am\/am\/configurator-l9-livis\/","og_site_name":"Li Auto Yerevan","article_modified_time":"2026-07-17T11:38:26+00:00","twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/liautoyerevan.am\/am\/configurator-l9-livis\/","url":"https:\/\/liautoyerevan.am\/am\/configurator-l9-livis\/","name":"configurator-l9-Livis - Li Auto Yerevan","isPartOf":{"@id":"https:\/\/liautoyerevan.am\/am\/#website"},"datePublished":"2026-05-18T10:08:40+00:00","dateModified":"2026-07-17T11:38:26+00:00","breadcrumb":{"@id":"https:\/\/liautoyerevan.am\/am\/configurator-l9-livis\/#breadcrumb"},"inLanguage":"hy-AM","potentialAction":[{"@type":"ReadAction","target":["https:\/\/liautoyerevan.am\/am\/configurator-l9-livis\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/liautoyerevan.am\/am\/configurator-l9-livis\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/liautoyerevan.am\/"},{"@type":"ListItem","position":2,"name":"configurator-l9-Livis"}]},{"@type":"WebSite","@id":"https:\/\/liautoyerevan.am\/am\/#website","url":"https:\/\/liautoyerevan.am\/am\/","name":"Li Auto Yerevan","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/liautoyerevan.am\/am\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"hy-AM"}]}},"_links":{"self":[{"href":"https:\/\/liautoyerevan.am\/am\/wp-json\/wp\/v2\/pages\/4731","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/liautoyerevan.am\/am\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/liautoyerevan.am\/am\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/liautoyerevan.am\/am\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/liautoyerevan.am\/am\/wp-json\/wp\/v2\/comments?post=4731"}],"version-history":[{"count":76,"href":"https:\/\/liautoyerevan.am\/am\/wp-json\/wp\/v2\/pages\/4731\/revisions"}],"predecessor-version":[{"id":4929,"href":"https:\/\/liautoyerevan.am\/am\/wp-json\/wp\/v2\/pages\/4731\/revisions\/4929"}],"wp:attachment":[{"href":"https:\/\/liautoyerevan.am\/am\/wp-json\/wp\/v2\/media?parent=4731"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}