{
  "version": 3,
  "sources": ["../alpine-components/photo-modals.js", "../alpine-components/highlight-polygon.js", "../alpine-components/highlight-brush.js", "../alpine-components/highlight-editor.js", "../alpine-components/batch.js", "../alpine-components/social-post.js", "../alpine-components/upload-validation.js", "../alpine-components/photo-upload.js", "../alpine-components/branding-logo-upload.js", "../alpine-components/estimated-progress.js", "../alpine-components/onboarding.js", "../alpine-components/guides.js", "../alpine-components/next-property-ask.js", "../alpine-components/property-create.js", "../alpine-components/property-search.js", "../alpine-components/address.js", "../alpine-components/listing-form.js", "../public-upload.js"],
  "sourcesContent": ["document.addEventListener('alpine:init', () => {\n    const I18N = window.I18N || {};\n\n    // Corners of a rectangle or polygon shape, normalized 0..1.\n    function shapePoints(shape) {\n        if (!shape) return [];\n        if (shape.type === 'rect') {\n            const {x, y, width, height} = shape.rect;\n            return [{x, y}, {x: x + width, y}, {x: x + width, y: y + height}, {x, y: y + height}];\n        }\n        return shape.points || [];\n    }\n\n    // Points for an SVG polygon in a 0..100 viewBox.\n    function svgPoints(shape) {\n        return shapePoints(shape).map((point) => (point.x * 100) + ',' + (point.y * 100)).join(' ');\n    }\n\n    Alpine.data('photoModals', () => ({\n        comparisonOpen: false,\n        retryOpen: false,\n        revisionOpen: false,\n        deleteOpen: false,\n        qaExplainOpen: false,\n        metadataOpen: false,\n        upscaleConfirmOpen: false,\n        rebaseOpen: false,\n        rebaseReturnToRetry: false,\n        rebaseRequestPending: false,\n        rebaseDismissedWhilePending: false,\n        rebaseSummaryRequest: null,\n        historyOpen: false,\n        deleting: false,\n        retryText: '',\n        refundText: '',\n        retryAction: '',\n        retryBodyCache: '',\n        revisionSrc: '',\n        revisionId: '',\n        revisionUrls: {},\n        editorOpen: false,\n        // \"M\u00F6belst\u00FCck platzieren\" in the retry dialog. The state lives\n        // here, outside the dialog body the server swaps, and belongs to one\n        // rejected job (placementJob). placementBase is the version the job\n        // was made from, which the space is drawn on.\n        placementJob: '',\n        placementShape: null,\n        placementLabel: '',\n        placementBase: null,\n        placementLocked: false,\n        // True while the dialog is hidden for the image editor.\n        placementDrawing: false,\n\n        init() {\n            const json = this.$el.dataset.revisionUrls;\n            if (json) {\n                try { this.revisionUrls = JSON.parse(json); } catch (e) { this.revisionUrls = {}; }\n            }\n\n            // The retry dialog closes itself once the server has swapped the\n            // page fragments it changed. The hint is cleared here and not in\n            // closeRetry(), so Escape keeps a draft but a sent hint is not\n            // re-sent when the next result is rejected too.\n            // The body now holds a dialog for the *new* active revision, so the\n            // cached choice step is stale: drop it and let the next open cache\n            // what the server just swapped in.\n            this._onRetryDone = () => {\n                this.retryText = ''; this.refundText = ''; this.retryAction = ''; this.retryBodyCache = '';\n                this.placementJob = ''; this.placementShape = null; this.placementLabel = '';\n                this.closeRetry();\n            };\n            window.addEventListener('retry-done', this._onRetryDone);\n        },\n\n        destroy() {\n            window.removeEventListener('retry-done', this._onRetryDone);\n        },\n\n        openComparison()  { this.comparisonOpen = true;  this.lockScroll(); },\n        closeComparison() { this.comparisonOpen = false; this.unlockScroll(); },\n        openRetry()       { this.cacheRetryBody(); this.retryOpen = true; this.lockScroll(); },\n        closeRetry() {\n            // Hidden for the image editor: the shell's Escape handler still\n            // listens, and the editor's own Escape must not close the dialog.\n            if (this.placementDrawing) return;\n            // A retry request can still be resolving its fast modal shell.\n            // Treat closing the retry sheet as declining that pending offer.\n            if (this.rebaseRequestPending && !this.rebaseOpen) {\n                this.rebaseDismissedWhilePending = true;\n            }\n            this.retryOpen = false;\n            this.unlockScroll();\n            // Without this the next open shows whatever the server last\n            // swapped in \u2014 the proposal of a result the user walked away\n            // from \u2014 and the other path is unreachable.\n            this.restoreRetryBody();\n        },\n        retryBody() { return document.getElementById('retry-dialog-body'); },\n        // The server swaps this body (proposal step, validation errors, the\n        // dialog for the next revision after an action). Cache the choice step\n        // the first time the dialog opens so closing it, or \"Zur\u00FCck\" inside the\n        // proposal step, can put that step back.\n        cacheRetryBody() {\n            const body = this.retryBody();\n            if (body && !this.retryBodyCache) this.retryBodyCache = body.innerHTML;\n        },\n        // Drop the proposal poll and its timer so neither can fire a retry the\n        // customer has stepped away from. An element htmx is posting right now\n        // is left in place; removing it would strand its own response.\n        stopSuggestionWait() {\n            const body = this.retryBody();\n            if (!body) return;\n            body.querySelectorAll('[data-suggestion-wait]:not(.htmx-request)')\n                .forEach((node) => node.remove());\n        },\n        restoreRetryBody() {\n            const body = this.retryBody();\n            if (!body || !this.retryBodyCache) return;\n            this.stopSuggestionWait();\n            body.innerHTML = this.retryBodyCache;\n            // Restored markup is new DOM to htmx: without this its forms fall\n            // back to native posts, the same trap teleported modals hit after\n            // an hx-boost navigation.\n            if (window.htmx) window.htmx.process(body);\n        },\n        backToRetryChoice() { this.restoreRetryBody(); },\n        // Only caller is the sidebar \"Versionen\" list, so the event measures\n        // that surface against the history modal opened from the \"Mehr\" menu.\n        showRevision(id)  { this.revisionSrc = this.revisionUrls[id] || ''; this.revisionId = String(id); this.revisionOpen = true; this.lockScroll(); window.trackEvent && window.trackEvent('revision_preview_open'); },\n        closeRevision()   { this.revisionOpen = false;   this.unlockScroll(); },\n        openHistory()     { this.historyOpen = true;  this.lockScroll(); window.trackEvent && window.trackEvent('revision_history_open'); },\n        closeHistory()    { this.historyOpen = false; this.unlockScroll(); },\n        openDelete()      { this.deleteOpen = true;      this.lockScroll(); },\n        closeDelete()     { this.deleteOpen = false;     this.unlockScroll(); },\n        openQaExplain()   { this.qaExplainOpen = true;   this.lockScroll(); },\n        closeQaExplain()  { this.qaExplainOpen = false;  this.unlockScroll(); },\n        openMetadata()        { this.metadataOpen = true;        this.lockScroll(); },\n        closeMetadata()       { this.metadataOpen = false;       this.unlockScroll(); },\n        openUpscaleConfirm()  { this.upscaleConfirmOpen = true;  this.lockScroll(); },\n        closeUpscaleConfirm() { this.upscaleConfirmOpen = false; this.unlockScroll(); },\n        prepareRebase() {\n            this.rebaseRequestPending = true;\n            this.rebaseDismissedWhilePending = false;\n            this.abortRebaseSummaryRequest();\n        },\n        openRebase() {\n            // The request can complete after the user closed the originating\n            // surface. Keep its prepared shell available, but do not reopen it.\n            if (this.rebaseDismissedWhilePending) {\n                this.rebaseRequestPending = false;\n                this.rebaseDismissedWhilePending = false;\n                return;\n            }\n            this.rebaseRequestPending = false;\n            this.showRebase();\n        },\n        showRebase() {\n            if (this.retryOpen) {\n                // Preserve the retry draft in photoModals. The rebase dialog\n                // replaces the sheet, so it is the only overlay on screen.\n                this.rebaseReturnToRetry = true;\n                this.retryOpen = false;\n            }\n            // The consolidation offer answers a retry posted from the proposal\n            // step and takes over the screen. Nothing behind it may still fire\n            // a retry, so the poll and the timer go now. The panel itself is\n            // rebuilt when the retry dialog comes back: this runs while htmx is\n            // still resolving the response that opened this modal, and pulling\n            // the requesting form out from under it breaks its retarget.\n            this.stopSuggestionWait();\n            if (!this.rebaseOpen) {\n                this.rebaseOpen = true;\n                this.lockScroll();\n            }\n        },\n        closeRebase() {\n            this.abortRebaseSummaryRequest();\n            if (this.rebaseRequestPending) this.rebaseDismissedWhilePending = true;\n            this.rebaseOpen = false;\n            if (this.rebaseReturnToRetry) {\n                this.rebaseReturnToRetry = false;\n                // The dialog underneath still shows the wait it was in when the\n                // offer interrupted it. That moment is over.\n                this.restoreRetryBody();\n                this.retryOpen = true;\n            }\n            this.unlockScroll();\n        },\n        finishRebase() {\n            this.rebaseRequestPending = false;\n            this.rebaseDismissedWhilePending = false;\n            this.rebaseReturnToRetry = false;\n            this.closeRebase();\n        },\n        setRebaseSummaryRequest(xhr) {\n            this.abortRebaseSummaryRequest();\n            this.rebaseSummaryRequest = xhr;\n        },\n        clearRebaseSummaryRequest(xhr) {\n            if (!xhr || this.rebaseSummaryRequest === xhr) {\n                this.rebaseSummaryRequest = null;\n            }\n        },\n        abortRebaseSummaryRequest() {\n            if (this.rebaseSummaryRequest) {\n                this.rebaseSummaryRequest.abort();\n                this.rebaseSummaryRequest = null;\n            }\n        },\n\n        // The box's data, once per rejected job: the dialog body is rendered\n        // again for errors and restored from the cache, and the space drawn\n        // so far must survive both.\n        initRetryPlacement(json) {\n            let config = {};\n            try { config = JSON.parse(json || '{}'); } catch (e) { config = {}; }\n            this.placementBase = config.base || null;\n            if (this.placementJob === config.job) return;\n            this.placementJob = config.job || '';\n            this.placementShape = config.placement?.shape || null;\n            this.placementLabel = config.placement?.label || '';\n        },\n        get placementImageUrl() { return this.placementBase?.url || ''; },\n        get placementAspect() {\n            const base = this.placementBase || {};\n            return base.width && base.height ? base.width + '/' + base.height : '4/3';\n        },\n        get placementSvgPoints() { return svgPoints(this.placementShape); },\n        get placementInputsDisabled() { return this.retryAction !== 'retry' || !this.placementShape; },\n        // The dialog steps out of the way while the space is drawn. It hides\n        // only once the editor opened: the editor refuses while an AI edit runs.\n        openPlacementEditor() {\n            this.$dispatch('open-place-editor', {\n                shape: this.placementShape, target: 'retry', base: this.placementBase,\n            });\n            if (!this.editorOpen) return;\n            this.placementDrawing = true;\n            this.retryOpen = false;\n            this.unlockScroll();\n        },\n        removePlacement() { this.placementShape = null; },\n        receivePlacement(detail) {\n            if (detail?.target !== 'retry') return;\n            this.placementShape = detail.shape || null;\n            this.$nextTick(() => {\n                const box = document.querySelector('[data-retry-placement]');\n                box?.scrollIntoView({block: 'nearest'});\n                if (this.placementShape && !this.placementLabel.trim()) box?.querySelector('input[name=\"placement_label\"]')?.focus({preventScroll: true});\n            });\n        },\n        // The image editor opened or closed. Closing it, with the space\n        // taken or not, brings the hidden retry dialog back.\n        editorModeChanged(open) {\n            this.editorOpen = open;\n            if (open || !this.placementDrawing) return;\n            this.retryOpen = true;\n            this.lockScroll();\n            // An Escape that closed the editor reaches the dialog's own Escape\n            // handler next. The guard in closeRetry() stays up until it passed.\n            setTimeout(() => { this.placementDrawing = false; }, 0);\n        },\n\n        get anyOpen() { return this.comparisonOpen || this.retryOpen || this.revisionOpen || this.deleteOpen || this.qaExplainOpen || this.metadataOpen || this.upscaleConfirmOpen || this.rebaseOpen || this.historyOpen; },\n        lockScroll()   { window._scrollLock.lock(); },\n        unlockScroll() { if (!this.anyOpen) window._scrollLock.unlock(); },\n    }));\n\n    Alpine.data('rebaseModal', () => ({\n        submitting: false,\n        summaryReady: false,\n        summaryLoading: false,\n        summaryRequestFailed: false,\n        rebaseBehavior: 'ask',\n\n        init() {\n            this.summaryLoading = this.$el.dataset.summaryLoading === 'true';\n            this.summaryReady = !this.summaryLoading;\n            const behavior = this.$el.dataset.rebaseBehavior;\n            this.rebaseBehavior = ['ask', 'always', 'never'].includes(behavior)\n                ? behavior\n                : 'ask';\n        },\n        startSummaryRequest(event) {\n            this.summaryLoading = true;\n            this.summaryRequestFailed = false;\n            this.$dispatch('rebase-summary-request-start', { xhr: event.detail.xhr });\n        },\n        finishSummaryRequest(event) {\n            const xhr = event.detail.xhr;\n            const successful = xhr.status >= 200 && xhr.status < 300;\n            this.summaryLoading = false;\n            this.summaryReady = successful;\n            this.summaryRequestFailed = !successful;\n            this.$dispatch('rebase-summary-request-finished', { xhr });\n        },\n    }));\n\n    Alpine.data('editForm', () => ({\n        activeTab: 'outdoor',\n        // Au\u00DFen or Innen inside the Vorlagen tab, kept while another tab is\n        // open so coming back shows the same preset family.\n        presetTab: 'outdoor',\n        submitting: false,\n        duplicateWarningBypassed: false,\n        floorplanWarningBypassed: false,\n        floorplanWarningPending: false,\n        rebaseOfferPossible: false,\n        previousPresets: [],\n        // staging_outdoor stays unchecked by default and is deliberately\n        // excluded from saved-prefs application below\n        outdoor: { optimize: true, sunshine: true, declutter: true, staging_outdoor: false },\n        indoor: { optimize: true, declutter: true, details: true, staging: true },\n        replaceFurniture: false,\n        // \"Baumaterial und Ger\u00FCste behalten\" under declutter, building sites\n        // only. Always on by default; not saved with the preset preferences.\n        keepSiteMaterials: true,\n        sceneSubtype: '',\n        lockCameraAngle: true,\n        resolution4k: false,\n        nextEditCredits2k: 0,\n        nextEditCredits4k: 0,\n        roomType: '',\n        outdoorType: '',\n        furnitureStyle: '',\n        furnitureInstructions: '',\n        intensity: 'normal',\n        additionalGuidance: '',\n        roomOpen: false,\n        styleOpen: false,\n        // \"Stelle \u00E4ndern\": one area marked in the image editor (mask mode)\n        // and one instruction. The shape is normalized to the clean base the\n        // editor draws on, the frame the server builds the mask in.\n        maskShape: null,\n        maskInstruction: '',\n        maskBaseUrl: '',\n        maskAspect: '4/3',\n        // Pixel size of that image, for the brush preview's viewBox.\n        maskWidth: 4000,\n        maskHeight: 3000,\n        // \"M\u00F6belst\u00FCck platzieren\" under the staging options: one space\n        // drawn in the image editor's place mode, on the same base as the\n        // mask, and what stands there.\n        placementShape: null,\n        placementLabel: '',\n        // \"Passend zu diesem Foto\": a staged photo to match, pre-selected from\n        // the same room or picked in the modal from all staged photos.\n        matchReference: false,\n        referenceRemoved: false,\n        referenceId: '',\n        referenceUrl: '',\n        referenceStyle: '',\n        referenceRemoveUrl: '',\n        // Only a reference from this photo's own room can be taken out of it.\n        referenceSameRoom: false,\n        roomOptions: [\n            {v:'',l:'Auto'}, {v:'living_room',l:I18N.roomLivingRoom || 'Wohnzimmer'},\n            {v:'bedroom',l:I18N.roomBedroom || 'Schlafzimmer'}, {v:'kitchen',l:I18N.roomKitchen || 'K\u00FCche'},\n            {v:'bathroom',l:I18N.roomBathroom || 'Badezimmer'}, {v:'kids_room',l:I18N.roomKidsRoom || 'Kinderzimmer'},\n            {v:'dining_room',l:I18N.roomDiningRoom || 'Esszimmer'}, {v:'hallway',l:I18N.roomHallway || 'Flur'},\n            {v:'home_office',l:I18N.roomHomeOffice || 'Arbeitszimmer'},\n            {v:'office',l:I18N.roomOffice || 'B\u00FCro'}, {v:'garage',l:'Garage'},\n            {v:'basement',l:I18N.roomBasement || 'Keller'},\n            {v:'dressing_room',l:I18N.roomDressingRoom || 'Ankleide'},\n            {v:'attic',l:I18N.roomAttic || 'Dachboden'},\n        ],\n        styleOptions: [\n            {v:'',l:I18N.styleUnspecified || 'Keine Vorgabe'}, {v:'modern',l:'Modern'},\n            {v:'skandinavisch',l:I18N.styleScandinavian || 'Skandinavisch'}, {v:'klassisch',l:I18N.styleClassic || 'Klassisch'},\n            {v:'landhausstil',l:I18N.styleCountry || 'Landhausstil'}, {v:'industriell',l:I18N.styleIndustrial || 'Industriell'},\n        ],\n\n        init() {\n            this.nextEditCredits2k = Number(this.$el.dataset.nextEditCreditsStandard || 0);\n            this.nextEditCredits4k = Number(this.$el.dataset.nextEditCreditsHigh || 0);\n            this.floorplanWarningPending = this.$el.dataset.floorplanWarningPending === 'true';\n            this.rebaseOfferPossible = this.$el.dataset.rebaseOfferPossible === 'true';\n            // Apply saved preset preferences\n            const prefsJson = this.$el.dataset.presetPrefs;\n            if (prefsJson) {\n                try {\n                    const prefs = JSON.parse(prefsJson);\n                    if (prefs.outdoor) {\n                        this.outdoor.optimize = prefs.outdoor.includes('optimize');\n                        this.outdoor.sunshine = prefs.outdoor.includes('sunshine');\n                        this.outdoor.declutter = prefs.outdoor.includes('declutter');\n                    }\n                    if (prefs.indoor) {\n                        this.indoor.optimize = prefs.indoor.includes('optimize_indoor');\n                        this.indoor.declutter = prefs.indoor.includes('declutter_indoor');\n                        this.indoor.details = prefs.indoor.includes('details');\n                        this.indoor.staging = prefs.indoor.includes('staging');\n                    }\n                    if (typeof prefs.replace_furniture === 'boolean')\n                        this.replaceFurniture = prefs.replace_furniture;\n                    if (typeof prefs.lock_camera_angle === 'boolean')\n                        this.lockCameraAngle = prefs.lock_camera_angle;\n                    if (prefs.intensity === 'subtle')\n                        this.intensity = 'subtle';\n                    if (prefs.resolution === '4K')\n                        this.resolution4k = true;\n                    if (prefs.furniture_style)\n                        this.furnitureStyle = prefs.furniture_style;\n                    if (prefs.aspect_ratio) {\n                        const sel = this.$el.querySelector('#aspect-ratio');\n                        if (sel) sel.value = prefs.aspect_ratio;\n                    }\n                } catch (e) {}\n            }\n\n            // Parse previous presets for duplicate detection\n            try {\n                this.previousPresets = JSON.parse(this.$el.dataset.previousPresets || '[]');\n            } catch (e) { this.previousPresets = []; }\n\n            this.activeTab = this.$el.dataset.initialTab || 'outdoor';\n            this.isFloorplan = this.activeTab === 'floorplan';\n            if (this.isPresetTab) this.presetTab = this.activeTab;\n\n            // The image the mask editor draws on, for the small preview here.\n            try {\n                const host = this.$el.closest('[data-highlight-editor-config]');\n                const config = JSON.parse(host?.dataset.highlightEditorConfig || '{}');\n                this.maskBaseUrl = config.baseUrl || '';\n                if (config.width && config.height) {\n                    this.maskAspect = config.width + '/' + config.height;\n                    this.maskWidth = Number(config.width);\n                    this.maskHeight = Number(config.height);\n                }\n            } catch (e) {}\n            // A refused submit or a result without change comes back to this\n            // mode with the same area and words, ready to adjust.\n            if (this.$el.dataset.maskPrefill && !this.isFloorplan) {\n                try {\n                    const prefill = JSON.parse(this.$el.dataset.maskPrefill);\n                    this.activeTab = 'mask';\n                    this.maskShape = prefill.shape || null;\n                    this.maskInstruction = prefill.instruction || '';\n                } catch (e) {}\n            }\n            this.roomType = this.$el.dataset.roomType || '';\n            this.outdoorType = this.$el.dataset.outdoorType || '';\n\n            // The metadata modal saves room and outdoor type without\n            // re-rendering this form. Pick the saved values up here, because\n            // the edit view persists whatever the form posts and would\n            // otherwise write the old values back on the next edit.\n            this._onMetadataUpdated = (event) => {\n                const detail = event.detail || {};\n                if ('room_type' in detail) this.roomType = detail.room_type || '';\n                if ('outdoor_type' in detail) this.outdoorType = detail.outdoor_type || '';\n            };\n            window.addEventListener('metadata-updated', this._onMetadataUpdated);\n\n            // A staged photo of the same room: matching it is pre-selected and\n            // takes its furniture style. Another style cannot match, so picking\n            // one unticks the box; ticking it again restores the style. The\n            // same rule holds for a reference picked in the modal.\n            if (this.$el.dataset.hasReference === 'true') {\n                this.matchReference = true;\n                this.referenceId = this.$el.dataset.referenceId || '';\n                this.referenceUrl = this.$el.dataset.referenceUrl || '';\n                this.referenceStyle = this.$el.dataset.referenceStyle || '';\n                this.referenceRemoveUrl = this.$el.dataset.referenceRemoveUrl || '';\n                this.referenceSameRoom = true;\n                this.furnitureStyle = this.referenceStyle;\n            }\n            if (this.$el.dataset.hasCandidates === 'true') {\n                this.$watch('furnitureStyle', (value) => {\n                    if (value !== this.referenceStyle) this.matchReference = false;\n                });\n                this.$watch('matchReference', (value) => {\n                    if (value) this.furnitureStyle = this.referenceStyle;\n                });\n            }\n\n            // Construction subtype override (photo-specific > account preference)\n            const subtype = this.$el.dataset.sceneSubtype || '';\n            this.sceneSubtype = subtype;\n            if (this.activeTab === 'indoor' && subtype === 'construction') {\n                this.indoor.declutter = false;\n                this.indoor.staging = false;\n                this.indoor.details = false;\n            }\n        },\n\n        destroy() {\n            window.removeEventListener('metadata-updated', this._onMetadataUpdated);\n        },\n\n        get isPresetTab() {\n            return this.activeTab === 'outdoor' || this.activeTab === 'indoor';\n        },\n\n        switchTab(tab) {\n            this.activeTab = tab;\n            if (this.isPresetTab) this.presetTab = tab;\n            if (tab !== 'indoor') {\n                this.indoor.staging = false;\n                this.replaceFurniture = false;\n                this.roomType = '';\n                this.furnitureStyle = '';\n            }\n        },\n\n        // \"Passend zu diesem Foto\" is ticked: the edit becomes a set photo,\n        // always 2K in the source format.\n        get followActive() {\n            return this.matchReference && !!this.referenceId && !this.referenceRemoved\n                && this.activeTab === 'indoor' && this.indoor.staging;\n        },\n\n        get nextEditCredits() {\n            // A mask edit is always 2K.\n            return this.resolution4k && !this.followActive && this.activeTab !== 'mask'\n                ? this.nextEditCredits4k : this.nextEditCredits2k;\n        },\n\n        get maskReady() {\n            return Boolean(this.maskShape) && this.maskInstruction.trim().length > 0;\n        },\n\n        get maskPreviewStyle() {\n            return 'aspect-ratio:' + this.maskAspect;\n        },\n\n        get maskIsBrush() {\n            return Boolean(this.maskShape) && this.maskShape.type === 'brush';\n        },\n\n        get maskViewBox() {\n            return '0 0 ' + this.maskWidth + ' ' + this.maskHeight;\n        },\n\n        // Brush strokes look as in the editor: translucent paint, no dimming.\n        get maskBrushMarkup() {\n            if (!this.maskIsBrush) return '';\n            return window.brushStrokesMarkup(this.maskShape.strokes, this.maskWidth, this.maskHeight);\n        },\n\n        get maskPoints() {\n            const shape = this.maskShape;\n            if (!shape || shape.type === 'brush') return [];\n            return shapePoints(shape);\n        },\n\n        get maskSvgPoints() {\n            return this.maskPoints.map((point) => (point.x * 100) + ',' + (point.y * 100)).join(' ');\n        },\n\n        // \"Passend zu diesem Foto\" stages to match another photo; it does not\n        // take a placement in this version, and the server refuses both.\n        get placementLocked() { return this.followActive; },\n        get placementInputsDisabled() {\n            return this.activeTab !== 'indoor' || !this.indoor.staging || this.followActive || !this.placementShape;\n        },\n        get placementImageUrl() { return this.maskBaseUrl; },\n        get placementAspect() { return this.maskAspect; },\n        get placementSvgPoints() { return svgPoints(this.placementShape); },\n\n        openPlacementEditor() {\n            if (this.placementLocked) return;\n            this.$dispatch('open-place-editor', {shape: this.placementShape, target: 'form'});\n        },\n\n        removePlacement() { this.placementShape = null; },\n\n        // Back from the editor: show the block again, ready for its label.\n        receivePlacement(detail) {\n            if (detail?.target !== 'form') return;\n            this.placementShape = detail.shape || null;\n            this.$nextTick(() => {\n                this.$el.querySelector('[data-placement-block]')?.scrollIntoView({block: 'center'});\n                if (this.placementShape && !this.placementLabel.trim()) this.$el.querySelector('#photo-placement-label')?.focus({preventScroll: true});\n            });\n        },\n\n        // The line in \"Stelle \u00E4ndern\": Vorlagen \u203A Innen with staging on, at the\n        // block. Called from that line's button, so $el is the button: $root\n        // is the form.\n        openPlacementFromMask() {\n            this.switchTab('indoor');\n            this.indoor.staging = true;\n            this.$nextTick(() => {\n                this.$root.querySelector('[data-placement-block]')?.scrollIntoView({block: 'center'});\n            });\n        },\n\n        // The marked area at full brightness over the dimmed photo.\n        get maskClipStyle() {\n            if (!this.maskShape || this.maskIsBrush) return 'display:none';\n            return 'clip-path:polygon(' + this.maskPoints.map((point) => (\n                (point.x * 100) + '% ' + (point.y * 100) + '%'\n            )).join(',') + ')';\n        },\n\n        // The image editor (photo column) opens in mask mode and answers\n        // with mask-area-selected.\n        openMaskEditor() {\n            this.$dispatch('open-mask-editor', {shape: this.maskShape});\n        },\n\n        receiveMaskArea(detail) {\n            this.maskShape = detail?.shape || null;\n            this.$nextTick(() => {\n                this.$el.querySelector('[data-mask-section]')?.scrollIntoView({block: 'center'});\n                if (this.maskShape && !this.maskInstruction.trim()) this.$el.querySelector('#mask-instruction')?.focus({preventScroll: true});\n            });\n        },\n\n        // The retry dialog's \"Nur eine Stelle stimmt nicht?\" link.\n        startMaskEdit() {\n            if (this.isFloorplan) return;\n            this.switchTab('mask');\n            this.openMaskEditor();\n        },\n\n        // \"Geh\u00F6rt nicht dazu\": the reference photo shows another room. A\n        // reference from this photo's own room leaves the room on the server\n        // and this photo is staged on its own. A reference picked from another\n        // room is only dropped: its own room is right.\n        removeReference() {\n            if (this.referenceRemoved) return;\n            this.referenceRemoved = true;\n            this.matchReference = false;\n            if (!this.referenceSameRoom) return;\n            const meta = document.querySelector('meta[name=\"csrf-token\"]');\n            fetch(this.referenceRemoveUrl, {\n                method: 'POST',\n                headers: { 'X-CSRFToken': meta ? meta.content : '', 'X-Requested-With': 'XMLHttpRequest' },\n            }).catch(err => console.error(err));\n        },\n\n        // \"Anderes Foto w\u00E4hlen\" opens the picker modal. It is teleported out of\n        // this form, so it gets the current reference and a callback through\n        // the modal store instead of reading this component's state.\n        openReferencePicker() {\n            Alpine.store('modal').show('reference-picker', {\n                current: this.referenceRemoved ? '' : this.referenceId,\n                choose: (button) => {\n                    this.chooseReference(button);\n                    Alpine.store('modal').close();\n                },\n            });\n        },\n\n        // A candidate from the picker becomes the reference, ticked, with its\n        // own furniture style.\n        chooseReference(button) {\n            const data = button.dataset;\n            this.referenceId = data.referenceCandidate;\n            this.referenceUrl = data.url;\n            this.referenceStyle = data.style || '';\n            this.referenceRemoveUrl = data.removeUrl;\n            this.referenceSameRoom = data.sameRoom === 'true';\n            this.referenceRemoved = false;\n            this.furnitureStyle = this.referenceStyle;\n            this.matchReference = true;\n        },\n\n        // GA4 funnel step: the customer asked for this edit. The onboarding\n        // demo edit reports itself with auto: true from its own partial.\n        trackEditStarted(propertyId) {\n            window.trackEvent('edit_started', {\n                property_id: propertyId,\n                preset: this.activeTab === 'mask' ? 'mask' : this.getSelectedPresets().join(','),\n                auto: false,\n            });\n        },\n\n        getSelectedPresets() {\n            if (this.activeTab === 'outdoor') {\n                // The same keys the server stores on the job (resolve_site_declutter).\n                const keepSite = this.keepSiteMaterials && this.sceneSubtype === 'construction';\n                return Object.entries(this.outdoor).filter(([, v]) => v)\n                    .map(([k]) => (k === 'declutter' && keepSite ? 'declutter_construction' : k));\n            }\n            if (this.activeTab === 'indoor') {\n                var presets = [];\n                if (this.indoor.optimize) presets.push('optimize_indoor');\n                if (this.indoor.declutter) presets.push('declutter_indoor');\n                if (this.indoor.details) presets.push('details');\n                if (this.indoor.staging) presets.push(this.replaceFurniture ? 'staging_replace' : 'staging');\n                return presets;\n            }\n            if (this.activeTab === 'floorplan') {\n                // One floorplan preset: the checked 2D/3D radio for this plan type.\n                var inputs = this.$el.querySelectorAll('input[name=\"presets\"][value^=\"floorplan\"]');\n                for (var i = 0; i < inputs.length; i++) {\n                    var inp = inputs[i];\n                    if (inp.disabled) continue;\n                    if (inp.type === 'radio' && !inp.checked) continue;\n                    return [inp.value];\n                }\n                return [];\n            }\n            return [];\n        },\n\n        isDuplicatePresets() {\n            if (!this.previousPresets.length) return false;\n            if (this.additionalGuidance.trim()) return false;\n            if (!this.placementInputsDisabled) return false;\n            var selected = this.getSelectedPresets();\n            if (!selected.length) return false;\n            return selected.every(function(p) { return this.previousPresets.indexOf(p) !== -1; }.bind(this));\n        },\n\n        // Pre-submit gates. handleConfirm walks this list and opens the\n        // first gate whose `needs()` returns true. Confirming a gate sets\n        // its `bypassed` flag and calls _resumeSubmit(), which re-walks the\n        // gates synchronously and either opens the next pending gate or\n        // fires the captured HTMX issueRequest.\n        gates() {\n            const self = this;\n            return [\n                {\n                    needs() {\n                        // Warning is for 3D outputs only. Any floorplan_2d_modern*\n                        // key (Grundriss/Ansicht/Schnitt) is a faithful redraw and\n                        // never warns.\n                        var has3d = self.getSelectedPresets().some(function(k) {\n                            return k.indexOf('floorplan') === 0 && k.indexOf('floorplan_2d_modern') !== 0;\n                        });\n                        return self.activeTab === 'floorplan'\n                            && self.floorplanWarningPending\n                            && !self.floorplanWarningBypassed\n                            && has3d;\n                    },\n                    open() {\n                        Alpine.store('modal').show('floorplan-warning', {\n                            onConfirm: () => self.confirmFloorplanWarning(),\n                        });\n                    },\n                },\n                {\n                    needs() {\n                        return self.activeTab !== 'custom'\n                            && !self.duplicateWarningBypassed\n                            && self.isDuplicatePresets();\n                    },\n                    open() {\n                        Alpine.store('modal').show('duplicate-warning', {\n                            onConfirm: () => self.confirmSubmit(),\n                        });\n                    },\n                },\n            ];\n        },\n\n        handleConfirm(event) {\n            for (const gate of this.gates()) {\n                if (gate.needs()) {\n                    event.preventDefault();\n                    this._issueRequest = event.detail.issueRequest;\n                    gate.open();\n                    return;\n                }\n            }\n        },\n\n        _resumeSubmit() {\n            // A bypassed gate may have unblocked the next one \u2014 walk the\n            // chain again before submitting. event.detail.issueRequest is\n            // the htmx:confirm completion callback and does NOT re-emit\n            // htmx:confirm, so the chain has to advance itself here.\n            for (const gate of this.gates()) {\n                if (gate.needs()) {\n                    gate.open();\n                    return;\n                }\n            }\n            this.submitting = true;\n            if (this._issueRequest) this._issueRequest();\n        },\n\n        confirmFloorplanWarning() {\n            Alpine.store('modal').close();\n            this.floorplanWarningBypassed = true;\n            this._resumeSubmit();\n        },\n\n        confirmSubmit() {\n            Alpine.store('modal').close();\n            this.duplicateWarningBypassed = true;\n            this._resumeSubmit();\n        },\n    }));\n\n});\n", "const MAX_POLYGON_POINTS = 64;\nconst MIN_POLYGON_AREA = 0.000001;\n// A second tap this soon and this close (screen px) closes an open polygon.\nconst DOUBLE_TAP_MS = 400;\nconst DOUBLE_TAP_SLOP = {mouse: 6, touch: 24, pen: 12};\n\nfunction clamp(value, min, max) {\n    return Math.min(max, Math.max(min, value));\n}\n\nfunction orientation(a, b, c) {\n    return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);\n}\n\nfunction pointOnSegment(a, b, point) {\n    const epsilon = 1e-12;\n    return Math.abs(orientation(a, b, point)) <= epsilon\n        && point.x >= Math.min(a.x, b.x) - epsilon\n        && point.x <= Math.max(a.x, b.x) + epsilon\n        && point.y >= Math.min(a.y, b.y) - epsilon\n        && point.y <= Math.max(a.y, b.y) + epsilon;\n}\n\nfunction segmentsIntersect(a, b, c, d) {\n    const abC = orientation(a, b, c);\n    const abD = orientation(a, b, d);\n    const cdA = orientation(c, d, a);\n    const cdB = orientation(c, d, b);\n    const epsilon = 1e-12;\n    const crosses = (\n        ((abC > epsilon && abD < -epsilon) || (abC < -epsilon && abD > epsilon))\n        && ((cdA > epsilon && cdB < -epsilon) || (cdA < -epsilon && cdB > epsilon))\n    );\n    return crosses\n        || pointOnSegment(a, b, c)\n        || pointOnSegment(a, b, d)\n        || pointOnSegment(c, d, a)\n        || pointOnSegment(c, d, b);\n}\n\nfunction polygonArea(points) {\n    return Math.abs(points.reduce((sum, point, index) => {\n        const next = points[(index + 1) % points.length];\n        return sum + point.x * next.y - next.x * point.y;\n    }, 0)) / 2;\n}\n\nfunction polygonIsSimple(points) {\n    if (points.length < 3 || polygonArea(points) < MIN_POLYGON_AREA) return false;\n    for (let first = 0; first < points.length; first++) {\n        const firstNext = (first + 1) % points.length;\n        for (let second = first + 1; second < points.length; second++) {\n            const secondNext = (second + 1) % points.length;\n            if (firstNext === second || secondNext === first) continue;\n            if (segmentsIntersect(\n                points[first], points[firstNext], points[second], points[secondNext]\n            )) return false;\n        }\n    }\n    return true;\n}\n\nfunction openPathIsSimple(points) {\n    if (points.length < 4) return true;\n    const lastStart = points.length - 2;\n    const lastEnd = points.length - 1;\n    for (let index = 0; index < lastStart - 1; index++) {\n        if (segmentsIntersect(\n            points[index], points[index + 1], points[lastStart], points[lastEnd]\n        )) return false;\n    }\n    return true;\n}\n\n/** Polygon-specific Alpine methods mixed into the shared image editor state. */\nwindow.highlightPolygonEditorMethods = function() {\n    return {\n        selectHighlightShape(shape) {\n            // The brush exists only for a mask: the manual highlight renderer\n            // has no brush operation.\n            const shapes = this.mode === 'mask' ? ['rect', 'polygon', 'brush'] : ['rect', 'polygon'];\n            if (!shapes.includes(shape) || this.applying) return;\n            if (shape === this.highlightShape) return;\n            this.polygonError = '';\n            this.brushError = '';\n            if (shape === 'brush' || this.highlightShape === 'brush') {\n                // Strokes and outlines cannot be converted into each other.\n                if (this.hasShapeContent) {\n                    this.pendingShape = shape;\n                    this.shapeChangeOpen = true;\n                    return;\n                }\n                this.highlightShape = shape;\n                this.guideVisible = false;\n                return;\n            }\n            if (shape === 'polygon') {\n                if (this.highlightRect) {\n                    const rect = this.highlightRect;\n                    this.polygonPoints = [\n                        {x: rect.x, y: rect.y},\n                        {x: rect.x + rect.width, y: rect.y},\n                        {x: rect.x + rect.width, y: rect.y + rect.height},\n                        {x: rect.x, y: rect.y + rect.height},\n                    ];\n                    this.polygonClosed = true;\n                    this.highlightRect = null;\n                    this.guideVisible = true;\n                } else {\n                    this.polygonPoints = [];\n                    this.polygonClosed = false;\n                    this.guideVisible = false;\n                }\n                this.highlightShape = 'polygon';\n                return;\n            }\n            if (this.polygonPoints.length) {\n                this.pendingShape = 'rect';\n                this.shapeChangeOpen = true;\n                return;\n            }\n            this.highlightShape = 'rect';\n            this.guideVisible = Boolean(this.highlightRect);\n        },\n\n        cancelShapeChange() {\n            this.shapeChangeOpen = false;\n            this.pendingShape = null;\n        },\n\n        confirmShapeChange() {\n            if (this.brushShapeChange) {\n                this.highlightRect = null;\n                this.resetPolygon();\n                this.resetBrush();\n                this.highlightShape = this.pendingShape;\n                this.pendingShape = null;\n                this.shapeChangeOpen = false;\n                return;\n            }\n            if (this.polygonClosed && this.polygonPoints.length >= 3) {\n                const xs = this.polygonPoints.map((point) => point.x);\n                const ys = this.polygonPoints.map((point) => point.y);\n                const left = Math.min(...xs);\n                const top = Math.min(...ys);\n                this.highlightRect = {\n                    x: left,\n                    y: top,\n                    width: Math.max(...xs) - left,\n                    height: Math.max(...ys) - top,\n                };\n            } else {\n                this.highlightRect = null;\n            }\n            this.polygonPoints = [];\n            this.polygonClosed = false;\n            this.polygonPointer = null;\n            this.polygonError = '';\n            this.highlightShape = 'rect';\n            this.pendingShape = null;\n            this.shapeChangeOpen = false;\n            this.guideVisible = Boolean(this.highlightRect);\n        },\n\n        startPolygonPoint(event) {\n            if (this.pointerCount > 0) {\n                this.trackPointer(event);\n                this.beginPinchIfNeeded();\n                return;\n            }\n            this.trackPointer(event);\n            const point = this.imagePoint(event);\n            this._gesture = this.polygonClosed ? 'polygon-dismiss' : 'polygon-point';\n            this._gestureStart = {\n                point,\n                client: {x: event.clientX, y: event.clientY},\n                moved: false,\n            };\n            if (!this.polygonClosed) this.polygonPointer = point;\n            this.capturePointer(event);\n        },\n\n        startPolygonVertex(event, index) {\n            this.trackPointer(event);\n            if (this.pointerCount > 1) {\n                this.beginPinchIfNeeded();\n                return;\n            }\n            this.polygonError = '';\n            this.guideVisible = true;\n            this._gesture = !this.polygonClosed && index === 0 && this.polygonPoints.length >= 3\n                ? 'polygon-close'\n                : 'polygon-vertex';\n            this._gestureStart = {\n                index,\n                client: {x: event.clientX, y: event.clientY},\n                originalPoints: this.polygonPoints.map((point) => ({...point})),\n                moved: false,\n            };\n            this.capturePointer(event);\n        },\n\n        startPolygonMove(event) {\n            this.trackPointer(event);\n            if (this.pointerCount > 1) {\n                this.beginPinchIfNeeded();\n                return;\n            }\n            this.guideVisible = true;\n            this._gesture = 'polygon-move';\n            this._gestureStart = {\n                point: this.imagePoint(event),\n                originalPoints: this.polygonPoints.map((point) => ({...point})),\n            };\n            this.capturePointer(event);\n        },\n\n        markPolygonGestureMoved(event, threshold) {\n            if (!this._gestureStart || this._gestureStart.moved) return;\n            this._gestureStart.moved = Math.hypot(\n                event.clientX - this._gestureStart.client.x,\n                event.clientY - this._gestureStart.client.y\n            ) >= threshold;\n        },\n\n        finishPolygonGesture(event, gesture) {\n            const start = this._gestureStart;\n            const cancelled = event.type === 'pointercancel';\n            // The first tap of a double tap already set the last point. The\n            // second one closes instead of adding a point of its own; it\n            // usually lands on that new point's handle.\n            const closingTap = (\n                !cancelled\n                && !start?.moved\n                && !this.polygonClosed\n                && this.polygonPoints.length >= 3\n                && (\n                    gesture === 'polygon-point'\n                    || (gesture === 'polygon-vertex' && start?.index === this.polygonPoints.length - 1)\n                )\n                && this.isDoubleTap(event)\n            );\n            if (closingTap) {\n                this._lastTap = null;\n                this.closePolygon();\n            } else if (cancelled) {\n                if (start?.originalPoints) this.polygonPoints = start.originalPoints;\n            } else if (gesture === 'polygon-point' && !start?.moved) {\n                this.addPolygonPoint(this.imagePoint(event));\n                this._lastTap = {time: event.timeStamp, x: event.clientX, y: event.clientY};\n            } else if (gesture === 'polygon-close' && !start?.moved) {\n                this.closePolygon();\n            } else if (gesture === 'polygon-vertex' && start?.moved) {\n                const valid = this.polygonClosed\n                    ? polygonIsSimple(this.polygonPoints)\n                    : openPathIsSimple(this.polygonPoints);\n                if (!valid) {\n                    const areaTooSmall = (\n                        this.polygonClosed\n                        && polygonArea(this.polygonPoints) < MIN_POLYGON_AREA\n                    );\n                    this.polygonPoints = start.originalPoints;\n                    this.polygonError = this.message(\n                        areaTooSmall ? 'polygonTooSmall' : 'polygonCrossed',\n                        areaTooSmall\n                            ? 'Die markierte Fl\u00E4che ist zu klein.'\n                            : 'Die Linien d\u00FCrfen sich nicht kreuzen.'\n                    );\n                }\n            } else if (gesture === 'polygon-dismiss' && !start?.moved) {\n                this.guideVisible = false;\n            }\n            this._gesture = null;\n            this._gestureStart = null;\n        },\n\n        isDoubleTap(event) {\n            const last = this._lastTap;\n            if (!last || event.timeStamp - last.time > DOUBLE_TAP_MS) return false;\n            const slop = DOUBLE_TAP_SLOP[event.pointerType] || DOUBLE_TAP_SLOP.touch;\n            return Math.hypot(event.clientX - last.x, event.clientY - last.y) <= slop;\n        },\n\n        addPolygonPoint(point) {\n            this.polygonError = '';\n            if (\n                this.polygonPoints.length >= 3\n                && this.pointScreenDistance(point, this.polygonPoints[0]) <= 28\n            ) {\n                this.closePolygon();\n                return;\n            }\n            if (this.polygonPoints.length >= MAX_POLYGON_POINTS) {\n                this.polygonError = this.message(\n                    'polygonMaxPoints', 'Die maximale Anzahl an Punkten ist erreicht.'\n                );\n                return;\n            }\n            const previous = this.polygonPoints[this.polygonPoints.length - 1];\n            if (previous && this.pointScreenDistance(point, previous) < 8) return;\n            const candidate = [...this.polygonPoints, point];\n            if (!openPathIsSimple(candidate)) {\n                this.polygonError = this.message(\n                    'polygonCrossed', 'Die Linien d\u00FCrfen sich nicht kreuzen.'\n                );\n                return;\n            }\n            this.polygonPoints = candidate;\n            this.polygonPointer = point;\n            this.guideVisible = true;\n        },\n\n        closePolygon() {\n            if (this.polygonPoints.length < 3) return;\n            if (polygonArea(this.polygonPoints) < MIN_POLYGON_AREA) {\n                this.polygonError = this.message(\n                    'polygonTooSmall', 'Die markierte Fl\u00E4che ist zu klein.'\n                );\n                return;\n            }\n            if (!polygonIsSimple(this.polygonPoints)) {\n                this.polygonError = this.message(\n                    'polygonCrossed', 'Die Linien d\u00FCrfen sich nicht kreuzen.'\n                );\n                return;\n            }\n            this.polygonClosed = true;\n            this.polygonPointer = null;\n            this.polygonError = '';\n            this.guideVisible = true;\n        },\n\n        undoPolygonPoint() {\n            if (!this.polygonPoints.length) return;\n            this.polygonClosed = false;\n            this.polygonPoints = this.polygonPoints.slice(0, -1);\n            this.polygonPointer = this.polygonPoints[this.polygonPoints.length - 1] || null;\n            this.polygonError = '';\n            this.guideVisible = Boolean(this.polygonPoints.length);\n            this._lastTap = null;\n        },\n\n        resetPolygon() {\n            this.polygonPoints = [];\n            this.polygonClosed = false;\n            this.polygonPointer = null;\n            this.polygonError = '';\n            this.guideVisible = false;\n            this._lastTap = null;\n        },\n\n        updatePolygonMove(point) {\n            const start = this._gestureStart;\n            const xs = start.originalPoints.map((item) => item.x);\n            const ys = start.originalPoints.map((item) => item.y);\n            const dx = clamp(point.x - start.point.x, -Math.min(...xs), 1 - Math.max(...xs));\n            const dy = clamp(point.y - start.point.y, -Math.min(...ys), 1 - Math.max(...ys));\n            this.polygonPoints = start.originalPoints.map((item) => ({\n                x: item.x + dx,\n                y: item.y + dy,\n            }));\n        },\n\n        pointScreenDistance(first, second) {\n            if (!this._viewportElement) return Infinity;\n            const viewport = this._viewportElement.getBoundingClientRect();\n            return Math.hypot(\n                (first.x - second.x) * viewport.width * this.zoom,\n                (first.y - second.y) * viewport.height * this.zoom\n            );\n        },\n    };\n};\n", "// Brush tool of the image editor's mask mode (\"Stelle \u00E4ndern\"). A stroke is\n// normalized to the image: x and y in 0..1, width as a share of the image\n// width. The limits match dashboard/mask_edit.py.\nconst BRUSH_MAX_STROKES = 40;\nconst BRUSH_MAX_POINTS = 400;\n// In screen pixels. Closer pointer samples add nothing visible.\nconst BRUSH_MIN_POINT_DISTANCE = 2.5;\n// A stroke at the point limit is simplified to this deviation (screen px).\nconst BRUSH_SIMPLIFY_TOLERANCE = 1;\n// The primary's hue, lighter, so the paint reads on dark and on light photos.\nconst BRUSH_COLOR = '#2b7fee';\nconst BRUSH_OPACITY = 0.5;\n\nfunction svgNumber(value) {\n    return Math.round(value * 100) / 100;\n}\n\n// Ramer-Douglas-Peucker in screen pixels. Keeps the first and last point.\nfunction simplifyStroke(points, scaleX, scaleY, tolerance) {\n    if (points.length < 3) return points;\n    const keep = new Array(points.length).fill(false);\n    keep[0] = true;\n    keep[points.length - 1] = true;\n    const stack = [[0, points.length - 1]];\n    while (stack.length) {\n        const [first, last] = stack.pop();\n        const ax = points[first].x * scaleX;\n        const ay = points[first].y * scaleY;\n        const dx = points[last].x * scaleX - ax;\n        const dy = points[last].y * scaleY - ay;\n        const length = Math.hypot(dx, dy);\n        let farthest = -1;\n        let farthestDistance = 0;\n        for (let index = first + 1; index < last; index++) {\n            const px = points[index].x * scaleX - ax;\n            const py = points[index].y * scaleY - ay;\n            const distance = length\n                ? Math.abs(dx * py - dy * px) / length\n                : Math.hypot(px, py);\n            if (distance > farthestDistance) {\n                farthest = index;\n                farthestDistance = distance;\n            }\n        }\n        if (farthest > 0 && farthestDistance > tolerance) {\n            keep[farthest] = true;\n            stack.push([first, farthest], [farthest, last]);\n        }\n    }\n    return points.filter((_, index) => keep[index]);\n}\n\n/**\n * SVG markup of brush strokes for a viewBox of `0 0 width height` (the image\n * in pixels), so stroke widths are exact. Points may be {x, y} objects (the\n * editor) or [x, y] pairs (the posted shape). The strokes are opaque inside\n * one translucent group, so overlaps do not add up. Only numbers go into the\n * markup.\n */\nwindow.brushStrokesMarkup = function(strokes, width, height) {\n    const items = (strokes || []).map((stroke) => {\n        const strokeWidth = Number(stroke.width) * width;\n        const points = (stroke.points || []).map((point) => (\n            Array.isArray(point)\n                ? [Number(point[0]) * width, Number(point[1]) * height]\n                : [Number(point.x) * width, Number(point.y) * height]\n        )).filter(([x, y]) => Number.isFinite(x) && Number.isFinite(y));\n        if (!points.length || !Number.isFinite(strokeWidth)) return '';\n        if (points.length === 1) {\n            return '<circle cx=\"' + svgNumber(points[0][0]) + '\" cy=\"' + svgNumber(points[0][1])\n                + '\" r=\"' + svgNumber(strokeWidth / 2) + '\" stroke=\"none\"></circle>';\n        }\n        return '<polyline fill=\"none\" stroke-width=\"' + svgNumber(strokeWidth) + '\" points=\"'\n            + points.map(([x, y]) => svgNumber(x) + ',' + svgNumber(y)).join(' ') + '\"></polyline>';\n    }).join('');\n    return '<g fill=\"' + BRUSH_COLOR + '\" stroke=\"' + BRUSH_COLOR + '\" stroke-linecap=\"round\"'\n        + ' stroke-linejoin=\"round\" opacity=\"' + BRUSH_OPACITY + '\">' + items + '</g>';\n};\n\n/**\n * Brush-specific Alpine methods mixed into the shared image editor state.\n * Methods only: a spread copies a getter's value, not the getter.\n */\nwindow.highlightBrushEditorMethods = function() {\n    return {\n        startBrushStroke(event) {\n            if (this.pointerCount > 0) {\n                // A second finger turns the stroke into a pinch.\n                this.trackPointer(event);\n                this.beginPinchIfNeeded();\n                return;\n            }\n            this.trackPointer(event);\n            this.brushError = '';\n            if (this.brushStrokes.length >= BRUSH_MAX_STROKES) {\n                this.brushError = this.message(\n                    'brushMaxStrokes', 'Die maximale Anzahl an Pinselstrichen ist erreicht.'\n                );\n                this._gesture = null;\n                this._gestureStart = null;\n                return;\n            }\n            this.brushStrokes.push({width: this.brushSize / 100, points: [this.imagePoint(event)]});\n            this._gesture = 'brush';\n            this._gestureStart = {strokeIndex: this.brushStrokes.length - 1};\n            this.capturePointer(event);\n        },\n\n        extendBrushStroke(event) {\n            const stroke = this.brushStrokes[this._gestureStart?.strokeIndex];\n            if (!stroke) return;\n            const point = this.imagePoint(event);\n            const last = stroke.points[stroke.points.length - 1];\n            if (this.pointScreenDistance(point, last) < BRUSH_MIN_POINT_DISTANCE) return;\n            if (stroke.points.length >= BRUSH_MAX_POINTS) {\n                const viewport = this._viewportElement.getBoundingClientRect();\n                stroke.points = simplifyStroke(\n                    stroke.points,\n                    viewport.width * this.zoom,\n                    viewport.height * this.zoom,\n                    BRUSH_SIMPLIFY_TOLERANCE\n                );\n                if (stroke.points.length >= BRUSH_MAX_POINTS) {\n                    this.brushError = this.message(\n                        'brushStrokeTooLong', 'Dieser Pinselstrich ist zu lang. Bitte setzen Sie neu an.'\n                    );\n                    return;\n                }\n            }\n            stroke.points.push(point);\n        },\n\n        // A pinch or a cancelled pointer drops the stroke in progress.\n        cancelBrushStroke() {\n            const index = this._gestureStart?.strokeIndex;\n            if (index !== undefined && index === this.brushStrokes.length - 1) {\n                this.brushStrokes.pop();\n            }\n        },\n\n        finishBrushStroke(event) {\n            if (event.type === 'pointercancel') this.cancelBrushStroke();\n            this._gesture = null;\n            this._gestureStart = null;\n        },\n\n        trackBrushCursor(event) {\n            this.brushCursor = event.pointerType === 'touch' ? null : this.imagePoint(event);\n        },\n\n        hideBrushCursor() {\n            this.brushCursor = null;\n        },\n\n        undoBrushStroke() {\n            this.brushStrokes = this.brushStrokes.slice(0, -1);\n            this.brushError = '';\n        },\n\n        resetBrush() {\n            this.brushStrokes = [];\n            this.brushError = '';\n        },\n    };\n};\n", "document.addEventListener('alpine:init', () => {\n    const EMPTY_RECT = null;\n    // Single source for the combined-strength mapping; the server stores the\n    // resolved blur/darken values, so preview and serialization must agree.\n    const STRENGTH_MAX_BLUR = 0.014;\n    const STRENGTH_MAX_DARKEN = 0.4;\n\n    function clamp(value, min, max) {\n        return Math.min(max, Math.max(min, value));\n    }\n\n    function rounded(value) {\n        return Math.round(value * 1000000) / 1000000;\n    }\n\n    function brushRounded(value) {\n        return Math.round(value * 10000) / 10000;\n    }\n\n    // Brush size slider, in percent of the image width. The server accepts\n    // 0.5% to 20% (dashboard/mask_edit.py).\n    const BRUSH_SIZE_MIN = 1;\n    const BRUSH_SIZE_MAX = 12;\n    // Viewport height kept free for the sticky header and the controls panel\n    // below the stage (checked by screenshot at 1440x900, mask mode), and the\n    // smallest stage height on short screens.\n    const STAGE_RESERVED_HEIGHT = '21rem';\n    const STAGE_MIN_HEIGHT = '16rem';\n\n    function distance(a, b) {\n        return Math.hypot(a.x - b.x, a.y - b.y);\n    }\n\n    function midpoint(a, b) {\n        return {x: (a.x + b.x) / 2, y: (a.y + b.y) / 2};\n    }\n\n    function highlightOperation(operations) {\n        const list = operations && Array.isArray(operations.operations)\n            ? operations.operations\n            : [];\n        return list.find((operation) => (\n            operation.type === 'highlight_rect' || operation.type === 'highlight_polygon'\n        )) || null;\n    }\n\n    function cropOperation(operations) {\n        const list = operations && Array.isArray(operations.operations)\n            ? operations.operations\n            : [];\n        return list.find((operation) => operation.type === 'crop_rect') || null;\n    }\n\n    Alpine.data('highlightEditor', () => ({\n        editing: false,\n        // 'manual' edits the photo's own recipe. 'mask' borrows the same\n        // drawing tools to mark the one area of a \"Stelle \u00E4ndern\" AI edit and\n        // hands the shape to the edit form instead of saving anything. 'place'\n        // draws the space of \"M\u00F6belst\u00FCck platzieren\" the same way, for\n        // the edit form or the retry dialog (placeTarget).\n        mode: 'manual',\n        placeTarget: 'form',\n        // The retry dialog draws on the version the rejected result was made\n        // from: {url, width, height}. Null draws on config.baseUrl.\n        baseOverride: null,\n        applying: false,\n        discardOpen: false,\n        error: '',\n        config: {},\n        activeTool: 'highlight',\n        highlightShape: 'rect',\n        highlightRect: EMPTY_RECT,\n        polygonPoints: [],\n        polygonClosed: false,\n        polygonPointer: null,\n        polygonError: '',\n        // Brush strokes, mask mode only: {width, points: [{x, y}]}, width\n        // as a share of the image width (highlight-brush.js).\n        brushStrokes: [],\n        // Percent of the image width, for the next stroke.\n        brushSize: 4,\n        brushSizeMin: BRUSH_SIZE_MIN,\n        brushSizeMax: BRUSH_SIZE_MAX,\n        brushCursor: null,\n        brushError: '',\n        cropRect: EMPTY_RECT,\n        guideVisible: false,\n        shapeChangeOpen: false,\n        pendingShape: null,\n        cornerRadius: 8,\n        strength: 55,\n        borderWidth: 2,\n        borderColor: '#ffffff',\n        zoom: 1,\n        panX: 0,\n        panY: 0,\n        // Bumped when the viewport box changes (editor opened, window resized).\n        // Style getters measure the viewport via getBoundingClientRect, which\n        // is not reactive \u2014 reading this counter makes them re-evaluate.\n        viewportTick: 0,\n        _initialOperations: '{}',\n        _pointers: {},\n        _gesture: null,\n        _gestureStart: null,\n        _beforeUnloadHandler: null,\n        _htmxHandler: null,\n        _resizeHandler: null,\n        _pendingNavHref: null,\n        _viewportElement: null,\n        _lastTap: null,\n\n        ...window.highlightPolygonEditorMethods(),\n        ...window.highlightBrushEditorMethods(),\n\n        init() {\n            this._viewportElement = this.$el.querySelector('[data-highlight-stage]');\n            const configElement = this.$el.closest('[data-highlight-editor-config]');\n            try {\n                this.config = JSON.parse(configElement?.dataset.highlightEditorConfig || '{}');\n            } catch (_) {\n                this.config = {};\n            }\n            this.loadOperations(this.config.operations || {});\n            this._initialOperations = JSON.stringify(this.serializeOperations());\n\n            this._beforeUnloadHandler = (event) => {\n                if (!this.editing || !this.dirty) return;\n                event.preventDefault();\n                event.returnValue = '';\n            };\n            this._htmxHandler = (event) => {\n                if (!this.editing || !this.dirty || !event.detail.boosted) return;\n                event.preventDefault();\n                this._pendingNavHref = event.detail.elt?.getAttribute?.('href')\n                    || event.detail.requestConfig?.path\n                    || null;\n                this.discardOpen = true;\n            };\n            this._resizeHandler = () => {\n                if (this.editing) this.viewportTick++;\n            };\n            window.addEventListener('beforeunload', this._beforeUnloadHandler);\n            document.addEventListener('htmx:beforeRequest', this._htmxHandler);\n            window.addEventListener('resize', this._resizeHandler);\n        },\n\n        destroy() {\n            window.removeEventListener('beforeunload', this._beforeUnloadHandler);\n            document.removeEventListener('htmx:beforeRequest', this._htmxHandler);\n            window.removeEventListener('resize', this._resizeHandler);\n            this._viewportElement = null;\n        },\n\n        loadOperations(operations) {\n            const highlight = highlightOperation(operations);\n            const crop = cropOperation(operations);\n            const params = highlight?.params || {};\n            const rect = params.rect;\n            this.highlightShape = highlight?.type === 'highlight_polygon' ? 'polygon' : 'rect';\n            this.highlightRect = rect ? {\n                x: Number(rect.x),\n                y: Number(rect.y),\n                width: Number(rect.width),\n                height: Number(rect.height),\n            } : EMPTY_RECT;\n            this.polygonPoints = highlight?.type === 'highlight_polygon'\n                ? (params.points || []).map((point) => ({\n                    x: Number(point.x),\n                    y: Number(point.y),\n                }))\n                : [];\n            this.polygonClosed = this.polygonPoints.length >= 3;\n            this.polygonPointer = null;\n            this.polygonError = '';\n            this.brushStrokes = [];\n            this.brushError = '';\n            const cropRect = crop?.params?.rect;\n            this.cropRect = cropRect ? {\n                x: Number(cropRect.x),\n                y: Number(cropRect.y),\n                width: Number(cropRect.width),\n                height: Number(cropRect.height),\n            } : EMPTY_RECT;\n            this.activeTool = highlight ? 'highlight' : (this.cropRect ? 'crop' : 'highlight');\n            this.cornerRadius = clamp(Math.round(Number(params.corner_radius_ratio ?? 0.08) * 100), 0, 40);\n            this.strength = clamp(Math.round((Number(params.outside?.darken ?? 0.22) / STRENGTH_MAX_DARKEN) * 100), 0, 100);\n            // Round to the slider's 0.5 steps so stored recipes survive a reload.\n            this.borderWidth = clamp(Math.round(Number(params.stroke?.width_ratio ?? 0.002) * 2000) / 2, 0, 12);\n            this.borderColor = /^#[0-9a-f]{6}$/i.test(params.stroke?.color || '')\n                ? params.stroke.color\n                : '#ffffff';\n            this.guideVisible = Boolean(this.rect || this.polygonClosed);\n        },\n\n        selectTool(tool) {\n            if (!['highlight', 'crop'].includes(tool) || this.applying) return;\n            this.activeTool = tool;\n            this.guideVisible = Boolean(\n                this.rect || (tool === 'highlight' && this.polygonClosed)\n            );\n            this._gesture = null;\n            this._gestureStart = null;\n        },\n\n        openEditor() {\n            if (this.config.aiBusy || !this.config.baseUrl || !this.config.applyUrl) return;\n            this.mode = 'manual';\n            this.baseOverride = null;\n            this.loadOperations(this.config.operations || {});\n            this._initialOperations = JSON.stringify(this.serializeOperations());\n            this.zoom = 1;\n            this.panX = 0;\n            this.panY = 0;\n            this.error = '';\n            this.editing = true;\n            this.$dispatch('highlight-editor-mode', {open: true});\n            // The viewport only gets real dimensions once x-show reveals it.\n            this.$nextTick(() => this.viewportTick++);\n        },\n\n        // Opened by the edit form's \"Stelle \u00E4ndern\" mode with the area marked\n        // so far, if any. Rectangle is the default shape.\n        openMaskEditor(detail) {\n            this.openAreaEditor('mask', detail);\n        },\n\n        // Opened by \"M\u00F6belst\u00FCck platzieren\" in the edit form or the\n        // retry dialog, with the space drawn so far. Rectangle and free form\n        // only: the block painted into the AI input needs one outline.\n        openPlaceEditor(detail) {\n            this.openAreaEditor('place', detail);\n        },\n\n        // Mask and place mode: draw one area and hand it back as a shape.\n        openAreaEditor(mode, detail) {\n            const base = mode === 'place' && detail?.base?.url ? detail.base : null;\n            if (this.config.aiBusy || !(base?.url || this.config.baseUrl)) return;\n            const shape = detail?.shape || null;\n            this.mode = mode;\n            this.placeTarget = detail?.target || 'form';\n            this.baseOverride = base;\n            this.activeTool = 'highlight';\n            this.cropRect = EMPTY_RECT;\n            this.highlightShape = ['polygon', 'brush'].includes(shape?.type) ? shape.type : 'rect';\n            this.highlightRect = shape?.type === 'rect' ? {...shape.rect} : EMPTY_RECT;\n            this.polygonPoints = shape?.type === 'polygon'\n                ? shape.points.map((point) => ({x: Number(point.x), y: Number(point.y)}))\n                : [];\n            this.polygonClosed = this.polygonPoints.length >= 3;\n            this.polygonPointer = null;\n            this.polygonError = '';\n            this.brushStrokes = shape?.type === 'brush'\n                ? shape.strokes.map((stroke) => ({\n                    width: Number(stroke.width),\n                    points: stroke.points.map(([x, y]) => ({x: Number(x), y: Number(y)})),\n                }))\n                : [];\n            this.brushError = '';\n            if (this.brushStrokes.length) {\n                const lastWidth = this.brushStrokes[this.brushStrokes.length - 1].width * 100;\n                this.brushSize = clamp(Math.round(lastWidth * 2) / 2, BRUSH_SIZE_MIN, BRUSH_SIZE_MAX);\n            }\n            this.cornerRadius = 0;\n            this.borderWidth = 0;\n            this.guideVisible = Boolean(shape) && shape.type !== 'brush';\n            this._initialOperations = JSON.stringify(this.maskShape());\n            this.zoom = 1;\n            this.panX = 0;\n            this.panY = 0;\n            this.error = '';\n            this.editing = true;\n            this.$dispatch('highlight-editor-mode', {open: true});\n            this.$nextTick(() => {\n                this.viewportTick++;\n                this.$el.scrollIntoView({block: 'start'});\n            });\n        },\n\n        // The marked area in the normalized base coordinates the server\n        // validates (dashboard/mask_edit.py), or null.\n        maskShape() {\n            if (this.highlightShape === 'rect' && this.highlightRect) {\n                return {type: 'rect', rect: this.serializedRect(this.highlightRect)};\n            }\n            if (this.highlightShape === 'polygon' && this.polygonClosed && this.polygonPoints.length >= 3) {\n                return {type: 'polygon', points: this.polygonPoints.map((point) => this.serializedPoint(point))};\n            }\n            if (this.highlightShape === 'brush' && this.brushStrokes.length) {\n                // Pairs at 4 decimals (under a pixel on any photo) keep the\n                // posted JSON small; a stroke can hold hundreds of points.\n                return {\n                    type: 'brush',\n                    strokes: this.brushStrokes.map((stroke) => ({\n                        width: brushRounded(stroke.width),\n                        points: stroke.points.map((point) => [brushRounded(point.x), brushRounded(point.y)]),\n                    })),\n                };\n            }\n            return null;\n        },\n\n        confirmMask() {\n            if (!this.canConfirmMask) return;\n            const shape = this.maskShape();\n            this._initialOperations = JSON.stringify(shape);\n            this.closeEditor();\n            this.$dispatch('mask-area-selected', {shape});\n        },\n\n        confirmPlace() {\n            if (!this.canConfirmMask) return;\n            const shape = this.maskShape();\n            this._initialOperations = JSON.stringify(shape);\n            this.closeEditor();\n            this.$dispatch('place-area-selected', {shape, target: this.placeTarget});\n        },\n\n        cancel() {\n            if (!this.editing || this.applying) return;\n            if (this.shapeChangeOpen) {\n                this.cancelShapeChange();\n                return;\n            }\n            if (this.discardOpen) return;\n            if (this.dirty) {\n                // Deferred: the modal shell's own window-escape handler runs\n                // later in the same keydown and would instantly re-close it.\n                this.$nextTick(() => { this.discardOpen = true; });\n                return;\n            }\n            this.closeEditor();\n        },\n\n        closeEditor() {\n            this.editing = false;\n            this.error = '';\n            this.shapeChangeOpen = false;\n            this.pendingShape = null;\n            this.brushCursor = null;\n            this.$dispatch('highlight-editor-mode', {open: false});\n        },\n\n        keepEditing() {\n            this.discardOpen = false;\n            this._pendingNavHref = null;\n        },\n\n        confirmDiscard() {\n            const target = this._pendingNavHref;\n            this._pendingNavHref = null;\n            this.discardOpen = false;\n            this.closeEditor();\n            if (target) window.location.href = target;\n        },\n\n        removeEdits() {\n            this.highlightRect = EMPTY_RECT;\n            this.polygonPoints = [];\n            this.polygonClosed = false;\n            this.polygonPointer = null;\n            this.polygonError = '';\n            this.cropRect = EMPTY_RECT;\n            this.guideVisible = false;\n            this.zoom = 1;\n            this.panX = 0;\n            this.panY = 0;\n        },\n\n        serializeOperations() {\n            const operations = [];\n            if (this.cropRect) {\n                operations.push({\n                    type: 'crop_rect',\n                    version: 1,\n                    params: {rect: this.serializedRect(this.cropRect)},\n                });\n            }\n            if (this.highlightRect) {\n                operations.push({\n                    type: 'highlight_rect',\n                    version: 1,\n                    params: {\n                        rect: this.serializedRect(this.highlightRect),\n                        corner_radius_ratio: rounded(this.cornerRadius / 100),\n                        outside: {\n                            blur_sigma_ratio: rounded((this.strength / 100) * STRENGTH_MAX_BLUR),\n                            darken: rounded((this.strength / 100) * STRENGTH_MAX_DARKEN),\n                        },\n                        stroke: {\n                            width_ratio: rounded(this.borderWidth / 1000),\n                            color: this.borderColor.toUpperCase(),\n                            opacity: 1,\n                        },\n                    },\n                });\n            } else if (this.polygonClosed && this.polygonPoints.length >= 3) {\n                operations.push({\n                    type: 'highlight_polygon',\n                    version: 1,\n                    params: {\n                        points: this.polygonPoints.map((point) => this.serializedPoint(point)),\n                        outside: {\n                            blur_sigma_ratio: rounded((this.strength / 100) * STRENGTH_MAX_BLUR),\n                            darken: rounded((this.strength / 100) * STRENGTH_MAX_DARKEN),\n                        },\n                        stroke: {\n                            width_ratio: rounded(this.borderWidth / 1000),\n                            color: this.borderColor.toUpperCase(),\n                            opacity: 1,\n                        },\n                    },\n                });\n            }\n            if (!operations.length) return {};\n            return {\n                schema_version: 1,\n                operations,\n            };\n        },\n\n        serializedRect(rect) {\n            return {\n                x: rounded(rect.x),\n                y: rounded(rect.y),\n                width: rounded(rect.width),\n                height: rounded(rect.height),\n            };\n        },\n\n        serializedPoint(point) {\n            return {x: rounded(point.x), y: rounded(point.y)};\n        },\n\n        removeCurrentTool() {\n            if (this.activeTool === 'highlight' && this.highlightShape === 'polygon') {\n                this.polygonPoints = [];\n                this.polygonClosed = false;\n                this.polygonPointer = null;\n                this.polygonError = '';\n            } else {\n                this.rect = EMPTY_RECT;\n            }\n            this.guideVisible = false;\n        },\n\n        async apply() {\n            if (!this.canApply) return;\n            this.applying = true;\n            this.error = '';\n            try {\n                const response = await fetch(this.config.applyUrl, {\n                    method: 'POST',\n                    credentials: 'same-origin',\n                    headers: {\n                        'Content-Type': 'application/json',\n                        'X-CSRFToken': this.csrfToken(),\n                        'Accept': 'application/json',\n                    },\n                    body: JSON.stringify({\n                        revision: this.config.revisionId || '',\n                        version: Number(this.config.version || 0),\n                        operations: this.serializeOperations(),\n                    }),\n                });\n                if (!response.ok) {\n                    let message = this.message('applyFailed', 'Die \u00C4nderungen konnten nicht angewendet werden.');\n                    try {\n                        const body = await response.json();\n                        if (body.error === 'ai_edit_active') {\n                            message = this.message('aiBusy', 'F\u00FCr dieses Foto l\u00E4uft gerade eine KI-Bearbeitung.');\n                        } else if (body.error === 'stale_version') {\n                            message = this.message('stale', 'Das Foto wurde zwischenzeitlich ge\u00E4ndert. Bitte laden Sie die Seite neu.');\n                        } else if (body.message) {\n                            message = body.message;\n                        }\n                    } catch (_) {}\n                    throw new Error(message);\n                }\n                const body = await response.json();\n                this.config.version = body.version;\n                this.config.operations = body.operations;\n                this.config.hasEdits = body.hasEdits;\n                this.loadOperations(body.operations);\n                this._initialOperations = JSON.stringify(this.serializeOperations());\n                this.updatePublishedImage(body);\n                this.$dispatch('revision-image-updated', {id: this.config.revisionId, url: body.imageUrl});\n                this.applying = false;\n                this.closeEditor();\n            } catch (error) {\n                this.error = error.message || this.message('applyFailed', 'Die \u00C4nderungen konnten nicht angewendet werden.');\n                this.applying = false;\n            }\n        },\n\n        updatePublishedImage(body) {\n            const image = document.getElementById('photo-image');\n            if (image) {\n                image.src = body.imageUrl;\n                const lightboxLink = image.closest('a');\n                if (lightboxLink) {\n                    lightboxLink.href = body.imageUrl;\n                    if (body.imageWidth && body.imageHeight) {\n                        lightboxLink.style.aspectRatio = body.imageWidth + '/' + body.imageHeight;\n                    }\n                }\n            }\n            document.querySelectorAll('[data-photo-current-image]').forEach((el) => {\n                el.src = body.imageUrl;\n            });\n            const id = this.config.revisionId;\n            if (!id) return;\n            document.querySelectorAll(`[data-manually-edited=\"${id}\"]`).forEach((el) => {\n                el.style.display = body.hasEdits ? '' : 'none';\n            });\n            document.querySelectorAll(`[data-revision-image=\"${id}\"]`).forEach((el) => {\n                el.src = body.imageUrl;\n            });\n            document.querySelectorAll(`[data-revision-link=\"${id}\"]`).forEach((el) => {\n                el.href = body.imageUrl;\n            });\n        },\n\n        message(key, fallback) {\n            return this.config.messages?.[key] || fallback;\n        },\n\n        csrfToken() {\n            return document.querySelector('meta[name=\"csrf-token\"]')?.content || '';\n        },\n\n        startDraw(event) {\n            if (this.activeTool === 'highlight' && this.highlightShape === 'polygon') {\n                this.startPolygonPoint(event);\n                return;\n            }\n            if (this.brushActive) {\n                this.startBrushStroke(event);\n                return;\n            }\n            if (this.pointerCount > 0) {\n                this.trackPointer(event);\n                this.beginPinchIfNeeded();\n                return;\n            }\n            this.trackPointer(event);\n            const point = this.imagePoint(event);\n            this._gesture = 'draw';\n            this._gestureStart = {\n                point,\n                client: {x: event.clientX, y: event.clientY},\n                originalRect: this.rect ? {...this.rect} : null,\n                moved: false,\n            };\n            this.capturePointer(event);\n        },\n\n        startMove(event) {\n            this.trackPointer(event);\n            if (this.pointerCount > 1) {\n                this.beginPinchIfNeeded();\n                return;\n            }\n            this.guideVisible = true;\n            this._gesture = 'move';\n            this._gestureStart = {point: this.imagePoint(event), rect: {...this.rect}};\n            this.capturePointer(event);\n        },\n\n        startResize(event, handle) {\n            this.trackPointer(event);\n            if (this.pointerCount > 1) {\n                this.beginPinchIfNeeded();\n                return;\n            }\n            this.guideVisible = true;\n            this._gesture = 'resize';\n            this._gestureStart = {\n                point: this.imagePoint(event),\n                rect: {...this.rect},\n                handle: handle || 'br',\n            };\n            this.capturePointer(event);\n        },\n\n        pointerMove(event) {\n            if (this.brushActive) this.trackBrushCursor(event);\n            if (!this._pointers[event.pointerId]) {\n                if (\n                    this.activeTool === 'highlight'\n                    && this.highlightShape === 'polygon'\n                    && !this.polygonClosed\n                    && this.polygonPoints.length\n                    && event.pointerType !== 'touch'\n                ) this.polygonPointer = this.imagePoint(event);\n                return;\n            }\n            this.trackPointer(event);\n            if (this.pointerCount > 1 || this._gesture === 'pinch') {\n                this.updatePinch();\n                return;\n            }\n            if (!this._gestureStart) return;\n            if (this._gesture === 'brush') {\n                this.extendBrushStroke(event);\n                return;\n            }\n            const point = this.imagePoint(event);\n            if (this._gesture === 'draw') this.updateDraw(event, point);\n            else if (this._gesture === 'move') this.updateMove(point);\n            else if (this._gesture === 'resize') this.updateResize(point);\n            else if (this._gesture === 'polygon-point') {\n                this.polygonPointer = point;\n                this.markPolygonGestureMoved(event, 8);\n            } else if (this._gesture === 'polygon-close') {\n                this.markPolygonGestureMoved(event, 8);\n            } else if (this._gesture === 'polygon-vertex') {\n                this.markPolygonGestureMoved(event, 3);\n                if (this._gestureStart.moved) {\n                    this.polygonPoints[this._gestureStart.index] = point;\n                }\n            } else if (this._gesture === 'polygon-move') {\n                this.updatePolygonMove(point);\n            }\n        },\n\n        pointerEnd(event) {\n            const gesture = this._gesture;\n            const wasDraw = this._gesture === 'draw';\n            const moved = this._gestureStart?.moved;\n            const originalRect = this._gestureStart?.originalRect;\n            delete this._pointers[event.pointerId];\n\n            if (this._gesture === 'pinch') {\n                if (this.pointerCount < 2) this._gestureStart = null;\n                if (this.pointerCount === 0) this._gesture = null;\n                return;\n            }\n            if (gesture?.startsWith('polygon-')) {\n                this.finishPolygonGesture(event, gesture);\n                return;\n            }\n            if (gesture === 'brush') {\n                this.finishBrushStroke(event);\n                return;\n            }\n            if (wasDraw && !moved) {\n                this.rect = originalRect;\n                this.guideVisible = false;\n            } else if (wasDraw && this.rect) {\n                // A straight-line drag yields a zero-size rect: invisible, yet\n                // it would enable Apply and be rejected by the server.\n                const minimum = this.minimumSelectionSize;\n                if (this.rect.width < minimum.x || this.rect.height < minimum.y) {\n                    this.rect = originalRect;\n                    this.guideVisible = Boolean(originalRect);\n                } else {\n                    this.guideVisible = true;\n                }\n            } else if (this.rect) {\n                this.guideVisible = true;\n            }\n            this._gesture = null;\n            this._gestureStart = null;\n        },\n\n        updateDraw(event, point) {\n            const distanceMoved = Math.hypot(\n                event.clientX - this._gestureStart.client.x,\n                event.clientY - this._gestureStart.client.y\n            );\n            if (!this._gestureStart.moved && distanceMoved < 5) return;\n            this._gestureStart.moved = true;\n            const start = this._gestureStart.point;\n            this.rect = {\n                x: clamp(Math.min(start.x, point.x), 0, 1),\n                y: clamp(Math.min(start.y, point.y), 0, 1),\n                width: Math.abs(point.x - start.x),\n                height: Math.abs(point.y - start.y),\n            };\n            this.guideVisible = true;\n        },\n\n        updateMove(point) {\n            const start = this._gestureStart;\n            const dx = point.x - start.point.x;\n            const dy = point.y - start.point.y;\n            this.rect = {\n                ...start.rect,\n                x: clamp(start.rect.x + dx, 0, 1 - start.rect.width),\n                y: clamp(start.rect.y + dy, 0, 1 - start.rect.height),\n            };\n        },\n\n        updateResize(point) {\n            const original = this._gestureStart.rect;\n            const handle = this._gestureStart.handle;\n            const minimum = this.minimumSelectionSize;\n            let left = original.x;\n            let top = original.y;\n            let right = original.x + original.width;\n            let bottom = original.y + original.height;\n            if (handle.includes('l')) left = clamp(point.x, 0, Math.max(0, right - minimum.x));\n            else if (handle.includes('r')) right = clamp(point.x, left + minimum.x, 1);\n            if (handle.includes('t')) top = clamp(point.y, 0, Math.max(0, bottom - minimum.y));\n            else if (handle.includes('b')) bottom = clamp(point.y, top + minimum.y, 1);\n            this.rect = {x: left, y: top, width: right - left, height: bottom - top};\n        },\n\n        trackPointer(event) {\n            this._pointers[event.pointerId] = {x: event.clientX, y: event.clientY};\n        },\n\n        capturePointer(event) {\n            try { this._viewportElement?.setPointerCapture(event.pointerId); } catch (_) {}\n        },\n\n        beginPinchIfNeeded() {\n            if (this.pointerCount < 2) return;\n            if (!this._viewportElement) return;\n            if (this._gestureStart?.originalPoints) {\n                this.polygonPoints = this._gestureStart.originalPoints;\n            }\n            if (this._gesture === 'brush') this.cancelBrushStroke();\n            this.polygonPointer = null;\n            const points = Object.values(this._pointers).slice(0, 2);\n            const viewport = this._viewportElement.getBoundingClientRect();\n            const mid = midpoint(points[0], points[1]);\n            this._gesture = 'pinch';\n            this._gestureStart = {\n                distance: Math.max(1, distance(points[0], points[1])),\n                midpoint: {x: mid.x - viewport.left, y: mid.y - viewport.top},\n                zoom: this.zoom,\n                panX: this.panX,\n                panY: this.panY,\n            };\n        },\n\n        updatePinch() {\n            if (this.pointerCount < 2) return;\n            if (this._gesture !== 'pinch') this.beginPinchIfNeeded();\n            if (!this._gestureStart || !this._viewportElement) return;\n            const points = Object.values(this._pointers).slice(0, 2);\n            const viewport = this._viewportElement.getBoundingClientRect();\n            const mid = midpoint(points[0], points[1]);\n            const currentMid = {x: mid.x - viewport.left, y: mid.y - viewport.top};\n            const nextZoom = clamp(\n                this._gestureStart.zoom * distance(points[0], points[1]) / this._gestureStart.distance,\n                1,\n                4\n            );\n            const anchorX = (this._gestureStart.midpoint.x - this._gestureStart.panX) / this._gestureStart.zoom;\n            const anchorY = (this._gestureStart.midpoint.y - this._gestureStart.panY) / this._gestureStart.zoom;\n            this.zoom = nextZoom;\n            this.panX = currentMid.x - anchorX * nextZoom;\n            this.panY = currentMid.y - anchorY * nextZoom;\n            this.clampPan();\n        },\n\n        clampPan() {\n            if (!this._viewportElement) return;\n            const viewport = this._viewportElement.getBoundingClientRect();\n            this.panX = clamp(this.panX, viewport.width * (1 - this.zoom), 0);\n            this.panY = clamp(this.panY, viewport.height * (1 - this.zoom), 0);\n        },\n\n        resetZoom() {\n            this.zoom = 1;\n            this.panX = 0;\n            this.panY = 0;\n        },\n\n        imagePoint(event) {\n            if (!this._viewportElement) return {x: 0, y: 0};\n            const viewport = this._viewportElement.getBoundingClientRect();\n            return {\n                x: clamp((event.clientX - viewport.left - this.panX) / (viewport.width * this.zoom), 0, 1),\n                y: clamp((event.clientY - viewport.top - this.panY) / (viewport.height * this.zoom), 0, 1),\n            };\n        },\n\n        get pointerCount() {\n            return Object.keys(this._pointers).length;\n        },\n\n        get rect() {\n            if (this.activeTool === 'crop') return this.cropRect;\n            return this.highlightShape === 'rect' ? this.highlightRect : EMPTY_RECT;\n        },\n\n        set rect(value) {\n            if (this.activeTool === 'crop') this.cropRect = value;\n            else this.highlightRect = value;\n        },\n\n        get dirty() {\n            const polygonDraft = (\n                this.highlightShape === 'polygon'\n                && this.polygonPoints.length > 0\n                && !this.polygonClosed\n            );\n            const current = this.mode === 'manual' ? this.serializeOperations() : this.maskShape();\n            return polygonDraft || JSON.stringify(current) !== this._initialOperations;\n        },\n\n        get canApply() {\n            // Mask mode never applies; skipping it here also keeps the button\n            // binding from serializing every brush point on each move.\n            if (this.mode !== 'manual') return false;\n            const polygonInProgress = (\n                this.highlightShape === 'polygon'\n                && this.polygonPoints.length > 0\n                && !this.polygonClosed\n            );\n            return this.dirty && !this.applying && !polygonInProgress;\n        },\n\n        get canConfirmMask() {\n            if (this.highlightShape === 'brush') return this.brushStrokes.length > 0;\n            const polygonInProgress = (\n                this.highlightShape === 'polygon'\n                && this.polygonPoints.length > 0\n                && !this.polygonClosed\n            );\n            return Boolean(this.maskShape()) && !polygonInProgress;\n        },\n\n        get brushActive() {\n            return this.activeTool === 'highlight' && this.highlightShape === 'brush';\n        },\n\n        // True when the current shape holds something a shape switch would lose.\n        get hasShapeContent() {\n            if (this.highlightShape === 'brush') return this.brushStrokes.length > 0;\n            if (this.highlightShape === 'polygon') return this.polygonPoints.length > 0;\n            return Boolean(this.highlightRect);\n        },\n\n        // The shape-change dialog discards instead of converting.\n        get brushShapeChange() {\n            return this.highlightShape === 'brush' || this.pendingShape === 'brush';\n        },\n\n        // The action row below the photo: one undo and one reset for the\n        // free form and the brush.\n        get canUndoShapeStep() {\n            if (this.highlightShape === 'brush') return this.brushStrokes.length > 0;\n            if (this.highlightShape === 'polygon') return this.polygonPoints.length > 0;\n            return false;\n        },\n\n        get shapeError() {\n            return this.highlightShape === 'brush' ? this.brushError : this.polygonError;\n        },\n\n        undoShapeStep() {\n            if (this.highlightShape === 'brush') this.undoBrushStroke();\n            else if (this.highlightShape === 'polygon') this.undoPolygonPoint();\n        },\n\n        resetShape() {\n            if (this.highlightShape === 'brush') this.resetBrush();\n            else if (this.highlightShape === 'polygon') this.resetPolygon();\n        },\n\n        // Shortcuts while the editor is open, so closing and undoing do not\n        // depend on a control: Enter closes an open free form, Backspace and\n        // Delete undo the last point or stroke. Escape has its own handler.\n        handleEditorKey(event) {\n            if (!this.editing || this.applying || this.discardOpen || this.shapeChangeOpen) return;\n            if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey) return;\n            if (this.activeTool !== 'highlight') return;\n            const target = event.target;\n            const nonTextInputs = ['range', 'color', 'checkbox', 'radio', 'button', 'submit', 'reset'];\n            if (\n                target?.isContentEditable\n                || ['TEXTAREA', 'SELECT'].includes(target?.tagName)\n                || (target?.tagName === 'INPUT' && !nonTextInputs.includes(target.type))\n            ) return;\n            if (event.key === 'Enter') {\n                // Enter on a focused button or link is that control's click.\n                if (['BUTTON', 'A'].includes(target?.tagName)) return;\n                if (this.highlightShape !== 'polygon' || this.polygonClosed || this.polygonPoints.length < 3) return;\n                event.preventDefault();\n                this.closePolygon();\n            } else if (event.key === 'Backspace' || event.key === 'Delete') {\n                if (!this.canUndoShapeStep) return;\n                event.preventDefault();\n                this.undoShapeStep();\n            }\n        },\n\n        get hasHighlightSelection() {\n            return Boolean(\n                this.highlightRect\n                || (this.polygonClosed && this.polygonPoints.length >= 3)\n            );\n        },\n\n        get hasSavedEdits() {\n            return Boolean(this.config.hasEdits);\n        },\n\n        get stageBaseUrl() {\n            return this.baseOverride?.url || this.config.baseUrl;\n        },\n\n        // Pixel size of the image the editor draws on; 4:3 when unknown.\n        get imageSize() {\n            const source = this.baseOverride || this.config;\n            const width = Number(source.width);\n            const height = Number(source.height);\n            return width > 0 && height > 0 ? {width, height} : {width: 4000, height: 3000};\n        },\n\n        get imageViewBox() {\n            return '0 0 ' + this.imageSize.width + ' ' + this.imageSize.height;\n        },\n\n        // The stage keeps the photo's aspect, and its height is capped so the\n        // controls below a portrait photo stay on screen. The cap narrows the\n        // stage, which stays centred in the dark frame.\n        get viewportStyle() {\n            const {width, height} = this.imageSize;\n            const ratio = width / height;\n            return 'aspect-ratio:' + width + '/' + height\n                + ';max-width:calc(max(100vh - ' + STAGE_RESERVED_HEIGHT + ', ' + STAGE_MIN_HEIGHT + ') * ' + ratio + ')'\n                + ';max-width:calc(max(100dvh - ' + STAGE_RESERVED_HEIGHT + ', ' + STAGE_MIN_HEIGHT + ') * ' + ratio + ')';\n        },\n\n        get brushMarkup() {\n            const {width, height} = this.imageSize;\n            return window.brushStrokesMarkup(this.brushStrokes, width, height);\n        },\n\n        get brushCursorRadius() {\n            return (this.brushSize / 100) * this.imageSize.width / 2;\n        },\n\n        get stageStyle() {\n            return 'transform:translate(' + this.panX + 'px,' + this.panY + 'px) scale(' + this.zoom + ');transform-origin:top left';\n        },\n\n        get outsideImageStyle() {\n            if (this.activeTool !== 'highlight' || !this.hasHighlightSelection) return '';\n            // A placement fills the space itself instead (selectionStyle and\n            // the polygon fill): the rest of the photo stays as it is.\n            if (this.mode === 'place') return '';\n            // A mask only dims the rest of the photo: blurring it would look\n            // like part of the requested change.\n            if (this.mode === 'mask') return 'filter:brightness(0.55)';\n            this.viewportTick;\n            const viewport = this._viewportElement?.getBoundingClientRect();\n            const shortEdge = viewport ? Math.min(viewport.width, viewport.height) : 600;\n            const blur = (this.strength / 100) * STRENGTH_MAX_BLUR * shortEdge;\n            const brightness = 1 - (this.strength / 100) * STRENGTH_MAX_DARKEN;\n            return 'filter:blur(' + blur + 'px) brightness(' + brightness + ')';\n        },\n\n        get sharpClipStyle() {\n            if (this.activeTool !== 'highlight' || !this.hasHighlightSelection || this.mode === 'place') return 'display:none';\n            if (this.highlightShape === 'polygon') {\n                return 'clip-path:polygon(' + this.polygonPoints.map((point) => (\n                    (point.x * 100) + '% ' + (point.y * 100) + '%'\n                )).join(',') + ')';\n            }\n            const top = this.highlightRect.y * 100;\n            const right = (1 - this.highlightRect.x - this.highlightRect.width) * 100;\n            const bottom = (1 - this.highlightRect.y - this.highlightRect.height) * 100;\n            const left = this.highlightRect.x * 100;\n            return 'clip-path:inset(' + top + '% ' + right + '% ' + bottom + '% ' + left + '% round ' + this.selectionRadiusPx + 'px)';\n        },\n\n        get selectionStyle() {\n            if (!this.rect) return 'display:none';\n            // The primary colour at 35%: the space the piece will take.\n            const fill = this.mode === 'place' && this.activeTool === 'highlight' ? ';background-color:rgba(31,59,97,0.35)' : '';\n            return 'left:' + (this.rect.x * 100) + '%;top:' + (this.rect.y * 100) + '%;width:' + (this.rect.width * 100) + '%;height:' + (this.rect.height * 100) + '%;border-radius:' + this.selectionRadiusPx + 'px' + fill;\n        },\n\n        get outputBorderStyle() {\n            return this.selectionStyle + ';border:' + this.outputBorderPx + 'px solid ' + this.borderColor;\n        },\n\n        get polygonSvgPoints() {\n            return this.polygonPoints.map((point) => (\n                (point.x * 100) + ',' + (point.y * 100)\n            )).join(' ');\n        },\n\n        get openPolygonSvgPoints() {\n            const points = [...this.polygonPoints];\n            if (!this.polygonClosed && this.polygonPointer && points.length) {\n                points.push(this.polygonPointer);\n            }\n            return points.map((point) => (\n                (point.x * 100) + ',' + (point.y * 100)\n            )).join(' ');\n        },\n\n        polygonHandleStyle(point) {\n            return 'left:' + (point.x * 100) + '%;top:' + (point.y * 100)\n                + '%;width:' + (44 / this.zoom) + 'px;height:' + (44 / this.zoom)\n                + 'px;transform:translate(-50%,-50%)';\n        },\n\n        get cropShadeStyle() {\n            return this.activeTool === 'crop' && this.cropRect\n                ? 'border:' + (1 / this.zoom) + 'px solid white;box-shadow:0 0 0 9999px rgba(0,0,0,0.62)'\n                : 'display:none';\n        },\n\n        get selectionRadiusPx() {\n            if (this.activeTool === 'crop' || !this.rect || !this._viewportElement) return 0;\n            this.viewportTick;\n            const viewport = this._viewportElement.getBoundingClientRect();\n            return (this.cornerRadius / 100) * Math.min(\n                this.rect.width * viewport.width,\n                this.rect.height * viewport.height\n            );\n        },\n\n        get outputBorderPx() {\n            if (!this._viewportElement) return 0;\n            this.viewportTick;\n            const viewport = this._viewportElement.getBoundingClientRect();\n            return (this.borderWidth / 1000) * Math.min(viewport.width, viewport.height);\n        },\n\n        get guideBorderPx() {\n            return 2 / this.zoom;\n        },\n\n        get handleStyle() {\n            return 'width:' + (36 / this.zoom) + 'px;height:' + (36 / this.zoom) + 'px';\n        },\n\n        get handleDotStyle() {\n            return 'width:' + (12 / this.zoom) + 'px;height:' + (12 / this.zoom) + 'px;border-width:' + (2 / this.zoom) + 'px';\n        },\n\n        get minimumSelectionSize() {\n            if (!this._viewportElement) return {x: 0.03, y: 0.03};\n            const viewport = this._viewportElement.getBoundingClientRect();\n            return {\n                x: Math.min(0.2, 32 / (viewport.width * this.zoom)),\n                y: Math.min(0.2, 32 / (viewport.height * this.zoom)),\n            };\n        },\n    }));\n});\n", "document.addEventListener('alpine:init', () => {\n    const I18N = window.I18N || {};\n\n    Alpine.data('batchOutdoorPresets', () => ({\n        outdoor: { optimize: true, sunshine: true, declutter: true },\n        init() {\n            const prefs = JSON.parse(this.$el.dataset.presetPrefs || '{}');\n            if (prefs.outdoor) {\n                this.outdoor.optimize = prefs.outdoor.includes('optimize');\n                this.outdoor.sunshine = prefs.outdoor.includes('sunshine');\n                this.outdoor.declutter = prefs.outdoor.includes('declutter');\n            }\n        },\n    }));\n\n    Alpine.data('batchIndoorPresets', () => ({\n        indoor: { optimize_indoor: true, declutter_indoor: true, details: true, staging: true },\n        replaceFurniture: false,\n        furnitureStyle: '',\n        furnitureInstructions: '',\n        styleOpen: false,\n        styleOptions: [\n            {v:'',l:I18N.styleUnspecified || 'Keine Vorgabe'}, {v:'modern',l:'Modern'},\n            {v:'skandinavisch',l:I18N.styleScandinavian || 'Skandinavisch'}, {v:'klassisch',l:I18N.styleClassic || 'Klassisch'},\n            {v:'landhausstil',l:I18N.styleCountry || 'Landhausstil'}, {v:'industriell',l:I18N.styleIndustrial || 'Industriell'},\n        ],\n        init() {\n            const prefs = JSON.parse(this.$el.dataset.presetPrefs || '{}');\n            if (prefs.indoor) {\n                this.indoor.optimize_indoor = prefs.indoor.includes('optimize_indoor');\n                this.indoor.declutter_indoor = prefs.indoor.includes('declutter_indoor');\n                this.indoor.details = prefs.indoor.includes('details');\n                this.indoor.staging = prefs.indoor.includes('staging');\n            }\n            if (typeof prefs.replace_furniture === 'boolean')\n                this.replaceFurniture = prefs.replace_furniture;\n            if (prefs.furniture_style)\n                this.furnitureStyle = prefs.furniture_style;\n        },\n    }));\n\n    // One \"Einheitlich m\u00F6blieren\" line in the batch modal: the photos of one\n    // room, staged as one set while ticked. A tap on a thumbnail takes that\n    // photo out of the room on the server too, so it is staged alone and the\n    // correction is recorded.\n    Alpine.data('roomSetLine', () => ({\n        checked: true,\n        photoIds: [],\n        removed: [],\n        countTemplate: '{n}',\n        removeUrlTemplate: '',\n\n        init() {\n            const data = this.$root.dataset;\n            this.photoIds = (data.photoIds || '').split(',').filter(Boolean);\n            this.countTemplate = data.countTemplate || '{n}';\n            this.removeUrlTemplate = data.removeUrlTemplate || '';\n        },\n\n        get activeIds() {\n            return this.photoIds.filter(id => !this.removed.includes(id));\n        },\n\n        get countText() {\n            return this.countTemplate.replace('{n}', this.activeIds.length);\n        },\n\n        remove(photoId) {\n            if (this.removed.includes(photoId)) return;\n            this.removed.push(photoId);\n            if (this.activeIds.length < 2) this.checked = false;\n            const url = this.removeUrlTemplate.replace('PHOTO_ID', photoId);\n            const meta = document.querySelector('meta[name=\"csrf-token\"]');\n            fetch(url, {\n                method: 'POST',\n                headers: { 'X-CSRFToken': meta ? meta.content : '', 'X-Requested-With': 'XMLHttpRequest' },\n            }).catch(err => console.error(err));\n        },\n    }));\n\n    Alpine.data('batchSelect', () => ({\n        selectedPhotos: {},\n        modalOpen: false,\n        loading: false,\n        modalUrl: '',\n        upscaleUrl: '',\n        downloadUrl: '',\n        deleteUrl: '',\n        csrf: '',\n\n        get count() { return Object.keys(this.selectedPhotos).length; },\n        get hasSelection() { return this.count > 0; },\n        get countLabel() {\n            return this.count === 1 ? (I18N.photoSelectedSingular || '1 Foto ausgew\u00E4hlt') : this.count + ' ' + (I18N.photosSelectedPlural || 'Fotos ausgew\u00E4hlt');\n        },\n\n        togglePhoto(id, checked) {\n            if (checked) this.selectedPhotos[id] = true;\n            else delete this.selectedPhotos[id];\n        },\n\n        isSelectableCheckbox(checkbox) {\n            return !checkbox.disabled && checkbox.offsetParent !== null;\n        },\n\n        // A group is a type section under \"Nach Bereich\" and a status group\n        // under \"Nach Status\". Only one arrangement renders at a time.\n        getGroupCheckboxes(groupKey) {\n            const selector = '.batch-select-checkbox[data-photo-group=\"' + groupKey + '\"]';\n            return Array.from(this.$root.querySelectorAll(selector))\n                .filter(cb => this.isSelectableCheckbox(cb));\n        },\n\n        isGroupFullySelected(groupKey) {\n            const checkboxes = this.getGroupCheckboxes(groupKey);\n            return checkboxes.length > 0 && checkboxes.every(cb => this.isSelected(cb.dataset.photoId));\n        },\n\n        isGroupPartiallySelected(groupKey) {\n            const checkboxes = this.getGroupCheckboxes(groupKey);\n            if (!checkboxes.length) return false;\n            const selectedCount = checkboxes.filter(cb => this.isSelected(cb.dataset.photoId)).length;\n            return selectedCount > 0 && selectedCount < checkboxes.length;\n        },\n\n        toggleGroup(groupKey, checked) {\n            this.getGroupCheckboxes(groupKey).forEach(cb => {\n                cb.checked = checked;\n                this.togglePhoto(cb.dataset.photoId, checked);\n            });\n        },\n\n        isSelected(id) { return !!this.selectedPhotos[id]; },\n\n        clearSelection() {\n            this.selectedPhotos = {};\n            this.$root.querySelectorAll('.batch-select-checkbox').forEach(cb => { cb.checked = false; });\n        },\n\n        // The container keeps the last modal after it closes. Empty it before the\n        // next one loads: else the old form shows at once and can be submitted,\n        // and the new form that replaces it can be submitted a second time.\n        clearModalContent() {\n            document.getElementById('batch-modal-content').replaceChildren();\n        },\n\n        openModal() {\n            if (!this.hasSelection || this.loading) return;\n            this.loading = true;\n            this.clearModalContent();\n            this.modalOpen = true;\n            const params = Object.keys(this.selectedPhotos).map(id => 'photos=' + encodeURIComponent(id)).join('&');\n            htmx.ajax('GET', this.modalUrl + '?' + params, { target: '#batch-modal-content', swap: 'innerHTML' })\n                .finally(() => { this.loading = false; });\n        },\n\n        openUpscaleModal() {\n            if (!this.hasSelection || this.loading) return;\n            this.loading = true;\n            this.clearModalContent();\n            this.modalOpen = true;\n            const params = Object.keys(this.selectedPhotos).map(id => 'photos=' + encodeURIComponent(id)).join('&');\n            htmx.ajax('GET', this.upscaleUrl + '?' + params, { target: '#batch-modal-content', swap: 'innerHTML' })\n                .finally(() => { this.loading = false; });\n        },\n\n        openDownloadModal() {\n            if (this.loading) return;\n            this.loading = true;\n            const params = Object.keys(this.selectedPhotos).map(id => 'photos=' + encodeURIComponent(id)).join('&');\n            const url = this.downloadUrl + (params ? '?' + params : '');\n            htmx.ajax('GET', url, { target: '#download-modal-container', swap: 'innerHTML' })\n                .finally(() => { this.loading = false; });\n        },\n\n        closeModal() { this.modalOpen = false; },\n        closeAndRefresh() { this.modalOpen = false; this.clearSelection(); htmx.trigger('#photo-grid', 'refresh'); },\n\n        openDeleteModal() {\n            const photoIds = Object.keys(this.selectedPhotos);\n            if (!photoIds.length) return;\n            Alpine.store('modal')?.show('batch-photo-delete', {\n                count: photoIds.length,\n                deleting: false,\n                onCancel: () => this.closeDeleteModal(),\n                onConfirm: () => this.submitDelete(photoIds),\n            });\n        },\n\n        closeDeleteModal() {\n            const modal = Alpine.store('modal');\n            if (modal?.context?.deleting) return;\n            modal?.close();\n        },\n\n        submitDelete(photoIds) {\n            const modal = Alpine.store('modal');\n            const context = modal?.context;\n            if (!photoIds?.length || context?.deleting) return;\n            context.deleting = true;\n            const url = this.deleteUrl;\n            const csrf = this.csrf;\n            const formData = new FormData();\n            photoIds.forEach(id => formData.append('photo_ids', id));\n            fetch(url, {\n                method: 'POST',\n                body: formData,\n                headers: { 'X-CSRFToken': csrf, 'X-Requested-With': 'XMLHttpRequest' },\n            })\n                .then(r => { if (!r.ok) throw new Error('Delete failed (' + r.status + ')'); })\n                .then(() => {\n                    Alpine.store('modal')?.close();\n                    this.clearSelection();\n                    htmx.trigger('#photo-grid', 'refresh');\n                })\n                .catch(err => {\n                    console.error(err);\n                    window.alert(I18N.deleteFailed || 'L\u00F6schen fehlgeschlagen. Bitte erneut versuchen.');\n                })\n                .finally(() => {\n                    if (modal?.context === context) context.deleting = false;\n                });\n        },\n\n        init() {\n            this.modalUrl = this.$root.dataset.modalUrl;\n            this.upscaleUrl = this.$root.dataset.upscaleUrl;\n            this.downloadUrl = this.$root.dataset.downloadUrl;\n            this.deleteUrl = this.$root.dataset.deleteUrl;\n            this.csrf = this.$root.dataset.csrf;\n\n            // Listen for batch clear events from HTMX-swapped partials\n            this._onBatchClear = () => this.clearSelection();\n            document.addEventListener('batch:clear', this._onBatchClear);\n\n            // Re-check boxes after HTMX swaps photo cards or re-renders the\n            // whole grid (arrangement switch, completion refresh). Selection\n            // lives on this component, which sits outside #photo-grid.\n            this.$root.addEventListener('htmx:afterSwap', () => {\n                this.$root.querySelectorAll('.batch-select-checkbox').forEach(cb => {\n                    cb.checked = this.isSelected(cb.dataset.photoId);\n                });\n                // Force Alpine to recompute section header tri-state bindings.\n                this.selectedPhotos = { ...this.selectedPhotos };\n            });\n        },\n\n        destroy() {\n            document.removeEventListener('batch:clear', this._onBatchClear);\n        },\n    }));\n\n});\n", "// The social media post panel in the property export modal\n// (dashboard/partials/social_post_panel.html, docs/code/social-posts.md).\n//\n// Every slide is fetched once, as a File, when the panel opens. A tap on\n// \"Teilen\" must call navigator.share inside the user gesture: iOS WebKit\n// rejects a share that follows an await. The tap also copies the post text\n// first, because Instagram ignores text from the share menu.\n\n// Three slides at a time, so one panel does not hold every server thread.\nconst PARALLEL_FETCHES = 3;\n// The cover is fetched again once the agent stops typing the label.\nconst LABEL_DELAY_MS = 600;\nconst MAX_LABEL_LENGTH = 30;\n\nfunction canShareFiles() {\n    if (!window.navigator.canShare || !window.File) return false;\n    try {\n        return window.navigator.canShare({ files: [new window.File([''], 'test.jpg', { type: 'image/jpeg' })] });\n    } catch (_) {\n        return false;\n    }\n}\n\nasync function fetchSlide(url, name) {\n    const response = await fetch(url, { credentials: 'same-origin' });\n    if (!response.ok) {\n        const error = new Error('Slide ' + name + ' failed (' + response.status + ')');\n        // 404: the photo file is gone. Anything else may work the next time.\n        error.status = response.status;\n        throw error;\n    }\n    const blob = await response.blob();\n    return new window.File([blob], name, { type: 'image/jpeg' });\n}\n\n// The generated price line out of an edited text, with its line break. A\n// price line alone in its block would leave two blank lines behind.\nfunction withoutLine(text, line) {\n    const lines = text.split('\\n');\n    const position = lines.indexOf(line);\n    if (!line || position === -1) return text;\n    lines.splice(position, 1);\n    if (lines[position - 1] === '' && lines[position] === '') lines.splice(position, 1);\n    return lines.join('\\n');\n}\n\n// The label as the server cleans it (social_posts.clean_label): one line, no\n// control or format characters, at most 30 code points. Cover and text then\n// agree, also for emoji and zero-width characters.\nfunction cleanLabel(value) {\n    const text = value.replace(/\\s+/g, ' ').replace(/\\p{C}/gu, '').trim();\n    return Array.from(text).slice(0, MAX_LABEL_LENGTH).join('').trim();\n}\n\n// Everything after the first line of a generated text.\nfunction afterFirstLine(text) {\n    const position = text.indexOf('\\n');\n    return position === -1 ? '' : text.slice(position);\n}\n\nfunction slideName(position) {\n    return String(position + 1).padStart(2, '0') + '.jpg';\n}\n\n// The price line back into an edited text, right after the generated facts\n// line. Without that line, the text stays as the agent left it.\nfunction withLineAfter(text, anchor, line) {\n    const lines = text.split('\\n');\n    const position = lines.indexOf(anchor);\n    if (!anchor || !line || position === -1 || lines.includes(line)) return text;\n    lines.splice(position + 1, 0, line);\n    return lines.join('\\n');\n}\n\ndocument.addEventListener('alpine:init', () => {\n    Alpine.data('socialPost', () => {\n        // Outside Alpine's reactive state: navigator.share needs the File\n        // objects themselves, not reactive proxies of them.\n        let files = [];\n        const objectUrls = new Set();\n        let destroyed = false;\n        let labelTimer = null;\n        // Only the newest cover request may change the cover.\n        let coverRequest = 0;\n        // The price and label choices the current cover file shows.\n        let rendered = {};\n\n        return {\n            slides: [],\n            index: 0,\n            text: '',\n            showPrice: false,\n            price: {},\n            downloadBase: '',\n            ready: false,\n            loaded: false,\n            hasCover: true,\n            label: '',\n            labelOn: true,\n            headline: '',\n            title: '',\n            cover: {},\n            loadFailed: false,\n            shareSupported: false,\n            copied: false,\n            // '' | 'copied' | 'failed': what the last share did with the text.\n            shareCopy: '',\n            shareFailed: false,\n            sharedUrl: '',\n\n            get firstLine() {\n                return this.text.split('\\n')[0];\n            },\n\n            get downloadUrl() {\n                const url = new URL(this.downloadBase, window.location.origin);\n                this.setChoices(url.searchParams);\n                return url.pathname + url.search;\n            },\n\n            // The price and label choices, as the slide and zip views read them.\n            setChoices(params) {\n                params.set('price', this.showPrice ? '1' : '0');\n                params.set('label_on', this.labelOn ? '1' : '0');\n                params.set('label', this.label);\n            },\n\n            coverUrl() {\n                const url = new URL(this.cover.path, window.location.origin);\n                url.searchParams.set('cover', '1');\n                this.setChoices(url.searchParams);\n                url.searchParams.set('v', this.cover.version);\n                return url.pathname + url.search;\n            },\n\n            // The first line the generator would write for the current label.\n            headlineFor() {\n                const label = this.labelOn ? cleanLabel(this.label) : '';\n                return label ? label + ': ' + this.title : this.title;\n            },\n\n            // The text the generator would write for the current label.\n            generated(withPrice) {\n                const caption = withPrice ? this.price.captionWith : this.price.captionWithout;\n                return this.headlineFor() + afterFirstLine(caption);\n            },\n\n            init() {\n                const data = this.$root.dataset;\n                this.slides = JSON.parse(data.slides || '[]').map(slide => ({ ...slide, src: '' }));\n                this.text = data.caption || '';\n                this.showPrice = data.showPrice === '1';\n                this.downloadBase = data.downloadUrl || '';\n                this.price = {\n                    captionWith: data.captionWithPrice || '',\n                    captionWithout: data.captionWithoutPrice || '',\n                    line: data.priceLine || '',\n                    factsLine: data.factsLine || '',\n                };\n                this.cover = { path: data.coverPath || '', version: data.coverVersion || '' };\n                this.label = data.label || '';\n                this.title = data.captionTitle || '';\n                this.headline = this.firstLine;\n                rendered = this.choices();\n                this.sharedUrl = data.sharedUrl || '';\n                this.shareSupported = canShareFiles();\n                // An edited text no longer matches the clipboard.\n                this.$watch('text', () => { this.shareCopy = ''; });\n                this.loadSlides();\n            },\n\n            destroy() {\n                destroyed = true;\n                objectUrls.forEach(url => URL.revokeObjectURL(url));\n            },\n\n            showSlide(position, file) {\n                if (destroyed) return;\n                const previous = this.slides[position].src;\n                const url = URL.createObjectURL(file);\n                objectUrls.add(url);\n                this.slides[position].src = url;\n                if (previous) {\n                    URL.revokeObjectURL(previous);\n                    objectUrls.delete(previous);\n                }\n            },\n\n            async loadSlides() {\n                const loaded = new Array(this.slides.length).fill(null);\n                let next = 0;\n                const worker = async () => {\n                    while (next < this.slides.length && !destroyed) {\n                        const position = next++;\n                        const slide = this.slides[position];\n                        try {\n                            loaded[position] = await fetchSlide(slide.url, slide.name);\n                            this.showSlide(position, loaded[position]);\n                        } catch (err) {\n                            // A missing photo file (404) or a failed render: the\n                            // post goes out without this slide.\n                            console.error(err);\n                        }\n                    }\n                };\n                const workers = [];\n                for (let count = 0; count < PARALLEL_FETCHES; count++) workers.push(worker());\n                await Promise.all(workers);\n                this.keepSlides(loaded);\n            },\n\n            // Keep the slides that have a file (null drops one) and number them\n            // again without gaps. Without the first slide the post has no cover,\n            // so the price switch has nothing to change any more.\n            keepSlides(loaded) {\n                if (destroyed) return;\n                const kept = [];\n                const keptFiles = [];\n                this.slides.forEach((slide, position) => {\n                    if (loaded[position]) {\n                        kept.push(slide);\n                        keptFiles.push(loaded[position]);\n                    } else if (slide.src) {\n                        URL.revokeObjectURL(slide.src);\n                        objectUrls.delete(slide.src);\n                    }\n                });\n                if (!loaded[0]) this.hasCover = false;\n                files = keptFiles.map((file, position) => new window.File(\n                    [file], slideName(position), { type: 'image/jpeg' },\n                ));\n                this.slides = kept.map((slide, position) => ({ ...slide, name: slideName(position) }));\n                this.index = 0;\n                this.ready = kept.length > 0;\n                this.loaded = true;\n                this.loadFailed = kept.length === 0;\n            },\n\n            choices() {\n                return { showPrice: this.showPrice, labelOn: this.labelOn, label: this.label };\n            },\n\n            // Fetch the cover for the current price and label. A newer request\n            // wins. A 404 means the photo file is gone: the post goes on without\n            // a cover. Any other failure keeps the old cover and takes the\n            // switches and their text change back, so text and cover agree.\n            async refreshCover() {\n                const request = ++coverRequest;\n                const choices = this.choices();\n                this.ready = false;\n                try {\n                    const file = await fetchSlide(this.coverUrl(), this.slides[0].name);\n                    if (request !== coverRequest || destroyed) return;\n                    files[0] = file;\n                    this.showSlide(0, file);\n                    rendered = choices;\n                    this.ready = true;\n                } catch (err) {\n                    if (request !== coverRequest || destroyed) return;\n                    console.error(err);\n                    if (err.status === 404) {\n                        this.keepSlides(files.map((file, position) => (position === 0 ? null : file)));\n                        return;\n                    }\n                    clearTimeout(labelTimer);\n                    this.labelOn = rendered.labelOn;\n                    this.label = rendered.label;\n                    this.applyLabelText();\n                    if (this.showPrice !== rendered.showPrice) this.applyPriceText(rendered.showPrice);\n                    this.ready = true;\n                }\n            },\n\n            // The first line of the text follows the label only while the agent\n            // has not changed that line.\n            applyLabelText() {\n                const headline = this.headlineFor();\n                const lines = this.text.split('\\n');\n                if (lines[0] === this.headline) {\n                    lines[0] = headline;\n                    this.text = lines.join('\\n');\n                }\n                this.headline = headline;\n            },\n\n            // An unedited text switches to the other generated text; an edited\n            // one only loses or gets back the generated price line.\n            applyPriceText(on) {\n                const price = this.price;\n                if (this.text === this.generated(!on)) {\n                    this.text = this.generated(on);\n                } else if (on) {\n                    this.text = withLineAfter(this.text, price.factsLine, price.line);\n                } else {\n                    this.text = withoutLine(this.text, price.line);\n                }\n                this.showPrice = on;\n            },\n\n            // The label is per post.\n            labelChanged() {\n                if (!this.hasCover) return;\n                clearTimeout(labelTimer);\n                this.applyLabelText();\n                this.refreshCover();\n            },\n\n            labelInput(value) {\n                this.label = value;\n                // The shared files would still show the old label until the\n                // new cover is in.\n                this.ready = false;\n                clearTimeout(labelTimer);\n                labelTimer = setTimeout(() => this.labelChanged(), LABEL_DELAY_MS);\n            },\n\n            // \"Preis zeigen\" without a reload, so the agent's text edits stay.\n            // Only the cover is fetched again.\n            togglePrice(on) {\n                if (on === this.showPrice || !this.ready || !this.hasCover) return;\n                this.applyPriceText(on);\n                this.refreshCover();\n            },\n\n            previous() {\n                this.index = (this.index + this.slides.length - 1) % this.slides.length;\n            },\n\n            next() {\n                this.index = (this.index + 1) % this.slides.length;\n            },\n\n            copyText() {\n                if (!window.navigator.clipboard) return Promise.reject(new Error('Clipboard unavailable'));\n                return window.navigator.clipboard.writeText(this.text).then(() => {\n                    this.copied = true;\n                    setTimeout(() => { this.copied = false; }, 1500);\n                });\n            },\n\n            copy() {\n                this.copyText().catch(err => {\n                    // No clipboard access: select the text, so a manual copy is one step.\n                    console.error(err);\n                    this.$root.querySelector('[data-social-text]')?.select();\n                });\n            },\n\n            share() {\n                if (!this.ready) return;\n                this.shareFailed = false;\n                this.shareCopy = '';\n                this.copyText()\n                    .then(() => { this.shareCopy = 'copied'; })\n                    .catch(err => {\n                        console.error(err);\n                        this.shareCopy = 'failed';\n                    });\n                window.navigator.share({ files })\n                    .then(() => this.recordShare())\n                    .catch(err => {\n                        // The user closed the share menu: nothing failed.\n                        if (err && err.name === 'AbortError') return;\n                        console.error(err);\n                        this.shareFailed = true;\n                    });\n            },\n\n            recordShare() {\n                const body = new FormData();\n                this.slides.forEach(slide => body.append('photo_ids', slide.photo_id));\n                const meta = document.querySelector('meta[name=\"csrf-token\"]');\n                fetch(this.sharedUrl, {\n                    method: 'POST',\n                    body: body,\n                    headers: { 'X-CSRFToken': meta ? meta.content : '' },\n                }).catch(err => console.error(err));\n            },\n        };\n    });\n});\n", "// The one browser-side check for photo uploads: JPG, PNG or WebP, at most\n// 20 MB. Used by the upload modal, the onboarding and gallery uploads, and the\n// public upload forms. The server validates every file again.\n\nconst VALID_TYPES = ['image/jpeg', 'image/png', 'image/webp'];\nconst MAX_BYTES = 20 * 1024 * 1024;\n\n// 'type', 'size', or '' when the file can be uploaded.\nexport function uploadRejection(file) {\n    // Some drag sources and Windows apps report an empty MIME type; fall back\n    // to the file extension instead of dropping the file.\n    const typeOk = file.type\n        ? VALID_TYPES.includes(file.type)\n        : /\\.(jpe?g|png|webp)$/i.test(file.name || '');\n    if (!typeOk) return 'type';\n    if (file.size > MAX_BYTES) return 'size';\n    return '';\n}\n\n// The app pages' message for a rejection (window.I18N comes from app_base.html).\nexport function rejectionMessage(reason) {\n    const I18N = window.I18N || {};\n    if (reason === 'size') return I18N.fileTooLarge || 'Datei zu gro\u00DF (max. 20 MB)';\n    return I18N.invalidImageType || 'Bitte w\u00E4hlen Sie ein JPG, PNG oder WebP Bild.';\n}\n", "import { rejectionMessage, uploadRejection } from './upload-validation.js';\n\ndocument.addEventListener('alpine:init', () => {\n    const I18N = window.I18N || {};\n\n    Alpine.data('photoUpload', () => ({\n        isOpen: false,\n        dragging: false,\n        hasUploads: false,\n        pendingUploads: 0,\n        // Photos stored since the last batch event, so one batch = one event.\n        uploadedInBatch: 0,\n        // URL import state\n        activeTab: 'device',\n        importUrl: '',\n        scraping: false,\n        importing: false,\n        scrapedImages: [],\n        scrapedMetadata: {},\n\n        open(files) {\n            this.isOpen = true;\n            this.lockScroll();\n            // The empty-state card is a drop target: it dispatches the open\n            // event with the dropped files so the modal starts uploading\n            // instead of asking for them again.\n            if (files && files.length) this.handleFiles(files);\n        },\n\n        close() {\n            if (this.pendingUploads > 0 || this.importing) return;\n            this.isOpen = false;\n            this.unlockScroll();\n            this.resetUrlImport();\n            if (this.hasUploads) htmx.trigger('#photo-grid', 'refresh');\n        },\n\n        // GA4 funnel step. Fires when the last upload of a batch lands, so a\n        // 12-photo drop reports once with count 12, not twelve times.\n        trackBatchComplete() {\n            if (this.pendingUploads > 0 || this.uploadedInBatch === 0) return;\n            window.trackEvent('photo_upload_complete', {\n                property_id: this.$root.dataset.propertyId || '',\n                count: this.uploadedInBatch,\n            });\n            this.uploadedInBatch = 0;\n        },\n\n        lockScroll()   { window._scrollLock.lock(); },\n        unlockScroll() { window._scrollLock.unlock(); },\n\n        openFilePicker() { this.$refs.fileInput?.click(); },\n\n        onFileSelect(e) {\n            this.handleFiles(e.target.files);\n            e.target.value = '';\n        },\n\n        onDrop(e) {\n            this.dragging = false;\n            this.handleFiles(e.dataTransfer.files);\n        },\n\n        handleFiles(files) {\n            Array.from(files).forEach(file => {\n                const rejection = uploadRejection(file);\n                if (rejection) {\n                    this.showRejected(file, rejectionMessage(rejection));\n                    return;\n                }\n                this.uploadFile(file);\n            });\n        },\n\n        // Rejected before any request: the file never reaches the server, so\n        // the row is final and carries no retry button.\n        showRejected(file, msg) {\n            this.hasUploads = true;\n            const rowId = 'rejected-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);\n            const uploadRows = this.$refs.uploadRows;\n            if (uploadRows) uploadRows.insertAdjacentHTML('beforeend', this.errorRowHtml(rowId, file.name, msg));\n        },\n\n        errorRowHtml(rowId, name, msg, retryId) {\n            return '<div id=\"' + rowId + '\" class=\"flex items-center gap-4 p-3 bg-red-50 rounded-lg border border-red-200\">' +\n                '<div class=\"w-16 h-12 bg-red-100 rounded flex items-center justify-center flex-shrink-0\">' +\n                '<svg class=\"w-5 h-5 text-red-500\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">' +\n                '<path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\"/>' +\n                '</svg></div>' +\n                '<div class=\"flex-1 min-w-0\">' +\n                '<p class=\"font-medium text-red-800 truncate text-sm\">' + this.escapeHtml(name) + '</p>' +\n                '<p class=\"text-sm text-red-600\">' + this.escapeHtml(msg) + '</p>' +\n                '</div>' +\n                (retryId ? '<button id=\"' + retryId + '\" class=\"px-3 py-1 text-xs font-medium text-red-700 bg-red-100 hover:bg-red-200 rounded transition-colors cursor-pointer flex-shrink-0\">' + (I18N.retry || 'Nochmal') + '</button>' : '') +\n                '</div>';\n        },\n\n        uploadFile(file) {\n            this.hasUploads = true;\n            this.pendingUploads++;\n\n            const tempId = 'temp-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);\n            const previewUrl = URL.createObjectURL(file);\n            const uploadUrl = this.$root.dataset.uploadUrl;\n            const csrfToken = document.querySelector('meta[name=\"csrf-token\"]').content;\n            // Capture the ref now: `this.$refs` throws (_x_refs) once an hx-boost\n            // navigation tears the component down mid-upload.\n            const uploadRows = this.$refs.uploadRows;\n\n            const placeholderHtml = '<div id=\"' + tempId + '\" class=\"flex items-center gap-4 p-3 bg-soft rounded-lg border border-border\">' +\n                '<div class=\"w-16 h-12 bg-muted rounded overflow-hidden flex-shrink-0\">' +\n                '<img src=\"' + previewUrl + '\" class=\"w-full h-full object-cover\">' +\n                '</div>' +\n                '<div class=\"flex-1 min-w-0\">' +\n                '<p class=\"font-medium text-foreground truncate text-sm\">' + this.escapeHtml(file.name) + '</p>' +\n                '<div class=\"flex items-center gap-1.5 text-sm text-muted-foreground mt-0.5\">' +\n                '<svg class=\"w-4 h-4 animate-spin\" fill=\"none\" viewBox=\"0 0 24 24\">' +\n                '<circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle>' +\n                '<path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z\"></path>' +\n                '</svg>' +\n                '<span>' + (I18N.uploading || 'Wird hochgeladen...') + '</span>' +\n                '</div></div></div>';\n            if (uploadRows) uploadRows.insertAdjacentHTML('beforeend', placeholderHtml);\n\n            const doUpload = () => {\n                const formData = new FormData();\n                formData.append('photo', file);\n                return fetch(uploadUrl, {\n                    method: 'POST',\n                    headers: { 'X-CSRFToken': csrfToken },\n                    body: formData\n                }).then(response => {\n                    if (!response.ok) return response.json().then(data => { throw new Error(data.error || I18N.uploadFailed || 'Upload fehlgeschlagen'); });\n                    return response.json();\n                });\n            };\n\n            const showError = (msg) => {\n                const placeholder = document.getElementById(tempId);\n                if (placeholder) {\n                    const retryId = tempId + '-retry';\n                    placeholder.outerHTML = this.errorRowHtml(tempId, file.name, msg, retryId);\n                    const retryBtn = document.getElementById(retryId);\n                    if (retryBtn) retryBtn.addEventListener('click', () => {\n                        const row = document.getElementById(tempId);\n                        if (row) row.outerHTML = placeholderHtml;\n                        this.pendingUploads++;\n                        doUpload().then(onSuccess).catch(e => showError(friendlyMsg(e))).finally(onDone);\n                    });\n                }\n            };\n\n            const friendlyMsg = (error) => {\n                if (error instanceof TypeError) return I18N.uploadFailedRetry || 'Upload fehlgeschlagen \u2014 bitte erneut versuchen';\n                return error.message || I18N.uploadFailed || 'Upload fehlgeschlagen';\n            };\n\n            const onSuccess = (data) => {\n                this.uploadedInBatch++;\n                const placeholder = document.getElementById(tempId);\n                if (placeholder && data.row_html) {\n                    placeholder.outerHTML = data.row_html;\n                    if (uploadRows) htmx.process(uploadRows);\n                }\n            };\n\n            const onDone = () => {\n                URL.revokeObjectURL(previewUrl);\n                this.pendingUploads--;\n                this.trackBatchComplete();\n            };\n\n            doUpload()\n            .then(onSuccess)\n            .catch(error => {\n                // Auto-retry once for transient errors (e.g. iOS Safari after photo picker)\n                if (error instanceof TypeError) {\n                    return new Promise(r => setTimeout(r, 500))\n                        .then(doUpload)\n                        .then(onSuccess)\n                        .catch(e => showError(friendlyMsg(e)));\n                }\n                showError(friendlyMsg(error));\n            })\n            .finally(onDone);\n        },\n\n        // --- URL Import ---\n\n        async startScrape() {\n            const url = this.importUrl.trim();\n            if (!url) return;\n\n            this.scraping = true;\n            this.scrapedImages = [];\n            // Capture the ref now, before any await \u2014 `this.$refs` throws (_x_refs)\n            // if an hx-boost navigation tears the component down mid-scrape.\n            const scrapeResults = this.$refs.scrapeResults;\n            if (scrapeResults) scrapeResults.innerHTML = '';\n\n            const scrapeUrl = this.$root.dataset.scrapeUrl;\n            const csrfToken = document.querySelector('meta[name=\"csrf-token\"]').content;\n\n            try {\n                const formData = new FormData();\n                formData.append('url', url);\n\n                const res = await fetch(scrapeUrl, {\n                    method: 'POST',\n                    headers: { 'X-CSRFToken': csrfToken },\n                    body: formData,\n                });\n\n                const html = await res.text();\n                if (scrapeResults) {\n                    scrapeResults.innerHTML = html;\n\n                    // Read image URLs and metadata from embedded JSON\n                    const dataEl = scrapeResults.querySelector('#scraped-image-data');\n                    if (dataEl) {\n                        try { this.scrapedImages = JSON.parse(dataEl.textContent); } catch(e) {}\n                    }\n                    const metaEl = scrapeResults.querySelector('#scraped-metadata');\n                    if (metaEl) {\n                        try { this.scrapedMetadata = JSON.parse(metaEl.textContent); } catch(e) {}\n                    }\n                }\n            } catch (e) {\n                if (scrapeResults) scrapeResults.innerHTML = '<p class=\"text-sm text-alert-error-foreground text-center py-4\">' + (I18N.connectionErrorRetry || 'Verbindungsfehler. Bitte erneut versuchen.') + '</p>';\n            } finally {\n                this.scraping = false;\n            }\n        },\n\n        resetUrlImport() {\n            this.scrapedImages = [];\n            this.scrapedMetadata = {};\n            this.importUrl = '';\n            this.scraping = false;\n            this.importing = false;\n            if (this.$refs.scrapeResults) this.$refs.scrapeResults.innerHTML = '';\n        },\n\n        async importSelectedUrls(selectedIndexes) {\n            if (this.importing) return;\n            const urls = selectedIndexes.map(i => this.scrapedImages[i]).filter(Boolean);\n            if (!urls.length) return;\n\n            this.importing = true;\n            this.hasUploads = true;\n            this.activeTab = 'device'; // Switch to show upload rows\n            // Capture refs now, before any await \u2014 `this.$refs` throws (_x_refs)\n            // if an hx-boost navigation tears the component down mid-import.\n            const scrapeResults = this.$refs.scrapeResults;\n            const uploadRows = this.$refs.uploadRows;\n            if (scrapeResults) scrapeResults.innerHTML = '';\n            this.scrapedImages = [];\n\n            const importUrl = this.$root.dataset.importUrl;\n            const csrfToken = document.querySelector('meta[name=\"csrf-token\"]').content;\n            let importedCount = 0;\n\n            // Show pending placeholders\n            const tempIds = urls.map((_, i) => {\n                const tempId = 'import-' + Date.now() + '-' + i;\n                const html = '<div id=\"' + tempId + '\" class=\"flex items-center gap-4 p-3 bg-soft rounded-lg border border-border\">' +\n                    '<div class=\"w-16 h-12 bg-muted rounded flex items-center justify-center flex-shrink-0\">' +\n                    '<svg class=\"w-5 h-5 text-muted-foreground\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1.5\" d=\"M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909M3.75 21h16.5\"/></svg>' +\n                    '</div>' +\n                    '<div class=\"flex-1 min-w-0\">' +\n                    '<p class=\"font-medium text-foreground text-sm\">' + (I18N.photoOfCount || 'Foto {n} von {total}').replace('{n}', i + 1).replace('{total}', urls.length) + '</p>' +\n                    '<div class=\"flex items-center gap-1.5 text-sm text-muted-foreground mt-0.5\">' +\n                    '<svg class=\"w-4 h-4 animate-spin\" fill=\"none\" viewBox=\"0 0 24 24\">' +\n                    '<circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle>' +\n                    '<path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z\"></path>' +\n                    '</svg><span>' + (I18N.importing || 'Wird importiert...') + '</span></div></div></div>';\n                if (uploadRows) uploadRows.insertAdjacentHTML('beforeend', html);\n                return tempId;\n            });\n\n            this.pendingUploads += urls.length;\n\n            try {\n                const res = await fetch(importUrl, {\n                    method: 'POST',\n                    headers: {\n                        'X-CSRFToken': csrfToken,\n                        'Content-Type': 'application/json',\n                    },\n                    body: JSON.stringify({ urls, metadata: this.scrapedMetadata }),\n                });\n\n                const data = await res.json();\n\n                // Replace placeholders with actual upload rows\n                (data.results || []).forEach((result, i) => {\n                    const placeholder = document.getElementById(tempIds[i]);\n                    if (placeholder && result.row_html) {\n                        placeholder.outerHTML = result.row_html;\n                    }\n                });\n\n                // Mark remaining placeholders as failed\n                const successCount = (data.results || []).length;\n                importedCount = successCount;\n                for (let i = successCount; i < tempIds.length; i++) {\n                    const placeholder = document.getElementById(tempIds[i]);\n                    if (placeholder) {\n                        placeholder.outerHTML = '<div class=\"flex items-center gap-4 p-3 bg-alert-error rounded-lg border border-alert-error-border\">' +\n                            '<div class=\"w-16 h-12 bg-alert-error rounded flex items-center justify-center flex-shrink-0\">' +\n                            '<svg class=\"w-5 h-5 text-alert-error-foreground\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">' +\n                            '<path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\"/></svg></div>' +\n                            '<div class=\"flex-1 min-w-0\"><p class=\"font-medium text-alert-error-foreground text-sm\">' + (I18N.importFailed || 'Import fehlgeschlagen') + '</p></div></div>';\n                    }\n                }\n\n                if (uploadRows) htmx.process(uploadRows);\n            } catch (e) {\n                // Network error \u2014 mark all as failed\n                tempIds.forEach(id => {\n                    const el = document.getElementById(id);\n                    if (el) el.querySelector('span').textContent = I18N.importFailed || 'Import fehlgeschlagen';\n                });\n            } finally {\n                this.pendingUploads -= urls.length;\n                this.importing = false;\n                this.uploadedInBatch += importedCount;\n                this.trackBatchComplete();\n            }\n        },\n\n        escapeHtml(text) {\n            const div = document.createElement('div');\n            div.textContent = text;\n            return div.innerHTML;\n        },\n    }));\n\n});\n", "document.addEventListener('alpine:init', () => {\n    Alpine.data('brandingLogoUpload', () => ({\n        dragging: false,\n        previewUrl: '',\n        fileName: '',\n        fileSize: '',\n        error: '',\n        helpOpen: false,\n\n        get hasSelection() {\n            return Boolean(this.previewUrl);\n        },\n\n        init() {\n            this.$watch('$store.modal.open', (openModal) => {\n                if (openModal === 'logo-upload') {\n                    this.$nextTick(() => this.$refs.dropzone?.focus());\n                } else {\n                    this.reset();\n                }\n            });\n        },\n\n        openFilePicker() {\n            this.$refs.fileInput?.click();\n        },\n\n        handleFileSelect(event) {\n            const file = event.target.files?.[0];\n            if (file) this.selectFile(file);\n        },\n\n        handleDrop(event) {\n            this.dragging = false;\n            const file = event.dataTransfer?.files?.[0];\n            if (!file) return;\n\n            this.$refs.fileInput.files = event.dataTransfer.files;\n            this.selectFile(file);\n        },\n\n        selectFile(file) {\n            const validExtension = /\\.(jpe?g|png|webp)$/i.test(file.name);\n            const validType = !file.type || [\n                'image/jpeg',\n                'image/png',\n                'image/webp',\n            ].includes(file.type);\n\n            if (!validExtension || !validType) {\n                this.clearSelection();\n                this.error = this.$root.dataset.invalidFormat;\n                return;\n            }\n            if (file.size > 5 * 1024 * 1024) {\n                this.clearSelection();\n                this.error = this.$root.dataset.fileTooLarge;\n                return;\n            }\n\n            if (this.previewUrl) URL.revokeObjectURL(this.previewUrl);\n            this.previewUrl = URL.createObjectURL(file);\n            this.fileName = file.name;\n            this.fileSize = file.size < 1024 * 1024\n                ? `${Math.max(1, Math.round(file.size / 1024))} KB`\n                : `${(file.size / (1024 * 1024)).toFixed(1)} MB`;\n            this.error = '';\n        },\n\n        clearSelection() {\n            if (this.previewUrl) URL.revokeObjectURL(this.previewUrl);\n            this.previewUrl = '';\n            this.fileName = '';\n            this.fileSize = '';\n            if (this.$refs.fileInput) this.$refs.fileInput.value = '';\n        },\n\n        reset() {\n            this.clearSelection();\n            this.dragging = false;\n            this.error = '';\n            this.helpOpen = false;\n        },\n\n        close() {\n            this.reset();\n            Alpine.store('modal')?.close();\n        },\n    }));\n});\n", "// Estimated progress for waits we cannot measure (scraping a listing page).\n// The bar runs fast at first and slows down near the end, reaching about 85%\n// at `expectedMs` and never passing 95% until the caller calls done().\n// Better than a spinner: the customer sees that the wait is finite.\n\nexport function estimateProgress(set, expectedMs) {\n    const started = Date.now();\n    const tau = expectedMs / 1.9; // 1 - e^-1.9 \u2248 0.85\n    set(3);\n    const timer = setInterval(() => {\n        const t = Date.now() - started;\n        set(Math.min(95, Math.round(95 * (1 - Math.exp(-t / tau)))));\n    }, 200);\n    return function done() {\n        clearInterval(timer);\n        set(100);\n    };\n}\n", "// Onboarding: listing link or photo upload, then the gallery page.\n// See docs/product/listing-gallery.md.\nimport { estimateProgress } from './estimated-progress.js';\nimport { rejectionMessage, uploadRejection } from './upload-validation.js';\n\n// Loading a listing: Firecrawl waits 5 s for the page to render, then we\n// download the title photo. Measured 2 to 11 s on staging (2026-09-24).\nconst LISTING_LOAD_MS = 12000;\n\nconst PARALLEL_UPLOADS = 3;\n\nfunction csrfToken() {\n    return document.querySelector('meta[name=\"csrf-token\"]').content;\n}\n\nasync function postForm(url, formData) {\n    const response = await fetch(url, {\n        method: 'POST',\n        headers: { 'X-CSRFToken': csrfToken() },\n        body: formData,\n    });\n    let data = {};\n    try { data = await response.json(); } catch (e) { data = {}; }\n    return { ok: response.ok && data.success, data, status: response.status };\n}\n\n// Server failure code for analytics: the view's `reason`, else the HTTP status\n// (a proxy timeout or a crash answers without JSON).\nfunction failureReason(data, status) {\n    return data.reason || `http_${status}`;\n}\n\nfunction hostOf(url) {\n    try { return new URL(url).hostname; } catch (e) { return ''; }\n}\n\n// Uploads files into the onboarding gallery. The first file creates the\n// gallery property unless `propertyId` is given; the rest follow in parallel.\nasync function uploadToGallery(uploadUrl, files, propertyId, onProgress) {\n    const I18N = window.I18N || {};\n    let done = 0;\n    let galleryUrl = '';\n    let lastError = '';\n    const send = async (file) => {\n        const formData = new FormData();\n        formData.append('photo', file);\n        if (propertyId) formData.append('property', propertyId);\n        let response;\n        try {\n            response = await postForm(uploadUrl, formData);\n        } catch (e) {\n            // A network error must not leave the upload hanging.\n            window.trackEvent?.('onboarding_error', { step: 'upload', reason: 'network' });\n            response = { ok: false, data: { error: I18N.connectionErrorTryAgain || 'Verbindungsfehler. Bitte versuchen Sie es erneut.' } };\n        }\n        const { ok, data } = response;\n        done += 1;\n        onProgress(done);\n        if (!ok) {\n            lastError = data.error || I18N.uploadFailed || 'Upload fehlgeschlagen';\n            return data;\n        }\n        propertyId = data.property_id;\n        galleryUrl = data.gallery_url;\n        return data;\n    };\n\n    const queue = [...files];\n    if (!propertyId && queue.length) {\n        const first = await send(queue.shift());\n        if (first.limit_reached || !propertyId) {\n            return { galleryUrl: '', error: lastError, limitReached: !!first.limit_reached };\n        }\n    }\n    const worker = async () => {\n        while (queue.length) await send(queue.shift());\n    };\n    await Promise.all(Array.from({ length: PARALLEL_UPLOADS }, worker));\n    return { galleryUrl, error: galleryUrl ? '' : lastError, limitReached: false };\n}\n\n// The files to upload, and the message for the first file left out ('' when\n// every file passed).\nfunction checkFiles(fileList) {\n    const files = [];\n    let error = '';\n    for (const file of fileList) {\n        const reason = uploadRejection(file);\n        if (!reason) files.push(file);\n        else if (!error) error = rejectionMessage(reason);\n    }\n    return { files, error };\n}\n\ndocument.addEventListener('alpine:init', () => {\n    const I18N = window.I18N || {};\n\n    // The onboarding page (/app/start/). \"Sp\u00E4ter\" is a plain form post.\n    Alpine.data('onboarding', () => ({\n        step: 'welcome', // welcome | start | uploading | scraping | select | error\n        progress: 0,\n        dragging: false,\n        error: '',\n        limitReached: false,\n        importUrl: '',\n        total: 0,\n        done: 0,\n        candidates: [],\n        selected: [],\n        small: [],       // candidates under the import's 400 x 300 minimum\n        touched: false,  // the customer changed the preselection\n        maxPicks: 9,     // plus the title photo = the 10 gallery slots\n        firstUrl: '',\n        confirmUrl: '',\n        galleryUrl: '',\n\n        begin() {\n            this.track('begin');\n            this.step = 'start';\n            window.scrollTo({ top: 0 });\n        },\n\n        track(path) {\n            if (window.trackEvent) window.trackEvent('onboarding_path', { path });\n        },\n\n        openFilePicker() { this.$refs.fileInput?.click(); },\n\n        onFileSelect(e) { this.handleFiles(e.target.files); },\n\n        onDrop(e) {\n            this.dragging = false;\n            this.handleFiles(e.dataTransfer.files);\n        },\n\n        async handleFiles(fileList) {\n            const { files, error } = checkFiles(fileList);\n            if (!files.length) {\n                this.error = error || rejectionMessage('type');\n                return;\n            }\n            this.error = '';\n            this.track('upload');\n            this.total = files.length;\n            this.done = 0;\n            this.step = 'uploading';\n            const result = await uploadToGallery(\n                this.$root.dataset.uploadUrl, files, '', (n) => { this.done = n; });\n            if (result.galleryUrl) {\n                window.trackEvent?.('photo_upload_complete', { count: this.done });\n                window.location.href = result.galleryUrl;\n                return;\n            }\n            this.showError(result.error, result.limitReached);\n        },\n\n        async startUrlImport() {\n            const url = this.importUrl.trim();\n            if (!url) return;\n            this.error = '';\n            this.step = 'scraping';\n            this.track('url');\n            const formData = new FormData();\n            formData.append('url', url);\n            const done = estimateProgress((v) => { this.progress = v; }, LISTING_LOAD_MS);\n            const host = hostOf(url);\n            let response;\n            try {\n                response = await postForm(this.$root.dataset.urlImportUrl, formData);\n            } catch (e) {\n                done();\n                window.trackEvent?.('listing_import_result', { result: 'error', reason: 'network', host });\n                this.step = 'start';\n                this.error = I18N.connectionErrorTryAgain || 'Verbindungsfehler. Bitte versuchen Sie es erneut.';\n                return;\n            }\n            done();\n            const { ok, data, status } = response;\n            if (!ok) {\n                window.trackEvent?.('listing_import_result', {\n                    result: 'error', reason: failureReason(data, status), host,\n                });\n                if (data.limit_reached) {\n                    this.showError(data.error, true);\n                } else {\n                    this.step = 'start';\n                    this.error = data.error || I18N.importFailed || 'Import fehlgeschlagen';\n                }\n                return;\n            }\n            window.trackEvent?.('listing_import_result', { result: 'ok', host });\n            window.trackEvent?.('photo_upload_complete', { property_id: data.property_id, count: 1 });\n            this.galleryUrl = data.gallery_url;\n            this.confirmUrl = data.confirm_url;\n            this.firstUrl = data.first_url;\n            this.candidates = data.candidates;\n            this.maxPicks = data.max_picks;\n            this.small = [];\n            this.touched = false;\n            if (!this.candidates.length) {\n                this.confirmSelection();\n                return;\n            }\n            this.step = 'select';\n        },\n\n        // Opt-in: the first usable photos are preselected until the customer\n        // changes the selection. Images the import would drop as too small\n        // never show, so no pick can silently vanish.\n        get visible() { return this.candidates.filter((u) => !this.small.includes(u)); },\n        get chosen() { return this.touched ? this.selected : this.visible.slice(0, this.maxPicks); },\n\n        isSelected(url) { return this.chosen.includes(url); },\n\n        toggle(url) {\n            if (!this.touched) {\n                this.selected = [...this.chosen];\n                this.touched = true;\n            }\n            if (this.selected.includes(url)) {\n                this.selected = this.selected.filter((u) => u !== url);\n            } else if (this.selected.length < this.maxPicks) {\n                this.selected = [...this.selected, url];\n            }\n        },\n\n        onCandidateLoad(event, url) {\n            const img = event.target;\n            if (img.naturalWidth < 400 || img.naturalHeight < 300) this.dropCandidate(url);\n        },\n\n        dropCandidate(url) {\n            if (!this.small.includes(url)) this.small = [...this.small, url];\n            this.selected = this.selected.filter((u) => u !== url);\n        },\n\n        async confirmSelection() {\n            const formData = new FormData();\n            this.chosen.forEach((u) => formData.append('selected', u));\n            this.step = 'scraping';\n            estimateProgress((v) => { this.progress = v; }, 2000);\n            // The gallery opens either way (it shows the title photo); a failed\n            // confirm only loses the picks, so report it instead of swallowing it.\n            try {\n                const { ok, data, status } = await postForm(this.confirmUrl, formData);\n                if (!ok) {\n                    window.trackEvent?.('onboarding_error', { step: 'confirm', reason: failureReason(data, status) });\n                }\n            } catch (e) {\n                window.trackEvent?.('onboarding_error', { step: 'confirm', reason: 'network' });\n            } finally {\n                window.location.href = this.galleryUrl;\n            }\n        },\n\n        showError(message, limitReached) {\n            this.step = 'error';\n            this.limitReached = !!limitReached;\n            this.error = message || I18N.uploadFailed || 'Upload fehlgeschlagen';\n        },\n\n        reset() { window.location.reload(); },\n    }));\n\n    // The gallery page. A photo click swaps the before/after comparison in\n    // place; the choice survives the 3 s refresh of #gallery-body, which\n    // re-renders the comparison on the first finished photo.\n    Alpine.data('galleryPage', () => ({\n        busy: false,\n        error: '',\n        progressText: '',\n        selectedId: '',\n        lightboxOpen: false,\n        lightboxBefore: '',\n        lightboxAfter: '',\n\n        init() {\n            this._onSettle = () => this.show(this.selectedId);\n            window.addEventListener('htmx:afterSettle', this._onSettle);\n        },\n\n        destroy() {\n            window.removeEventListener('htmx:afterSettle', this._onSettle);\n        },\n\n        // The comparison large, like \"Vergleichen\" on the photo page. Only\n        // once the after image exists.\n        openLightbox() {\n            const after = document.getElementById('compare-after');\n            if (!after || after.classList.contains('hidden')) return;\n            this.lightboxBefore = document.getElementById('compare-before').src;\n            this.lightboxAfter = after.src;\n            this.lightboxOpen = true;\n            window._scrollLock.lock();\n        },\n\n        closeLightbox() {\n            this.lightboxOpen = false;\n            window._scrollLock.unlock();\n        },\n\n        // The normal export dialog, with the account's watermark choices.\n        downloadShown() {\n            const url = document.getElementById('compare-download')?.dataset.url;\n            if (url) htmx.ajax('GET', url, { target: '#download-modal-container', swap: 'innerHTML' });\n        },\n\n        // Start the unlock from the comparison: submit the guide's unlock form\n        // (pack checkout or own credits), else take the customer to it.\n        unlockNow() {\n            const form = document.getElementById('gallery-unlock-form');\n            if (form) {\n                form.requestSubmit();\n                return;\n            }\n            document.querySelector('[data-testid=\"gallery-unlock\"]')?.scrollIntoView({ behavior: 'smooth', block: 'center' });\n        },\n\n        choose(id) {\n            this.selectedId = id;\n            this.show(id);\n            document.getElementById('gallery-compare-anchor')?.scrollIntoView({ behavior: 'smooth', block: 'start' });\n        },\n\n        show(id) {\n            if (!id) return;\n            const card = document.querySelector(`[data-gallery-card][data-photo-id=\"${id}\"]`);\n            const before = document.getElementById('compare-before');\n            if (!card || !before) return;\n            const d = card.dataset;\n            before.src = d.before;\n            const after = document.getElementById('compare-after');\n            if (d.after) after.src = d.after;\n            after.classList.toggle('hidden', !d.after);\n            document.getElementById('compare-pending').classList.toggle('hidden', !!d.after);\n            document.getElementById('compare-preview').classList.toggle('hidden', d.state !== 'locked');\n            document.getElementById('compare-label').textContent = d.label;\n            const download = document.getElementById('compare-download');\n            download.dataset.url = d.downloadUrl || '';\n            document.getElementById('compare-cta-done').classList.toggle('hidden', d.state !== 'done');\n            document.getElementById('compare-cta-locked').classList.toggle('hidden', d.state !== 'locked');\n            document.getElementById('compare-approximate').classList.toggle('hidden', !d.approximate);\n            // A floor plan is shown whole; photos fill the frame.\n            [before, after].forEach((img) => {\n                img.classList.toggle('object-contain', !!d.floorplan);\n                img.classList.toggle('object-cover', !d.floorplan);\n            });\n            document.querySelectorAll('[data-gallery-card] [data-card-frame]').forEach((frame) => {\n                const selected = frame.closest('[data-gallery-card]') === card;\n                frame.classList.toggle('border-primary', selected);\n                frame.classList.toggle('border-transparent', !selected);\n            });\n        },\n\n        async upload(fileList) {\n            const { files, error: rejected } = checkFiles(fileList);\n            if (!files.length) {\n                this.error = rejected;\n                return;\n            }\n            this.busy = true;\n            this.error = '';\n            const total = files.length;\n            const label = I18N.uploadingOf || 'Wird hochgeladen';\n            this.progressText = `${label} 0 / ${total}`;\n            const result = await uploadToGallery(\n                this.$root.dataset.uploadUrl, files, this.$root.dataset.propertyId,\n                (n) => { this.progressText = `${label} ${n} / ${total}`; });\n            this.busy = false;\n            this.error = result.error || rejected;\n            htmx.ajax('GET', this.$root.dataset.gridUrl, { target: '#gallery-body', swap: 'outerHTML' });\n        },\n    }));\n});\n", "// In-app guides: carry out the server's auto-open decision and record state.\n//\n// The server decides which guide opens (dashboard/guides.py) and renders its\n// key into data-guide-key. This component only executes that decision, which\n// is what makes hx-boost navigation work for free: the swapped body carries a\n// fresh x-data root, Alpine initialises it, and init() opens the guide for the\n// page that was just navigated to \u2014 exactly as on a full load.\n//\n// State POSTs are fire-and-forget. Guide bookkeeping must never be the reason\n// a modal fails to open or close.\ndocument.addEventListener('alpine:init', () => {\n    const modalStore = () => window.Alpine && window.Alpine.store('modal');\n\n    function csrfToken() {\n        const meta = document.querySelector('meta[name=\"csrf-token\"]');\n        return meta ? meta.content : '';\n    }\n\n    function post(url, params) {\n        if (!url) return;\n        const body = new FormData();\n        Object.keys(params || {}).forEach(key => body.append(key, params[key]));\n        fetch(url, {\n            method: 'POST',\n            headers: { 'X-CSRFToken': csrfToken() },\n            body: body,\n            // A url-kind CTA navigates away in the same tick; keepalive stops\n            // the browser cancelling the dismissal on the way out.\n            keepalive: true,\n        }).catch(() => {});\n    }\n\n    function track(name, params) {\n        if (window.trackEvent) window.trackEvent(name, params);\n    }\n\n    // Called from the header help menu and the mobile menu, which are plain\n    // inline x-data dropdowns with no component of their own.\n    window.immopixGuides = {\n        open(key, seenUrl) {\n            const store = modalStore();\n            if (store) store.show('guide-' + key);\n            track('guide_shown', { guide: key, auto: false });\n            post(seenUrl, { reopen: '1' });\n        },\n    };\n\n    Alpine.data('guideAutoOpen', () => ({\n        init() {\n            const key = this.$el.dataset.guideKey;\n            if (!key) return;\n\n            const forced = this.$el.dataset.forced === '1';\n            const seenUrl = this.$el.dataset.seenUrl;\n\n            // $nextTick: the modal body is teleported into <body> by Alpine,\n            // and on a full load that clone may not be mounted yet when this\n            // root initialises. Showing after the tick keeps the first paint\n            // of the modal correct instead of relying on the store's\n            // reactivity to catch up.\n            this.$nextTick(() => {\n                const store = modalStore();\n                // A forced open (?guide=<key>) is a review aid, so it must not\n                // change what this user sees on their next visit. The flag\n                // rides along in the store context, which guideModal reads\n                // when it sees itself open.\n                if (store) store.show('guide-' + key, forced ? { forced: true } : {});\n                track('guide_shown', { guide: key, auto: true });\n                if (!forced) post(seenUrl, {});\n            });\n        },\n    }));\n\n    Alpine.data('guideModal', () => ({\n        // Guards one dismissal record per open. Reset every time this guide\n        // opens, set by whichever close path gets there first.\n        recorded: false,\n        forcedOpen: false,\n\n        init() {\n            // $el is the component root here; capture it instead of reading\n            // $root from the watcher, which fires during hx-boost teardown\n            // when magic properties can no longer resolve across the teleport\n            // boundary.\n            const el = this.$el;\n            const id = 'guide-' + el.dataset.guideKey;\n            const self = this;\n\n            // Every way out of the modal counts as \"understood\": Verstanden,\n            // Escape, the backdrop, the X, and navigating away (the\n            // htmx:beforeHistorySave hook closes the store). Escape and the\n            // backdrop are wired by the modal shell straight to\n            // $store.modal.close(), so watching the store is the only place\n            // that sees all of them.\n            this.$watch('$store.modal?.open', (value, previous) => {\n                if (value === id) {\n                    const store = modalStore();\n                    const context = (store && store.context) || {};\n                    self.recorded = false;\n                    self.forcedOpen = !!context.forced;\n                    return;\n                }\n                if (previous === id) self.record(el);\n            });\n        },\n\n        record(el) {\n            if (this.recorded || this.forcedOpen) return;\n            this.recorded = true;\n            track('guide_dismissed', { guide: el.dataset.guideKey });\n            post(el.dataset.dismissUrl, {});\n        },\n\n        dismiss() {\n            // Record before closing so the store watcher finds the guard set\n            // and does not post a second time.\n            this.record(this.$root);\n            const store = modalStore();\n            if (store) store.close();\n        },\n\n        mute() {\n            const el = this.$root;\n            // Muting is the recorded action for this click. Claiming the\n            // guard keeps the close from also firing a dismissal.\n            this.recorded = true;\n            const store = modalStore();\n            if (store) store.close();\n            track('guide_muted', { guide: el.dataset.guideKey });\n            post(el.dataset.muteUrl, {});\n        },\n    }));\n});\n", "// The one-time \"other properties?\" question for payers on /app/.\n//\n// The server decides who is asked and claims every showing\n// (dashboard/next_property_ask.py). This file decides when in a tab session to\n// ask for the claim: from the second app page view on, once per tab. The modal\n// opens only when the server answers {\"show\": true}. Closing it without an\n// answer records nothing, because the showing was already claimed.\n// See docs/product/next-property-ask.md.\n\nconst VIEWS_KEY = 'immopix.appPageViews';\nconst SHOWN_KEY = 'immopix.nextPropertyAsk.shown';\nconst MODAL_ID = 'next-property-ask';\n\n// sessionStorage can throw (blocked site data, some private modes). A blocked\n// counter reads as 0, so the question never shows. That is the safe side.\nfunction appPageViews() {\n    try { return parseInt(window.sessionStorage.getItem(VIEWS_KEY), 10) || 0; }\n    catch (_) { return 0; }\n}\n\nfunction countAppPageView() {\n    try { window.sessionStorage.setItem(VIEWS_KEY, String(appPageViews() + 1)); }\n    catch (_) { /* storage blocked: the question stays off */ }\n}\n\nfunction shownInThisTab() {\n    try { return window.sessionStorage.getItem(SHOWN_KEY) === '1'; }\n    catch (_) { return true; }\n}\n\nfunction markShownInThisTab() {\n    try { window.sessionStorage.setItem(SHOWN_KEY, '1'); }\n    catch (_) { /* nothing to remember */ }\n}\n\n// An app page view is a page in the boosted app shell (app_base.html). A full\n// load counts when this bundle runs and its body carries hx-boost. A boosted\n// navigation counts in htmx:beforeSwap, before the new body and this\n// component initialise. A history restore fires no beforeSwap, so going back\n// is not a new view.\nif (document.body && document.body.getAttribute('hx-boost') === 'true') countAppPageView();\ndocument.addEventListener('htmx:beforeSwap', (event) => {\n    if (!event.detail.boosted || !event.detail.shouldSwap) return;\n    countAppPageView();\n});\n\ndocument.addEventListener('alpine:init', () => {\n    const modalStore = () => window.Alpine && window.Alpine.store('modal');\n\n    function csrfToken() {\n        const meta = document.querySelector('meta[name=\"csrf-token\"]');\n        return meta ? meta.content : '';\n    }\n\n    function post(url, params, options) {\n        const body = new FormData();\n        Object.keys(params || {}).forEach(key => body.append(key, params[key]));\n        return fetch(url, Object.assign({\n            method: 'POST',\n            headers: { 'X-CSRFToken': csrfToken() },\n            body: body,\n        }, options || {}));\n    }\n\n    Alpine.data('nextPropertyAsk', () => ({\n        step: 'question',  // question | decline | thanks | help | sent\n        reason: '',\n        note: '',\n        message: '',\n        sending: false,\n        failed: false,\n\n        init() {\n            // $el is the root inside the teleported modal body. Capture it\n            // before the request: an hx-boost swap can remove it meanwhile.\n            const el = this.$el;\n            if (appPageViews() < 2 || shownInThisTab()) return;\n            // Set before the request, so a quick boosted round trip cannot ask\n            // for a second showing while the first claim is in flight.\n            markShownInThisTab();\n            post(el.dataset.shownUrl)\n                .then(response => (response.ok ? response.json() : { show: false }))\n                .then(data => {\n                    const store = modalStore();\n                    if (!data.show || !el.isConnected || !store || store.open) return;\n                    store.show(MODAL_ID);\n                })\n                .catch(() => {});\n        },\n\n        go(step) {\n            this.failed = false;\n            this.step = step;\n        },\n\n        create() {\n            // keepalive: the answer must arrive even though the property\n            // dialog takes over at once.\n            post(this.$root.dataset.answerUrl, { answer: 'create' }, { keepalive: true })\n                .catch(() => {});\n            const store = modalStore();\n            if (!store) return;\n            // Close first: show() over an open modal measures the scrollbar\n            // while it is already hidden, and the page shifts sideways.\n            store.close();\n            store.show('property-create');\n        },\n\n        decline() {\n            if (!this.reason) return;\n            this.submit({ answer: 'decline', reason: this.reason, text: this.note.trim() }, 'thanks');\n        },\n\n        askForHelp() {\n            const text = this.message.trim();\n            if (!text) return;\n            this.submit({ answer: 'help', text: text }, 'sent');\n        },\n\n        async submit(params, nextStep) {\n            if (this.sending) return;\n            const url = this.$root.dataset.answerUrl;\n            this.sending = true;\n            this.failed = false;\n            try {\n                const response = await post(url, params);\n                if (response.ok) this.step = nextStep;\n                else this.failed = true;\n            } catch (_) {\n                this.failed = true;\n            }\n            this.sending = false;\n        },\n    }));\n});\n", "import { estimateProgress } from './estimated-progress.js';\n\n// Scraping renders the page first (Firecrawl waits 5 s); the photo download\n// runs in parallel waves, roughly half a second per photo.\nconst SCRAPE_MS = 12000;\nconst IMPORT_BASE_MS = 2000;\nconst IMPORT_PER_PHOTO_MS = 500;\n\ndocument.addEventListener('alpine:init', () => {\n    const I18N = window.I18N || {};\n\n    Alpine.data('propertyCreate', () => ({\n        step: 'choice',  // choice | manual | url | scraping | results | importing\n        progress: 0,\n        importUrl: '',\n        scraping: false,\n        importing: false,\n        scrapedImages: [],\n        scrapedMetadata: {},\n        propertyName: '',\n        importError: '',\n\n        // Integration requests \u2014 the chip footer under the two choice cards.\n        integrationSources: ['onOffice', 'Propstack', 'FlowFact', 'Dropbox / Drive'],\n        integrationSelected: [],\n        integrationSent: [],\n        integrationOtherOpen: false,\n        integrationOther: '',\n        integrationThanks: false,\n        integrationSending: false,\n\n        get isOpen() { return !!Alpine.store('modal')?.is('property-create'); },\n\n        init() {\n            // The component lives inside the teleported modal body. Reset it\n            // no matter how the shared shell closes (button, Escape, backdrop,\n            // or hx-boost navigation).\n            this.$watch('isOpen', isOpen => {\n                if (!isOpen) this.reset();\n            });\n\n            // /app/?create=1 (trial guide + onboarding CTAs) opens the modal directly.\n            // The flag is server-rendered so it works for full loads and hx-boost swaps alike.\n            if (this.$root.dataset.autoOpen === '1') {\n                // Strip the param so refresh/back doesn't reopen the modal.\n                const url = new URL(window.location);\n                if (url.searchParams.has('create')) {\n                    url.searchParams.delete('create');\n                    window.history.replaceState(\n                        window.history.state,\n                        '',\n                        url.pathname + url.search + url.hash,\n                    );\n                }\n                this.$nextTick(() => this.openModal());\n            }\n        },\n\n        openModal() {\n            Alpine.store('modal')?.show('property-create');\n        },\n\n        closeModal() {\n            if (this.importing) return;\n            Alpine.store('modal')?.close();\n            this.reset();\n        },\n\n        reset() {\n            this.step = 'choice';\n            this.importUrl = '';\n            this.scraping = false;\n            this.importing = false;\n            this.scrapedImages = [];\n            this.scrapedMetadata = {};\n            this.propertyName = '';\n            this.importError = '';\n            this.integrationSelected = [];\n            this.integrationSent = [];\n            this.integrationOtherOpen = false;\n            this.integrationOther = '';\n            this.integrationThanks = false;\n            this.integrationSending = false;\n            if (this.$refs.scrapeResults) this.$refs.scrapeResults.innerHTML = '';\n        },\n\n        // Sent sources typed into \"Andere \u2026\" \u2014 rendered as extra success chips so\n        // the \"Andere \u2026\" chip itself stays available for a second suggestion.\n        get integrationSentCustom() {\n            return this.integrationSent.filter(s => !this.integrationSources.includes(s));\n        },\n\n        get showIntegrationSend() {\n            return this.integrationSelected.length > 0 || this.integrationOtherOpen;\n        },\n\n        toggleIntegrationChip(source) {\n            if (this.integrationSent.includes(source)) return;\n            const i = this.integrationSelected.indexOf(source);\n            if (i === -1) this.integrationSelected.push(source);\n            else this.integrationSelected.splice(i, 1);\n            this.integrationThanks = false;\n        },\n\n        toggleIntegrationOther() {\n            this.integrationOtherOpen = !this.integrationOtherOpen;\n            this.integrationThanks = false;\n            if (this.integrationOtherOpen) {\n                this.$nextTick(() => this.$refs.integrationOtherInput?.focus());\n            }\n        },\n\n        async sendIntegrationRequest() {\n            if (this.integrationSending) return;\n            const other = this.integrationOther.trim();\n            // Only \"Andere \u2026\" is open and nothing was typed \u2014 ask for the name\n            // instead of reporting an empty request.\n            if (!other && !this.integrationSelected.length) {\n                this.$refs.integrationOtherInput?.focus();\n                return;\n            }\n\n            // Dedupe against already-sent sources \u2014 a repeated custom name would\n            // otherwise render twice and break the x-for keys.\n            const sources = [...new Set([...this.integrationSelected, ...(other ? [other] : [])])]\n                .filter(s => !this.integrationSent.includes(s));\n            if (!sources.length) {\n                this.integrationOtherOpen = false;\n                this.integrationOther = '';\n                return;\n            }\n\n            const requestUrl = this.$root.dataset.integrationRequestUrl;\n            const csrfToken = document.querySelector('meta[name=\"csrf-token\"]').content;\n\n            this.integrationSending = true;\n            try {\n                const res = await fetch(requestUrl, {\n                    method: 'POST',\n                    headers: {\n                        'X-CSRFToken': csrfToken,\n                        'Content-Type': 'application/json',\n                    },\n                    body: JSON.stringify({ sources }),\n                });\n                // Leave the selection intact on failure so the user can retry.\n                if (!res.ok) return;\n            } catch (e) {\n                return;\n            } finally {\n                this.integrationSending = false;\n            }\n\n            // The modal may have closed (and reset) while the request ran \u2014\n            // don't resurrect success state onto a fresh component.\n            if (!this.isOpen) return;\n\n            this.integrationSent.push(...sources);\n            this.integrationSelected = [];\n            this.integrationOtherOpen = false;\n            this.integrationOther = '';\n            this.integrationThanks = true;\n        },\n\n        goManual() { this.step = 'manual'; },\n\n        goUrl() { this.step = 'url'; },\n\n        goBack() {\n            if (this.step === 'results') {\n                this.step = 'url';\n                this.scrapedImages = [];\n                this.scrapedMetadata = {};\n                this.propertyName = '';\n                this.importError = '';\n                if (this.$refs.scrapeResults) this.$refs.scrapeResults.innerHTML = '';\n            } else {\n                this.step = 'choice';\n            }\n        },\n\n        async startScrape() {\n            const url = this.importUrl.trim();\n            if (!url) return;\n\n            this.step = 'scraping';\n            const done = estimateProgress((v) => { this.progress = v; }, SCRAPE_MS);\n            this.scrapedImages = [];\n            this.importError = '';\n            // Capture the ref now, before any await. After an hx-boost navigation\n            // the component is torn down and `this.$refs` itself throws (_x_refs).\n            const scrapeResults = this.$refs.scrapeResults;\n            if (scrapeResults) scrapeResults.innerHTML = '';\n\n            const scrapeUrl = this.$root.dataset.scrapeUrl;\n            const csrfToken = document.querySelector('meta[name=\"csrf-token\"]').content;\n\n            try {\n                const formData = new FormData();\n                formData.append('url', url);\n\n                const res = await fetch(scrapeUrl, {\n                    method: 'POST',\n                    headers: { 'X-CSRFToken': csrfToken },\n                    body: formData,\n                });\n\n                const html = await res.text();\n                if (scrapeResults) {\n                    scrapeResults.innerHTML = html;\n\n                    // Read image URLs from embedded JSON\n                    const dataEl = scrapeResults.querySelector('#scraped-image-data');\n                    if (dataEl) {\n                        try { this.scrapedImages = JSON.parse(dataEl.textContent); } catch(e) {}\n                    }\n\n                    // Read metadata\n                    const metaEl = scrapeResults.querySelector('#scraped-metadata');\n                    if (metaEl) {\n                        try { this.scrapedMetadata = JSON.parse(metaEl.textContent); } catch(e) {}\n                    }\n                }\n\n                // Prefill property name from metadata\n                if (this.scrapedMetadata.name) {\n                    this.propertyName = this.scrapedMetadata.name;\n                }\n\n                done();\n                this.step = 'results';\n            } catch (e) {\n                done();\n                if (scrapeResults) {\n                    scrapeResults.innerHTML = '<p class=\"text-sm text-alert-error-foreground text-center py-4\">' + (I18N.connectionErrorRetry || 'Verbindungsfehler. Bitte erneut versuchen.') + '</p>';\n                }\n                this.step = 'results';\n            }\n        },\n\n        resetScrape() {\n            this.scrapedImages = [];\n            this.scrapedMetadata = {};\n            this.propertyName = '';\n            this.importUrl = '';\n            this.importError = '';\n            if (this.$refs.scrapeResults) this.$refs.scrapeResults.innerHTML = '';\n            this.step = 'url';\n        },\n\n        async createWithImport(selectedIndexes) {\n            if (this.importing) return;\n\n            const name = this.propertyName.trim();\n            if (!name) {\n                this.importError = I18N.nameRequired || 'Name ist erforderlich';\n                return;\n            }\n\n            const urls = selectedIndexes.map(i => this.scrapedImages[i]).filter(Boolean);\n            if (!urls.length) return;\n\n            this.importError = '';\n            this.step = 'importing';\n            this.importing = true;\n            estimateProgress((v) => { this.progress = v; }, IMPORT_BASE_MS + IMPORT_PER_PHOTO_MS * urls.length);\n\n            const createUrl = this.$root.dataset.createUrl;\n            const csrfToken = document.querySelector('meta[name=\"csrf-token\"]').content;\n\n            try {\n                const res = await fetch(createUrl, {\n                    method: 'POST',\n                    headers: {\n                        'X-CSRFToken': csrfToken,\n                        'Content-Type': 'application/json',\n                    },\n                    body: JSON.stringify({ name, urls, metadata: this.scrapedMetadata }),\n                });\n\n                const data = await res.json();\n\n                if (!res.ok) {\n                    this.importError = data.error || I18N.importFailed || 'Import fehlgeschlagen';\n                    this.step = 'results';\n                    this.importing = false;\n                    return;\n                }\n\n                // Redirect to new property\n                if (data.redirect_url) {\n                    window.location.href = data.redirect_url;\n                    return;\n                }\n            } catch (e) {\n                this.importError = I18N.connectionErrorRetry || 'Verbindungsfehler. Bitte erneut versuchen.';\n                this.step = 'results';\n            } finally {\n                this.importing = false;\n            }\n        },\n    }));\n\n});\n", "// Search box on the property list (/app/), shown from\n// PROPERTY_SEARCH_MIN_PROPERTIES properties on (dashboard/views/account.py).\n//\n// The list renders every property on one page, so the filter runs here,\n// without a request. Matching ignores case, accents and the German umlaut\n// spellings: \"strasse\" finds \"Stra\u00DFe\", \"muller\" and \"mueller\" find \"M\u00FCller\".\n// Every word must match, in any order. See docs/code/property-search.md.\n\n// Letters that NFD does not split into a base letter and an accent.\nconst SPECIAL_LETTERS = { '\u00DF': 'ss', '\u00E6': 'ae', '\u0153': 'oe', '\u00F8': 'o', '\u0142': 'l', '\u0111': 'd' };\nconst COMBINING_MARKS = /[\u0300-\u036F]/g;\n// \"mueller\" and \"m\u00FCller\" both become \"muller\" in the loose form.\nconst UMLAUT_SPELLINGS = { a: 'e', o: 'e', u: 'e' };\nconst URL_DELAY_MS = 300;\n\n// Lower-cases and strips accents. Each output character remembers the range\n// of the input it came from, so a match can be highlighted in the original.\nfunction fold(text) {\n    let out = '';\n    const ranges = [];\n    let i = 0;\n    for (const ch of text) {\n        const lower = ch.toLowerCase().normalize('NFD').replace(COMBINING_MARKS, '');\n        for (const c of lower) {\n            const plain = SPECIAL_LETTERS[c] || c;\n            for (const p of plain) {\n                out += p;\n                ranges.push([i, i + ch.length]);\n            }\n        }\n        i += ch.length;\n    }\n    return { text: out, ranges };\n}\n\n// Folded text with \"ae\", \"oe\" and \"ue\" written as one letter.\nfunction loosen(folded) {\n    let out = '';\n    const ranges = [];\n    const src = folded.text;\n    for (let i = 0; i < src.length; i++) {\n        if (UMLAUT_SPELLINGS[src[i]] && src[i + 1] === UMLAUT_SPELLINGS[src[i]]) {\n            out += src[i];\n            ranges.push([folded.ranges[i][0], folded.ranges[i + 1][1]]);\n            i++;\n        } else {\n            out += src[i];\n            ranges.push(folded.ranges[i]);\n        }\n    }\n    return { text: out, ranges };\n}\n\nfunction searchable(text) {\n    const strict = fold(text);\n    return { strict, loose: loosen(strict) };\n}\n\n// Ranges of the original text where the word occurs. The strict form finds\n// \"er\" in \"Hauer\"; the loose form finds \"mueller\" in \"M\u00FCller\".\nfunction wordRanges(name, word) {\n    // A lone accent folds to nothing. It matches everything, marks nothing.\n    if (!word.strict.text) return [];\n    for (const form of ['strict', 'loose']) {\n        const haystack = name[form];\n        const needle = word[form].text;\n        const found = [];\n        let at = haystack.text.indexOf(needle);\n        while (at !== -1) {\n            found.push([haystack.ranges[at][0], haystack.ranges[at + needle.length - 1][1]]);\n            at = haystack.text.indexOf(needle, at + needle.length);\n        }\n        if (found.length) return found;\n    }\n    return null;\n}\n\nfunction mergeRanges(ranges) {\n    const sorted = ranges.slice().sort((a, b) => a[0] - b[0]);\n    const merged = [];\n    for (const r of sorted) {\n        const last = merged[merged.length - 1];\n        if (last && r[0] <= last[1]) last[1] = Math.max(last[1], r[1]);\n        else merged.push([r[0], r[1]]);\n    }\n    return merged;\n}\n\n// Writes the name as text nodes plus <mark> elements. No innerHTML, so a\n// property name can never inject markup.\nfunction renderName(el, name, ranges) {\n    if (!ranges.length) {\n        el.textContent = name;\n        return;\n    }\n    const parts = document.createDocumentFragment();\n    let pos = 0;\n    for (const [start, end] of ranges) {\n        if (start > pos) parts.append(name.slice(pos, start));\n        const mark = document.createElement('mark');\n        mark.textContent = name.slice(start, end);\n        parts.append(mark);\n        pos = end;\n    }\n    if (pos < name.length) parts.append(name.slice(pos));\n    el.replaceChildren(parts);\n}\n\nfunction isTypingTarget(el) {\n    return !!el && (el.isContentEditable || ['INPUT', 'TEXTAREA', 'SELECT'].includes(el.tagName));\n}\n\ndocument.addEventListener('alpine:init', () => {\n    Alpine.data('propertySearch', () => {\n        // Plain closure state, not Alpine data: DOM nodes do not belong in\n        // a reactive proxy.\n        let root = null;\n        let cards = [];\n        let countTemplate = '';\n        let emptyTemplate = '';\n        let urlTimer = null;\n\n        return {\n            query: '',\n            shown: 0,\n            total: 0,\n            announcement: '',\n\n            init() {\n                root = this.$el;\n                countTemplate = root.dataset.countTemplate || '';\n                emptyTemplate = root.dataset.emptyTemplate || '';\n                cards = Array.from(root.querySelectorAll('[data-property-card]')).map((card) => {\n                    const nameEl = card.querySelector('[data-property-name]');\n                    // NFC keeps an accent inside the <mark> with its letter.\n                    const name = nameEl.textContent.normalize('NFC');\n                    return { card, nameEl, name, search: searchable(name) };\n                });\n                this.total = cards.length;\n                // The URL is the source of truth, also when htmx restores a\n                // snapshot of this page on back navigation.\n                this.query = new URL(window.location.href).searchParams.get('q') || '';\n                this.filter();\n                this.announce();\n                this.$watch('query', () => {\n                    this.filter();\n                    clearTimeout(urlTimer);\n                    urlTimer = setTimeout(() => this.settle(), URL_DELAY_MS);\n                });\n            },\n\n            destroy() {\n                clearTimeout(urlTimer);\n            },\n\n            get hasQuery() {\n                return this.query.trim() !== '';\n            },\n\n            get noMatch() {\n                return this.hasQuery && this.shown === 0;\n            },\n\n            get countText() {\n                return countTemplate.replace('{shown}', this.shown).replace('{total}', this.total);\n            },\n\n            get emptyText() {\n                // A function, so \"$&\" in a query stays literal text.\n                const q = this.query.trim();\n                return emptyTemplate.replace('{query}', () => q);\n            },\n\n            filter() {\n                const words = Array.from(new Set(this.query.trim().split(/\\s+/).filter(Boolean)))\n                    .map((w) => searchable(w.normalize('NFC')));\n                let shown = 0;\n                for (const item of cards) {\n                    let ranges = [];\n                    let match = true;\n                    for (const word of words) {\n                        const found = wordRanges(item.search, word);\n                        if (!found) {\n                            match = false;\n                            break;\n                        }\n                        ranges = ranges.concat(found);\n                    }\n                    item.card.hidden = !match;\n                    renderName(item.nameEl, item.name, match ? mergeRanges(ranges) : []);\n                    if (match) shown++;\n                }\n                this.shown = shown;\n            },\n\n            // Once typing pauses: put the query in the URL and tell screen\n            // readers the count. Every keystroke would flood both, and\n            // WebKit throws after 100 history calls in 10 seconds.\n            settle() {\n                clearTimeout(urlTimer);\n                urlTimer = null;\n                // After an hx-boost navigation the URL belongs to another page.\n                if (!root.isConnected) return;\n                this.announce();\n                const url = new URL(window.location.href);\n                const q = this.query.trim();\n                if (q) url.searchParams.set('q', q);\n                else url.searchParams.delete('q');\n                const path = url.pathname + url.search + url.hash;\n                if (path === window.location.pathname + window.location.search + window.location.hash) return;\n                window.history.replaceState(window.history.state, '', path);\n                // htmx 2 files its snapshot of this page under the path it\n                // last pushed, kept in this sessionStorage key. Without the\n                // update, going back misses the cache and reloads the page.\n                // The reload also restores the query, only slower.\n                try { window.sessionStorage.setItem('htmx-current-path-for-history', url.pathname + url.search); }\n                catch (_) { /* storage blocked: back navigation reloads instead */ }\n            },\n\n            // A click on a card can come before the delay ran out.\n            flushUrl() {\n                if (urlTimer) this.settle();\n            },\n\n            announce() {\n                this.announcement = this.hasQuery ? this.countText : '';\n            },\n\n            clear() {\n                this.query = '';\n                this.$nextTick(() => this.input().focus());\n            },\n\n            onEscape(event) {\n                if (this.query) {\n                    event.preventDefault();\n                    this.clear();\n                } else {\n                    event.target.blur();\n                }\n            },\n\n            // \"/\" jumps to the search box, as on many websites. Not while the\n            // user types in a field or a dialog is open.\n            onWindowKey(event) {\n                if (event.key !== '/' || event.ctrlKey || event.metaKey || event.altKey) return;\n                if (event.defaultPrevented || event.isComposing) return;\n                // currentModalId comes from shared_base.html; a page without it has no dialogs.\n                if (isTypingTarget(event.target) || window.currentModalId?.()) return;\n                event.preventDefault();\n                const input = this.input();\n                input.focus();\n                input.select();\n            },\n\n            input() {\n                return root.querySelector('[data-property-search-input]');\n            },\n        };\n    });\n});\n", "document.addEventListener('alpine:init', () => {\n\n    // Reusable address autocomplete via Google Places Autocomplete Data API\n    // Uses our own input + dropdown \u2014 no Google widget, no full-screen overlay on mobile\n    Alpine.data('addressAutocomplete', (initialStreet = '', initialPostalCode = '', initialCity = '', apiKey = '') => ({\n        street: initialStreet,\n        postalCode: initialPostalCode,\n        city: initialCity,\n        query: '',\n        suggestions: [],\n        showDropdown: false,\n        _debounceTimer: null,\n        _sessionToken: null,\n        _places: null,\n        _ready: false,\n        _els: null,\n\n        init() {\n            this.query = [this.street, this.postalCode, this.city].filter(Boolean).join(', ');\n            // Cache DOM refs now \u2014 they won't work after await in async methods\n            this._els = {\n                street: this.$refs.hiddenStreet,\n                postalCode: this.$refs.hiddenPostalCode,\n                city: this.$refs.hiddenCity,\n                root: this.$el,\n            };\n            if (apiKey) this._loadApi(apiKey);\n        },\n\n        async _loadApi(key) {\n            try {\n                // Dynamically load Google Maps API if not yet present.\n                // Done here instead of a <script> tag so it works with hx-boost navigation.\n                if (!window.google?.maps?.importLibrary) {\n                    await this._injectScript(key);\n                }\n                // Use the module reference from importLibrary directly to avoid\n                // instanceof mismatches when the API is loaded dynamically.\n                this._places = await google.maps.importLibrary('places');\n                this._sessionToken = new this._places.AutocompleteSessionToken();\n                this._ready = true;\n            } catch (e) {\n                console.error('Google Places API failed to load:', e);\n            }\n        },\n\n        _injectScript(key) {\n            // Deduplicate: reuse existing loading promise if another instance started it\n            if (window._gmapsLoading) return window._gmapsLoading;\n            window._gmapsLoading = new Promise((resolve, reject) => {\n                const cb = '__gmapsReady';\n                window[cb] = () => { delete window[cb]; resolve(); };\n                const s = document.createElement('script');\n                s.src = 'https://maps.googleapis.com/maps/api/js?key=' + key + '&loading=async&callback=' + cb;\n                s.async = true;\n                s.onerror = () => { window._gmapsLoading = null; reject(new Error('Google Maps script failed')); };\n                document.head.appendChild(s);\n            });\n            return window._gmapsLoading;\n        },\n\n        onInput() {\n            const q = this.query.trim();\n            if (q.length < 3 || !this._ready) {\n                this.suggestions = [];\n                this.showDropdown = false;\n                return;\n            }\n            clearTimeout(this._debounceTimer);\n            this._debounceTimer = setTimeout(() => this._fetchSuggestions(q), 250);\n        },\n\n        async _fetchSuggestions(input) {\n            try {\n                const { suggestions } = await this._places.AutocompleteSuggestion.fetchAutocompleteSuggestions({\n                    input,\n                    sessionToken: this._sessionToken,\n                    region: 'de',\n                    includedPrimaryTypes: ['street_address', 'route', 'premise', 'subpremise'],\n                    language: 'de',\n                });\n                this.suggestions = suggestions.map(s => ({\n                    text: s.placePrediction.text.text,\n                    mainText: s.placePrediction.mainText?.text || s.placePrediction.text.text,\n                    secondaryText: s.placePrediction.secondaryText?.text || '',\n                    place: s.placePrediction.toPlace(),\n                }));\n                this.showDropdown = this.suggestions.length > 0;\n            } catch (e) {\n                console.error('Address autocomplete error:', e);\n                this.suggestions = [];\n                this.showDropdown = false;\n            }\n        },\n\n        async selectSuggestion(suggestion) {\n            this.query = suggestion.text;\n            this.showDropdown = false;\n            this.suggestions = [];\n\n            try {\n                const place = suggestion.place;\n                await place.fetchFields({ fields: ['addressComponents'] });\n                if (place.addressComponents) {\n                    this._parsePlaceComponents(place.addressComponents);\n                } else {\n                    this.street = suggestion.mainText;\n                    this._syncAndNotify();\n                }\n            } catch (e) {\n                this.street = suggestion.mainText;\n                this._syncAndNotify();\n            }\n\n            this._sessionToken = new this._places.AutocompleteSessionToken();\n        },\n\n        onBlur() {\n            setTimeout(() => { this.showDropdown = false; }, 200);\n        },\n\n        _parsePlaceComponents(components) {\n            let route = '';\n            let streetNumber = '';\n            this.postalCode = '';\n            this.city = '';\n\n            for (const component of components) {\n                const type = component.types[0];\n                if (type === 'route') route = component.longText;\n                else if (type === 'street_number') streetNumber = component.longText;\n                else if (type === 'postal_code') this.postalCode = component.longText;\n                else if (type === 'locality') this.city = component.longText;\n                else if (type === 'sublocality_level_1' && !this.city) this.city = component.longText;\n            }\n\n            this.street = streetNumber ? `${route} ${streetNumber}` : route;\n            this.query = [this.street, this.postalCode, this.city].filter(Boolean).join(', ');\n            this._syncAndNotify();\n        },\n\n        _syncAndNotify() {\n            if (this._els.street) this._els.street.value = this.street;\n            if (this._els.postalCode) this._els.postalCode.value = this.postalCode;\n            if (this._els.city) this._els.city.value = this.city;\n\n            // Root may be detached if an hx-boost navigation tore the component\n            // down while a Places callback was in flight \u2014 guard the dispatch.\n            if (this._els.root && this._els.root.isConnected) {\n                this._els.root.dispatchEvent(new CustomEvent('address-changed', { bubbles: true }));\n            }\n        },\n    }));\n\n});\n", "document.addEventListener('alpine:init', () => {\n\n    Alpine.data('listingForm', function() {\n        return {\n            listingType: '',\n            propertyType: '',\n            hasParking: false,\n            hasProvision: false,\n            energyValue: '',\n            energySources: [],\n            energySourceLabels: {},\n            counts: {},\n            sections: {\n                basisdaten: ['property_type', 'condition', 'construction_year'],\n                adresse: ['street'],\n                flaechen: ['living_area', 'rooms'],\n                finanzen_sale: ['price', 'buyer_commission'],\n                finanzen_rental: ['cold_rent', 'utilities', 'deposit'],\n                energie: ['energy_cert_type', 'energy_value', 'energy_source', 'heating_type']\n            },\n            _saveTimer: null,\n            saveStatus: '',\n            _saveUrl: '',\n\n            init() {\n                const d = this.$el.dataset;\n                this.listingType = d.listingType || 'sale';\n                this.propertyType = d.propertyType || '';\n                this.hasParking = d.hasParking === 'true';\n                this.hasProvision = d.hasProvision === 'true';\n                this.energyValue = d.energyValue || '';\n                this._saveUrl = d.saveUrl || '';\n\n                try { this.energySources = JSON.parse(d.energySources || '[]'); } catch(e) { this.energySources = []; }\n                try { this.energySourceLabels = JSON.parse(d.energySourceLabels || '{}'); } catch(e) { this.energySourceLabels = {}; }\n\n                this.$nextTick(() => this.updateAll());\n            },\n\n            updateAll() {\n                for (const [key, fields] of Object.entries(this.sections)) {\n                    if (key === 'finanzen_sale' || key === 'finanzen_rental') continue;\n                    this.counts[key] = this._count(fields);\n                }\n                const finKey = this.listingType === 'sale' ? 'finanzen_sale' : 'finanzen_rental';\n                this.counts.finanzen = this._count(this.sections[finKey]);\n            },\n\n            _count(fields) {\n                let filled = 0;\n                for (const f of fields) {\n                    if (f === 'energy_source') { if (this.energySources.length > 0) filled++; continue; }\n                    const el = this.$el.querySelector('[name=\"' + f + '\"]');\n                    if (el && el.value && el.value.trim() !== '') filled++;\n                }\n                return { filled, total: fields.length };\n            },\n\n            filled(s) { return this.counts[s]?.filled || 0; },\n            total(s) { return this.counts[s]?.total || 0; },\n            pct(s) { const t = this.total(s); return t > 0 ? Math.round(this.filled(s) / t * 100) : 0; },\n            colorClass(s) { const p = this.pct(s); return p === 100 ? 'text-emerald-600' : p > 0 ? 'text-amber-600' : 'text-muted-foreground'; },\n            strokeClass(s) { const p = this.pct(s); return p === 100 ? 'stroke-emerald-500' : p > 0 ? 'stroke-amber-500' : 'stroke-muted-foreground/30'; },\n\n            // Same rules as _parse_decimal in dashboard/views/expose.py: with dot\n            // and comma the later one is the decimal separator, a lone\n            // separator before groups of three digits groups thousands\n            // (\"219.000\", \"450,000\"), any other comma is a decimal separator.\n            parseNumber(raw) {\n                let value = (raw || '').trim();\n                if (value.includes(',') && value.includes('.')) {\n                    value = value.lastIndexOf('.') > value.lastIndexOf(',')\n                        ? value.replace(/,/g, '')\n                        : value.replace(/\\./g, '').replace(',', '.');\n                } else if (/^[1-9]\\d{0,2}([.,])\\d{3}(?:\\1\\d{3})*$/.test(value)) {\n                    value = value.replace(/[.,]/g, '');\n                } else {\n                    value = value.replace(',', '.');\n                }\n                return parseFloat(value);\n            },\n\n            computedWarmRent() {\n                const cold = this.parseNumber(this.$el.querySelector('[name=\"cold_rent\"]')?.value);\n                const util = this.parseNumber(this.$el.querySelector('[name=\"utilities\"]')?.value);\n                if (!isNaN(cold) && !isNaN(util)) return (cold + util).toLocaleString('de-DE', {minimumFractionDigits: 0, maximumFractionDigits: 2});\n                if (!isNaN(cold)) return cold.toLocaleString('de-DE', {minimumFractionDigits: 0, maximumFractionDigits: 2});\n                return '';\n            },\n\n            computedEnergyClass() {\n                const val = this.parseNumber(this.energyValue);\n                if (isNaN(val)) return '';\n                if (val <= 30) return 'A+';\n                if (val <= 50) return 'A';\n                if (val <= 75) return 'B';\n                if (val <= 100) return 'C';\n                if (val <= 130) return 'D';\n                if (val <= 160) return 'E';\n                if (val <= 200) return 'F';\n                if (val <= 250) return 'G';\n                return 'H';\n            },\n\n            autoSave() {\n                clearTimeout(this._saveTimer);\n                this._saveTimer = setTimeout(() => this._doSave(), 1500);\n            },\n\n            async _doSave() {\n                this.saveStatus = 'saving';\n                try {\n                    const formData = new FormData(this.$el);\n                    const res = await fetch(this._saveUrl, {\n                        method: 'POST',\n                        headers: {\n                            'X-Auto-Save': '1',\n                            'X-CSRFToken': formData.get('csrfmiddlewaretoken'),\n                        },\n                        body: formData,\n                    });\n                    const data = await res.json();\n                    this.saveStatus = data.ok ? 'saved' : '';\n                    if (data.expose_notice_changed) {\n                        window.dispatchEvent(new CustomEvent('expose-settings-changed'));\n                    }\n                } catch {\n                    this.saveStatus = '';\n                }\n                if (this.saveStatus === 'saved') {\n                    setTimeout(() => { if (this.saveStatus === 'saved') this.saveStatus = ''; }, 2000);\n                }\n            }\n        };\n    });\n\n});\n", "/**\n * Shared Alpine.js component for public upload forms.\n * Used by: free tool, foto check, landing pages.\n *\n * Config via data attributes on the x-data element:\n *   data-upload-url    \u2014 POST endpoint for the upload\n *   data-prefill-email \u2014 optional pre-filled email\n *   data-require-consent \u2014 if present, consent checkbox is required\n *   data-error-*       \u2014 messages in the page language, from\n *                        templates/components/public_upload_messages.html\n *                        (marketing pages have no window.I18N)\n */\nimport { uploadRejection } from './alpine-components/upload-validation.js';\n\ndocument.addEventListener('alpine:init', () => {\n    Alpine.data('publicUpload', function() {\n        return {\n            step: 1,\n            dragOver: false,\n            file: null,\n            fileName: '',\n            fileSize: '',\n            previewUrl: '',\n            email: '',\n            consent: false,\n            error: '',\n            submitting: false,\n\n            init() {\n                this.email = this.$root.dataset.prefillEmail || '';\n            },\n\n            handleFileSelect(event) {\n                const file = event.target.files[0];\n                if (file) this.selectFile(file);\n            },\n\n            handleDrop(event) {\n                this.dragOver = false;\n                const file = event.dataTransfer.files[0];\n                if (file) this.selectFile(file);\n            },\n\n            selectFile(file) {\n                const rejection = uploadRejection(file);\n                if (rejection) {\n                    this.error = rejection === 'size'\n                        ? this.$root.dataset.errorFileSize\n                        : this.$root.dataset.errorFileType;\n                    return;\n                }\n\n                this.file = file;\n                this.fileName = file.name;\n                this.fileSize = (file.size / (1024 * 1024)).toFixed(1) + ' MB';\n                this.previewUrl = URL.createObjectURL(file);\n                this.error = '';\n                this.step = 2;\n            },\n\n            reset() {\n                if (this.previewUrl) URL.revokeObjectURL(this.previewUrl);\n                this.step = 1;\n                this.file = null;\n                this.fileName = '';\n                this.fileSize = '';\n                this.previewUrl = '';\n                this.error = '';\n                this.consent = false;\n                const fileInput = this.$root.querySelector('input[type=file]');\n                if (fileInput) fileInput.value = '';\n            },\n\n            async submit() {\n                if (this.$root.dataset.requireConsent !== undefined && !this.consent) {\n                    this.error = this.$root.dataset.errorConsent;\n                    return;\n                }\n\n                this.error = '';\n                this.submitting = true;\n\n                const formData = new FormData();\n                formData.append('image', this.file);\n                formData.append('email', this.email);\n\n                if (this.consent) {\n                    formData.append('consent_public', 'on');\n                }\n\n                // Turnstile token\n                const turnstileInput = this.$root.querySelector('[name=\"cf-turnstile-response\"]');\n                if (turnstileInput) {\n                    formData.append('cf-turnstile-response', turnstileInput.value);\n                }\n\n                const csrfToken = document.querySelector('meta[name=\"csrf-token\"]').content;\n                const uploadUrl = this.$root.dataset.uploadUrl;\n\n                try {\n                    const response = await fetch(uploadUrl, {\n                        method: 'POST',\n                        headers: { 'X-CSRFToken': csrfToken },\n                        body: formData,\n                    });\n\n                    const data = await response.json();\n\n                    if (!response.ok) {\n                        this.error = data.error || this.$root.dataset.errorGeneric;\n                        const widget = this.$root.querySelector('.cf-turnstile');\n                        if (widget && typeof turnstile !== 'undefined') {\n                            turnstile.reset(widget);\n                        }\n                        this.submitting = false;\n                        return;\n                    }\n\n                    window.location.href = data.redirect_url;\n                } catch (e) {\n                    this.error = this.$root.dataset.errorConnection;\n                    this.submitting = false;\n                }\n            }\n        };\n    });\n});\n"],
  "mappings": "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",
  "names": ["I18N", "shapePoints", "shape", "x", "y", "width", "height", "svgPoints", "point", "json", "body", "node", "id", "xhr", "_a", "_b", "config", "base", "detail", "box", "open", "behavior", "event", "successful", "prefsJson", "prefs", "sel", "host", "prefill", "value", "subtype", "tab", "meta", "err", "button", "data", "propertyId", "keepSite", "v", "k", "presets", "inputs", "i", "inp", "selected", "p", "self", "has3d", "gate", "DOUBLE_TAP_SLOP", "clamp", "value", "min", "max", "orientation", "a", "b", "c", "pointOnSegment", "point", "segmentsIntersect", "d", "abC", "abD", "cdA", "cdB", "epsilon", "polygonArea", "points", "sum", "index", "next", "polygonIsSimple", "first", "firstNext", "second", "secondNext", "openPathIsSimple", "lastStart", "lastEnd", "shape", "rect", "xs", "ys", "left", "top", "event", "threshold", "gesture", "start", "cancelled", "areaTooSmall", "last", "slop", "previous", "candidate", "item", "dx", "dy", "viewport", "BRUSH_COLOR", "svgNumber", "value", "simplifyStroke", "points", "scaleX", "scaleY", "tolerance", "keep", "stack", "first", "last", "ax", "ay", "dx", "dy", "length", "farthest", "farthestDistance", "index", "px", "py", "distance", "_", "strokes", "width", "height", "items", "stroke", "strokeWidth", "point", "x", "y", "BRUSH_COLOR", "event", "_a", "viewport", "clamp", "value", "min", "max", "rounded", "brushRounded", "BRUSH_SIZE_MIN", "BRUSH_SIZE_MAX", "STAGE_RESERVED_HEIGHT", "STAGE_MIN_HEIGHT", "distance", "a", "b", "midpoint", "highlightOperation", "operations", "operation", "cropOperation", "configElement", "event", "_a", "_b", "_c", "_d", "_e", "_f", "_g", "highlight", "crop", "params", "rect", "point", "cropRect", "tool", "detail", "mode", "base", "shape", "stroke", "x", "y", "lastWidth", "target", "response", "message", "body", "error", "image", "lightboxLink", "el", "id", "key", "fallback", "handle", "gesture", "wasDraw", "moved", "originalRect", "minimum", "distanceMoved", "start", "dx", "dy", "original", "left", "top", "right", "bottom", "points", "viewport", "mid", "currentMid", "nextZoom", "anchorX", "anchorY", "polygonDraft", "current", "polygonInProgress", "nonTextInputs", "source", "width", "height", "ratio", "shortEdge", "blur", "brightness", "fill", "I18N", "prefs", "data", "id", "photoId", "url", "meta", "err", "checked", "checkbox", "groupKey", "selector", "cb", "checkboxes", "selectedCount", "params", "_a", "photoIds", "modal", "context", "csrf", "formData", "r", "canShareFiles", "fetchSlide", "url", "name", "response", "error", "blob", "withoutLine", "text", "line", "lines", "position", "cleanLabel", "value", "afterFirstLine", "slideName", "withLineAfter", "anchor", "files", "objectUrls", "destroyed", "labelTimer", "coverRequest", "rendered", "params", "label", "withPrice", "caption", "data", "slide", "file", "previous", "loaded", "next", "worker", "err", "workers", "count", "kept", "keptFiles", "request", "choices", "headline", "on", "price", "_a", "body", "meta", "VALID_TYPES", "uploadRejection", "file", "VALID_TYPES", "rejectionMessage", "reason", "I18N", "I18N", "files", "_a", "file", "rejection", "uploadRejection", "rejectionMessage", "msg", "rowId", "uploadRows", "name", "retryId", "tempId", "previewUrl", "uploadUrl", "csrfToken", "placeholderHtml", "doUpload", "formData", "response", "data", "showError", "placeholder", "retryBtn", "row", "onSuccess", "e", "friendlyMsg", "onDone", "error", "r", "url", "scrapeResults", "scrapeUrl", "html", "dataEl", "metaEl", "selectedIndexes", "urls", "i", "importUrl", "importedCount", "tempIds", "_", "result", "successCount", "id", "el", "text", "div", "openModal", "_a", "event", "file", "_b", "validExtension", "validType", "estimateProgress", "set", "expectedMs", "started", "tau", "timer", "t", "LISTING_LOAD_MS", "PARALLEL_UPLOADS", "csrfToken", "postForm", "url", "formData", "response", "data", "failureReason", "status", "hostOf", "uploadToGallery", "uploadUrl", "files", "propertyId", "onProgress", "I18N", "done", "galleryUrl", "lastError", "send", "file", "_a", "ok", "queue", "first", "worker", "checkFiles", "fileList", "error", "reason", "uploadRejection", "rejectionMessage", "path", "result", "_b", "_c", "_d", "estimateProgress", "v", "host", "u", "event", "img", "message", "limitReached", "after", "form", "id", "card", "before", "d", "download", "frame", "selected", "rejected", "total", "label", "n", "modalStore", "csrfToken", "meta", "post", "url", "params", "body", "key", "track", "name", "seenUrl", "store", "forced", "el", "id", "self", "value", "previous", "context", "VIEWS_KEY", "SHOWN_KEY", "MODAL_ID", "appPageViews", "countAppPageView", "shownInThisTab", "markShownInThisTab", "event", "modalStore", "csrfToken", "meta", "post", "url", "params", "options", "body", "key", "el", "response", "data", "store", "step", "text", "nextStep", "SCRAPE_MS", "IMPORT_BASE_MS", "IMPORT_PER_PHOTO_MS", "I18N", "_a", "isOpen", "url", "s", "source", "i", "other", "sources", "requestUrl", "csrfToken", "done", "estimateProgress", "v", "scrapeResults", "scrapeUrl", "formData", "html", "dataEl", "metaEl", "selectedIndexes", "name", "urls", "createUrl", "res", "data", "SPECIAL_LETTERS", "COMBINING_MARKS", "UMLAUT_SPELLINGS", "URL_DELAY_MS", "fold", "text", "out", "ranges", "ch", "lower", "c", "plain", "p", "loosen", "folded", "src", "i", "searchable", "strict", "wordRanges", "name", "word", "form", "haystack", "needle", "found", "at", "mergeRanges", "sorted", "a", "b", "merged", "r", "last", "renderName", "el", "parts", "pos", "start", "end", "mark", "isTypingTarget", "root", "cards", "countTemplate", "emptyTemplate", "urlTimer", "card", "nameEl", "q", "words", "w", "shown", "item", "match", "url", "path", "event", "_a", "input", "initialStreet", "initialPostalCode", "initialCity", "apiKey", "key", "_a", "_b", "e", "resolve", "reject", "cb", "s", "q", "input", "suggestions", "suggestion", "place", "components", "route", "streetNumber", "component", "type", "d", "key", "fields", "finKey", "filled", "f", "el", "s", "_a", "t", "p", "raw", "value", "_b", "cold", "util", "val", "formData", "data", "event", "file", "rejection", "uploadRejection", "fileInput", "formData", "turnstileInput", "csrfToken", "uploadUrl", "response", "data", "widget"]
}
