{"id":2546,"date":"2026-06-15T02:56:42","date_gmt":"2026-06-15T02:56:42","guid":{"rendered":"https:\/\/aiper.com\/blogs\/?page_id=2546"},"modified":"2026-06-24T04:19:09","modified_gmt":"2026-06-24T04:19:09","slug":"gravel-calculator","status":"publish","type":"page","link":"https:\/\/aiper.com\/blogs\/gravel-calculator\/","title":{"rendered":"Gravel Calculator"},"content":{"rendered":"\n<script src=\"https:\/\/cdn.tailwindcss.com\"><\/script>\n<script>\n    tailwind.config = {\n        corePlugins: { preflight: false },\n        theme: {\n            extend: {\n                colors: {\n                    primary: '#1ecad3',\n                    'primary-light': '#5cdce4',\n                    'primary-dark': '#139ea8',\n                    accent: '#9486f7',\n                    'accent-light': '#b5abfa',\n                    'accent-dark': '#7a6fe0',\n                    surface: '#f8faf9',\n                    'surface-card': '#ffffff',\n                    'text-main': '#1a1f1d',\n                    'text-subtle': '#5f6b65',\n                    'border-soft': '#dde5e0',\n                },\n                fontFamily: {\n                    sans: ['\"Inter\"', 'system-ui', 'sans-serif'],\n                },\n                borderRadius: {\n                    'xl': '0.875rem',\n                    '2xl': '1.125rem',\n                },\n                boxShadow: {\n                    'card': '0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04)',\n                    'card-hover': '0 4px 12px rgba(0,0,0,0.08), 0 2px 4px rgba(0,0,0,0.04)',\n                    'result': '0 0 0 1px rgba(30,202,211,0.12), 0 2px 8px rgba(30,202,211,0.08)',\n                },\n            }\n        }\n    }\n<\/script>\n<link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin>\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=Inter:wght@400;500;600;700&#038;display=swap\" rel=\"stylesheet\">\n<style>\n    :root {\n        --transition-fast: 180ms cubic-bezier(0.4, 0, 0.2, 1);\n        --transition-smooth: 280ms cubic-bezier(0.4, 0, 0.2, 1);\n    }\n    * {\n        box-sizing: border-box;\n    }\n    html {\n        scroll-behavior: smooth;\n        -webkit-tap-highlight-color: transparent;\n    }\n    body {\n        font-family: 'Inter', system-ui, sans-serif;\n        background: #f3f6f4;\n        color: #1a1f1d;\n        min-height: 100vh;\n    }\n    input[type=\"range\"] {\n        -webkit-appearance: none;\n        appearance: none;\n        width: 100%;\n        height: 8px;\n        border-radius: 999px;\n        background: #dde5e0;\n        outline: none;\n        cursor: pointer;\n        transition: background var(--transition-fast);\n    }\n    input[type=\"range\"]::-webkit-slider-thumb {\n        -webkit-appearance: none;\n        appearance: none;\n        width: 28px;\n        height: 28px;\n        border-radius: 50%;\n        background: #1ecad3;\n        border: 3px solid #fff;\n        box-shadow: 0 1px 4px rgba(0, 0, 0, 0.18);\n        cursor: pointer;\n        transition: transform var(--transition-fast), box-shadow var(--transition-fast);\n    }\n    input[type=\"range\"]::-webkit-slider-thumb:hover {\n        transform: scale(1.12);\n        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.22);\n    }\n    input[type=\"range\"]::-webkit-slider-thumb:active {\n        transform: scale(1.06);\n        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.16);\n    }\n    input[type=\"range\"]::-moz-range-thumb {\n        width: 28px;\n        height: 28px;\n        border-radius: 50%;\n        background: #1ecad3;\n        border: 3px solid #fff;\n        box-shadow: 0 1px 4px rgba(0, 0, 0, 0.18);\n        cursor: pointer;\n    }\n    input[type=\"number\"]::-webkit-inner-spin-button,\n    input[type=\"number\"]::-webkit-outer-spin-button {\n        opacity: 0.4;\n    }\n    .depth-focus-indicator {\n        opacity: 0;\n        transform: translateY(-4px);\n        transition: opacity var(--transition-smooth), transform var(--transition-smooth);\n        pointer-events: none;\n    }\n    .depth-wrapper:focus-within .depth-focus-indicator {\n        opacity: 1;\n        transform: translateY(0);\n    }\n    .card-transition {\n        transition: box-shadow var(--transition-smooth), border-color var(--transition-smooth);\n    }\n    .card-transition:hover {\n        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.04);\n    }\n    .mode-tab {\n        transition: all var(--transition-fast);\n        position: relative;\n    }\n    .mode-tab.active {\n        background: #1ecad3;\n        color: #fff;\n        box-shadow: 0 2px 8px rgba(30, 202, 211, 0.3);\n    }\n    .mode-tab:not(.active):hover {\n        background: #e0f7fa;\n    }\n    @keyframes subtlePulse {\n        0%, 100% { transform: scale(1); }\n        50% { transform: scale(1.015); }\n    }\n    .result-pulse {\n        animation: subtlePulse 350ms ease-out;\n    }\n    .area-card {\n        transition: all var(--transition-smooth);\n        border-left: 3px solid #1ecad3;\n    }\n    .area-card:hover {\n        border-left-color: #5cdce4;\n        background: #fafcfb;\n    }\n    .toast {\n        position: fixed;\n        bottom: 2rem;\n        left: 50%;\n        transform: translateX(-50%) translateY(120px);\n        background: #1a1f1d;\n        color: #fff;\n        padding: 0.75rem 1.5rem;\n        border-radius: 999px;\n        font-weight: 500;\n        font-size: 0.9rem;\n        z-index: 100;\n        transition: transform var(--transition-smooth);\n        box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);\n    }\n    .toast.show {\n        transform: translateX(-50%) translateY(0);\n    }\n    select {\n        appearance: none;\n        -webkit-appearance: none;\n        background-image: url(\"data:image\/svg+xml,%3Csvg xmlns='http:\/\/www.w3.org\/2000\/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%235f6b65' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C\/polyline%3E%3C\/svg%3E\");\n        background-repeat: no-repeat;\n        background-position: right 0.7rem center;\n        padding-right: 2.2rem;\n    }\n    @media (max-width: 480px) {\n        .mode-tab {\n            font-size: 0.8rem;\n            padding: 0.5rem 0.6rem;\n        }\n        .result-value {\n            font-size: 1.5rem;\n        }\n    }\n<\/style>\n\n<div id=\"toast\" class=\"toast\" role=\"status\" aria-live=\"polite\"><\/div>\n\n<div class=\"max-w-3xl mx-auto px-3 sm:px-4 md:px-6 pt-4 sm:pt-6 md:pt-8\">\n\n    <header class=\"text-center mb-5 sm:mb-7\">\n\n        <p class=\"text-text-subtle text-sm sm:text-base mt-1.5\">\n            Accurately calculate volume, weight, and cost \u2013 with multi-area support &#038; reverse calculation\n        <\/p>\n    <\/header>\n\n    <!-- Top config bar -->\n    <div class=\"bg-surface-card rounded-2xl shadow-card p-4 sm:p-5 mb-4 card-transition\">\n        <div class=\"flex flex-col sm:flex-row sm:items-center gap-3 sm:gap-4\">\n            <div class=\"flex-1 min-w-0\">\n                <label for=\"materialSelect\" class=\"block text-xs font-semibold text-text-subtle uppercase tracking-wide mb-1.5\">\n                    Material\n                <\/label>\n                <select id=\"materialSelect\"\n                class=\"w-full bg-surface border border-border-soft rounded-lg px-3 py-2.5 text-sm font-medium text-text-main focus:outline-none focus:ring-2 focus:ring-primary\/30 focus:border-primary transition-all\">\n                <option value=\"pea-gravel\">Pea Gravel<\/option>\n                <option value=\"nominal-gravel\">Nominal Gravel (1\/4&#8243; &#8211; 2&#8243;)<\/option>\n                <option value=\"coarse-crushed\">Coarse Crushed Stone (2&#8243; &#8211; 6&#8243;)<\/option>\n                <option value=\"dry-sand\">Dry Sand<\/option>\n                <option value=\"wet-sand\">Wet Sand<\/option>\n                <option value=\"custom\">Custom Material<\/option>\n            <\/select>\n        <\/div>\n        <div class=\"flex-1 min-w-0\">\n            <label for=\"densityInput\" class=\"block text-xs font-semibold text-text-subtle uppercase tracking-wide mb-1.5\">\n                Density (<span id=\"densityUnitLabel\">kg\/m\u00b3<\/span>)\n            <\/label>\n            <input type=\"number\" id=\"densityInput\" value=\"1485\" step=\"0.1\" min=\"1\"\n            class=\"w-full bg-surface border border-border-soft rounded-lg px-3 py-2.5 text-sm font-medium text-text-main focus:outline-none focus:ring-2 focus:ring-primary\/30 focus:border-primary transition-all read-only:bg-gray-100 read-only:text-text-subtle read-only:cursor-default\"\n            readonly>\n        <\/div>\n        <div class=\"flex-shrink-0\">\n            <label class=\"block text-xs font-semibold text-text-subtle uppercase tracking-wide mb-1.5\">\n                Unit System\n            <\/label>\n            <button id=\"unitToggleBtn\"\n            class=\"w-full sm:w-auto inline-flex items-center justify-center gap-2 px-4 py-2.5 rounded-lg font-semibold text-sm transition-all duration-200 bg-primary text-white shadow-md hover:bg-primary-light active:scale-[0.97] focus:outline-none focus:ring-2 focus:ring-primary\/40\"\n            aria-label=\"Toggle unit system\">\n            <span id=\"unitToggleIcon\">&#127760;<\/span>\n            <span id=\"unitToggleLabel\">Metric<\/span>\n        <\/button>\n    <\/div>\n<\/div>\n<\/div>\n\n<!-- Mode tabs -->\n<div class=\"flex flex-wrap gap-1.5 sm:gap-2 mb-4\" role=\"tablist\" aria-label=\"Calculation mode\">\n    <button class=\"mode-tab active flex-1 min-w-[90px] py-2.5 px-3 rounded-lg font-medium text-sm text-center transition-all\"\n    data-mode=\"A\" role=\"tab\" aria-selected=\"true\" id=\"tabA\">\n    By Dimensions\n<\/button>\n<button class=\"mode-tab flex-1 min-w-[90px] py-2.5 px-3 rounded-lg font-medium text-sm text-center transition-all\"\ndata-mode=\"B\" role=\"tab\" aria-selected=\"false\" id=\"tabB\">\nDirect Area\n<\/button>\n<button class=\"mode-tab flex-1 min-w-[90px] py-2.5 px-3 rounded-lg font-medium text-sm text-center transition-all\"\ndata-mode=\"C\" role=\"tab\" aria-selected=\"false\" id=\"tabC\">\nMulti-Area Combine\n<\/button>\n<\/div>\n\n<!-- Mode A: Length x Width x Depth -->\n<div id=\"panelModeA\" class=\"bg-surface-card rounded-2xl shadow-card p-4 sm:p-5 mb-4 card-transition\">\n    <div class=\"flex items-center gap-2 mb-4\">\n        <span class=\"text-xs font-semibold text-text-subtle uppercase tracking-wide\">Shape:<\/span>\n        <button id=\"shapeRectBtn\"\n        class=\"px-3 py-1.5 rounded-full text-sm font-medium bg-primary text-white shadow-sm transition-all duration-200 focus:outline-none\"\n        data-shape=\"rectangle\">Rectangle<\/button>\n        <button id=\"shapeCircleBtn\"\n        class=\"px-3 py-1.5 rounded-full text-sm font-medium bg-gray-200 text-text-subtle hover:bg-gray-300 transition-all duration-200 focus:outline-none\"\n        data-shape=\"circle\">Circle<\/button>\n    <\/div>\n\n    <div id=\"rectInputs\" class=\"space-y-3\">\n        <div class=\"grid grid-cols-1 sm:grid-cols-2 gap-3\">\n            <div>\n                <label for=\"lengthInput\" class=\"block text-xs font-semibold text-text-subtle uppercase tracking-wide mb-1\">Length<\/label>\n                <div class=\"flex gap-1.5\">\n                    <input type=\"number\" id=\"lengthInput\" value=\"5\" min=\"0\" step=\"any\"\n                    class=\"flex-1 min-w-0 bg-surface border border-border-soft rounded-lg px-3 py-2.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-primary\/30 focus:border-primary transition-all\">\n                    <select id=\"lengthUnit\"\n                    class=\"w-24 sm:w-28 bg-surface border border-border-soft rounded-lg px-2 py-2.5 text-xs font-medium focus:outline-none focus:ring-2 focus:ring-primary\/30 transition-all\">\n                    <option value=\"m\">m<\/option>\n                    <option value=\"cm\">cm<\/option>\n                    <option value=\"ft\">ft<\/option>\n                    <option value=\"in\">in<\/option>\n                    <option value=\"yd\">yd<\/option>\n                <\/select>\n            <\/div>\n        <\/div>\n        <div>\n            <label for=\"widthInput\" class=\"block text-xs font-semibold text-text-subtle uppercase tracking-wide mb-1\">Width<\/label>\n            <div class=\"flex gap-1.5\">\n                <input type=\"number\" id=\"widthInput\" value=\"3\" min=\"0\" step=\"any\"\n                class=\"flex-1 min-w-0 bg-surface border border-border-soft rounded-lg px-3 py-2.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-primary\/30 focus:border-primary transition-all\">\n                <select id=\"widthUnit\"\n                class=\"w-24 sm:w-28 bg-surface border border-border-soft rounded-lg px-2 py-2.5 text-xs font-medium focus:outline-none focus:ring-2 focus:ring-primary\/30 transition-all\">\n                <option value=\"m\">m<\/option>\n                <option value=\"cm\">cm<\/option>\n                <option value=\"ft\">ft<\/option>\n                <option value=\"in\">in<\/option>\n                <option value=\"yd\">yd<\/option>\n            <\/select>\n        <\/div>\n    <\/div>\n<\/div>\n<\/div>\n\n<div id=\"circleInputs\" class=\"space-y-3 hidden\">\n    <div>\n        <label for=\"diameterInput\" class=\"block text-xs font-semibold text-text-subtle uppercase tracking-wide mb-1\">Diameter<\/label>\n        <div class=\"flex gap-1.5\">\n            <input type=\"number\" id=\"diameterInput\" value=\"4\" min=\"0\" step=\"any\"\n            class=\"flex-1 min-w-0 bg-surface border border-border-soft rounded-lg px-3 py-2.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-primary\/30 focus:border-primary transition-all\">\n            <select id=\"diameterUnit\"\n            class=\"w-24 sm:w-28 bg-surface border border-border-soft rounded-lg px-2 py-2.5 text-xs font-medium focus:outline-none focus:ring-2 focus:ring-primary\/30 transition-all\">\n            <option value=\"m\">m<\/option>\n            <option value=\"cm\">cm<\/option>\n            <option value=\"ft\">ft<\/option>\n            <option value=\"in\">in<\/option>\n            <option value=\"yd\">yd<\/option>\n        <\/select>\n    <\/div>\n<\/div>\n<\/div>\n\n<div class=\"depth-wrapper mt-3\">\n    <label for=\"depthInput\" class=\"block text-xs font-semibold text-text-subtle uppercase tracking-wide mb-1\">\n        Depth \/ Thickness\n    <\/label>\n    <div class=\"flex gap-1.5\">\n        <input type=\"number\" id=\"depthInput\" value=\"10\" min=\"0\" step=\"any\"\n        class=\"flex-1 min-w-0 bg-surface border border-border-soft rounded-lg px-3 py-2.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-accent\/50 focus:border-accent transition-all\"\n        aria-label=\"Layer depth (thickness)\">\n        <select id=\"depthUnit\"\n        class=\"w-24 sm:w-28 bg-surface border border-border-soft rounded-lg px-2 py-2.5 text-xs font-medium focus:outline-none focus:ring-2 focus:ring-primary\/30 transition-all\">\n        <option value=\"cm\">cm<\/option>\n        <option value=\"m\">m<\/option>\n        <option value=\"in\">in<\/option>\n        <option value=\"ft\">ft<\/option>\n    <\/select>\n<\/div>\n<p class=\"depth-focus-indicator text-xs text-accent mt-1 font-medium flex items-center gap-1\">\n    Tip: <span>Thickness is the layer depth, e.g., driveway typically 10-15 cm<\/span>\n<\/p>\n<\/div>\n<\/div>\n\n<!-- Mode B: Direct Area -->\n<div id=\"panelModeB\" class=\"bg-surface-card rounded-2xl shadow-card p-4 sm:p-5 mb-4 card-transition hidden\">\n    <div class=\"space-y-3\">\n        <div>\n            <label for=\"areaInputB\" class=\"block text-xs font-semibold text-text-subtle uppercase tracking-wide mb-1\">Total Area<\/label>\n            <div class=\"flex gap-1.5\">\n                <input type=\"number\" id=\"areaInputB\" value=\"50\" min=\"0\" step=\"any\"\n                class=\"flex-1 min-w-0 bg-surface border border-border-soft rounded-lg px-3 py-2.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-primary\/30 focus:border-primary transition-all\">\n                <select id=\"areaUnitB\"\n                class=\"w-28 bg-surface border border-border-soft rounded-lg px-2 py-2.5 text-xs font-medium focus:outline-none focus:ring-2 focus:ring-primary\/30 transition-all\">\n                <option value=\"m2\">m\u00b2<\/option>\n                <option value=\"ft2\">ft\u00b2<\/option>\n                <option value=\"yd2\">yd\u00b2<\/option>\n            <\/select>\n        <\/div>\n    <\/div>\n    <div class=\"depth-wrapper mt-3\">\n        <label for=\"depthInputB\" class=\"block text-xs font-semibold text-text-subtle uppercase tracking-wide mb-1\">\n            Depth \/ Thickness\n        <\/label>\n        <div class=\"flex gap-1.5\">\n            <input type=\"number\" id=\"depthInputB\" value=\"10\" min=\"0\" step=\"any\"\n            class=\"flex-1 min-w-0 bg-surface border border-border-soft rounded-lg px-3 py-2.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-accent\/50 focus:border-accent transition-all\"\n            aria-label=\"Layer depth (thickness)\">\n            <select id=\"depthUnitB\"\n            class=\"w-24 sm:w-28 bg-surface border border-border-soft rounded-lg px-2 py-2.5 text-xs font-medium focus:outline-none focus:ring-2 focus:ring-primary\/30 transition-all\">\n            <option value=\"cm\">cm<\/option>\n            <option value=\"m\">m<\/option>\n            <option value=\"in\">in<\/option>\n            <option value=\"ft\">ft<\/option>\n        <\/select>\n    <\/div>\n    <p class=\"depth-focus-indicator text-xs text-accent mt-1 font-medium flex items-center gap-1\">\n        Tip: <span>Thickness is the layer depth<\/span>\n    <\/p>\n<\/div>\n<\/div>\n<\/div>\n\n<!-- Mode C: Multi-Area Combiner -->\n<div id=\"panelModeC\" class=\"bg-surface-card rounded-2xl shadow-card p-4 sm:p-5 mb-4 card-transition hidden\">\n    <div id=\"multiAreaList\" class=\"space-y-3 mb-3\">\n    <\/div>\n    <div id=\"multiAreaEmpty\" class=\"text-center py-6 text-text-subtle text-sm\">\n        <p class=\"text-3xl mb-2\">+<\/p>\n        <p>No areas added yet. Click the button below to start.<\/p>\n    <\/div>\n    <button id=\"addAreaBtn\"\n    class=\"w-full py-2.5 rounded-lg font-medium text-sm border-2 border-dashed border-primary\/40 text-primary hover:bg-primary\/5 hover:border-primary transition-all duration-200 flex items-center justify-center gap-2\">\n    <span class=\"text-lg\">+<\/span> Add New Area\n<\/button>\n<div class=\"mt-3 p-3 bg-primary\/5 rounded-lg text-center\">\n    <span class=\"text-xs text-text-subtle\">Total Combined Area: <\/span>\n    <span id=\"totalAreaDisplay\" class=\"text-lg font-bold text-primary\">0.00 m\u00b2<\/span>\n<\/div>\n<div class=\"depth-wrapper mt-3\">\n    <label for=\"depthInputC\" class=\"block text-xs font-semibold text-text-subtle uppercase tracking-wide mb-1\">\n        Uniform Depth \/ Thickness\n    <\/label>\n    <div class=\"flex gap-1.5\">\n        <input type=\"number\" id=\"depthInputC\" value=\"10\" min=\"0\" step=\"any\"\n        class=\"flex-1 min-w-0 bg-surface border border-border-soft rounded-lg px-3 py-2.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-accent\/50 focus:border-accent transition-all\"\n        aria-label=\"Uniform layer depth\">\n        <select id=\"depthUnitC\"\n        class=\"w-24 sm:w-28 bg-surface border border-border-soft rounded-lg px-2 py-2.5 text-xs font-medium focus:outline-none focus:ring-2 focus:ring-primary\/30 transition-all\">\n        <option value=\"cm\">cm<\/option>\n        <option value=\"m\">m<\/option>\n        <option value=\"in\">in<\/option>\n        <option value=\"ft\">ft<\/option>\n    <\/select>\n<\/div>\n<p class=\"depth-focus-indicator text-xs text-accent mt-1 font-medium flex items-center gap-1\">\n    Tip: <span>All areas use the same depth<\/span>\n<\/p>\n<\/div>\n<\/div>\n\n<!-- Advanced Adjustments -->\n<div class=\"bg-surface-card rounded-2xl shadow-card p-4 sm:p-5 mb-4 card-transition\">\n    <h2 class=\"text-base font-semibold text-text-main mb-4 flex items-center gap-2\">\n        Advanced Construction Adjustments\n    <\/h2>\n    <div class=\"mb-4\">\n        <div class=\"flex items-center justify-between mb-2\">\n            <label for=\"wasteRateSlider\" class=\"text-sm font-medium text-text-main\">\n                Overage \/ Waste Rate: <span id=\"wasteRateValue\" class=\"font-bold text-primary\">10%<\/span>\n            <\/label>\n        <\/div>\n        <input type=\"range\" id=\"wasteRateSlider\" min=\"0\" max=\"20\" value=\"10\" step=\"0.5\"\n        class=\"w-full\">\n        <p class=\"text-xs text-text-subtle mt-1\">Used for filling transportation overflow and trimming losses<\/p>\n    <\/div>\n    <div>\n        <div class=\"flex items-center justify-between mb-2\">\n            <label for=\"compactionRateSlider\" class=\"text-sm font-medium text-text-main\">\n                Scene Compaction Rate: <span id=\"compactionRateValue\" class=\"font-bold text-accent\">5%<\/span>\n            <\/label>\n        <\/div>\n        <div class=\"flex flex-wrap gap-2 mb-2\">\n            <button class=\"compaction-preset px-3 py-1.5 rounded-full text-xs font-medium transition-all duration-200 bg-gray-200 text-text-subtle hover:bg-gray-300\"\n            data-rate=\"0\">Sidewalk\/Decoration: 0%<\/button>\n            <button class=\"compaction-preset px-3 py-1.5 rounded-full text-xs font-medium transition-all duration-200 bg-accent\/15 text-accent font-semibold border border-accent\/30\"\n            data-rate=\"5\">Light Compaction: 5%<\/button>\n            <button class=\"compaction-preset px-3 py-1.5 rounded-full text-xs font-medium transition-all duration-200 bg-gray-200 text-text-subtle hover:bg-gray-300\"\n            data-rate=\"20\">Roadbed (Heavy Compaction): 20%<\/button>\n        <\/div>\n        <input type=\"range\" id=\"compactionRateSlider\" min=\"0\" max=\"30\" value=\"5\" step=\"0.5\"\n        class=\"w-full\">\n        <p class=\"text-xs text-text-subtle mt-1\">Gravel volume shrinks after compaction; the <a href=\"https:\/\/aiper.com\/blogs\/retaining-wall-calculator\/\">calculator<\/a> has already replenished the purchase quantity for you.<\/p>\n    <\/div>\n<\/div>\n\n<!-- Results Panel -->\n<div class=\"bg-surface-card rounded-2xl shadow-result p-4 sm:p-5 mb-4 card-transition\" id=\"resultsPanel\">\n    <h2 class=\"text-base font-semibold text-text-main mb-4 flex items-center gap-2\">\n        Real-time Results\n    <\/h2>\n    <div class=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n        <div class=\"bg-primary\/5 rounded-xl p-4 text-center\">\n            <label for=\"resultVolume\" class=\"block text-xs font-semibold text-text-subtle uppercase tracking-wide mb-1\">\n                Volume (<span id=\"resultVolumeUnit\">m\u00b3<\/span>)\n            <\/label>\n            <input type=\"number\" id=\"resultVolume\" value=\"1.73\" step=\"0.001\" min=\"0\"\n            class=\"result-value w-full text-center text-2xl sm:text-3xl font-bold text-primary bg-transparent border-none outline-none focus:ring-2 focus:ring-primary\/20 rounded-lg transition-all py-1\"\n            aria-label=\"Final volume (editable for reverse calculation)\">\n            <p class=\"text-xs text-text-subtle mt-1\" id=\"resultVolumeSub\">Includes waste &#038; compaction<\/p>\n        <\/div>\n        <div class=\"bg-accent\/8 rounded-xl p-4 text-center\">\n            <label for=\"resultWeight\" class=\"block text-xs font-semibold text-text-subtle uppercase tracking-wide mb-1\">\n                Weight (<span id=\"resultWeightUnit\">metric tons<\/span>)\n            <\/label>\n            <input type=\"number\" id=\"resultWeight\" value=\"2.57\" step=\"0.001\" min=\"0\"\n            class=\"result-value w-full text-center text-2xl sm:text-3xl font-bold text-accent bg-transparent border-none outline-none focus:ring-2 focus:ring-accent\/30 rounded-lg transition-all py-1\"\n            aria-label=\"Final weight (editable for reverse calculation)\">\n            <p class=\"text-xs text-text-subtle mt-1\" id=\"resultWeightSub\">Based on density<\/p>\n        <\/div>\n    <\/div>\n    <p class=\"text-xs text-text-subtle text-center mt-3\" id=\"reverseHint\" style=\"display:none;\">\n        Reverse calculation mode: Depth updated based on your result input.\n    <\/p>\n<\/div>\n\n<!-- Budget & Retail -->\n<div class=\"bg-surface-card rounded-2xl shadow-card p-4 sm:p-5 mb-4 card-transition\">\n    <h2 class=\"text-base font-semibold text-text-main mb-4 flex items-center gap-2\">\n        Budget &#038; Retail Conversion\n    <\/h2>\n    <div class=\"flex flex-col sm:flex-row gap-3 mb-4\">\n        <div class=\"flex-1\">\n            <label for=\"unitPriceInput\" class=\"block text-xs font-semibold text-text-subtle uppercase tracking-wide mb-1\">Unit Price<\/label>\n            <input type=\"number\" id=\"unitPriceInput\" value=\"120\" min=\"0\" step=\"any\"\n            class=\"w-full bg-surface border border-border-soft rounded-lg px-3 py-2.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-primary\/30 focus:border-primary transition-all\">\n        <\/div>\n        <div class=\"flex-shrink-0\">\n            <label for=\"priceUnitSelect\" class=\"block text-xs font-semibold text-text-subtle uppercase tracking-wide mb-1\">Per<\/label>\n            <select id=\"priceUnitSelect\"\n            class=\"w-full bg-surface border border-border-soft rounded-lg px-3 py-2.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-primary\/30 transition-all\">\n            <option value=\"per-tonne\">metric ton<\/option>\n            <option value=\"per-m3\">cubic meter (m\u00b3)<\/option>\n            <option value=\"per-yd3\">cubic yard (yd\u00b3)<\/option>\n            <option value=\"per-kg\">kg<\/option>\n            <option value=\"per-lb\">lb<\/option>\n        <\/select>\n    <\/div>\n<\/div>\n<div class=\"bg-accent\/10 rounded-xl p-4 text-center mb-4\">\n    <span class=\"text-sm text-text-subtle\">Estimated Total Cost: <\/span>\n    <span id=\"totalCostDisplay\" class=\"text-3xl font-bold text-accent\">$308.70<\/span>\n<\/div>\n<div class=\"flex items-center gap-3 mb-3\">\n    <span class=\"text-sm font-medium text-text-main\">I&#8217;m a DIY user and need bagged gravel<\/span>\n    <button id=\"baggedToggle\"\n    class=\"relative inline-flex h-6 w-11 items-center rounded-full transition-colors duration-200 bg-gray-300 focus:outline-none focus:ring-2 focus:ring-primary\/40\"\n    role=\"switch\" aria-checked=\"false\">\n    <span class=\"inline-block h-5 w-5 transform rounded-full bg-white shadow transition-transform duration-200 translate-x-0.5\"\n    id=\"baggedToggleDot\"><\/span>\n<\/button>\n<\/div>\n<div id=\"baggedDetails\" class=\"hidden space-y-3 pl-2 border-l-2 border-primary\/20\">\n    <div class=\"flex gap-2 items-end\">\n        <div class=\"flex-1\">\n            <label for=\"bagWeightInput\" class=\"block text-xs font-semibold text-text-subtle uppercase tracking-wide mb-1\">Weight per Bag<\/label>\n            <input type=\"number\" id=\"bagWeightInput\" value=\"20\" min=\"0.1\" step=\"any\"\n            class=\"w-full bg-surface border border-border-soft rounded-lg px-3 py-2.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-primary\/30 focus:border-primary transition-all\">\n        <\/div>\n        <div class=\"flex-shrink-0\">\n            <select id=\"bagWeightUnit\"\n            class=\"bg-surface border border-border-soft rounded-lg px-3 py-2.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-primary\/30 transition-all\">\n            <option value=\"kg\">kg<\/option>\n            <option value=\"lb\">lb<\/option>\n        <\/select>\n    <\/div>\n<\/div>\n<div class=\"bg-primary\/5 rounded-lg p-3 text-center\">\n    <span class=\"text-sm text-text-subtle\">You need to buy approximately: <\/span>\n    <span id=\"bagCountDisplay\" class=\"text-2xl font-bold text-primary\">&#8212; bags<\/span>\n<\/div>\n<\/div>\n<div class=\"mt-3 p-3 bg-surface rounded-lg text-xs text-text-subtle leading-relaxed\" id=\"transportInfo\">\n    Transport reference: <span id=\"transportText\">about 3 standard pickup trucks (1 ton each) or 1 large dump truck<\/span>\n<\/div>\n<\/div>\n\n<!-- Export & Reset buttons -->\n<div class=\"text-center mb-6\">\n    <div class=\"flex justify-center gap-3 flex-wrap\">\n        <button id=\"copyReportBtn\"\n        class=\"inline-flex items-center gap-2 px-6 py-3 rounded-xl font-semibold text-sm bg-primary text-white shadow-lg hover:bg-primary-light active:scale-[0.97] transition-all duration-200 focus:outline-none focus:ring-4 focus:ring-primary\/25\">\n        Copy Results Report\n    <\/button>\n    <button id=\"resetBtn\"\n    class=\"inline-flex items-center gap-2 px-6 py-3 rounded-xl font-semibold text-sm bg-gray-200 text-text-subtle hover:bg-gray-300 active:scale-[0.97] transition-all duration-200 focus:outline-none focus:ring-4 focus:ring-primary\/25\">\n    Reset Calculator\n<\/button>\n<\/div>\n<p class=\"text-xs text-text-subtle mt-2\">One-click copy parameters and results to share with suppliers or owners<\/p>\n<\/div>\n\n<\/div>\n\n<script>\n    (function() {\n        \/\/ ==================== CONSTANTS & DATABASE ====================\n        const MATERIAL_DB = {\n            'pea-gravel': { name: 'Pea Gravel', densitySI: 1485 },\n            'nominal-gravel': { name: 'Nominal Gravel (1\/4\" - 2\")', densitySI: 1680 },\n            'coarse-crushed': { name: 'Coarse Crushed Stone (2\" - 6\")', densitySI: 1300 },\n            'dry-sand': { name: 'Dry Sand', densitySI: 1540 },\n            'wet-sand': { name: 'Wet Sand', densitySI: 1780 },\n            'custom': { name: 'Custom Material', densitySI: 1485 },\n        };\n\n        const LENGTH_TO_M = {\n            'm': 1,\n            'cm': 0.01,\n            'ft': 0.3048,\n            'in': 0.0254,\n            'yd': 0.9144,\n        };\n        const AREA_TO_M2 = {\n            'm2': 1,\n            'ft2': 0.09290304,\n            'yd2': 0.83612736,\n        };\n        const VOLUME_TO_M3 = {\n            'm3': 1,\n            'yd3': 0.76455485798,\n        };\n        const WEIGHT_TO_KG = {\n            'kg': 1,\n            'tonne': 1000,\n            'lb': 0.45359237,\n            'uston': 907.18474,\n        };\n        const KGPM3_TO_LBSPFT3 = 0.06242796;\n\n        \/\/ ==================== STATE ====================\n        const state = {\n            unitSystem: 'metric',\n            materialKey: 'pea-gravel',\n            customDensitySI: 1485,\n            mode: 'A',\n            shapeA: 'rectangle',\n            lengthSI: 5,\n            widthSI: 3,\n            diameterSI: 4,\n            depthSI: 0.1,\n            areaSI_B: 50,\n            multiAreas: [],\n            wasteRate: 10,\n            compactionRate: 5,\n            basicVolumeSI: 0,\n            finalVolumeSI: 0,\n            finalWeightKG: 0,\n            unitPrice: 120,\n            priceUnit: 'per-tonne',\n            baggedMode: false,\n            bagWeight: 20,\n            bagWeightUnit: 'kg',\n            lastEditedField: null,\n            isReverseCalc: false,\n            focusedResultField: null,\n        };\n\n        let areaIdCounter = 0;\n\n        const $ = (sel) => document.querySelector(sel);\n        const $$ = (sel) => document.querySelectorAll(sel);\n\n        const materialSelect = $('#materialSelect');\n        const densityInput = $('#densityInput');\n        const densityUnitLabel = $('#densityUnitLabel');\n        const unitToggleBtn = $('#unitToggleBtn');\n        const unitToggleIcon = $('#unitToggleIcon');\n        const unitToggleLabel = $('#unitToggleLabel');\n        const tabA = $('#tabA');\n        const tabB = $('#tabB');\n        const tabC = $('#tabC');\n        const panelA = $('#panelModeA');\n        const panelB = $('#panelModeB');\n        const panelC = $('#panelModeC');\n        const shapeRectBtn = $('#shapeRectBtn');\n        const shapeCircleBtn = $('#shapeCircleBtn');\n        const rectInputs = $('#rectInputs');\n        const circleInputs = $('#circleInputs');\n        const lengthInput = $('#lengthInput');\n        const lengthUnit = $('#lengthUnit');\n        const widthInput = $('#widthInput');\n        const widthUnit = $('#widthUnit');\n        const diameterInput = $('#diameterInput');\n        const diameterUnit = $('#diameterUnit');\n        const depthInput = $('#depthInput');\n        const depthUnit = $('#depthUnit');\n        const areaInputB = $('#areaInputB');\n        const areaUnitB = $('#areaUnitB');\n        const depthInputB = $('#depthInputB');\n        const depthUnitB = $('#depthUnitB');\n        const multiAreaList = $('#multiAreaList');\n        const multiAreaEmpty = $('#multiAreaEmpty');\n        const addAreaBtn = $('#addAreaBtn');\n        const totalAreaDisplay = $('#totalAreaDisplay');\n        const depthInputC = $('#depthInputC');\n        const depthUnitC = $('#depthUnitC');\n        const wasteRateSlider = $('#wasteRateSlider');\n        const wasteRateValue = $('#wasteRateValue');\n        const compactionRateSlider = $('#compactionRateSlider');\n        const compactionRateValue = $('#compactionRateValue');\n        const compactionPresets = $$('.compaction-preset');\n        const resultVolume = $('#resultVolume');\n        const resultWeight = $('#resultWeight');\n        const resultVolumeUnit = $('#resultVolumeUnit');\n        const resultWeightUnit = $('#resultWeightUnit');\n        const resultVolumeSub = $('#resultVolumeSub');\n        const resultWeightSub = $('#resultWeightSub');\n        const reverseHint = $('#reverseHint');\n        const unitPriceInput = $('#unitPriceInput');\n        const priceUnitSelect = $('#priceUnitSelect');\n        const totalCostDisplay = $('#totalCostDisplay');\n        const baggedToggle = $('#baggedToggle');\n        const baggedToggleDot = $('#baggedToggleDot');\n        const baggedDetails = $('#baggedDetails');\n        const bagWeightInput = $('#bagWeightInput');\n        const bagWeightUnit = $('#bagWeightUnit');\n        const bagCountDisplay = $('#bagCountDisplay');\n        const transportText = $('#transportText');\n        const copyReportBtn = $('#copyReportBtn');\n        const resetBtn = $('#resetBtn');\n        const toast = $('#toast');\n\n        function lengthFromSI(siValue, unit) { return siValue \/ LENGTH_TO_M[unit]; }\n\n        function lengthToSI(displayValue, unit) { return displayValue * LENGTH_TO_M[unit]; }\n\n        function areaFromSI(siValue, unit) { return siValue \/ AREA_TO_M2[unit]; }\n\n        function areaToSI(displayValue, unit) { return displayValue * AREA_TO_M2[unit]; }\n\n        function getDensitySI() {\n            if (state.materialKey === 'custom') return state.customDensitySI;\n            return MATERIAL_DB[state.materialKey].densitySI;\n        }\n\n        function getDensityDisplay() {\n            const si = getDensitySI();\n            return state.unitSystem === 'imperial' ? si * KGPM3_TO_LBSPFT3 : si;\n        }\n\n        function getDensityUnitStr() {\n            return state.unitSystem === 'metric' ? 'kg\/m\u00b3' : 'lbs\/ft\u00b3';\n        }\n\n        function weightFromSI(siValue) {\n            if (state.unitSystem === 'metric') return siValue \/ 1000;\n            return siValue \/ WEIGHT_TO_KG['uston'];\n        }\n\n        function weightToSI(displayValue) {\n            if (state.unitSystem === 'metric') return displayValue * 1000;\n            return displayValue * WEIGHT_TO_KG['uston'];\n        }\n\n        function getWeightUnitStr() {\n            return state.unitSystem === 'metric' ? 'metric tons' : 'US tons';\n        }\n\n        function getVolumeUnitStr() {\n            return state.unitSystem === 'metric' ? 'm\u00b3' : 'yd\u00b3';\n        }\n\n        function volumeFromSI(siValue) {\n            if (state.unitSystem === 'metric') return siValue;\n            return siValue \/ VOLUME_TO_M3['yd3'];\n        }\n\n        function volumeToSI(displayValue) {\n            if (state.unitSystem === 'metric') return displayValue;\n            return displayValue * VOLUME_TO_M3['yd3'];\n        }\n\n        function showToast(message, duration = 2200) {\n            toast.textContent = message;\n            toast.classList.add('show');\n            clearTimeout(toast._timeout);\n            toast._timeout = setTimeout(() => { toast.classList.remove('show'); }, duration);\n        }\n\n        function getTotalAreaSI() {\n            if (state.mode === 'A') {\n                if (state.shapeA === 'rectangle') return state.lengthSI * state.widthSI;\n                else { const r = state.diameterSI \/ 2; return Math.PI * r * r; }\n            } else if (state.mode === 'B') return state.areaSI_B;\n            else if (state.mode === 'C') return state.multiAreas.reduce((s, a) => s + (a.areaSI || 0), 0);\n            return 0;\n        }\n\n        function getDepthSI() {\n            if (state.mode === 'A') return state.depthSI;\n            if (state.mode === 'B') return state.depthSI;\n            if (state.mode === 'C') return state.depthSI;\n            return 0;\n        }\n\n        function setDepthSI(siValue) {\n            state.depthSI = Math.max(0, siValue);\n            updateAllDepthInputs();\n        }\n\n        function forwardCalculate() {\n            const area = getTotalAreaSI();\n            const depth = getDepthSI();\n            const basicVolume = area * depth;\n            state.basicVolumeSI = basicVolume;\n            state.finalVolumeSI = basicVolume * (1 + state.wasteRate \/ 100) * (1 + state.compactionRate \/ 100);\n            state.finalWeightKG = state.finalVolumeSI * getDensitySI();\n            state.lastEditedField = null;\n            state.isReverseCalc = false;\n            updateResultsDisplay();\n            updateBudgetDisplay();\n            updateTransportInfo();\n            reverseHint.style.display = 'none';\n        }\n\n        function reverseFromWeight(displayWeight) {\n            const weightKG = weightToSI(displayWeight);\n            if (weightKG <= 0 || getDensitySI() <= 0) return;\n            state.finalWeightKG = weightKG;\n            state.finalVolumeSI = weightKG \/ getDensitySI();\n            const area = getTotalAreaSI();\n            if (area > 0) {\n                const basicVolume = state.finalVolumeSI \/ ((1 + state.wasteRate \/ 100) * (1 + state.compactionRate \/ 100));\n                state.basicVolumeSI = basicVolume;\n                setDepthSI(basicVolume \/ area);\n            }\n            state.lastEditedField = 'weight';\n            state.isReverseCalc = true;\n            updateResultsDisplay();\n            updateBudgetDisplay();\n            updateTransportInfo();\n            reverseHint.style.display = 'flex';\n            setTimeout(() => { reverseHint.style.display = 'none'; }, 3000);\n        }\n\n        function reverseFromVolume(displayVolume) {\n            const volumeSI = volumeToSI(displayVolume);\n            if (volumeSI <= 0) return;\n            state.finalVolumeSI = volumeSI;\n            state.finalWeightKG = volumeSI * getDensitySI();\n            const area = getTotalAreaSI();\n            if (area > 0) {\n                const basicVolume = state.finalVolumeSI \/ ((1 + state.wasteRate \/ 100) * (1 + state.compactionRate \/ 100));\n                state.basicVolumeSI = basicVolume;\n                setDepthSI(basicVolume \/ area);\n            }\n            state.lastEditedField = 'volume';\n            state.isReverseCalc = true;\n            updateResultsDisplay();\n            updateBudgetDisplay();\n            updateTransportInfo();\n            reverseHint.style.display = 'flex';\n            setTimeout(() => { reverseHint.style.display = 'none'; }, 3000);\n        }\n\n        function updateAllDepthInputs() {\n            const depthDisplay = lengthFromSI(state.depthSI,\n                state.mode === 'A' ? depthUnit.value : (state.mode === 'B' ? depthUnitB.value : depthUnitC.value));\n            if (state.mode === 'A') depthInput.value = parseFloat(depthDisplay.toFixed(6));\n            else if (state.mode === 'B') depthInputB.value = parseFloat(depthDisplay.toFixed(6));\n            else if (state.mode === 'C') depthInputC.value = parseFloat(depthDisplay.toFixed(6));\n        }\n\n        function updateResultsDisplay() {\n            const volDisp = volumeFromSI(state.finalVolumeSI);\n            const wtDisp = weightFromSI(state.finalWeightKG);\n            resultVolumeUnit.textContent = getVolumeUnitStr();\n            resultWeightUnit.textContent = getWeightUnitStr();\n            if (state.focusedResultField !== 'volume') resultVolume.value = parseFloat(volDisp.toFixed(4));\n            if (state.focusedResultField !== 'weight') resultWeight.value = parseFloat(wtDisp.toFixed(4));\n            resultVolumeSub.textContent = state.isReverseCalc ? '(reverse calculation)' : 'Includes waste & compaction';\n            resultWeightSub.textContent = state.isReverseCalc ? '(reverse calculation)' : 'Based on density';\n            const resultsPanel = $('#resultsPanel');\n            resultsPanel.classList.remove('result-pulse');\n            void resultsPanel.offsetWidth;\n            resultsPanel.classList.add('result-pulse');\n        }\n\n        function updateBudgetDisplay() {\n            const price = state.unitPrice || 0;\n            let totalCost = 0;\n            const pu = state.priceUnit;\n            if (pu === 'per-tonne') totalCost = weightFromSI(state.finalWeightKG) * price;\n            else if (pu === 'per-m3') totalCost = volumeFromSI(state.finalVolumeSI) * price;\n            else if (pu === 'per-yd3') totalCost = (state.finalVolumeSI \/ VOLUME_TO_M3['yd3']) * price;\n            else if (pu === 'per-kg') totalCost = state.finalWeightKG * price;\n            else if (pu === 'per-lb') totalCost = (state.finalWeightKG \/ WEIGHT_TO_KG['lb']) * price;\n            totalCostDisplay.textContent = '$' + totalCost.toFixed(2);\n            if (state.baggedMode && state.bagWeight > 0) {\n                let bagWeightKG = state.bagWeight;\n                if (state.bagWeightUnit === 'lb') bagWeightKG = state.bagWeight * WEIGHT_TO_KG['lb'];\n                const bagsNeeded = Math.ceil(state.finalWeightKG \/ bagWeightKG);\n                bagCountDisplay.textContent = bagsNeeded + ' bags';\n            } else {\n                bagCountDisplay.textContent = '-- bags';\n            }\n        }\n\n        function updateTransportInfo() {\n            const weightTonnes = state.finalWeightKG \/ 1000;\n            if (weightTonnes <= 0) { transportText.textContent = 'No transport needed'; return; }\n            const pickups = Math.ceil(weightTonnes \/ 1);\n            const dumpTrucks = Math.ceil(weightTonnes \/ 15);\n            transportText.textContent =\n                `about ${pickups} standard pickup trucks (1 ton each) or ${dumpTrucks} large dump trucks (15 tons each)`;\n        }\n\n        function fullRecalculate() {\n            if (state.isReverseCalc &#038;&#038; state.lastEditedField) {\n                if (state.lastEditedField === 'weight') reverseFromWeight(parseFloat(resultWeight.value) || 0);\n                else if (state.lastEditedField === 'volume') reverseFromVolume(parseFloat(resultVolume.value) || 0);\n                return;\n            }\n            forwardCalculate();\n        }\n\n        function syncModeAInputs() {\n            if (state.shapeA === 'rectangle') {\n                state.lengthSI = lengthToSI(parseFloat(lengthInput.value) || 0, lengthUnit.value);\n                state.widthSI = lengthToSI(parseFloat(widthInput.value) || 0, widthUnit.value);\n            } else {\n                state.diameterSI = lengthToSI(parseFloat(diameterInput.value) || 0, diameterUnit.value);\n            }\n            state.depthSI = lengthToSI(parseFloat(depthInput.value) || 0, depthUnit.value);\n        }\n\n        function syncModeBInputs() {\n            state.areaSI_B = areaToSI(parseFloat(areaInputB.value) || 0, areaUnitB.value);\n            state.depthSI = lengthToSI(parseFloat(depthInputB.value) || 0, depthUnitB.value);\n        }\n\n        function syncModeCInputs() {\n            state.depthSI = lengthToSI(parseFloat(depthInputC.value) || 0, depthUnitC.value);\n        }\n\n        function syncAllInputs() {\n            if (state.mode === 'A') syncModeAInputs();\n            else if (state.mode === 'B') syncModeBInputs();\n            else if (state.mode === 'C') syncModeCInputs();\n        }\n\n        function updateDensityDisplay() {\n            const d = getDensityDisplay();\n            densityInput.value = parseFloat(d.toFixed(2));\n            densityUnitLabel.textContent = getDensityUnitStr();\n            densityInput.readOnly = (state.materialKey !== 'custom');\n        }\n\n        function updateUnitToggleUI() {\n            if (state.unitSystem === 'metric') {\n                unitToggleIcon.innerHTML = '&#127760;';\n                unitToggleLabel.textContent = 'Metric';\n            } else {\n                unitToggleIcon.innerHTML = '&#127758;';\n                unitToggleLabel.textContent = 'Imperial';\n            }\n        }\n\n        function updateAllUnitDropdowns() {\n            const lengthDefaults = state.unitSystem === 'metric' ? ['m', 'cm', 'ft', 'in', 'yd'] : ['ft', 'in', 'yd',\n                'm', 'cm'\n            ];\n            const depthDefaults = state.unitSystem === 'metric' ? ['cm', 'm', 'in', 'ft'] : ['in', 'ft', 'cm', 'm'];\n            const areaDefaults = state.unitSystem === 'metric' ? ['m2', 'ft2', 'yd2'] : ['ft2', 'yd2', 'm2'];\n\n            function rebuildSelect(selectEl, options, currentValue) {\n                const val = currentValue || selectEl.value;\n                selectEl.innerHTML = '';\n                const labels = {\n                    'm': 'm',\n                    'cm': 'cm',\n                    'ft': 'ft',\n                    'in': 'in',\n                    'yd': 'yd',\n                    'm2': 'm\u00b2',\n                    'ft2': 'ft\u00b2',\n                    'yd2': 'yd\u00b2'\n                };\n                options.forEach(opt => {\n                    const optionEl = document.createElement('option');\n                    optionEl.value = opt;\n                    optionEl.textContent = labels[opt] || opt;\n                    if (opt === val) optionEl.selected = true;\n                    selectEl.appendChild(optionEl);\n                });\n            }\n            rebuildSelect(lengthUnit, lengthDefaults, lengthUnit.value);\n            rebuildSelect(widthUnit, lengthDefaults, widthUnit.value);\n            rebuildSelect(diameterUnit, lengthDefaults, diameterUnit.value);\n            rebuildSelect(depthUnit, depthDefaults, depthUnit.value);\n            rebuildSelect(depthUnitB, depthDefaults, depthUnitB.value);\n            rebuildSelect(depthUnitC, depthDefaults, depthUnitC.value);\n            rebuildSelect(areaUnitB, areaDefaults, areaUnitB.value);\n            if (state.unitSystem === 'metric') bagWeightUnit.value = 'kg';\n            else bagWeightUnit.value = 'lb';\n            state.bagWeightUnit = bagWeightUnit.value;\n        }\n\n        function updateAllDisplayValues() {\n            lengthInput.value = parseFloat(lengthFromSI(state.lengthSI, lengthUnit.value).toFixed(6));\n            widthInput.value = parseFloat(lengthFromSI(state.widthSI, widthUnit.value).toFixed(6));\n            diameterInput.value = parseFloat(lengthFromSI(state.diameterSI, diameterUnit.value).toFixed(6));\n            depthInput.value = parseFloat(lengthFromSI(state.depthSI, depthUnit.value).toFixed(6));\n            areaInputB.value = parseFloat(areaFromSI(state.areaSI_B, areaUnitB.value).toFixed(6));\n            depthInputB.value = parseFloat(lengthFromSI(state.depthSI, depthUnitB.value).toFixed(6));\n            depthInputC.value = parseFloat(lengthFromSI(state.depthSI, depthUnitC.value).toFixed(6));\n            updateMultiAreaDisplay();\n            updateTotalAreaDisplay();\n        }\n\n        function updateTotalAreaDisplay() {\n            const totalArea = getTotalAreaSI();\n            const unitStr = state.unitSystem === 'metric' ? 'm\u00b2' : 'ft\u00b2';\n            const displayArea = state.unitSystem === 'metric' ? totalArea : areaFromSI(totalArea, 'ft2');\n            totalAreaDisplay.textContent = parseFloat(displayArea.toFixed(4)) + ' ' + unitStr;\n        }\n\n        function createAreaCard(areaData) {\n            const card = document.createElement('div');\n            card.className = 'area-card bg-surface rounded-lg p-3 flex flex-col sm:flex-row gap-2 items-start';\n            card.dataset.areaId = areaData.id;\n            const shapeOptions = [\n                { value: 'rectangle', label: 'Rectangle' },\n                { value: 'circle', label: 'Circle' },\n            ];\n            const shapeSelectHTML = shapeOptions.map(s =>\n                `<option value=\"${s.value}\" ${areaData.shape === s.value ? 'selected' : ''}>${s.label}<\/option>`\n            ).join('');\n            const lengthUnitOptions = ['m', 'cm', 'ft', 'in', 'yd'];\n            const luHTML = lengthUnitOptions.map(u =>\n                `<option value=\"${u}\" ${areaData.dim1Unit === u ? 'selected' : ''}>${u}<\/option>`\n            ).join('');\n            const wuHTML = lengthUnitOptions.map(u =>\n                `<option value=\"${u}\" ${areaData.dim2Unit === u ? 'selected' : ''}>${u}<\/option>`\n            ).join('');\n            const isRect = areaData.shape === 'rectangle';\n            const dim1Label = isRect ? 'Length' : 'Diameter';\n            const dim1Val = parseFloat(lengthFromSI(areaData.dim1SI, areaData.dim1Unit).toFixed(6));\n            const dim2Val = isRect ? parseFloat(lengthFromSI(areaData.dim2SI, areaData.dim2Unit).toFixed(6)) : 0;\n            const areaDisplay = state.unitSystem === 'metric' ?\n                parseFloat(areaData.areaSI.toFixed(4)) + ' m\u00b2' :\n                parseFloat(areaFromSI(areaData.areaSI, 'ft2').toFixed(4)) + ' ft\u00b2';\n\n            card.innerHTML = `\n            <div class=\"flex-shrink-0\">\n              <select class=\"area-shape-select bg-surface border border-border-soft rounded-md px-2 py-1.5 text-xs font-medium focus:outline-none focus:ring-2 focus:ring-primary\/30 transition-all\">\n                ${shapeSelectHTML}\n              <\/select>\n            <\/div>\n            <div class=\"flex-1 grid grid-cols-1 xs:grid-cols-2 gap-2 w-full\">\n              <div class=\"flex gap-1\">\n                <input type=\"number\" class=\"area-dim1 flex-1 min-w-0 bg-surface border border-border-soft rounded-md px-2 py-1.5 text-xs font-medium focus:outline-none focus:ring-2 focus:ring-primary\/30 transition-all\" value=\"${dim1Val}\" min=\"0\" step=\"any\" placeholder=\"${dim1Label}\">\n                <select class=\"area-dim1-unit w-20 bg-surface border border-border-soft rounded-md px-1.5 py-1.5 text-xs focus:outline-none transition-all\">${luHTML}<\/select>\n              <\/div>\n              <div class=\"area-dim2-wrapper flex gap-1\" style=\"${isRect ? '' : 'display:none;'}\">\n                <input type=\"number\" class=\"area-dim2 flex-1 min-w-0 bg-surface border border-border-soft rounded-md px-2 py-1.5 text-xs font-medium focus:outline-none focus:ring-2 focus:ring-primary\/30 transition-all\" value=\"${dim2Val}\" min=\"0\" step=\"any\" placeholder=\"Width\">\n                <select class=\"area-dim2-unit w-20 bg-surface border border-border-soft rounded-md px-1.5 py-1.5 text-xs focus:outline-none transition-all\">${wuHTML}<\/select>\n              <\/div>\n            <\/div>\n            <div class=\"flex items-center gap-2 flex-shrink-0\">\n              <span class=\"text-xs font-semibold text-primary whitespace-nowrap area-card-display\">${areaDisplay}<\/span>\n              <button class=\"area-remove-btn text-red-400 hover:text-red-600 hover:bg-red-50 rounded-full w-7 h-7 flex items-center justify-center transition-all text-lg leading-none\" title=\"Remove area\">&times;<\/button>\n            <\/div>\n          `;\n\n            const shapeSelect = card.querySelector('.area-shape-select');\n            const dim1Input = card.querySelector('.area-dim1');\n            const dim1UnitSel = card.querySelector('.area-dim1-unit');\n            const dim2Input = card.querySelector('.area-dim2');\n            const dim2UnitSel = card.querySelector('.area-dim2-unit');\n            const dim2Wrapper = card.querySelector('.area-dim2-wrapper');\n            const removeBtn = card.querySelector('.area-remove-btn');\n            const displaySpan = card.querySelector('.area-card-display');\n\n            function updateAreaCardData() {\n                areaData.shape = shapeSelect.value;\n                areaData.dim1Unit = dim1UnitSel.value;\n                areaData.dim1SI = lengthToSI(parseFloat(dim1Input.value) || 0, dim1UnitSel.value);\n                if (areaData.shape === 'rectangle') {\n                    areaData.dim2Unit = dim2UnitSel.value;\n                    areaData.dim2SI = lengthToSI(parseFloat(dim2Input.value) || 0, dim2UnitSel.value);\n                    areaData.areaSI = areaData.dim1SI * areaData.dim2SI;\n                } else {\n                    const radius = areaData.dim1SI \/ 2;\n                    areaData.areaSI = Math.PI * radius * radius;\n                }\n                const ad = state.unitSystem === 'metric' ?\n                    parseFloat(areaData.areaSI.toFixed(4)) + ' m\u00b2' :\n                    parseFloat(areaFromSI(areaData.areaSI, 'ft2').toFixed(4)) + ' ft\u00b2';\n                displaySpan.textContent = ad;\n                if (areaData.shape === 'rectangle') {\n                    dim2Wrapper.style.display = '';\n                    dim1Input.placeholder = 'Length';\n                } else {\n                    dim2Wrapper.style.display = 'none';\n                    dim1Input.placeholder = 'Diameter';\n                }\n                updateTotalAreaDisplay();\n                fullRecalculate();\n            }\n            shapeSelect.addEventListener('change', updateAreaCardData);\n            dim1Input.addEventListener('input', updateAreaCardData);\n            dim1UnitSel.addEventListener('change', updateAreaCardData);\n            dim2Input.addEventListener('input', updateAreaCardData);\n            dim2UnitSel.addEventListener('change', updateAreaCardData);\n            removeBtn.addEventListener('click', () => {\n                state.multiAreas = state.multiAreas.filter(a => a.id !== areaData.id);\n                card.remove();\n                if (state.multiAreas.length === 0) multiAreaEmpty.style.display = '';\n                updateTotalAreaDisplay();\n                fullRecalculate();\n            });\n            return card;\n        }\n\n        function updateMultiAreaDisplay() {\n            const cards = multiAreaList.querySelectorAll('.area-card');\n            cards.forEach(card => {\n                const areaId = parseInt(card.dataset.areaId);\n                const areaData = state.multiAreas.find(a => a.id === areaId);\n                if (areaData) {\n                    const displaySpan = card.querySelector('.area-card-display');\n                    if (displaySpan) {\n                        const ad = state.unitSystem === 'metric' ?\n                            parseFloat(areaData.areaSI.toFixed(4)) + ' m\u00b2' :\n                            parseFloat(areaFromSI(areaData.areaSI, 'ft2').toFixed(4)) + ' ft\u00b2';\n                        displaySpan.textContent = ad;\n                    }\n                }\n            });\n            updateTotalAreaDisplay();\n        }\n\n        function addNewArea() {\n            const id = ++areaIdCounter;\n            const defaultUnit = state.unitSystem === 'metric' ? 'm' : 'ft';\n            const areaData = {\n                id,\n                shape: 'rectangle',\n                dim1SI: lengthToSI(3, defaultUnit),\n                dim1Unit: defaultUnit,\n                dim2SI: lengthToSI(2, defaultUnit),\n                dim2Unit: defaultUnit,\n                areaSI: lengthToSI(3, defaultUnit) * lengthToSI(2, defaultUnit),\n            };\n            state.multiAreas.push(areaData);\n            const card = createAreaCard(areaData);\n            multiAreaList.appendChild(card);\n            multiAreaEmpty.style.display = 'none';\n            updateTotalAreaDisplay();\n            fullRecalculate();\n        }\n\n        function rebuildAllAreaCards() {\n            multiAreaList.innerHTML = '';\n            state.multiAreas.forEach(areaData => {\n                const card = createAreaCard(areaData);\n                multiAreaList.appendChild(card);\n            });\n            if (state.multiAreas.length === 0) multiAreaEmpty.style.display = '';\n            else multiAreaEmpty.style.display = 'none';\n            updateTotalAreaDisplay();\n        }\n\n        function switchMode(mode) {\n            state.mode = mode;\n            state.isReverseCalc = false;\n            state.lastEditedField = null;\n            reverseHint.style.display = 'none';\n            [tabA, tabB, tabC].forEach(t => { t.classList.remove('active');\n                t.setAttribute('aria-selected', 'false'); });\n            if (mode === 'A') { tabA.classList.add('active');\n                tabA.setAttribute('aria-selected', 'true'); } else if (mode === 'B') { tabB.classList.add('active');\n                tabB.setAttribute('aria-selected', 'true'); } else { tabC.classList.add('active');\n                tabC.setAttribute('aria-selected', 'true'); }\n            panelA.classList.add('hidden');\n            panelB.classList.add('hidden');\n            panelC.classList.add('hidden');\n            if (mode === 'A') panelA.classList.remove('hidden');\n            else if (mode === 'B') panelB.classList.remove('hidden');\n            else panelC.classList.remove('hidden');\n            updateAllDisplayValues();\n            if (mode === 'C') rebuildAllAreaCards();\n            fullRecalculate();\n        }\n\n        materialSelect.addEventListener('change', () => {\n            state.materialKey = materialSelect.value;\n            if (state.materialKey !== 'custom') state.customDensitySI = MATERIAL_DB[state.materialKey]\n            .densitySI;\n            updateDensityDisplay();\n            state.isReverseCalc = false;\n            state.lastEditedField = null;\n            reverseHint.style.display = 'none';\n            fullRecalculate();\n        });\n        densityInput.addEventListener('input', () => {\n            if (state.materialKey === 'custom') {\n                const displayVal = parseFloat(densityInput.value) || 0;\n                state.customDensitySI = state.unitSystem === 'imperial' ? displayVal \/ KGPM3_TO_LBSPFT3 :\n                    displayVal;\n                state.isReverseCalc = false;\n                state.lastEditedField = null;\n                reverseHint.style.display = 'none';\n                fullRecalculate();\n            }\n        });\n        unitToggleBtn.addEventListener('click', () => {\n            state.unitSystem = state.unitSystem === 'metric' ? 'imperial' : 'metric';\n            updateUnitToggleUI();\n            updateAllUnitDropdowns();\n            updateDensityDisplay();\n            updateAllDisplayValues();\n            if (state.mode === 'C') rebuildAllAreaCards();\n            state.isReverseCalc = false;\n            state.lastEditedField = null;\n            reverseHint.style.display = 'none';\n            fullRecalculate();\n        });\n        tabA.addEventListener('click', () => switchMode('A'));\n        tabB.addEventListener('click', () => switchMode('B'));\n        tabC.addEventListener('click', () => switchMode('C'));\n        shapeRectBtn.addEventListener('click', () => {\n            state.shapeA = 'rectangle';\n            shapeRectBtn.classList.add('bg-primary', 'text-white', 'shadow-sm');\n            shapeRectBtn.classList.remove('bg-gray-200', 'text-text-subtle', 'hover:bg-gray-300');\n            shapeCircleBtn.classList.remove('bg-primary', 'text-white', 'shadow-sm');\n            shapeCircleBtn.classList.add('bg-gray-200', 'text-text-subtle', 'hover:bg-gray-300');\n            rectInputs.classList.remove('hidden');\n            circleInputs.classList.add('hidden');\n            syncModeAInputs();\n            state.isReverseCalc = false;\n            state.lastEditedField = null;\n            reverseHint.style.display = 'none';\n            fullRecalculate();\n        });\n        shapeCircleBtn.addEventListener('click', () => {\n            state.shapeA = 'circle';\n            shapeCircleBtn.classList.add('bg-primary', 'text-white', 'shadow-sm');\n            shapeCircleBtn.classList.remove('bg-gray-200', 'text-text-subtle', 'hover:bg-gray-300');\n            shapeRectBtn.classList.remove('bg-primary', 'text-white', 'shadow-sm');\n            shapeRectBtn.classList.add('bg-gray-200', 'text-text-subtle', 'hover:bg-gray-300');\n            rectInputs.classList.add('hidden');\n            circleInputs.classList.remove('hidden');\n            syncModeAInputs();\n            state.isReverseCalc = false;\n            state.lastEditedField = null;\n            reverseHint.style.display = 'none';\n            fullRecalculate();\n        });\n        [lengthInput, widthInput, diameterInput, depthInput].forEach(el => {\n            el.addEventListener('input', () => {\n                if (state.mode !== 'A') return;\n                syncModeAInputs();\n                state.isReverseCalc = false;\n                state.lastEditedField = null;\n                reverseHint.style.display = 'none';\n                fullRecalculate();\n            });\n        });\n        [lengthUnit, widthUnit, diameterUnit, depthUnit].forEach(el => {\n            el.addEventListener('change', () => {\n                if (state.mode !== 'A') return;\n                syncModeAInputs();\n                updateAllDisplayValues();\n                state.isReverseCalc = false;\n                state.lastEditedField = null;\n                reverseHint.style.display = 'none';\n                fullRecalculate();\n            });\n        });\n        [areaInputB, depthInputB].forEach(el => {\n            el.addEventListener('input', () => {\n                if (state.mode !== 'B') return;\n                syncModeBInputs();\n                state.isReverseCalc = false;\n                state.lastEditedField = null;\n                reverseHint.style.display = 'none';\n                fullRecalculate();\n            });\n        });\n        [areaUnitB, depthUnitB].forEach(el => {\n            el.addEventListener('change', () => {\n                if (state.mode !== 'B') return;\n                syncModeBInputs();\n                updateAllDisplayValues();\n                state.isReverseCalc = false;\n                state.lastEditedField = null;\n                reverseHint.style.display = 'none';\n                fullRecalculate();\n            });\n        });\n        [depthInputC].forEach(el => {\n            el.addEventListener('input', () => {\n                if (state.mode !== 'C') return;\n                syncModeCInputs();\n                state.isReverseCalc = false;\n                state.lastEditedField = null;\n                reverseHint.style.display = 'none';\n                fullRecalculate();\n            });\n        });\n        [depthUnitC].forEach(el => {\n            el.addEventListener('change', () => {\n                if (state.mode !== 'C') return;\n                syncModeCInputs();\n                updateAllDisplayValues();\n                state.isReverseCalc = false;\n                state.lastEditedField = null;\n                reverseHint.style.display = 'none';\n                fullRecalculate();\n            });\n        });\n        addAreaBtn.addEventListener('click', addNewArea);\n        wasteRateSlider.addEventListener('input', () => {\n            state.wasteRate = parseFloat(wasteRateSlider.value);\n            wasteRateValue.textContent = state.wasteRate + '%';\n            state.isReverseCalc = false;\n            state.lastEditedField = null;\n            reverseHint.style.display = 'none';\n            fullRecalculate();\n        });\n        compactionRateSlider.addEventListener('input', () => {\n            state.compactionRate = parseFloat(compactionRateSlider.value);\n            compactionRateValue.textContent = state.compactionRate + '%';\n            updateCompactionPresetStyles();\n            state.isReverseCalc = false;\n            state.lastEditedField = null;\n            reverseHint.style.display = 'none';\n            fullRecalculate();\n        });\n        compactionPresets.forEach(btn => {\n            btn.addEventListener('click', () => {\n                const rate = parseFloat(btn.dataset.rate);\n                state.compactionRate = rate;\n                compactionRateSlider.value = rate;\n                compactionRateValue.textContent = rate + '%';\n                updateCompactionPresetStyles();\n                state.isReverseCalc = false;\n                state.lastEditedField = null;\n                reverseHint.style.display = 'none';\n                fullRecalculate();\n            });\n        });\n\n        function updateCompactionPresetStyles() {\n            compactionPresets.forEach(btn => {\n                const rate = parseFloat(btn.dataset.rate);\n                if (Math.abs(rate - state.compactionRate) < 0.01) {\n                    btn.classList.add('bg-accent\/15', 'text-accent', 'font-semibold', 'border',\n                        'border-accent\/30');\n                    btn.classList.remove('bg-gray-200', 'text-text-subtle', 'hover:bg-gray-300');\n                } else {\n                    btn.classList.remove('bg-accent\/15', 'text-accent', 'font-semibold', 'border',\n                        'border-accent\/30');\n                    btn.classList.add('bg-gray-200', 'text-text-subtle', 'hover:bg-gray-300');\n                }\n            });\n        }\n        resultVolume.addEventListener('focus', () => {\n            state.focusedResultField = 'volume';\n            resultVolume.dataset.prevValue = resultVolume.value;\n        });\n        resultVolume.addEventListener('blur', () => {\n            state.focusedResultField = null;\n            const prevVal = parseFloat(resultVolume.dataset.prevValue || 0);\n            const newVal = parseFloat(resultVolume.value);\n            if (!isNaN(newVal) && Math.abs(newVal - prevVal) > 0.0001 && newVal >= 0) reverseFromVolume(newVal);\n        });\n        resultVolume.addEventListener('keydown', (e) => { if (e.key === 'Enter') resultVolume.blur(); });\n        resultWeight.addEventListener('focus', () => {\n            state.focusedResultField = 'weight';\n            resultWeight.dataset.prevValue = resultWeight.value;\n        });\n        resultWeight.addEventListener('blur', () => {\n            state.focusedResultField = null;\n            const prevVal = parseFloat(resultWeight.dataset.prevValue || 0);\n            const newVal = parseFloat(resultWeight.value);\n            if (!isNaN(newVal) && Math.abs(newVal - prevVal) > 0.0001 && newVal >= 0) reverseFromWeight(newVal);\n        });\n        resultWeight.addEventListener('keydown', (e) => { if (e.key === 'Enter') resultWeight.blur(); });\n        unitPriceInput.addEventListener('input', () => {\n            state.unitPrice = parseFloat(unitPriceInput.value) || 0;\n            updateBudgetDisplay();\n        });\n        priceUnitSelect.addEventListener('change', () => {\n            state.priceUnit = priceUnitSelect.value;\n            updateBudgetDisplay();\n        });\n        baggedToggle.addEventListener('click', () => {\n            state.baggedMode = !state.baggedMode;\n            if (state.baggedMode) {\n                baggedToggle.classList.remove('bg-gray-300');\n                baggedToggle.classList.add('bg-primary');\n                baggedToggleDot.classList.remove('translate-x-0.5');\n                baggedToggleDot.classList.add('translate-x-5');\n                baggedToggle.setAttribute('aria-checked', 'true');\n                baggedDetails.classList.remove('hidden');\n            } else {\n                baggedToggle.classList.add('bg-gray-300');\n                baggedToggle.classList.remove('bg-primary');\n                baggedToggleDot.classList.add('translate-x-0.5');\n                baggedToggleDot.classList.remove('translate-x-5');\n                baggedToggle.setAttribute('aria-checked', 'false');\n                baggedDetails.classList.add('hidden');\n            }\n            updateBudgetDisplay();\n        });\n        bagWeightInput.addEventListener('input', () => {\n            state.bagWeight = parseFloat(bagWeightInput.value) || 0;\n            updateBudgetDisplay();\n        });\n        bagWeightUnit.addEventListener('change', () => {\n            state.bagWeightUnit = bagWeightUnit.value;\n            updateBudgetDisplay();\n        });\n        copyReportBtn.addEventListener('click', () => {\n            const report = generateReport();\n            if (navigator.clipboard && navigator.clipboard.writeText) {\n                navigator.clipboard.writeText(report).then(() => showToast('Report copied to clipboard!'))\n                    .catch(() => fallbackCopy(report));\n            } else fallbackCopy(report);\n        });\n\n        resetBtn.addEventListener('click', () => {\n            init();\n            showToast('Calculator has been reset.');\n        });\n\n        function fallbackCopy(text) {\n            const textarea = document.createElement('textarea');\n            textarea.value = text;\n            textarea.style.position = 'fixed';\n            textarea.style.opacity = '0';\n            document.body.appendChild(textarea);\n            textarea.select();\n            try { document.execCommand('copy');\n                showToast('Report copied to clipboard!'); } catch (e) { showToast('Copy failed, please select text manually'); }\n            document.body.removeChild(textarea);\n        }\n\n        function generateReport() {\n            const matName = MATERIAL_DB[state.materialKey]?.name || 'Custom Material';\n            const densityDisp = getDensityDisplay().toFixed(2);\n            const densityU = getDensityUnitStr();\n            const volDisp = volumeFromSI(state.finalVolumeSI).toFixed(4);\n            const volU = getVolumeUnitStr();\n            const wtDisp = weightFromSI(state.finalWeightKG).toFixed(4);\n            const wtU = getWeightUnitStr();\n            const areaDisp = state.unitSystem === 'metric' ?\n                getTotalAreaSI().toFixed(4) + ' m\u00b2' :\n                areaFromSI(getTotalAreaSI(), 'ft2').toFixed(4) + ' ft\u00b2';\n            const depthDisp = state.unitSystem === 'metric' ?\n                (state.depthSI * 100).toFixed(2) + ' cm' :\n                (state.depthSI \/ 0.0254).toFixed(2) + ' in';\n            const price = state.unitPrice || 0;\n            const puLabels = {\n                'per-tonne': 'per metric ton',\n                'per-m3': 'per m\u00b3',\n                'per-yd3': 'per yd\u00b3',\n                'per-kg': 'per kg',\n                'per-lb': 'per lb'\n            };\n            let totalCost = 0;\n            const pu = state.priceUnit;\n            if (pu === 'per-tonne') totalCost = weightFromSI(state.finalWeightKG) * price;\n            else if (pu === 'per-m3') totalCost = volumeFromSI(state.finalVolumeSI) * price;\n            else if (pu === 'per-yd3') totalCost = (state.finalVolumeSI \/ VOLUME_TO_M3['yd3']) * price;\n            else if (pu === 'per-kg') totalCost = state.finalWeightKG * price;\n            else if (pu === 'per-lb') totalCost = (state.finalWeightKG \/ WEIGHT_TO_KG['lb']) * price;\n            let report = '';\n            report += '========================================\\n';\n            report += '   Gravel Calculator - Results Report\\n';\n            report += '========================================\\n\\n';\n            report += `Material: ${matName}\\n`;\n            report += `Density: ${densityDisp} ${densityU}\\n`;\n            report += `Total Area: ${areaDisp}\\n`;\n            report += `Depth: ${depthDisp}\\n`;\n            report += `Waste Rate: ${state.wasteRate}%\\n`;\n            report += `Compaction Rate: ${state.compactionRate}%\\n\\n`;\n            report += `Final Volume: ${volDisp} ${volU}\\n`;\n            report += `Final Weight: ${wtDisp} ${wtU}\\n\\n`;\n            report += `Unit Price: $${price.toFixed(2)} ${puLabels[pu] || pu}\\n`;\n            report += `Estimated Total Cost: $${totalCost.toFixed(2)}\\n`;\n            if (state.baggedMode && state.bagWeight > 0) {\n                let bagWKG = state.bagWeight;\n                if (state.bagWeightUnit === 'lb') bagWKG = state.bagWeight * WEIGHT_TO_KG['lb'];\n                const bags = Math.ceil(state.finalWeightKG \/ bagWKG);\n                report += `Bagged Quantity: ${bags} bags (${state.bagWeight} ${state.bagWeightUnit} each)\\n`;\n            }\n            const wtTonnes = state.finalWeightKG \/ 1000;\n            const pickups = Math.ceil(wtTonnes \/ 1);\n            const dumpTrucks = Math.ceil(wtTonnes \/ 15);\n            report += `Transport: about ${pickups} pickup trucks or ${dumpTrucks} large dump trucks\\n`;\n            report += '\\n========================================\\n';\n            report += 'Generated: ' + new Date().toLocaleString('en-US') + '\\n';\n            return report;\n        }\n\n        function init() {\n            state.unitSystem = 'metric';\n            state.materialKey = 'pea-gravel';\n            state.customDensitySI = MATERIAL_DB['pea-gravel'].densitySI;\n            state.mode = 'A';\n            state.shapeA = 'rectangle';\n            state.lengthSI = lengthToSI(5, 'm');\n            state.widthSI = lengthToSI(3, 'm');\n            state.diameterSI = lengthToSI(4, 'm');\n            state.depthSI = lengthToSI(10, 'cm');\n            state.areaSI_B = areaToSI(50, 'm2');\n            state.wasteRate = 10;\n            state.compactionRate = 5;\n            state.unitPrice = 120;\n            state.priceUnit = 'per-tonne';\n            state.baggedMode = false;\n            state.bagWeight = 20;\n            state.bagWeightUnit = 'kg';\n            state.multiAreas = [];\n            areaIdCounter = 0;\n            state.lastEditedField = null;\n            state.isReverseCalc = false;\n            state.focusedResultField = null;\n\n            updateUnitToggleUI();\n            updateAllUnitDropdowns();\n            updateDensityDisplay();\n            updateAllDisplayValues();\n            wasteRateSlider.value = state.wasteRate;\n            wasteRateValue.textContent = state.wasteRate + '%';\n            compactionRateSlider.value = state.compactionRate;\n            compactionRateValue.textContent = state.compactionRate + '%';\n            updateCompactionPresetStyles();\n            materialSelect.value = state.materialKey;\n            densityInput.value = getDensityDisplay().toFixed(2);\n            densityInput.readOnly = true;\n            unitPriceInput.value = state.unitPrice;\n            priceUnitSelect.value = state.priceUnit;\n            bagWeightInput.value = state.bagWeight;\n            bagWeightUnit.value = state.bagWeightUnit;\n            baggedDetails.classList.add('hidden');\n            baggedToggle.classList.add('bg-gray-300');\n            baggedToggle.classList.remove('bg-primary');\n            baggedToggleDot.classList.add('translate-x-0.5');\n            baggedToggleDot.classList.remove('translate-x-5');\n            baggedToggle.setAttribute('aria-checked', 'false');\n            reverseHint.style.display = 'none';\n            multiAreaEmpty.style.display = '';\n            multiAreaList.innerHTML = '';\n            panelA.classList.remove('hidden');\n            panelB.classList.add('hidden');\n            panelC.classList.add('hidden');\n            tabA.classList.add('active');\n            tabB.classList.remove('active');\n            tabC.classList.remove('active');\n            tabA.setAttribute('aria-selected', 'true');\n            tabB.setAttribute('aria-selected', 'false');\n            tabC.setAttribute('aria-selected', 'false');\n            rectInputs.classList.remove('hidden');\n            circleInputs.classList.add('hidden');\n            shapeRectBtn.classList.add('bg-primary', 'text-white', 'shadow-sm');\n            shapeRectBtn.classList.remove('bg-gray-200', 'text-text-subtle', 'hover:bg-gray-300');\n            shapeCircleBtn.classList.remove('bg-primary', 'text-white', 'shadow-sm');\n            shapeCircleBtn.classList.add('bg-gray-200', 'text-text-subtle', 'hover:bg-gray-300');\n            forwardCalculate();\n            updateTotalAreaDisplay();\n        }\n        init();\n    })();\n<\/script>\n\n\n<!-- aiper-tool-links -->\n<div class=\"aiper-tool-links\">\n  <div class=\"aiper-tl-section\">\n    <div class=\"aiper-tl-heading\">More Tools<\/div>\n    <div class=\"aiper-tl-tags\">\n      <a href=\"https:\/\/aiper.com\/blogs\/fence-calculator\/\" class=\"aiper-tl-tag\">Fence Calculator<\/a>\n<a href=\"https:\/\/aiper.com\/blogs\/paver-calculator\/\" class=\"aiper-tl-tag\">Paver Calculator<\/a>\n<a href=\"https:\/\/aiper.com\/blogs\/decking-calculator\/\" class=\"aiper-tl-tag\">Decking Calculator<\/a>\n    <\/div>\n  <\/div>\n  <div class=\"aiper-tl-section\">\n    <div class=\"aiper-tl-heading\">Related Articles<\/div>\n    <div class=\"aiper-tl-tags\">\n      <a href=\"https:\/\/aiper.com\/blogs\/how-to-keep-geese-off-lawn\/\" class=\"aiper-tl-tag\">How to Keep Geese Off Your Lawn<\/a>\n<a href=\"https:\/\/aiper.com\/blogs\/how-to-get-rid-of-mushrooms-in-lawn\/\" class=\"aiper-tl-tag\">How to Get Rid of Mushrooms in a Lawn (And Should You?)<\/a>\n<a href=\"https:\/\/aiper.com\/blogs\/how-to-dethatch-a-lawn\/\" class=\"aiper-tl-tag\">How to Dethatch a Lawn: Diagnose Thatch, Then Treat It<\/a>\n<a href=\"https:\/\/aiper.com\/blogs\/how-to-get-rid-of-creeping-charlie-in-lawn\/\" class=\"aiper-tl-tag\">How to Get Rid of Creeping Charlie in Your Lawn<\/a>\n    <\/div>\n  <\/div>\n<\/div>\n\n<style>\n.aiper-tool-links{margin:2.5rem 0 1.5rem;padding:1.5rem 1.75rem;background:linear-gradient(135deg,rgba(148,134,247,.04),rgba(30,202,211,.04));border-radius:12px;border:1px solid rgba(148,134,247,.10)}\n.aiper-tl-section{margin-bottom:1.25rem}\n.aiper-tl-section:last-child{margin-bottom:0}\n.aiper-tl-heading{font-size:.75rem;font-weight:600;letter-spacing:.09em;text-transform:uppercase;color:#8899aa;margin:0 0 .65rem}\n.aiper-tl-tags{display:flex;flex-wrap:wrap;gap:.5rem}\n.aiper-tl-tag{display:inline-block;padding:.4rem .95rem;font-size:.82rem;font-weight:500;line-height:1.35;color:#9486f7;background:rgba(148,134,247,.055);border:1px solid rgba(30,202,211,.25);border-radius:22px;text-decoration:none;transition:all .18s}\n.aiper-tl-tag:hover{background:#1ecad3;color:#fff;border-color:#1ecad3;box-shadow:0 2px 10px rgba(30,202,211,.22)}\n<\/style>","protected":false},"excerpt":{"rendered":"<p>Accurately calculate volume, weight, and cost \u2013 with multi-area support &#038; reverse calculation Material Pea GravelNominal Gravel (1\/4&#8243; &#8211; 2&#8243;)Coarse Crushed Stone (2&#8243; &#8211; 6&#8243;)Dry SandWet SandCustom Material Density (kg\/m\u00b3) Unit System &#127760; Metric By Dimensions Direct Area Multi-Area Combine Shape: Rectangle Circle Length mcmftinyd Width mcmftinyd Diameter mcmftinyd Depth \/ Thickness cmminft Tip: Thickness&#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-2546","page","type-page","status-publish","hentry"],"lang":"en","translations":{"en":2546,"es":2552,"de":2548,"fr":2550},"_links":{"self":[{"href":"https:\/\/aiper.com\/blogs\/wp-json\/wp\/v2\/pages\/2546","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=2546"}],"version-history":[{"count":7,"href":"https:\/\/aiper.com\/blogs\/wp-json\/wp\/v2\/pages\/2546\/revisions"}],"predecessor-version":[{"id":4303,"href":"https:\/\/aiper.com\/blogs\/wp-json\/wp\/v2\/pages\/2546\/revisions\/4303"}],"wp:attachment":[{"href":"https:\/\/aiper.com\/blogs\/wp-json\/wp\/v2\/media?parent=2546"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}