{"id":212,"date":"2026-05-12T20:57:52","date_gmt":"2026-05-12T18:57:52","guid":{"rendered":"https:\/\/gradprotect.pl\/sklep\/?page_id=212"},"modified":"2026-05-12T20:57:52","modified_gmt":"2026-05-12T18:57:52","slug":"konfigurator","status":"publish","type":"page","link":"https:\/\/gradprotect.pl\/sklep\/konfigurator\/","title":{"rendered":"Konfigurator"},"content":{"rendered":"\n    <div class=\"gp-configurator\">\n        <div class=\"gp-header\">\n            <h2>Kalkulator dla samochod\u00f3w osobowych<\/h2>\n            <p class=\"gp-subtitle\">Wymiary powierzchni parkingowej do zabezpieczenia przed gradem<\/p>\n        <\/div>\n\n        <div class=\"gp-input-panel\">\n            <h3>Wymiary powierzchni parkingowej<\/h3>\n            <div class=\"gp-input-grid\">\n                <label class=\"gp-input-field\">\n                    <span>D\u0142ugo\u015b\u0107 L [m]<\/span>\n                    <input type=\"number\" id=\"gp_parking_length\" min=\"1\" step=\"0.01\" value=\"12\">\n                <\/label>\n                <label class=\"gp-input-field\">\n                    <span>Szeroko\u015b\u0107 H [m]<\/span>\n                    <input type=\"number\" id=\"gp_parking_width\" min=\"1\" step=\"0.01\" value=\"10\">\n                <\/label>\n                <label class=\"gp-input-field\">\n                    <span>Uk\u0142ad miejsc<\/span>\n                    <select id=\"gp_parking_orientation\">\n                        <option value=\"vertical\">pionowy<\/option>\n                        <option value=\"horizontal\">poziomy<\/option>\n                    <\/select>\n                <\/label>\n            <\/div>\n            <div class=\"gp-dimension-mini\">\n                <div class=\"gp-dim-top\">L<\/div>\n                <div class=\"gp-dim-left\">H<\/div>\n                <div class=\"gp-dim-box\"><\/div>\n            <\/div>\n            <div class=\"gp-note-small\">Za\u0142o\u017cony wymiar miejsca: 2,5 \u00d7 5,0 m<\/div>\n            <div class=\"gp-footnotes\">\n                <div>* Za\u0142o\u017cono wymiar miejsca 2,5 na 5,0 m - chcesz inny, skontaktuj si\u0119 z nami!<\/div>\n                <div>** Kalkulator okre\u015bla szacunkow\u0105 cen\u0119 na dostaw\u0119 element\u00f3w systemu.<\/div>\n            <\/div>\n        <\/div>\n\n        <div class=\"gp-top-actions\">\n            <button type=\"button\" id=\"gp_calculate\">Oblicz zestaw<\/button>\n            <!-- Bezpo\u015brednie dodawanie zestawu do koszyka jest tymczasowo wy\u0142\u0105czone. -->\n        <\/div>\n        <div id=\"gp_price_preview\" class=\"gp-price-bar\" style=\"display:none;\"><\/div>\n\n        <div id=\"gp_visualization\" class=\"gp-viz-section\"><\/div>\n        <div id=\"gp_viz_modal\" class=\"gp-viz-modal\" aria-hidden=\"true\">\n            <div class=\"gp-viz-modal__backdrop\" data-gp-viz-close><\/div>\n            <div class=\"gp-viz-modal__dialog\" role=\"dialog\" aria-modal=\"true\">\n                <button type=\"button\" class=\"gp-viz-modal__close\" data-gp-viz-close>\u00d7<\/button>\n                <div class=\"gp-viz-modal__title\" id=\"gp_viz_modal_title\"><\/div>\n                <div class=\"gp-viz-modal__body\" id=\"gp_viz_modal_body\"><\/div>\n            <\/div>\n        <\/div>\n        <div id=\"gp_result\" class=\"gp-summary-panel\"><\/div>\n\n        <div class=\"gp-bottom-cta\" id=\"gp_bottom_cta\" style=\"display:none;\">\n            <div class=\"gp-offer-note\">\n                <strong>Podana cena jest szacunkowa.<\/strong>\n                <span>\u017beby otrzyma\u0107 dok\u0142adn\u0105 ofert\u0119 dla tego zestawu, napisz do nas.<\/span>\n            <\/div>\n            <a href=\"https:\/\/gradprotect.pl\/#kontakt\" class=\"gp-contact-link\">Napisz po dok\u0142adn\u0105 ofert\u0119<\/a>\n        <\/div>\n    <\/div>\n\n    <script>\n    document.addEventListener('DOMContentLoaded', function () {\n        const calculateBtn = document.getElementById('gp_calculate');\n        const addToCartBtn = document.getElementById('gp_add_to_cart');\n        const addToCartBtnBottom = document.getElementById('gp_add_to_cart_bottom');\n        const bottomCta = document.getElementById('gp_bottom_cta');\n        const resultBox = document.getElementById('gp_result');\n        const pricePreview = document.getElementById('gp_price_preview');\n        const gpAjaxNonce = \"bdb0ffe5c8\";\n        const gpComponentPrices = {\"gp_posts\":1138.240000000000009094947017729282379150390625,\"gp_anchors\":216,\"gp_clips\":1.3000000000000000444089209850062616169452667236328125,\"gp_rope\":4.089999999999999857891452847979962825775146484375,\"gp_net\":2.87999999999999989341858963598497211933135986328125,\"gp_tensioners\":3.660000000000000142108547152020037174224853515625,\"gp_clamps\":1.8899999999999999023003738329862244427204132080078125,\"gp_thimbles\":0.810000000000000053290705182007513940334320068359375,\"gp_shackles\":6.0800000000000000710542735760100185871124267578125};\n        const PLACE_SHORT_SIDE = 2.5;\n        const PLACE_LONG_SIDE = 5.0;\n        const MAX_FIELD_AREA = 75;\n        let calculatedData = {};\n        let gpLastVisualizationModel = null;\n\n        if (!calculateBtn || !resultBox) {\n            return;\n        }\n\n        function gpSetOfferCtaVisible(isVisible) {\n            if (addToCartBtn) {\n                addToCartBtn.style.display = 'none';\n            }\n            if (addToCartBtnBottom) {\n                addToCartBtnBottom.style.display = 'none';\n            }\n            if (bottomCta) {\n                bottomCta.style.display = isVisible ? 'flex' : 'none';\n            }\n        }\n\n        function gpRound(value, decimals = 2) {\n            return Number(Math.round(value + 'e' + decimals) + 'e-' + decimals);\n        }\n\n        function gpOpenVizModal(title, type) {\n            const modal = document.getElementById('gp_viz_modal');\n            const modalTitle = document.getElementById('gp_viz_modal_title');\n            const modalBody = document.getElementById('gp_viz_modal_body');\n            if (!modal || !modalTitle || !modalBody || !gpLastVisualizationModel) {\n                return;\n            }\n            modalTitle.textContent = title || 'Powi\u0119kszenie wizualizacji';\n            let html = '';\n            if (type === 'fields') {\n                html = gpRenderFieldsSvg(gpLastVisualizationModel, true);\n            } else if (type === 'places') {\n                html = gpRenderPlacesSvg(gpLastVisualizationModel, true);\n            }\n            modalBody.innerHTML = html;\n            modal.classList.add('is-open');\n            modal.setAttribute('aria-hidden', 'false');\n            document.body.style.overflow = 'hidden';\n        }\n\n        function gpCloseVizModal() {\n            const modal = document.getElementById('gp_viz_modal');\n            const modalBody = document.getElementById('gp_viz_modal_body');\n            if (!modal) {\n                return;\n            }\n            modal.classList.remove('is-open');\n            modal.setAttribute('aria-hidden', 'true');\n            if (modalBody) {\n                modalBody.innerHTML = '';\n            }\n            document.body.style.overflow = '';\n        }\n\n        function gpGetParkingPlaceLabel(counter, placesCount, slotW, slotH, isModal = false) {\n            if (slotW < 10 || slotH < 8) {\n                return '';\n            }\n\n            if (isModal) {\n                if (placesCount <= 80) {\n                    if (slotW >= 56) {\n                        if (slotH >= 20) {\n                            return `Miejsce ${counter}`;\n                        }\n                    }\n                }\n                if (placesCount <= 160) {\n                    if (slotW >= 30) {\n                        if (slotH >= 15) {\n                            return `M${counter}`;\n                        }\n                    }\n                }\n                if (placesCount <= 300) {\n                    if (slotW >= 18) {\n                        if (slotH >= 12) {\n                            return `${counter}`;\n                        }\n                    }\n                }\n                return '';\n            }\n\n            if (placesCount <= 20) {\n                if (slotW >= 52) {\n                    if (slotH >= 18) {\n                        return `Miejsce ${counter}`;\n                    }\n                }\n            }\n\n            if (placesCount <= 50) {\n                if (slotW >= 24) {\n                    if (slotH >= 14) {\n                        return `M${counter}`;\n                    }\n                }\n            }\n\n            if (placesCount <= 100) {\n                if (slotW >= 16) {\n                    if (slotH >= 12) {\n                        return `${counter}`;\n                    }\n                }\n            }\n\n            return '';\n        }\n\n        function gpFetchBackendPreview(parkingLength, parkingWidth, orientation) {\n            const formData = new FormData();\n            formData.append('action', 'gp_calculate_custom_set_preview');\n            formData.append('parking_length', parkingLength);\n            formData.append('parking_width', parkingWidth);\n            formData.append('orientation', orientation);\n            formData.append('nonce', gpAjaxNonce);\n\n            return fetch('https:\/\/gradprotect.pl\/sklep\/wp-admin\/admin-ajax.php', {\n                method: 'POST',\n                body: formData\n            }).then(response => response.json());\n        }\n\n        function gpRenderLoadingSummary(layout) {\n            const orientationLabel = layout.orientation === 'vertical' ? 'pionowy' : 'poziomy';\n\n            resultBox.innerHTML = `\n                <div class=\"gp-result-section\">\n                    <h3>Podsumowanie powierzchni<\/h3>\n                    <div class=\"gp-summary-grid\">\n                        <div class=\"gp-summary-item\"><span class=\"gp-label\">Wprowadzony wymiar<\/span><strong>${gpRound(layout.parkingLength,2)} m \u00d7 ${gpRound(layout.parkingWidth,2)} m<\/strong><\/div>\n                        <div class=\"gp-summary-item\"><span class=\"gp-label\">Uk\u0142ad miejsc<\/span><strong>${orientationLabel}<\/strong><\/div>\n                        <div class=\"gp-summary-item\"><span class=\"gp-label\">Liczba miejsc<\/span><strong>${layout.placesCount}<\/strong><\/div>\n                        <div class=\"gp-summary-item\"><span class=\"gp-label\">Wykorzystany wymiar<\/span><strong>${gpRound(layout.usedLength,2)} m \u00d7 ${gpRound(layout.usedWidth,2)} m<\/strong><\/div>\n                        <div class=\"gp-summary-item\"><span class=\"gp-label\">Wykorzystana powierzchnia<\/span><strong>${gpRound(layout.usedArea,2)} m\u00b2<\/strong><\/div>\n                        <div class=\"gp-summary-item\"><span class=\"gp-label\">Niewykorzystany zapas<\/span><strong>${gpRound(layout.unusedLength,2)} m d\u0142. \/ ${gpRound(layout.unusedWidth,2)} m szer.<\/strong><\/div>\n                    <\/div>\n                <\/div>\n\n                <div class=\"gp-result-section\">\n                    <h3>Ilo\u015bci element\u00f3w systemu<\/h3>\n                    <div class=\"gp-notes-panel\">\n                        Obliczanie ilo\u015bci element\u00f3w...\n                    <\/div>\n                <\/div>\n            `;\n        }\n\n        function gpShouldShowStepLabel(index, totalSegments, isModal = false) {\n            if (isModal) {\n                if (totalSegments <= 20) return true;\n                if (totalSegments <= 40) return index % 2 === 0;\n                if (totalSegments <= 80) return index % 4 === 0;\n                return index % 8 === 0;\n            }\n            if (totalSegments <= 8) return true;\n            if (totalSegments <= 16) return index % 2 === 0;\n            if (totalSegments <= 28) return index % 3 === 0;\n            return false;\n        }\n\n        document.querySelectorAll('[data-gp-viz-close]').forEach(function (el) {\n            el.addEventListener('click', gpCloseVizModal);\n        });\n        document.addEventListener('keydown', function (e) {\n            if (e.key === 'Escape') {\n                gpCloseVizModal();\n            }\n        });\n\n        function gpCalculateParkingLayout(parkingLength, parkingWidth, orientation) {\n            if (!parkingLength || parkingLength <= 0 || !parkingWidth || parkingWidth <= 0) {\n                throw new Error('Podaj poprawne wymiary powierzchni parkingowej.');\n            }\n            if (!['vertical', 'horizontal'].includes(orientation)) {\n                throw new Error('Wybierz poprawny uk\u0142ad miejsc.');\n            }\n\n            const placeLengthAlongParking = orientation === 'vertical' ? PLACE_SHORT_SIDE : PLACE_LONG_SIDE;\n            const placeWidthAlongParking = orientation === 'vertical' ? PLACE_LONG_SIDE : PLACE_SHORT_SIDE;\n\n            const cols = Math.floor(parkingLength \/ placeLengthAlongParking);\n            const rows = Math.floor(parkingWidth \/ placeWidthAlongParking);\n            if (cols < 1 || rows < 1) {\n                throw new Error('W podanych wymiarach nie mie\u015bci si\u0119 pe\u0142ne miejsce postojowe 2,5 \u00d7 5,0 m.');\n            }\n\n            const inputArea = parkingLength * parkingWidth;\n            const usedLength = cols * placeLengthAlongParking;\n            const usedWidth = rows * placeWidthAlongParking;\n            const usedArea = usedLength * usedWidth;\n            const placesCount = cols * rows;\n            const unusedLength = parkingLength - usedLength;\n            const unusedWidth = parkingWidth - usedWidth;\n            const unusedArea = inputArea - usedArea;\n\n            return {\n                parkingLength,\n                parkingWidth,\n                orientation,\n                placeLengthAlongParking,\n                placeWidthAlongParking,\n                cols,\n                rows,\n                placesCount,\n                usedLength,\n                usedWidth,\n                usedArea,\n                inputArea,\n                unusedLength,\n                unusedWidth,\n                unusedArea\n            };\n        }\n\n        function gpSplitParkingIntoTechnicalFields(layout) {\n            const placeArea = layout.placeLengthAlongParking * layout.placeWidthAlongParking;\n            const maxPlacesPerField = Math.floor(MAX_FIELD_AREA \/ placeArea);\n            if (maxPlacesPerField < 1) {\n                throw new Error('Nie mo\u017cna wyznaczy\u0107 pola technicznego dla tego uk\u0142adu miejsc.');\n            }\n\n            const maxColsBySpan = Math.max(1, Math.floor(10 \/ layout.placeLengthAlongParking));\n            const maxRowsBySpan = Math.max(1, Math.floor(10 \/ layout.placeWidthAlongParking));\n            const colsPerField = Math.max(1, Math.min(layout.cols, maxColsBySpan, maxPlacesPerField));\n            const rowsPerField = Math.max(1, Math.min(layout.rows, maxRowsBySpan, Math.floor(maxPlacesPerField \/ colsPerField)));\n            const fields = [];\n            let index = 1;\n\n            for (let startRow = 0; startRow < layout.rows; startRow += rowsPerField) {\n                const endRow = Math.min(layout.rows - 1, startRow + rowsPerField - 1);\n                const rowsCount = endRow - startRow + 1;\n\n                for (let startCol = 0; startCol < layout.cols; startCol += colsPerField) {\n                    const endCol = Math.min(layout.cols - 1, startCol + colsPerField - 1);\n                    const colsCount = endCol - startCol + 1;\n                    const length = colsCount * layout.placeLengthAlongParking;\n                    const width = rowsCount * layout.placeWidthAlongParking;\n                    const area = length * width;\n\n                    if (length > (10 + 0.000001) || width > (10 + 0.000001)) {\n                        throw new Error('Pole techniczne przekracza maksymalny rozstaw 10 m.');\n                    }\n                    if (area > (MAX_FIELD_AREA + 0.000001)) {\n                        throw new Error('Pole techniczne przekracza 75 m\u00b2.');\n                    }\n\n                    fields.push({\n                        index,\n                        startCol,\n                        endCol,\n                        startRow,\n                        endRow,\n                        colsCount,\n                        rowsCount,\n                        length,\n                        width,\n                        area\n                    });\n                    index++;\n                }\n            }\n\n            return fields;\n        }\n\n        function gpCalculateTechnicalGridLines(layout, fields) {\n            const verticalSet = new Set([0, gpRound(layout.usedLength, 4)]);\n            const horizontalSet = new Set([0, gpRound(layout.usedWidth, 4)]);\n\n            fields.forEach((field) => {\n                verticalSet.add(gpRound(field.startCol * layout.placeLengthAlongParking, 4));\n                verticalSet.add(gpRound((field.endCol + 1) * layout.placeLengthAlongParking, 4));\n                horizontalSet.add(gpRound(field.startRow * layout.placeWidthAlongParking, 4));\n                horizontalSet.add(gpRound((field.endRow + 1) * layout.placeWidthAlongParking, 4));\n            });\n\n            const verticalLines = Array.from(verticalSet).sort((a, b) => a - b);\n            const horizontalLines = Array.from(horizontalSet).sort((a, b) => a - b);\n            const gridPoints = [];\n\n            horizontalLines.forEach((y) => {\n                verticalLines.forEach((x) => {\n                    const onLeft = Math.abs(x) < 0.0001;\n                    const onRight = Math.abs(x - layout.usedLength) < 0.0001;\n                    const onTop = Math.abs(y) < 0.0001;\n                    const onBottom = Math.abs(y - layout.usedWidth) < 0.0001;\n                    let type = 'internal';\n                    if (onLeft || onRight) {\n                        if (onTop || onBottom) {\n                            type = 'corner';\n                        } else {\n                            type = 'edge';\n                        }\n                    } else if (onTop || onBottom) {\n                        type = 'edge';\n                    }\n                    gridPoints.push({ x, y, type });\n                });\n            });\n\n            return { verticalLines, horizontalLines, gridPoints };\n        }\n\n        function gpCalculateRopeRawForField(length, width, postHeight = 2.4) {\n            const panels = [\n                [length, width],\n                [postHeight, length],\n                [postHeight, length],\n                [postHeight, width],\n                [postHeight, width]\n            ];\n\n            return panels.reduce((sum, [a, b]) => {\n                const perimeter = 2 * (a + b);\n                const diagonal = Math.sqrt((a ** 2) + (b ** 2));\n                return sum + perimeter + (2 * diagonal);\n            }, 0);\n        }\n\n        function gpCountPanelSegmentsForLength(length) {\n            const segments = gpSplitByMaxSpan(length);\n            if (!Array.isArray(segments) || segments.length < 1) {\n                return 0;\n            }\n\n            return segments.length;\n        }\n\n        function gpCalculateClipsRawForField(length, width, postHeight = 2.4) {\n            if (length <= 0 || width <= 0 || postHeight <= 0) {\n                return 0;\n            }\n\n            return (4 * (length + width)) + (4 * postHeight);\n        }\n\n        function gpCalculateLegacyComponents(layout) {\n            \/\/ Deprecated: frontend component calculation is no longer used for price preview.\n            const clipSpacing = 0.3;\n            const postHeight = 2.4;\n            const fields = gpSplitParkingIntoTechnicalFields(layout);\n            const gridLines = gpCalculateTechnicalGridLines(layout, fields);\n\n            let ropeRaw = 0;\n            let clipsRaw = 0;\n            fields.forEach((field) => {\n                const length = Number(field.length || 0);\n                const width = Number(field.width || 0);\n                if (length <= 0 || width <= 0) {\n                    return;\n                }\n\n                ropeRaw += gpCalculateRopeRawForField(length, width, postHeight);\n                clipsRaw += gpCalculateClipsRawForField(length, width, postHeight) \/ clipSpacing;\n            });\n\n            const rope = Math.ceil(ropeRaw * 1.02);\n            const clips = Math.ceil(clipsRaw);\n            const verticalCount = gridLines.verticalLines.length;\n            const horizontalCount = gridLines.horizontalLines.length;\n            const cornerPosts = (verticalCount >= 2 && horizontalCount >= 2) ? 4 : 0;\n            const edgePosts = Math.max(0, (verticalCount - 2) * 2) + Math.max(0, (horizontalCount - 2) * 2);\n            const innerPosts = Math.max(0, (verticalCount - 2) * (horizontalCount - 2));\n            const intermediatePosts = edgePosts + innerPosts;\n            const posts = cornerPosts + intermediatePosts;\n            const anchors = posts;\n            const totalLength = layout.usedLength;\n            const totalWidth = layout.usedWidth;\n            const totalArea = layout.usedArea;\n            const netRaw = totalArea + (postHeight * ((2 * totalLength) + (2 * totalWidth)));\n            const net = Math.ceil(netRaw * 1.15);\n            const topPanelCount = fields.length;\n            const sidePanelCount =\n                gpCountPanelSegmentsForLength(totalLength) +\n                gpCountPanelSegmentsForLength(totalLength) +\n                gpCountPanelSegmentsForLength(totalWidth) +\n                gpCountPanelSegmentsForLength(totalWidth);\n            const topTensioners = topPanelCount * 12;\n            const sideTensioners = sidePanelCount * 12;\n            const tensioners = topTensioners + sideTensioners;\n            const clamps = tensioners * 2;\n            const thimbles = tensioners;\n            const shackles = tensioners;\n\n            return {\n                posts,\n                anchors,\n                rope,\n                clips,\n                net,\n                tensioners,\n                clamps,\n                thimbles,\n                shackles\n            };\n        }\n\n        function gpRenderFieldsSvg(model, isModal = false) {\n            const { layout, fields, gridLines } = model;\n            const panelWidth = isModal ? 1200 : 470;\n            const panelHeight = isModal ? 780 : 300;\n            const drawingWidth = panelWidth - (isModal ? 160 : 72);\n            const drawingHeight = panelHeight - (isModal ? 180 : 110);\n            const scaleX = drawingWidth \/ layout.parkingLength;\n            const scaleY = drawingHeight \/ layout.parkingWidth;\n            const scale = Math.min(scaleX, scaleY);\n            const fullW = layout.parkingLength * scale;\n            const fullH = layout.parkingWidth * scale;\n            const usedW = layout.usedLength * scale;\n            const usedH = layout.usedWidth * scale;\n            const startX = (panelWidth - fullW) \/ 2;\n            const startY = 48;\n            const usedX = startX;\n            const usedY = startY;\n            const manyFields = fields.length > 12;\n            const veryManyFields = fields.length > 24;\n            const manyVerticalSegments = gridLines.verticalLines.length > 8;\n            const manyHorizontalSegments = gridLines.horizontalLines.length > 8;\n\n            let left = `<svg viewBox=\"0 0 ${panelWidth} ${panelHeight}\" width=\"100%\" style=\"border:1px solid #ddd;background:#fafafa;border-radius:8px;\">\n                <rect x=\"${startX}\" y=\"${startY}\" width=\"${fullW}\" height=\"${fullH}\" fill=\"#f4f4f4\" stroke=\"#999\" stroke-width=\"1\"\/>\n                <rect x=\"${usedX}\" y=\"${usedY}\" width=\"${usedW}\" height=\"${usedH}\" fill=\"#eef7ff\" stroke=\"#111\" stroke-width=\"2\"\/>`;\n\n            fields.forEach((field, i) => {\n                const x = usedX + (field.startCol * layout.placeLengthAlongParking * scale);\n                const y = usedY + (field.startRow * layout.placeWidthAlongParking * scale);\n                const w = field.length * scale;\n                const h = field.width * scale;\n                let fieldLabel = '';\n                if (isModal) {\n                    if (fields.length <= 80) {\n                        fieldLabel = fields.length > 24 ? `P${field.index}` : `Pole ${field.index}`;\n                    }\n                } else {\n                    if (fields.length <= 12) {\n                        fieldLabel = `Pole ${field.index}`;\n                    } else if (fields.length <= 24) {\n                        fieldLabel = `P${field.index}`;\n                    }\n                }\n                left += `<rect x=\"${x}\" y=\"${y}\" width=\"${w}\" height=\"${h}\" fill=\"${i % 2 === 0 ? '#e7f3ff' : '#fff0e0'}\" stroke=\"#333\" stroke-width=\"1\"\/>\n                `;\n                if (fieldLabel) {\n                    if (w > 22) {\n                        if (h > 14) {\n                            let fieldFontSize = 11;\n                            if (manyFields) {\n                                fieldFontSize = isModal ? 10 : 8;\n                            }\n                            left += `<text x=\"${x + (w\/2)}\" y=\"${y + (h\/2)}\" text-anchor=\"middle\" font-size=\"${fieldFontSize}\" fill=\"#111\">${fieldLabel}<\/text>`;\n                        }\n                    }\n                }\n            });\n\n            gridLines.verticalLines.forEach((x) => {\n                left += `<line x1=\"${usedX + (x * scale)}\" y1=\"${usedY}\" x2=\"${usedX + (x * scale)}\" y2=\"${usedY + usedH}\" stroke=\"#ff6600\" stroke-width=\"1\"\/>`;\n            });\n            gridLines.horizontalLines.forEach((y) => {\n                left += `<line x1=\"${usedX}\" y1=\"${usedY + (y * scale)}\" x2=\"${usedX + usedW}\" y2=\"${usedY + (y * scale)}\" stroke=\"#ff6600\" stroke-width=\"1\"\/>`;\n            });\n\n            for (let i = 0; i < gridLines.verticalLines.length - 1; i++) {\n                if (!gpShouldShowStepLabel(i, gridLines.verticalLines.length - 1, isModal)) {\n                    continue;\n                }\n                const a = gridLines.verticalLines[i];\n                const b = gridLines.verticalLines[i + 1];\n                const seg = gpRound(b - a, 2);\n                const xm = usedX + ((a + b) \/ 2) * scale;\n                let topFontSize = 10;\n                if (manyVerticalSegments) {\n                    topFontSize = isModal ? 10 : 9;\n                } else if (isModal) {\n                    topFontSize = 11;\n                }\n                left += `<text x=\"${xm}\" y=\"${usedY - 8}\" text-anchor=\"middle\" font-size=\"${topFontSize}\" fill=\"#27313e\">${seg} m<\/text>`;\n            }\n            for (let i = 0; i < gridLines.horizontalLines.length - 1; i++) {\n                if (!gpShouldShowStepLabel(i, gridLines.horizontalLines.length - 1, isModal)) {\n                    continue;\n                }\n                const a = gridLines.horizontalLines[i];\n                const b = gridLines.horizontalLines[i + 1];\n                const seg = gpRound(b - a, 2);\n                const ym = usedY + ((a + b) \/ 2) * scale;\n                let sideFontSize = 10;\n                if (manyHorizontalSegments) {\n                    sideFontSize = isModal ? 10 : 9;\n                } else if (isModal) {\n                    sideFontSize = 11;\n                }\n                left += `<text x=\"${usedX - 8}\" y=\"${ym + 3}\" text-anchor=\"end\" font-size=\"${sideFontSize}\" fill=\"#27313e\">${seg} m<\/text>`;\n            }\n\n            gridLines.gridPoints.forEach((p) => {\n                left += `<circle cx=\"${usedX + (p.x * scale)}\" cy=\"${usedY + (p.y * scale)}\" r=\"4\" fill=\"#111\"\/>`;\n            });\n\n            left += `<text x=\"${usedX + usedW \/ 2}\" y=\"${usedY + usedH + 30}\" text-anchor=\"middle\" font-size=\"12\">Wykorzystany obszar: ${gpRound(layout.usedLength,2)} \u00d7 ${gpRound(layout.usedWidth,2)} m<\/text>`;\n            if (layout.unusedArea > 0.0001) {\n                left += `<text x=\"${usedX + usedW \/ 2}\" y=\"${usedY + usedH + 45}\" text-anchor=\"middle\" font-size=\"11\" fill=\"#666\">Niewykorzystana powierzchnia: ${gpRound(layout.unusedArea,2)} m\u00b2<\/text>`;\n            }\n            left += `<\/svg>`;\n            return left;\n        }\n\n        function gpRenderPlacesSvg(model, isModal = false) {\n            const { layout } = model;\n            const panelWidth = isModal ? 1200 : 470;\n            const panelHeight = isModal ? 780 : 300;\n            const drawingWidth = panelWidth - (isModal ? 120 : 72);\n            const drawingHeight = panelHeight - (isModal ? 150 : 110);\n            const scaleX = drawingWidth \/ layout.parkingLength;\n            const scaleY = drawingHeight \/ layout.parkingWidth;\n            const scale = Math.min(scaleX, scaleY);\n            const fullW = layout.parkingLength * scale;\n            const fullH = layout.parkingWidth * scale;\n            const usedW = layout.usedLength * scale;\n            const usedH = layout.usedWidth * scale;\n            const startX = (panelWidth - fullW) \/ 2;\n            const startY = 48;\n            const usedX = startX;\n            const usedY = startY;\n            let right = `<svg viewBox=\"0 0 ${panelWidth} ${panelHeight}\" width=\"100%\" style=\"border:1px solid #ddd;background:#fafafa;border-radius:8px;\">\n                <rect x=\"${usedX}\" y=\"${usedY}\" width=\"${usedW}\" height=\"${usedH}\" fill=\"#f8faff\" stroke=\"#355070\" stroke-width=\"1.5\"\/>`;\n            let counter = 1;\n            const slotW = usedW \/ layout.cols;\n            const slotH = usedH \/ layout.rows;\n            for (let r = 0; r < layout.rows; r++) {\n                for (let c = 0; c < layout.cols; c++) {\n                    const x = usedX + (c * slotW);\n                    const y = usedY + (r * slotH);\n                    const label = gpGetParkingPlaceLabel(counter, layout.placesCount, slotW, slotH, isModal);\n                    let labelFontSize = isModal ? 10 : 8;\n                    if (label.indexOf('Miejsce') === 0) {\n                        labelFontSize = isModal ? 11 : 10;\n                    } else if (label.indexOf('M') === 0) {\n                        labelFontSize = isModal ? 10 : 8;\n                    } else if (\/^\\d+$\/.test(label)) {\n                        labelFontSize = isModal ? 9 : 7;\n                    }\n                    right += `<rect x=\"${x}\" y=\"${y}\" width=\"${slotW}\" height=\"${slotH}\" fill=\"${(r + c) % 2 === 0 ? '#edf3ff' : '#fff'}\" stroke=\"#9db5d4\" stroke-width=\"1\"\/>`;\n                    if (label) {\n                        right += `<text x=\"${x + slotW\/2}\" y=\"${y + slotH\/2 + 3}\" text-anchor=\"middle\" font-size=\"${labelFontSize}\" fill=\"#223\">${label}<\/text>`;\n                    }\n                    counter++;\n                }\n            }\n            right += `<text x=\"${panelWidth \/ 2}\" y=\"${usedY + usedH + 22}\" text-anchor=\"middle\" font-size=\"13\" fill=\"#111\">Symulacja miejsc parkingowych<\/text><\/svg>`;\n            return right;\n        }\n\n        function gpRenderVisualization(model) {\n            gpLastVisualizationModel = model;\n            const container = document.getElementById('gp_visualization');\n            if (!container) return;\n            const left = gpRenderFieldsSvg(model, false);\n            const right = gpRenderPlacesSvg(model, false);\n\n            container.innerHTML = `<div class=\"gp-viz-row\"><div class=\"gp-viz-panel\" data-viz-type=\"fields\" data-title=\"Pola techniczne i rozstaw s\u0142up\u00f3w\">${left}<\/div><div class=\"gp-viz-panel\" data-viz-type=\"places\" data-title=\"Symulacja miejsc parkingowych\">${right}<\/div><\/div><div class=\"gp-viz-hint\">Kliknij wizualizacj\u0119, aby powi\u0119kszy\u0107.<\/div>`;\n\n            container.querySelectorAll('.gp-viz-panel').forEach(function(panel) {\n                panel.addEventListener('click', function () {\n                    const type = panel.getAttribute('data-viz-type');\n                    const title = panel.getAttribute('data-title') || 'Powi\u0119kszenie wizualizacji';\n                    gpOpenVizModal(title, type);\n                });\n            });\n        }\n\n        calculateBtn.addEventListener('click', function () {\n            const defaultCalculateLabel = 'Oblicz zestaw';\n            const parkingLength = parseFloat(document.getElementById('gp_parking_length').value);\n            const parkingWidth = parseFloat(document.getElementById('gp_parking_width').value);\n            const orientation = document.getElementById('gp_parking_orientation').value;\n            if (!parkingLength || parkingLength <= 0 || !parkingWidth || parkingWidth <= 0 || parkingLength > 500 || parkingWidth > 500) {\n                resultBox.innerHTML = 'Podaj poprawne wymiary powierzchni parkingowej.';\n                document.getElementById('gp_visualization').innerHTML = '';\n                gpSetOfferCtaVisible(false);\n                pricePreview.style.display = 'none';\n                return;\n            }\n\n            calculateBtn.disabled = true;\n            calculateBtn.textContent = 'Obliczam...';\n\n            resultBox.innerHTML = `\n                <div class=\"gp-notes-panel\">\n                    Przygotowywanie podsumowania...\n                <\/div>\n            `;\n            pricePreview.innerHTML = `\n                <span class=\"gp-price-loading\">\n                    <span class=\"gp-spinner\"><\/span>\n                    Obliczanie ceny...\n                <\/span>\n            `;\n            pricePreview.style.display = 'block';\n            gpSetOfferCtaVisible(false);\n\n            let localModel = null;\n\n            try {\n                const layout = gpCalculateParkingLayout(parkingLength, parkingWidth, orientation);\n                const fields = gpSplitParkingIntoTechnicalFields(layout);\n                const gridLines = gpCalculateTechnicalGridLines(layout, fields);\n                localModel = { layout, fields, gridLines, legacy: {}, price: {} };\n                gpRenderVisualization(localModel);\n                gpRenderLoadingSummary(layout);\n            } catch (error) {\n                resultBox.innerHTML = error.message || 'Nie uda\u0142o si\u0119 obliczy\u0107 uk\u0142adu.';\n                document.getElementById('gp_visualization').innerHTML = '';\n                gpSetOfferCtaVisible(false);\n                pricePreview.style.display = 'none';\n                calculateBtn.disabled = false;\n                calculateBtn.textContent = defaultCalculateLabel;\n                return;\n            }\n\n            gpFetchBackendPreview(parkingLength, parkingWidth, orientation)\n                .then(function(response) {\n                    if (!response || !response.success || !response.data) {\n                        let errorMessage = 'Nie uda\u0142o si\u0119 pobra\u0107 wyceny.';\n                        if (response) {\n                            if (response.data) {\n                                if (response.data.message) {\n                                    errorMessage = response.data.message;\n                                }\n                            }\n                        }\n                        throw new Error(errorMessage);\n                    }\n\n                    calculatedData = {\n                        layout: localModel.layout,\n                        fields: localModel.fields,\n                        gridLines: localModel.gridLines,\n                        legacy: response.data.legacy || {},\n                        price: response.data.price || {}\n                    };\n\n                    const orientationLabel = calculatedData.layout.orientation === 'vertical' ? 'pionowy' : 'poziomy';\n                    resultBox.innerHTML = `\n                        <div class=\"gp-result-section\">\n                            <h3>Podsumowanie powierzchni<\/h3>\n                            <div class=\"gp-summary-grid\">\n                                <div class=\"gp-summary-item\"><span class=\"gp-label\">Wprowadzony wymiar<\/span><strong>${gpRound(calculatedData.layout.parkingLength,2)} m \u00d7 ${gpRound(calculatedData.layout.parkingWidth,2)} m<\/strong><\/div>\n                                <div class=\"gp-summary-item\"><span class=\"gp-label\">Uk\u0142ad miejsc<\/span><strong>${orientationLabel}<\/strong><\/div>\n                                <div class=\"gp-summary-item\"><span class=\"gp-label\">Liczba miejsc<\/span><strong>${calculatedData.layout.placesCount}<\/strong><\/div>\n                                <div class=\"gp-summary-item\"><span class=\"gp-label\">Wykorzystany wymiar<\/span><strong>${gpRound(calculatedData.layout.usedLength,2)} m \u00d7 ${gpRound(calculatedData.layout.usedWidth,2)} m<\/strong><\/div>\n                                <div class=\"gp-summary-item\"><span class=\"gp-label\">Wykorzystana powierzchnia<\/span><strong>${gpRound(calculatedData.layout.usedArea,2)} m\u00b2<\/strong><\/div>\n                                <div class=\"gp-summary-item\"><span class=\"gp-label\">Niewykorzystany zapas<\/span><strong>${gpRound(calculatedData.layout.unusedLength,2)} m d\u0142. \/ ${gpRound(calculatedData.layout.unusedWidth,2)} m szer.<\/strong><\/div>\n                            <\/div>\n                        <\/div>\n\n                        <div class=\"gp-result-section\">\n                            <h3>Ilo\u015bci element\u00f3w systemu<\/h3>\n                            <div class=\"gp-components-grid\">\n                                <div class=\"gp-component-item\"><span>S\u0142upy<\/span><strong>${calculatedData.legacy.posts} szt.<\/strong><\/div>\n                                <div class=\"gp-component-item\"><span>Kotwy<\/span><strong>${calculatedData.legacy.anchors} szt.<\/strong><\/div>\n                                <div class=\"gp-component-item\"><span>Siatka<\/span><strong>${calculatedData.legacy.net} m\u00b2<\/strong><\/div>\n                                <div class=\"gp-component-item\"><span>Linka stalowa<\/span><strong>${calculatedData.legacy.rope} mb<\/strong><\/div>\n                                <div class=\"gp-component-item\"><span>Klipsy<\/span><strong>${calculatedData.legacy.clips} szt.<\/strong><\/div>\n                                <div class=\"gp-component-item\"><span>\u015aci\u0105gacze<\/span><strong>${calculatedData.legacy.tensioners} szt.<\/strong><\/div>\n                                <div class=\"gp-component-item\"><span>Zaciski<\/span><strong>${calculatedData.legacy.clamps} szt.<\/strong><\/div>\n                                <div class=\"gp-component-item\"><span>Kausze<\/span><strong>${calculatedData.legacy.thimbles} szt.<\/strong><\/div>\n                                <div class=\"gp-component-item\"><span>Szekle<\/span><strong>${calculatedData.legacy.shackles} szt.<\/strong><\/div>\n                            <\/div>\n                        <\/div>\n                        <div class=\"gp-notes-panel\">\n                            <div>Wynik orientacyjny.<\/div>\n                            <div>Rozstaw s\u0142up\u00f3w pokazano na wizualizacji.<\/div>\n                        <\/div>\n                    `;\n\n                    pricePreview.innerHTML = `<strong>Cena netto:<\/strong> ${gpRound(calculatedData.price.net, 2)} z\u0142 &nbsp;|&nbsp; <strong>Cena brutto:<\/strong> ${gpRound(calculatedData.price.gross, 2)} z\u0142`;\n                    pricePreview.style.display = 'block';\n                    gpSetOfferCtaVisible(true);\n                })\n                .catch(function(error) {\n                    resultBox.innerHTML = error.message || 'Nie uda\u0142o si\u0119 pobra\u0107 wyceny.';\n                    pricePreview.style.display = 'none';\n                    gpSetOfferCtaVisible(false);\n                })\n                .finally(function() {\n                    calculateBtn.disabled = false;\n                    calculateBtn.textContent = defaultCalculateLabel;\n                });\n        });\n    });\n    <\/script>\n\n    \n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-212","page","type-page","status-publish","hentry"],"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/gradprotect.pl\/sklep\/wp-json\/wp\/v2\/pages\/212","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/gradprotect.pl\/sklep\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/gradprotect.pl\/sklep\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/gradprotect.pl\/sklep\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/gradprotect.pl\/sklep\/wp-json\/wp\/v2\/comments?post=212"}],"version-history":[{"count":1,"href":"https:\/\/gradprotect.pl\/sklep\/wp-json\/wp\/v2\/pages\/212\/revisions"}],"predecessor-version":[{"id":213,"href":"https:\/\/gradprotect.pl\/sklep\/wp-json\/wp\/v2\/pages\/212\/revisions\/213"}],"wp:attachment":[{"href":"https:\/\/gradprotect.pl\/sklep\/wp-json\/wp\/v2\/media?parent=212"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}