{"id":8904,"date":"2026-08-19T06:23:17","date_gmt":"2026-08-19T06:23:17","guid":{"rendered":"https:\/\/aiper.com\/blogs\/?page_id=8904"},"modified":"2026-10-21T09:13:09","modified_gmt":"2026-10-21T09:13:09","slug":"plant-spacing-visualizer","status":"publish","type":"page","link":"https:\/\/aiper.com\/blogs\/plant-spacing-visualizer\/","title":{"rendered":"Plant &#038; Tree Spacing Visualizer \u2013 See Your Garden Layout Before You Plant"},"content":{"rendered":"\n<style>\n\/* ==========================================================================\n   Plant & Tree Spacing Visualizer \u2014 Brand: #1ecad3 + #9486f7 blended\n   Pure DOM\/CSS visualization (no Canvas). WAF-compatible: no inline styles.\n   ========================================================================== *\/\n#plant-visualizer {\n    max-width: 750px;\n    margin: 20px auto;\n    padding: 0 16px;\n    font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n    box-sizing: border-box;\n}\n#plant-visualizer *, #plant-visualizer *::before, #plant-visualizer *::after { box-sizing: border-box; }\n\n\/* \u2500\u2500 Title \u2500\u2500 *\/\n.pv-title { text-align: center; font-size: 1.4rem; font-weight: 700; color: #1a202c; margin-bottom: 4px; }\n.pv-subtitle { text-align: center; font-size: 14px; color: #64748b; margin-bottom: 20px; }\n\n\/* \u2500\u2500 Unit Toggle \u2500\u2500 *\/\n.pv-unit-toggle { display: flex; justify-content: center; gap: 0; margin-bottom: 20px; }\n.pv-unit-btn {\n    padding: 10px 24px; font-size: 13px; font-weight: 600;\n    background: #f5f7fa; color: #64748b; border: 1px solid #e2e8f0;\n    cursor: pointer; transition: all 0.2s ease;\n}\n.pv-unit-btn:first-child { border-radius: 8px 0 0 8px; }\n.pv-unit-btn:last-child { border-radius: 0 8px 8px 0; }\n.pv-unit-btn:hover { background: #e8f7f8; color: #1ecad3; }\n.pv-unit-btn.active {\n    background: linear-gradient(135deg, #1ecad3, #9486f7);\n    color: #fff; border-color: transparent;\n}\n.pv-unit-btn:focus, .pv-unit-btn:active { outline: none; }\n\n\/* \u2500\u2500 Controls Grid \u2500\u2500 *\/\n.pv-controls {\n    display: grid; grid-template-columns: 1fr 1fr; gap: 14px;\n    margin-bottom: 16px;\n}\n.pv-field { display: flex; flex-direction: column; }\n.pv-field label {\n    font-size: 13px; font-weight: 600; color: #2c3e50; margin-bottom: 4px;\n}\n.pv-field input {\n    padding: 10px 12px; border: 1px solid #e2e8f0; border-radius: 8px;\n    font-size: 14px; color: #2c3e50; transition: border-color 0.2s ease;\n    background: #fff;\n}\n.pv-field input:focus { border-color: #9486f7; outline: none; box-shadow: 0 0 0 3px rgba(148,134,247,0.1); }\n.pv-unit-suffix { font-size: 11px; color: #94a3b8; margin-left: 2px; }\n\n\/* \u2500\u2500 Presets \u2500\u2500 *\/\n.pv-preset-label { font-size: 13px; font-weight: 600; color: #2c3e50; margin-bottom: 8px; }\n.pv-preset-buttons { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; }\n.pv-preset-btn {\n    padding: 8px 14px; font-size: 12px; font-weight: 600;\n    border: 1px solid #e2e8f0; border-radius: 20px;\n    background: #fff; color: #475569; cursor: pointer;\n    transition: all 0.2s ease;\n}\n.pv-preset-btn:hover { border-color: #b2dfdb; background: #f8fdfc; }\n.pv-preset-btn:focus, .pv-preset-btn:active { color: #2c3e50; outline: none; }\n.pv-preset-btn.selected {\n    border-color: #9486f7; background: #f5f3ff; color: #2c3e50;\n    box-shadow: 0 0 0 2px rgba(148,134,247,0.12);\n}\n\n\/* \u2500\u2500 Pattern Selector \u2500\u2500 *\/\n.pv-pattern-label { font-size: 13px; font-weight: 600; color: #2c3e50; margin-bottom: 8px; }\n.pv-pattern-buttons { display: flex; gap: 10px; margin-bottom: 16px; }\n.pv-pattern-btn {\n    flex: 1; padding: 12px 16px; font-size: 13px; font-weight: 600;\n    border: 2px solid #e2e8f0; border-radius: 10px;\n    background: #fff; color: #475569; cursor: pointer;\n    transition: all 0.2s ease; text-align: center;\n}\n.pv-pattern-btn:hover { border-color: #b2dfdb; }\n.pv-pattern-btn:focus, .pv-pattern-btn:active { color: #2c3e50; outline: none; }\n.pv-pattern-btn.selected {\n    border-color: #9486f7; background: #f5f3ff; color: #2c3e50;\n}\n\n\/* \u2500\u2500 Bed Visualization Area \u2500\u2500 *\/\n.pv-canvas-wrapper {\n    text-align: center; margin-bottom: 16px;\n    background: #fafafa; border-radius: 12px; padding: 20px 16px 40px;\n    border: 1px solid #e2e8f0;\n}\n#pv-bed-frame {\n    position: relative; display: inline-block;\n    max-width: 100%;\n}\n#pv-dim-label {\n    text-align: center; font-size: 11px; color: #2c3e50; font-weight: 600;\n    margin-bottom: 6px;\n}\n#pv-bed-area {\n    position: relative; background: #f5ebe0;\n    border: 2px solid #8B7355; border-radius: 2px;\n    margin: 0 auto;\n}\n.pv-grid-line-h, .pv-grid-line-v {\n    position: absolute; pointer-events: none;\n}\n.pv-grid-line-h { left: 0; right: 0; border-top: 1px dashed #d5c4a1; }\n.pv-grid-line-v { top: 0; bottom: 0; border-left: 1px dashed #d5c4a1; }\n.pv-plant {\n    position: absolute; border-radius: 50%; transform: translate(-50%, -50%);\n}\n.pv-plant-canopy {\n    background: rgba(143, 188, 143, 0.45);\n    border: 1px solid #5b8c5a;\n}\n.pv-plant-stem {\n    background: #5b8c5a; z-index: 1;\n}\n#pv-scale-bar {\n    display: flex; align-items: center; justify-content: center;\n    margin-top: 10px; gap: 6px;\n}\n#pv-scale-line {\n    height: 1.5px; background: #2c3e50;\n}\n#pv-scale-label {\n    font-size: 11px; color: #2c3e50; white-space: nowrap;\n}\n\n\/* \u2500\u2500 Results \u2500\u2500 *\/\n.pv-results {\n    display: flex; flex-wrap: wrap; justify-content: center; gap: 12px 24px;\n    padding: 14px 20px; background: #f5f7fa; border-radius: 10px;\n    margin-bottom: 20px;\n}\n.pv-result-item { text-align: center; }\n.pv-result-value { font-size: 1.3rem; font-weight: 700; color: #1ecad3; }\n.pv-result-label { font-size: 11px; color: #64748b; text-transform: uppercase; letter-spacing: 0.5px; }\n\n\/* \u2500\u2500 Tool Links (cross-refs) \u2500\u2500 *\/\n.pv-tool-links { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-bottom: 20px; }\n.pv-tool-link {\n    display: inline-block; padding: 8px 16px;\n    background: #f5f7fa; color: #6b5ce7; border: 1px solid #e8e3fc;\n    border-radius: 22px; font-size: 12px; font-weight: 600;\n    text-decoration: none; transition: all 0.2s ease;\n}\n.pv-tool-link:hover { background: #ede9fe; color: #5a4bd1; border-color: #9486f7; }\n.pv-tool-link:focus, .pv-tool-link:active { color: #5a4bd1; outline: none; }\n\n\/* \u2500\u2500 CTA \u2500\u2500 *\/\n.pv-cta {\n    padding: 16px 20px; background: #f0fcfc; border-radius: 12px;\n    border: 1px solid #b2dfdb; text-align: center; margin-bottom: 20px;\n}\n.pv-cta p { margin: 0 0 8px; font-size: 13px; color: #475569; line-height: 1.5; }\n.pv-cta a {\n    display: inline-block; padding: 10px 24px;\n    background: linear-gradient(135deg, #1ecad3, #9486f7);\n    color: #fff; border-radius: 8px; text-decoration: none;\n    font-weight: 700; font-size: 14px;\n    transition: all 0.2s ease;\n}\n.pv-cta a:hover { transform: translateY(-1px); box-shadow: 0 4px 16px rgba(148,134,247,0.35); }\n\n\/* \u2500\u2500 Validation \u2500\u2500 *\/\n.pv-error { color: #e53e3e; font-size: 11px; margin-top: 2px; min-height: 16px; }\n\n\/* \u2500\u2500 JS Status \/ Error Banner \u2500\u2500 *\/\n#pv-js-status {\n    text-align: center; padding: 8px 12px; margin-bottom: 12px;\n    background: #fffbe6; border: 1px solid #f6e05e; border-radius: 8px;\n    font-size: 12px; font-weight: 600; color: #975a16;\n}\n#pv-error-msg {\n    display: none; padding: 12px 16px; background: #fff5f5;\n    border: 1px solid #feb2b2; border-radius: 8px;\n    color: #c53030; font-size: 13px; margin-bottom: 16px;\n}\n\n\/* \u2500\u2500 Responsive \u2500\u2500 *\/\n@media (max-width: 600px) {\n    .pv-controls { grid-template-columns: 1fr; }\n    .pv-preset-buttons { justify-content: center; }\n    .pv-preset-btn { flex: 1; min-width: 80px; text-align: center; }\n    .pv-results { flex-direction: column; gap: 8px; }\n    .pv-canvas-wrapper { padding: 12px 8px 32px; }\n    #plant-visualizer { padding: 0 8px; }\n}\n<\/style>\n\n<div id=\"plant-visualizer\">\n    <div id=\"pv-js-status\">LOADING&#8230; JS not yet executed<\/div>\n\n    <div class=\"pv-title\" id=\"pv-title\">Plant &amp; Tree Spacing Visualizer<\/div>\n    <div class=\"pv-subtitle\" id=\"pv-subtitle\">See how your plants will look before you dig<\/div>\n\n    <div id=\"pv-error-msg\"><\/div>\n\n    <!-- Unit Toggle -->\n    <div class=\"pv-unit-toggle\" role=\"radiogroup\" aria-label=\"Unit System\">\n        <button type=\"button\" class=\"pv-unit-btn active\" data-unit=\"imperial\" id=\"pv-unit-imp\">ft \/ in<\/button>\n        <button type=\"button\" class=\"pv-unit-btn\" data-unit=\"metric\" id=\"pv-unit-met\">m \/ cm<\/button>\n    <\/div>\n\n    <!-- Bed Dimensions -->\n    <div class=\"pv-controls\">\n        <div class=\"pv-field\">\n            <label for=\"pv-bed-width\" id=\"pv-label-width\">Bed Width (<span class=\"pv-unit-suffix\" id=\"pv-unit-w\">ft<\/span>)<\/label>\n            <input type=\"number\" id=\"pv-bed-width\" step=\"any\" min=\"0.1\" value=\"8\" placeholder=\"e.g. 8\" inputmode=\"decimal\">\n            <div class=\"pv-error\" id=\"pv-err-width\"><\/div>\n        <\/div>\n        <div class=\"pv-field\">\n            <label for=\"pv-bed-length\" id=\"pv-label-length\">Bed Length (<span class=\"pv-unit-suffix\" id=\"pv-unit-l\">ft<\/span>)<\/label>\n            <input type=\"number\" id=\"pv-bed-length\" step=\"any\" min=\"0.1\" value=\"12\" placeholder=\"e.g. 12\" inputmode=\"decimal\">\n            <div class=\"pv-error\" id=\"pv-err-length\"><\/div>\n        <\/div>\n    <\/div>\n\n    <!-- Plant Type Presets -->\n    <div class=\"pv-preset-label\" id=\"pv-preset-label\">Quick Presets<\/div>\n    <div class=\"pv-preset-buttons\" id=\"pv-presets\"><\/div>\n\n    <!-- Spacing + Canopy -->\n    <div class=\"pv-controls\">\n        <div class=\"pv-field\">\n            <label for=\"pv-spacing\" id=\"pv-label-spacing\">Plant Spacing (<span class=\"pv-unit-suffix\" id=\"pv-unit-s\">in<\/span>)<\/label>\n            <input type=\"number\" id=\"pv-spacing\" step=\"any\" min=\"0.1\" value=\"18\" placeholder=\"e.g. 18\" inputmode=\"decimal\">\n            <div class=\"pv-error\" id=\"pv-err-spacing\"><\/div>\n        <\/div>\n        <div class=\"pv-field\">\n            <label for=\"pv-canopy\" id=\"pv-label-canopy\">Canopy at Maturity (<span class=\"pv-unit-suffix\" id=\"pv-unit-c\">in<\/span>)<\/label>\n            <input type=\"number\" id=\"pv-canopy\" step=\"any\" min=\"0.1\" value=\"14\" placeholder=\"e.g. 14\" inputmode=\"decimal\">\n            <div class=\"pv-error\" id=\"pv-err-canopy\"><\/div>\n        <\/div>\n    <\/div>\n\n    <!-- Layout Pattern -->\n    <div class=\"pv-pattern-label\" id=\"pv-pattern-label\">Layout Pattern<\/div>\n    <div class=\"pv-pattern-buttons\" role=\"radiogroup\" aria-label=\"Layout Pattern\">\n        <button type=\"button\" class=\"pv-pattern-btn selected\" data-pattern=\"square\" id=\"pv-pat-square\">Square Grid<\/button>\n        <button type=\"button\" class=\"pv-pattern-btn\" data-pattern=\"staggered\" id=\"pv-pat-staggered\">Staggered \/ Triangular<\/button>\n    <\/div>\n\n    <!-- Bed Visualization (DOM-based, no Canvas) -->\n    <div class=\"pv-canvas-wrapper\">\n        <div id=\"pv-dim-label\"><\/div>\n        <div id=\"pv-bed-frame\">\n            <div id=\"pv-bed-area\"><\/div>\n        <\/div>\n        <div id=\"pv-scale-bar\">\n            <span id=\"pv-scale-left\"><\/span>\n            <span id=\"pv-scale-line\"><\/span>\n            <span id=\"pv-scale-right\"><\/span>\n            <span id=\"pv-scale-label\"><\/span>\n        <\/div>\n    <\/div>\n\n    <!-- Results -->\n    <div class=\"pv-results\" id=\"pv-results\">\n        <div class=\"pv-result-item\">\n            <div class=\"pv-result-value\" id=\"pv-val-total\">0<\/div>\n            <div class=\"pv-result-label\" id=\"pv-lbl-total\">Total Plants<\/div>\n        <\/div>\n        <div class=\"pv-result-item\">\n            <div class=\"pv-result-value\" id=\"pv-val-rows\">0<\/div>\n            <div class=\"pv-result-label\" id=\"pv-lbl-rows\">Rows<\/div>\n        <\/div>\n        <div class=\"pv-result-item\">\n            <div class=\"pv-result-value\" id=\"pv-val-cols\">0<\/div>\n            <div class=\"pv-result-label\" id=\"pv-lbl-cols\">Columns<\/div>\n        <\/div>\n        <div class=\"pv-result-item\">\n            <div class=\"pv-result-value\" id=\"pv-val-area\">0<\/div>\n            <div class=\"pv-result-label\" id=\"pv-lbl-area\">Bed Area<\/div>\n        <\/div>\n    <\/div>\n\n    <!-- CTA -->\n    <div class=\"pv-cta\" id=\"pv-cta\">\n        <p id=\"pv-cta-text\"><\/p>\n        <a href=\"https:\/\/aiper.com\/blogs\/plant-spacing-calculator\/\" id=\"pv-cta-link\">Calculate plant quantity<\/a>\n    <\/div>\n\n    <!-- Cross-reference tool links -->\n    <div class=\"pv-tool-links\" id=\"pv-tools\"><\/div>\n<\/div>\n\n<script>\n(function() {\n    \/\/ \u2500\u2500 Labels \u2500\u2500\n    var LABELS = {\"title\": \"Plant & Tree Spacing Visualizer\", \"subtitle\": \"See how your plants will look before you dig\", \"unitImperial\": \"ft \/ in\", \"unitMetric\": \"m \/ cm\", \"bedLabelWidth\": \"Bed Width\", \"bedLabelLength\": \"Bed Length\", \"bedLabelWidthM\": \"Bed Width (m)\", \"bedLabelLengthM\": \"Bed Length (m)\", \"unitFt\": \"ft\", \"unitIn\": \"in\", \"unitM\": \"m\", \"unitCm\": \"cm\", \"unitSqft\": \"sq ft\", \"unitSqm\": \"sq m\", \"presetLabel\": \"Quick Presets\", \"presetKeys\": [\"vegetables\", \"root_veg\", \"leafy\", \"hedge\", \"shrubs\", \"trees_small\", \"custom\"], \"presets\": {\"vegetables\": {\"label\": \"Vegetables (tomatoes, peppers)\", \"spacing_in\": 24, \"canopy_in\": 18}, \"root_veg\": {\"label\": \"Root Veg (carrots, onions)\", \"spacing_in\": 4, \"canopy_in\": 3}, \"leafy\": {\"label\": \"Leafy Greens (lettuce, kale)\", \"spacing_in\": 12, \"canopy_in\": 10}, \"hedge\": {\"label\": \"Hedge \/ Border Plants\", \"spacing_in\": 16, \"canopy_in\": 14}, \"shrubs\": {\"label\": \"Shrubs \/ Bushes\", \"spacing_in\": 36, \"canopy_in\": 30}, \"trees_small\": {\"label\": \"Small Trees (6-8 ft)\", \"spacing_in\": 84, \"canopy_in\": 72}, \"custom\": {\"label\": \"Custom\", \"spacing_in\": 0, \"canopy_in\": 0}}, \"spacingLabel\": \"Plant Spacing\", \"canopyLabel\": \"Canopy at Maturity\", \"patternLabel\": \"Layout Pattern\", \"patternSquare\": \"Square Grid\", \"patternStaggered\": \"Staggered \/ Triangular\", \"resultTotalPlants\": \"Total Plants\", \"resultRows\": \"Rows\", \"resultCols\": \"Columns\", \"resultArea\": \"Bed Area\", \"toolLinks\": {\"plantCalc\": {\"href\": \"https:\/\/aiper.com\/blogs\/plant-spacing-calculator\/\", \"label\": \"plant spacing calculator\"}, \"soilCalc\": {\"href\": \"https:\/\/aiper.com\/blogs\/garden-soil-calculator\/\", \"label\": \"garden soil calculator\"}, \"mulchCalc\": {\"href\": \"https:\/\/aiper.com\/blogs\/mulch-calculator\/\", \"label\": \"mulch calculator\"}, \"grassSeed\": {\"href\": \"https:\/\/aiper.com\/blogs\/grass-seed-calculator\/\", \"label\": \"grass seed calculator\"}}, \"validationError\": \"Enter a positive number\", \"canopyWarning\": \"Canopy > spacing: plants may overlap\", \"canvasPlaceholder\": \"Enter bed dimensions and spacing to see the layout\", \"ctaText\": \"Ready to buy? Use our <strong>Plant Spacing Calculator<\/strong> to get the exact number of plants, seeds, or bulbs you need for your garden bed \u2014 with overage buffer included.\", \"ctaLink\": \"Calculate plant quantity\"};\n\n    \/\/ \u2500\u2500 Helpers \u2500\u2500\n    var $ = function(id) { return document.getElementById(id); };\n    function L(path) {\n        var keys = path.split('.'), v = LABELS;\n        for (var i = 0; i < keys.length; i++) { v = v[keys[i]]; if (v === undefined) return ''; }\n        return v;\n    }\n    function setText(id, txt) { var e = $(id); if (e) e.textContent = txt; }\n    function _and() { var n = arguments.length; while (n--) { if (!arguments[n]) return false; } return true; }\n\n    \/\/ \u2500\u2500 State \u2500\u2500\n    var state = { unit: 'imperial', bedW: 8, bedL: 12, spacing: 18, canopy: 14, pattern: 'square' };\n\n    \/\/ \u2500\u2500 DOM refs \u2500\u2500\n    var bedAreaEl = $('pv-bed-area');\n    var bedFrameEl = $('pv-bed-frame');\n    var dimLabel = $('pv-dim-label');\n    var scaleBar = $('pv-scale-bar');\n    var scaleLine = $('pv-scale-line');\n    var scaleLabel = $('pv-scale-label');\n    var scaleLeft = $('pv-scale-left');\n    var scaleRight = $('pv-scale-right');\n\n    \/\/ \u2500\u2500 Set initial UI text \u2500\u2500\n    setText('pv-title', L('title'));\n    setText('pv-subtitle', L('subtitle'));\n    setText('pv-unit-imp', L('unitImperial'));\n    setText('pv-unit-met', L('unitMetric'));\n    setText('pv-preset-label', L('presetLabel'));\n    setText('pv-pattern-label', L('patternLabel'));\n    setText('pv-pat-square', L('patternSquare'));\n    setText('pv-pat-staggered', L('patternStaggered'));\n    setText('pv-lbl-total', L('resultTotalPlants'));\n    setText('pv-lbl-rows', L('resultRows'));\n    setText('pv-lbl-cols', L('resultCols'));\n    setText('pv-lbl-area', L('resultArea'));\n    var ctaTextEl = $('pv-cta-text');\n    if (ctaTextEl) ctaTextEl.innerHTML = L('ctaText');\n    setText('pv-cta-link', L('ctaLink'));\n\n    \/\/ \u2500\u2500 Build preset buttons \u2500\u2500\n    var presetDiv = $('pv-presets');\n    var pkeys = L('presetKeys');\n    if (_and(presetDiv, pkeys, pkeys.length)) {\n        presetDiv.innerHTML = '';\n        for (var i = 0; i < pkeys.length; i++) {\n            var key = pkeys[i];\n            var p = L('presets.' + key);\n            if (!p || !p.label) continue;\n            var btn = document.createElement('button');\n            btn.type = 'button';\n            btn.className = 'pv-preset-btn';\n            btn.setAttribute('data-preset', key);\n            btn.textContent = p.label;\n            btn.addEventListener('click', (function(k) { return function() { applyPreset(k); }; })(key));\n            presetDiv.appendChild(btn);\n        }\n    }\n\n    \/\/ \u2500\u2500 Build tool links \u2500\u2500\n    var toolsDiv = $('pv-tools');\n    if (toolsDiv) {\n        toolsDiv.innerHTML = '';\n        var tlKeys = Object.keys(LABELS.toolLinks || {});\n        for (var j = 0; j < tlKeys.length; j++) {\n            var tl = L('toolLinks.' + tlKeys[j]);\n            if (!tl || !tl.href) continue;\n            var a = document.createElement('a');\n            a.className = 'pv-tool-link';\n            a.href = tl.href;\n            a.textContent = tl.label;\n            toolsDiv.appendChild(a);\n        }\n    }\n\n    \/\/ \u2500\u2500 Unit labels \u2500\u2500\n    function updateUnitLabels() {\n        var isImp = state.unit === 'imperial';\n        var unitLen = isImp ? L('unitFt') : L('unitM');\n        var unitSmall = isImp ? L('unitIn') : L('unitCm');\n        var labelW = $('pv-label-width');\n        if (labelW) labelW.innerHTML = L('bedLabelWidth') + ' (<span class=\"pv-unit-suffix\" id=\"pv-unit-w\">' + unitLen + '<\/span>)';\n        var labelL = $('pv-label-length');\n        if (labelL) labelL.innerHTML = L('bedLabelLength') + ' (<span class=\"pv-unit-suffix\" id=\"pv-unit-l\">' + unitLen + '<\/span>)';\n        var spLabel = $('pv-label-spacing');\n        if (spLabel) spLabel.innerHTML = L('spacingLabel') + ' (<span class=\"pv-unit-suffix\" id=\"pv-unit-s\">' + unitSmall + '<\/span>)';\n        var cnLabel = $('pv-label-canopy');\n        if (cnLabel) cnLabel.innerHTML = L('canopyLabel') + ' (<span class=\"pv-unit-suffix\" id=\"pv-unit-c\">' + unitSmall + '<\/span>)';\n        var bwEl = $('pv-bed-width'); if (bwEl) bwEl.placeholder = isImp ? 'e.g. 8' : 'e.g. 2.5';\n        var blEl = $('pv-bed-length'); if (blEl) blEl.placeholder = isImp ? 'e.g. 12' : 'e.g. 3.5';\n        var spEl = $('pv-spacing'); if (spEl) spEl.placeholder = isImp ? 'e.g. 18' : 'e.g. 45';\n        var cnEl = $('pv-canopy'); if (cnEl) cnEl.placeholder = isImp ? 'e.g. 14' : 'e.g. 35';\n    }\n    updateUnitLabels();\n\n    \/\/ \u2500\u2500 Apply Preset \u2500\u2500\n    function applyPreset(key) {\n        var btns = document.querySelectorAll('.pv-preset-btn');\n        btns.forEach(function(b) { b.classList.remove('selected'); });\n        var active = document.querySelector('.pv-preset-btn[data-preset=\"' + key + '\"]');\n        if (active) active.classList.add('selected');\n        if (key === 'custom') return;\n        var p = L('presets.' + key);\n        if (!p) return;\n        if (state.unit === 'imperial') { state.spacing = p.spacing_in; state.canopy = p.canopy_in; }\n        else { state.spacing = Math.round(p.spacing_in * 2.54 * 10) \/ 10; state.canopy = Math.round(p.canopy_in * 2.54 * 10) \/ 10; }\n        var spEl = $('pv-spacing'); if (spEl) spEl.value = state.spacing;\n        var cnEl = $('pv-canopy'); if (cnEl) cnEl.value = state.canopy;\n        draw();\n    }\n\n    \/\/ \u2500\u2500 Unit Toggle \u2500\u2500\n    function setUnit(unit) {\n        if (state.unit === unit) return;\n        var wasImp = state.unit === 'imperial', nowImp = unit === 'imperial';\n        state.unit = unit;\n        var impBtn = $('pv-unit-imp'), metBtn = $('pv-unit-met');\n        if (impBtn) { impBtn.classList.remove('active'); if (nowImp) impBtn.classList.add('active'); }\n        if (metBtn) { metBtn.classList.remove('active'); if (!nowImp) metBtn.classList.add('active'); }\n        if (_and(wasImp, !nowImp)) {\n            state.bedW = Math.round(state.bedW * 0.3048 * 100) \/ 100;\n            state.bedL = Math.round(state.bedL * 0.3048 * 100) \/ 100;\n            state.spacing = Math.round(state.spacing * 2.54 * 10) \/ 10;\n            state.canopy = Math.round(state.canopy * 2.54 * 10) \/ 10;\n        } else if (_and(!wasImp, nowImp)) {\n            state.bedW = Math.round(state.bedW \/ 0.3048 * 100) \/ 100;\n            state.bedL = Math.round(state.bedL \/ 0.3048 * 100) \/ 100;\n            state.spacing = Math.round(state.spacing \/ 2.54 * 10) \/ 10;\n            state.canopy = Math.round(state.canopy \/ 2.54 * 10) \/ 10;\n        }\n        var bwEl = $('pv-bed-width'); if (bwEl) bwEl.value = state.bedW;\n        var blEl = $('pv-bed-length'); if (blEl) blEl.value = state.bedL;\n        var spEl = $('pv-spacing'); if (spEl) spEl.value = state.spacing;\n        var cnEl = $('pv-canopy'); if (cnEl) cnEl.value = state.canopy;\n        updateUnitLabels();\n        draw();\n    }\n\n    \/\/ \u2500\u2500 Event Listeners \u2500\u2500\n    $('pv-unit-imp').addEventListener('click', function() { setUnit('imperial'); });\n    $('pv-unit-met').addEventListener('click', function() { setUnit('metric'); });\n    $('pv-pat-square').addEventListener('click', function() {\n        state.pattern = 'square';\n        $('pv-pat-square').classList.add('selected');\n        $('pv-pat-staggered').classList.remove('selected');\n        draw();\n    });\n    $('pv-pat-staggered').addEventListener('click', function() {\n        state.pattern = 'staggered';\n        $('pv-pat-staggered').classList.add('selected');\n        $('pv-pat-square').classList.remove('selected');\n        draw();\n    });\n\n    function readInputs() {\n        var bwEl = $('pv-bed-width'), blEl = $('pv-bed-length');\n        var spEl = $('pv-spacing'), cnEl = $('pv-canopy');\n        if (!bwEl || !blEl || !spEl || !cnEl) return;\n        var bw = parseFloat(bwEl.value), bl = parseFloat(blEl.value);\n        var sp = parseFloat(spEl.value), cn = parseFloat(cnEl.value);\n        var valid = true;\n        var errW = $('pv-err-width'), errL = $('pv-err-length');\n        var errS = $('pv-err-spacing'), errC = $('pv-err-canopy');\n        if (isNaN(bw) || bw <= 0) { if (errW) errW.textContent = L('validationError'); valid = false; }\n        else { if (errW) errW.textContent = ''; state.bedW = bw; }\n        if (isNaN(bl) || bl <= 0) { if (errL) errL.textContent = L('validationError'); valid = false; }\n        else { if (errL) errL.textContent = ''; state.bedL = bl; }\n        if (isNaN(sp) || sp <= 0) { if (errS) errS.textContent = L('validationError'); valid = false; }\n        else { if (errS) errS.textContent = ''; state.spacing = sp; }\n        if (isNaN(cn) || cn <= 0) { if (errC) errC.textContent = L('validationError'); valid = false; }\n        else if (cn > sp) { if (errC) errC.textContent = L('canopyWarning'); state.canopy = cn; }\n        else { if (errC) errC.textContent = ''; state.canopy = cn; }\n        var btns = document.querySelectorAll('.pv-preset-btn');\n        btns.forEach(function(b) { b.classList.remove('selected'); });\n        if (valid) draw();\n    }\n\n    function debounce(fn, delay) { var t; return function() { clearTimeout(t); t = setTimeout(fn, delay); }; }\n    ['pv-bed-width','pv-bed-length','pv-spacing','pv-canopy'].forEach(function(id) {\n        var el = $(id); if (el) el.addEventListener('input', debounce(readInputs, 200));\n    });\n\n    \/\/ \u2500\u2500 DOM-based Drawing (no Canvas) \u2500\u2500\n    var MAX_PX = 550;  \/\/ max bed display width in px\n\n    function draw() {\n        if (!bedAreaEl || !bedFrameEl) return;\n\n        \/\/ Normalize to bed units (ft or m)\n        var unitFactor = state.unit === 'imperial' ? 12 : 100;\n        var spacingBU = state.spacing \/ unitFactor;\n        var canopyBU = state.canopy \/ unitFactor;\n\n        \/\/ Compute positions first (for results + draw)\n        var rows, cols, positions = [];\n        if (_and(state.bedW > 0, state.bedL > 0, spacingBU > 0)) {\n            if (state.pattern === 'square') {\n                cols = Math.floor(state.bedW \/ spacingBU);\n                rows = Math.floor(state.bedL \/ spacingBU);\n                if (_and(cols > 0, rows > 0)) {\n                    var edgeX = (state.bedW - (cols - 1) * spacingBU) \/ 2;\n                    var edgeY = (state.bedL - (rows - 1) * spacingBU) \/ 2;\n                    for (var r = 0; r < rows; r++) {\n                        for (var c = 0; c < cols; c++) {\n                            positions.push({ x: edgeX + c * spacingBU, y: edgeY + r * spacingBU });\n                        }\n                    }\n                }\n            } else {\n                var rowSpacing = spacingBU * Math.sqrt(3) \/ 2;\n                cols = Math.floor(state.bedW \/ spacingBU);\n                rows = Math.floor((state.bedL - spacingBU) \/ rowSpacing) + 1;\n                if (_and(cols > 0, rows > 0)) {\n                    var edgeX = (state.bedW - (cols - 1) * spacingBU) \/ 2;\n                    var edgeY = (state.bedL - (rows - 1) * rowSpacing) \/ 2;\n                    for (var r = 0; r < rows; r++) {\n                        var offset = (r % 2 === 1) ? spacingBU \/ 2 : 0;\n                        var colsInRow = (_and(r % 2 === 1, (edgeX + (cols - 1) * spacingBU + offset + spacingBU \/ 2) > state.bedW)) ? cols - 1 : cols;\n                        for (var c = 0; c < colsInRow; c++) {\n                            positions.push({ x: edgeX + c * spacingBU + offset, y: edgeY + r * rowSpacing });\n                        }\n                    }\n                }\n            }\n        }\n        if (cols < 1) cols = 0;\n        if (rows < 1) rows = 0;\n\n        \/\/ \u2500\u2500 Render bed area as DOM \u2500\u2500\n        var aspect = _and(state.bedW > 0, state.bedL > 0) ? state.bedW \/ state.bedL : 1.33;\n        var pxW, pxH;\n        if (aspect >= 1) { pxW = MAX_PX; pxH = Math.round(MAX_PX \/ aspect); }\n        else { pxH = MAX_PX; pxW = Math.round(MAX_PX * aspect); }\n\n        bedFrameEl.style.width = pxW + 'px';\n        bedAreaEl.style.width = pxW + 'px';\n        bedAreaEl.style.height = pxH + 'px';\n\n        \/\/ Scale factor: px per bed unit\n        var scale = pxW \/ state.bedW;\n\n        \/\/ Clear previous plants and grid lines\n        bedAreaEl.innerHTML = '';\n\n        \/\/ Draw grid lines\n        if (spacingBU > 0) {\n            var x = spacingBU;\n            while (x < state.bedW) {\n                var gx = Math.round(x * scale);\n                var line = document.createElement('div');\n                line.className = 'pv-grid-line-v';\n                line.style.left = gx + 'px';\n                bedAreaEl.appendChild(line);\n                x += spacingBU;\n            }\n            var y = spacingBU;\n            while (y < state.bedL) {\n                var gy = Math.round(y * scale);\n                var line = document.createElement('div');\n                line.className = 'pv-grid-line-h';\n                line.style.top = gy + 'px';\n                bedAreaEl.appendChild(line);\n                y += spacingBU;\n            }\n        }\n\n        \/\/ Draw plants\n        for (var i = 0; i < positions.length; i++) {\n            var px = Math.round(positions[i].x * scale);\n            var py = Math.round(positions[i].y * scale);\n            var canopyPx = Math.max(Math.round(canopyBU * scale), 3);\n\n            \/\/ Canopy circle\n            var canopy = document.createElement('div');\n            canopy.className = 'pv-plant pv-plant-canopy';\n            canopy.style.left = px + 'px';\n            canopy.style.top = py + 'px';\n            canopy.style.width = canopyPx + 'px';\n            canopy.style.height = canopyPx + 'px';\n            bedAreaEl.appendChild(canopy);\n\n            \/\/ Stem dot\n            var stemPx = Math.max(Math.round(canopyPx * 0.25), 2);\n            var stem = document.createElement('div');\n            stem.className = 'pv-plant pv-plant-stem';\n            stem.style.left = px + 'px';\n            stem.style.top = py + 'px';\n            stem.style.width = stemPx + 'px';\n            stem.style.height = stemPx + 'px';\n            bedAreaEl.appendChild(stem);\n        }\n\n        \/\/ Dimension label\n        var dimUnit = state.unit === 'imperial' ? L('unitFt') : L('unitM');\n        dimLabel.textContent = state.bedW + ' ' + dimUnit + ' x ' + state.bedL + ' ' + dimUnit;\n\n        \/\/ Scale bar\n        var scaleBarLen = Math.min(100, pxW * 0.35);\n        var scaleVal, scaleUnitLbl;\n        if (state.unit === 'imperial') {\n            var ftPer100 = 100 \/ scale;\n            if (ftPer100 < 1) { scaleVal = 1; scaleUnitLbl = L('unitFt'); scaleBarLen = Math.round(1 * scale); }\n            else if (ftPer100 < 5) { scaleVal = Math.round(ftPer100); scaleUnitLbl = L('unitFt'); scaleBarLen = Math.round(scaleVal * scale); }\n            else { scaleVal = Math.round(ftPer100 \/ 5) * 5; scaleUnitLbl = L('unitFt'); scaleBarLen = Math.round(scaleVal * scale); }\n        } else {\n            var mPer100 = 100 \/ scale;\n            if (mPer100 < 0.3) { scaleVal = Math.round(mPer100 * 100 \/ 10) * 10; scaleUnitLbl = L('unitCm'); scaleBarLen = Math.round(scaleVal \/ 100 * scale); }\n            else { scaleVal = Math.round(mPer100 * 10) \/ 10; scaleUnitLbl = L('unitM'); scaleBarLen = Math.round(scaleVal * scale); }\n        }\n        scaleLine.style.width = Math.max(scaleBarLen, 20) + 'px';\n        scaleLabel.textContent = scaleVal + ' ' + scaleUnitLbl;\n\n        \/\/ Results\n        updateResults(positions.length, rows, cols, state.bedW * state.bedL);\n    }\n\n    function updateResults(total, rows, cols, area) {\n        var vt = $('pv-val-total'); if (vt) vt.textContent = total;\n        var vr = $('pv-val-rows'); if (vr) vr.textContent = rows;\n        var vc = $('pv-val-cols'); if (vc) vc.textContent = cols;\n        var va = $('pv-val-area');\n        if (va) { var isImp = state.unit === 'imperial'; va.textContent = Math.round(area * 100) \/ 100 + ' ' + (isImp ? L('unitSqft') : L('unitSqm')); }\n    }\n\n    \/\/ \u2500\u2500 Initial draw \u2500\u2500\n    draw();\n\n    \/\/ Hide loading banner\n    var statusEl = $('pv-js-status');\n    if (statusEl) { statusEl.textContent = ''; statusEl.style.display = 'none'; }\n})();\n<\/script>\n<h2 class=\"wp-block-heading\">How This Visualizer Works<\/h2>\n\n<p class=\"wp-block-paragraph\">This interactive tool draws a scaled top-down view of your garden bed, showing exactly how your plants, shrubs, or trees will be arranged. Adjust the dimensions, spacing, and layout pattern to see the result update in real time \u2014 no guesswork, no graph paper needed.<\/p>\n\n<h3 class=\"wp-block-heading\">Step 1: Set Your Bed Dimensions<\/h3>\n\n<p class=\"wp-block-paragraph\">Enter the width and length of your garden bed in feet or meters. The Canvas automatically scales to fit your bed proportions, so a long narrow bed and a square bed both display correctly.<\/p>\n\n<h3 class=\"wp-block-heading\">Step 2: Choose Your Plant Type & Spacing<\/h3>\n\n<p class=\"wp-block-paragraph\">Use the <strong>Quick Presets<\/strong> to auto-fill typical spacing and canopy values for common plant types \u2014 vegetables like tomatoes and carrots, shrubs, and small trees. Or enter custom spacing and canopy diameter manually. The canopy diameter controls how large each plant circle appears on the diagram.<\/p>\n\n<h3 class=\"wp-block-heading\">Step 3: Pick a Layout Pattern<\/h3>\n\n<p class=\"wp-block-paragraph\"><strong>Square Grid<\/strong> arranges plants in straight rows and columns \u2014 best for row crops and formal gardens. <strong>Staggered (Triangular)<\/strong> offsets every other row, allowing up to 15% more plants in the same area by packing them more efficiently. This is the pattern commercial growers use.<\/p>\n\n<h3 class=\"wp-block-heading\">Step 4: Visualize & Adjust<\/h3>\n\n<p class=\"wp-block-paragraph\">The Canvas updates instantly as you change any parameter. Tweak your spacing or switch patterns to see how plant count changes. The results panel shows total plants, rows, columns, and bed area at a glance.<\/p>\n\n<p class=\"wp-block-paragraph\">When you are happy with the layout, head over to our <a href=\"https:\/\/aiper.com\/blogs\/plant-spacing-calculator\/\">Plant Spacing Calculator<\/a> to get the exact number of plants, seeds, or bulbs you need \u2014 including overage for waste.<\/p>\n\n","protected":false},"excerpt":{"rendered":"<p>LOADING&#8230; JS not yet executed Plant &amp; Tree Spacing Visualizer See how your plants will look before you dig ft \/ in m \/ cm Bed Width (ft) Bed Length (ft) Quick Presets Plant Spacing (in) Canopy at Maturity (in) Layout Pattern Square Grid Staggered \/ Triangular 0 Total Plants 0 Rows 0 Columns 0&#8230;<\/p>\n","protected":false},"author":3,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_kad_post_transparent":"","_kad_post_title":"","_kad_post_layout":"","_kad_post_sidebar_id":"","_kad_post_content_style":"","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"_kad_post_classname":"","footnotes":""},"class_list":["post-8904","page","type-page","status-publish","hentry"],"lang":"en","translations":{"en":8904,"de":8905,"fr":8906,"es":8907},"_links":{"self":[{"href":"https:\/\/aiper.com\/blogs\/wp-json\/wp\/v2\/pages\/8904","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/aiper.com\/blogs\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/aiper.com\/blogs\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/aiper.com\/blogs\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/aiper.com\/blogs\/wp-json\/wp\/v2\/comments?post=8904"}],"version-history":[{"count":3,"href":"https:\/\/aiper.com\/blogs\/wp-json\/wp\/v2\/pages\/8904\/revisions"}],"predecessor-version":[{"id":11854,"href":"https:\/\/aiper.com\/blogs\/wp-json\/wp\/v2\/pages\/8904\/revisions\/11854"}],"wp:attachment":[{"href":"https:\/\/aiper.com\/blogs\/wp-json\/wp\/v2\/media?parent=8904"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}