{
  "site": "EstimaHome",
  "url": "https://estimahome.com/",
  "version": "2.0.0",
  "lastUpdated": "2026-10-20",
  "publisher": {
    "name": "EstimaHome",
    "url": "https://estimahome.com/",
    "email": "hello@estimahome.com"
  },
  "citation": "“<calculator name>.” EstimaHome, <calculator URL>.",
  "notes": "ceil() rounds up to purchasable units. Price inputs default to /api/prices.json, which holds ballpark U.S. home-center prices, not quotes. Any input can be set with a URL query parameter on the calculator page, for example /deck-calculator/?lengthFt=20&widthFt=12.",
  "pricesUrl": "https://estimahome.com/api/prices.json",
  "guidesUrl": "https://estimahome.com/api/guides.json",
  "categories": [
    {
      "id": "outdoor",
      "name": "Decks, fences & hardscape",
      "calculators": [
        "deck",
        "fence",
        "concrete",
        "pavers",
        "retaining-wall"
      ]
    },
    {
      "id": "landscape",
      "name": "Landscaping",
      "calculators": [
        "mulch",
        "gravel",
        "topsoil"
      ]
    },
    {
      "id": "interior",
      "name": "Walls & ceilings",
      "calculators": [
        "drywall",
        "paint",
        "wallpaper",
        "insulation"
      ]
    },
    {
      "id": "floors",
      "name": "Floors",
      "calculators": [
        "flooring",
        "tile"
      ]
    },
    {
      "id": "roofing",
      "name": "Roofing",
      "calculators": [
        "roofing"
      ]
    }
  ],
  "calculators": [
    {
      "id": "deck",
      "name": "Deck Materials Calculator",
      "url": "https://estimahome.com/deck-calculator/",
      "category": "outdoor",
      "description": "Deck boards, joists, screws, posts and footing concrete for a rectangular deck.",
      "related": [
        "https://estimahome.com/concrete-calculator/",
        "https://estimahome.com/fence-calculator/",
        "https://estimahome.com/paver-calculator/"
      ],
      "guides": [
        "https://estimahome.com/guides/deck-cost-12x16/"
      ],
      "inputs": [
        {
          "name": "lengthFt",
          "label": "Deck length (parallel to house)",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 16,
          "min": 1,
          "max": 200,
          "help": "Decking runs this direction."
        },
        {
          "name": "widthFt",
          "label": "Deck depth (out from house)",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 12,
          "min": 1,
          "max": 100,
          "help": "Joists span this direction."
        },
        {
          "name": "boardWidthIn",
          "label": "Board width, actual",
          "unit": "in",
          "type": "number",
          "group": "options",
          "default": 5.5,
          "min": 1,
          "max": 12,
          "help": "A 5/4×6 or 2×6 is 5.5 in wide."
        },
        {
          "name": "gapIn",
          "label": "Gap between boards",
          "unit": "in",
          "type": "number",
          "group": "options",
          "default": 0.125,
          "min": 0,
          "max": 1
        },
        {
          "name": "boardLengthFt",
          "label": "Board length",
          "unit": "ft",
          "type": "select",
          "group": "options",
          "default": "16",
          "options": [
            "8",
            "10",
            "12",
            "16",
            "20"
          ]
        },
        {
          "name": "joistSpacingIn",
          "label": "Joist spacing, on center",
          "unit": "in",
          "type": "select",
          "group": "options",
          "default": "16",
          "options": [
            "12",
            "16",
            "24"
          ]
        },
        {
          "name": "wastePct",
          "label": "Decking waste",
          "unit": "%",
          "type": "number",
          "group": "options",
          "default": 10,
          "min": 0,
          "max": 50
        },
        {
          "name": "beamRows",
          "label": "Rows of footings",
          "unit": "rows",
          "type": "number",
          "group": "options",
          "default": 1,
          "min": 1,
          "max": 10,
          "help": "Usually 1 with a ledger on the house, 2 for a freestanding deck."
        },
        {
          "name": "postSpacingFt",
          "label": "Max post spacing along beam",
          "unit": "ft",
          "type": "number",
          "group": "options",
          "default": 8,
          "min": 2,
          "max": 16
        },
        {
          "name": "footingDiaIn",
          "label": "Footing diameter",
          "unit": "in",
          "type": "number",
          "group": "options",
          "default": 12,
          "min": 4,
          "max": 36
        },
        {
          "name": "footingDepthIn",
          "label": "Footing depth",
          "unit": "in",
          "type": "number",
          "group": "options",
          "default": 36,
          "min": 6,
          "max": 96,
          "help": "Go below your local frost line."
        },
        {
          "name": "bagYieldCuFt",
          "label": "Yield per 80 lb bag",
          "unit": "cu ft",
          "type": "number",
          "group": "options",
          "default": 0.6,
          "min": 0.1,
          "max": 2
        },
        {
          "name": "screwsPerBox",
          "label": "Screws per box",
          "unit": "ea",
          "type": "number",
          "group": "options",
          "default": 350,
          "min": 1,
          "max": 5000
        },
        {
          "name": "priceBoard",
          "label": "Deck board",
          "unit": "$/ea",
          "type": "number",
          "group": "prices",
          "default": 18,
          "min": 0,
          "priceKey": "deck_board"
        },
        {
          "name": "priceJoist",
          "label": "Joist",
          "unit": "$/ea",
          "type": "number",
          "group": "prices",
          "default": 22,
          "min": 0,
          "priceKey": "deck_joist"
        },
        {
          "name": "priceScrewBox",
          "label": "Box of deck screws",
          "unit": "$/box",
          "type": "number",
          "group": "prices",
          "default": 38,
          "min": 0,
          "priceKey": "deck_screw_box"
        },
        {
          "name": "pricePost",
          "label": "Post",
          "unit": "$/ea",
          "type": "number",
          "group": "prices",
          "default": 25,
          "min": 0,
          "priceKey": "deck_post"
        },
        {
          "name": "priceBag",
          "label": "80 lb concrete",
          "unit": "$/bag",
          "type": "number",
          "group": "prices",
          "default": 6.5,
          "min": 0,
          "priceKey": "concrete_bag_80"
        },
        {
          "name": "spreadPct",
          "label": "Estimate range (±)",
          "unit": "%",
          "type": "number",
          "group": "prices",
          "default": 15,
          "min": 0,
          "max": 100
        }
      ],
      "formulas": [
        {
          "output": "boardRows",
          "text": "Board rows = ⌈ depth × 12 ÷ (board width + gap) ⌉",
          "expr": "ceil(widthFt*12/(boardWidthIn+gapIn))"
        },
        {
          "output": "deckingLinearFt",
          "text": "Decking linear feet = board rows × deck length",
          "expr": "boardRows*lengthFt"
        },
        {
          "output": "boards",
          "text": "Boards = ⌈ linear feet × (1 + waste ÷ 100) ÷ board length ⌉",
          "expr": "ceil(deckingLinearFt*(1+wastePct/100)/boardLengthFt)"
        },
        {
          "output": "joists",
          "text": "Joists = ⌊ length × 12 ÷ joist spacing ⌋ + 1",
          "expr": "floor(lengthFt*12/joistSpacingIn)+1"
        },
        {
          "output": "screws",
          "text": "Screws = board rows × joists × 2",
          "expr": "boardRows*joists*2"
        },
        {
          "output": "screwBoxes",
          "text": "Screw boxes = ⌈ screws ÷ screws per box ⌉",
          "expr": "ceil(screws/screwsPerBox)"
        },
        {
          "output": "footings",
          "text": "Footings = footing rows × (⌈ length ÷ post spacing ⌉ + 1)",
          "expr": "beamRows*(ceil(lengthFt/postSpacingFt)+1)"
        },
        {
          "output": "concreteCuFt",
          "text": "Footing concrete (cu ft) = footings × π × (diameter ÷ 24)² × (depth ÷ 12)",
          "expr": "footings*PI*(footingDiaIn/24)^2*(footingDepthIn/12)"
        },
        {
          "output": "bags",
          "text": "Bags = ⌈ concrete ÷ bag yield ⌉",
          "expr": "ceil(concreteCuFt/bagYieldCuFt)"
        },
        {
          "output": "cost",
          "text": "Cost = Σ (quantity × unit price); range = cost × (1 ± range % ÷ 100)",
          "expr": "sum(qty*unitPrice)*(1±spreadPct/100)"
        }
      ],
      "defaultResult": {
        "values": {
          "areaSqFt": 192,
          "boardRows": 26,
          "deckingLinearFt": 416,
          "boards": 29,
          "joists": 13,
          "screws": 676,
          "screwBoxes": 2,
          "footings": 3,
          "concreteCuFt": 7.07,
          "bags": 12
        },
        "groups": [
          {
            "title": "Materials",
            "subtotal": 1037,
            "low": 881.45,
            "high": 1192.55,
            "items": [
              {
                "key": "deck_board",
                "label": "Deck boards, 16 ft",
                "qty": 29,
                "unit": "ea",
                "unitPrice": 18,
                "cost": 522
              },
              {
                "key": "deck_joist",
                "label": "Joists, 12 ft span",
                "qty": 13,
                "unit": "ea",
                "unitPrice": 22,
                "cost": 286
              },
              {
                "key": "deck_screw_box",
                "label": "Deck screws (676 pcs)",
                "qty": 2,
                "unit": "box",
                "unitPrice": 38,
                "cost": 76
              },
              {
                "key": "deck_post",
                "label": "Posts",
                "qty": 3,
                "unit": "ea",
                "unitPrice": 25,
                "cost": 75
              },
              {
                "key": "concrete_bag_80",
                "label": "Concrete, 80 lb bags",
                "qty": 12,
                "unit": "bag",
                "unitPrice": 6.5,
                "cost": 78
              }
            ]
          }
        ]
      }
    },
    {
      "id": "fence",
      "name": "Fence Materials Calculator",
      "url": "https://estimahome.com/fence-calculator/",
      "category": "outdoor",
      "description": "Posts, rails, pickets and post-hole concrete for a straight wood fence.",
      "related": [
        "https://estimahome.com/concrete-calculator/",
        "https://estimahome.com/deck-calculator/",
        "https://estimahome.com/retaining-wall-calculator/"
      ],
      "guides": [
        "https://estimahome.com/guides/privacy-fence-cost-100-ft/"
      ],
      "inputs": [
        {
          "name": "lengthFt",
          "label": "Fence length",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 100,
          "min": 1,
          "max": 5000
        },
        {
          "name": "heightFt",
          "label": "Fence height",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 6,
          "min": 2,
          "max": 10
        },
        {
          "name": "postSpacingFt",
          "label": "Post spacing, on center",
          "unit": "ft",
          "type": "number",
          "group": "options",
          "default": 8,
          "min": 2,
          "max": 12
        },
        {
          "name": "railsPerSection",
          "label": "Rails per section",
          "unit": "ea",
          "type": "select",
          "group": "options",
          "default": "3",
          "options": [
            "2",
            "3",
            "4"
          ]
        },
        {
          "name": "picketWidthIn",
          "label": "Picket width, actual",
          "unit": "in",
          "type": "number",
          "group": "options",
          "default": 5.5,
          "min": 1,
          "max": 12
        },
        {
          "name": "picketGapIn",
          "label": "Gap between pickets",
          "unit": "in",
          "type": "number",
          "group": "options",
          "default": 0,
          "min": 0,
          "max": 12,
          "help": "0 for a privacy fence."
        },
        {
          "name": "holeDiaIn",
          "label": "Post hole diameter",
          "unit": "in",
          "type": "number",
          "group": "options",
          "default": 10,
          "min": 4,
          "max": 24
        },
        {
          "name": "holeDepthIn",
          "label": "Post hole depth",
          "unit": "in",
          "type": "number",
          "group": "options",
          "default": 24,
          "min": 6,
          "max": 72
        },
        {
          "name": "postSizeIn",
          "label": "Post width, actual",
          "unit": "in",
          "type": "number",
          "group": "options",
          "default": 3.5,
          "min": 1,
          "max": 8,
          "help": "A 4×4 is 3.5 in square."
        },
        {
          "name": "bagYieldCuFt",
          "label": "Yield per 80 lb bag",
          "unit": "cu ft",
          "type": "number",
          "group": "options",
          "default": 0.6,
          "min": 0.1,
          "max": 2
        },
        {
          "name": "wastePct",
          "label": "Picket waste",
          "unit": "%",
          "type": "number",
          "group": "options",
          "default": 5,
          "min": 0,
          "max": 50
        },
        {
          "name": "pricePost",
          "label": "Post",
          "unit": "$/ea",
          "type": "number",
          "group": "prices",
          "default": 16,
          "min": 0,
          "priceKey": "fence_post"
        },
        {
          "name": "priceRail",
          "label": "Rail",
          "unit": "$/ea",
          "type": "number",
          "group": "prices",
          "default": 6,
          "min": 0,
          "priceKey": "fence_rail"
        },
        {
          "name": "pricePicket",
          "label": "Picket",
          "unit": "$/ea",
          "type": "number",
          "group": "prices",
          "default": 3.5,
          "min": 0,
          "priceKey": "fence_picket"
        },
        {
          "name": "priceBag",
          "label": "80 lb concrete",
          "unit": "$/bag",
          "type": "number",
          "group": "prices",
          "default": 6.5,
          "min": 0,
          "priceKey": "concrete_bag_80"
        },
        {
          "name": "spreadPct",
          "label": "Estimate range (±)",
          "unit": "%",
          "type": "number",
          "group": "prices",
          "default": 15,
          "min": 0,
          "max": 100
        }
      ],
      "formulas": [
        {
          "output": "sections",
          "text": "Sections = ⌈ fence length ÷ post spacing ⌉",
          "expr": "ceil(lengthFt/postSpacingFt)"
        },
        {
          "output": "posts",
          "text": "Posts = sections + 1",
          "expr": "sections+1"
        },
        {
          "output": "rails",
          "text": "Rails = sections × rails per section",
          "expr": "sections*railsPerSection"
        },
        {
          "output": "pickets",
          "text": "Pickets = ⌈ length × 12 ÷ (picket width + gap) × (1 + waste ÷ 100) ⌉",
          "expr": "ceil(lengthFt*12/(picketWidthIn+picketGapIn)*(1+wastePct/100))"
        },
        {
          "output": "concretePerHoleCuFt",
          "text": "Concrete per hole (cu ft) = π × (hole dia ÷ 24)² × (depth ÷ 12) − (post width ÷ 12)² × (depth ÷ 12)",
          "expr": "PI*(holeDiaIn/24)^2*(holeDepthIn/12)-(postSizeIn/12)^2*(holeDepthIn/12)"
        },
        {
          "output": "bags",
          "text": "Bags = ⌈ posts × concrete per hole ÷ bag yield ⌉",
          "expr": "ceil(posts*concretePerHoleCuFt/bagYieldCuFt)"
        },
        {
          "output": "postLengthFt",
          "text": "Minimum post length = fence height + hole depth ÷ 12",
          "expr": "heightFt+holeDepthIn/12"
        },
        {
          "output": "cost",
          "text": "Cost = Σ (quantity × unit price); range = cost × (1 ± range % ÷ 100)",
          "expr": "sum(qty*unitPrice)*(1±spreadPct/100)"
        }
      ],
      "defaultResult": {
        "values": {
          "sections": 13,
          "posts": 14,
          "rails": 39,
          "pickets": 230,
          "concretePerHoleCuFt": 0.921,
          "concreteCuFt": 12.89,
          "bags": 22,
          "postLengthFt": 8
        },
        "groups": [
          {
            "title": "Materials",
            "subtotal": 1406,
            "low": 1195.1,
            "high": 1616.9,
            "items": [
              {
                "key": "fence_post",
                "label": "Posts, 8 ft (min. 8 ft)",
                "qty": 14,
                "unit": "ea",
                "unitPrice": 16,
                "cost": 224
              },
              {
                "key": "fence_rail",
                "label": "Rails, 8 ft",
                "qty": 39,
                "unit": "ea",
                "unitPrice": 6,
                "cost": 234
              },
              {
                "key": "fence_picket",
                "label": "Pickets, 6 ft",
                "qty": 230,
                "unit": "ea",
                "unitPrice": 3.5,
                "cost": 805
              },
              {
                "key": "concrete_bag_80",
                "label": "Concrete, 80 lb bags",
                "qty": 22,
                "unit": "bag",
                "unitPrice": 6.5,
                "cost": 143
              }
            ]
          }
        ]
      }
    },
    {
      "id": "concrete",
      "name": "Concrete Calculator",
      "url": "https://estimahome.com/concrete-calculator/",
      "category": "outdoor",
      "description": "Cubic yards and 60 lb or 80 lb bag counts for a slab or round footings.",
      "related": [
        "https://estimahome.com/deck-calculator/",
        "https://estimahome.com/fence-calculator/",
        "https://estimahome.com/paver-calculator/"
      ],
      "guides": [
        "https://estimahome.com/guides/concrete-10x10-slab-4-inch/"
      ],
      "inputs": [
        {
          "name": "shape",
          "label": "Pour",
          "unit": "",
          "type": "select",
          "group": "size",
          "default": "slab",
          "options": [
            "slab",
            "footings"
          ]
        },
        {
          "name": "lengthFt",
          "label": "Slab length",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 10,
          "min": 0.5,
          "max": 500,
          "showWhen": {
            "shape": "slab"
          }
        },
        {
          "name": "widthFt",
          "label": "Slab width",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 10,
          "min": 0.5,
          "max": 500,
          "showWhen": {
            "shape": "slab"
          }
        },
        {
          "name": "thicknessIn",
          "label": "Slab thickness",
          "unit": "in",
          "type": "number",
          "group": "size",
          "default": 4,
          "min": 1,
          "max": 24,
          "showWhen": {
            "shape": "slab"
          }
        },
        {
          "name": "count",
          "label": "Number of footings",
          "unit": "ea",
          "type": "number",
          "group": "size",
          "default": 6,
          "min": 1,
          "max": 500,
          "showWhen": {
            "shape": "footings"
          }
        },
        {
          "name": "diameterIn",
          "label": "Footing diameter",
          "unit": "in",
          "type": "number",
          "group": "size",
          "default": 12,
          "min": 2,
          "max": 60,
          "showWhen": {
            "shape": "footings"
          }
        },
        {
          "name": "depthIn",
          "label": "Footing depth",
          "unit": "in",
          "type": "number",
          "group": "size",
          "default": 36,
          "min": 2,
          "max": 120,
          "showWhen": {
            "shape": "footings"
          }
        },
        {
          "name": "wastePct",
          "label": "Extra for spillage and grade",
          "unit": "%",
          "type": "number",
          "group": "options",
          "default": 10,
          "min": 0,
          "max": 50
        },
        {
          "name": "yield80",
          "label": "Yield per 80 lb bag",
          "unit": "cu ft",
          "type": "number",
          "group": "options",
          "default": 0.6,
          "min": 0.1,
          "max": 2,
          "help": "Printed on the bag."
        },
        {
          "name": "yield60",
          "label": "Yield per 60 lb bag",
          "unit": "cu ft",
          "type": "number",
          "group": "options",
          "default": 0.45,
          "min": 0.1,
          "max": 2
        },
        {
          "name": "price80",
          "label": "80 lb bag",
          "unit": "$/bag",
          "type": "number",
          "group": "prices",
          "default": 6.5,
          "min": 0,
          "priceKey": "concrete_bag_80"
        },
        {
          "name": "price60",
          "label": "60 lb bag",
          "unit": "$/bag",
          "type": "number",
          "group": "prices",
          "default": 5.25,
          "min": 0,
          "priceKey": "concrete_bag_60"
        },
        {
          "name": "priceYard",
          "label": "Ready-mix",
          "unit": "$/cu yd",
          "type": "number",
          "group": "prices",
          "default": 175,
          "min": 0,
          "priceKey": "ready_mix_yard"
        },
        {
          "name": "spreadPct",
          "label": "Estimate range (±)",
          "unit": "%",
          "type": "number",
          "group": "prices",
          "default": 15,
          "min": 0,
          "max": 100
        }
      ],
      "formulas": [
        {
          "output": "volumeCuFt",
          "text": "Slab (cu ft) = length × width × thickness ÷ 12",
          "expr": "lengthFt*widthFt*thicknessIn/12"
        },
        {
          "output": "volumeCuFt",
          "text": "Footings (cu ft) = count × π × (diameter ÷ 24)² × (depth ÷ 12)",
          "expr": "count*PI*(diameterIn/24)^2*(depthIn/12)"
        },
        {
          "output": "cubicYards",
          "text": "Cubic yards = cubic feet ÷ 27",
          "expr": "volumeCuFt/27"
        },
        {
          "output": "orderCuFt",
          "text": "Order volume = volume × (1 + extra ÷ 100)",
          "expr": "volumeCuFt*(1+wastePct/100)"
        },
        {
          "output": "bags80",
          "text": "80 lb bags = ⌈ order volume ÷ 80 lb yield ⌉",
          "expr": "ceil(orderCuFt/yield80)"
        },
        {
          "output": "bags60",
          "text": "60 lb bags = ⌈ order volume ÷ 60 lb yield ⌉",
          "expr": "ceil(orderCuFt/yield60)"
        },
        {
          "output": "readyMixYards",
          "text": "Ready-mix order = order yards rounded up to the next ¼ yd",
          "expr": "ceil(orderCuFt/27*4)/4"
        },
        {
          "output": "cost",
          "text": "Cost per option = quantity × unit price; range = cost × (1 ± range % ÷ 100)",
          "expr": "qty*unitPrice*(1±spreadPct/100)"
        }
      ],
      "defaultResult": {
        "values": {
          "volumeCuFt": 33.33,
          "cubicYards": 1.23,
          "orderCuFt": 36.67,
          "orderYards": 1.36,
          "bags80": 62,
          "bags60": 82,
          "readyMixYards": 1.5
        },
        "groups": [
          {
            "title": "Option A: 80 lb bags",
            "subtotal": 403,
            "low": 342.55,
            "high": 463.45,
            "items": [
              {
                "key": "concrete_bag_80",
                "label": "Concrete mix, 80 lb",
                "qty": 62,
                "unit": "bag",
                "unitPrice": 6.5,
                "cost": 403
              }
            ]
          },
          {
            "title": "Option B: 60 lb bags",
            "subtotal": 430.5,
            "low": 365.93,
            "high": 495.08,
            "items": [
              {
                "key": "concrete_bag_60",
                "label": "Concrete mix, 60 lb",
                "qty": 82,
                "unit": "bag",
                "unitPrice": 5.25,
                "cost": 430.5
              }
            ]
          },
          {
            "title": "Option C: ready-mix truck",
            "subtotal": 262.5,
            "low": 223.13,
            "high": 301.88,
            "items": [
              {
                "key": "ready_mix_yard",
                "label": "Ready-mix concrete",
                "qty": 1.5,
                "unit": "cu yd",
                "unitPrice": 175,
                "cost": 262.5
              }
            ]
          }
        ]
      }
    },
    {
      "id": "pavers",
      "name": "Paver Patio Calculator",
      "url": "https://estimahome.com/paver-calculator/",
      "category": "outdoor",
      "description": "Pavers, compacted gravel base and bedding sand for a patio or walkway.",
      "related": [
        "https://estimahome.com/retaining-wall-calculator/",
        "https://estimahome.com/gravel-calculator/",
        "https://estimahome.com/concrete-calculator/"
      ],
      "guides": [
        "https://estimahome.com/guides/paver-patio-cost-12x12/"
      ],
      "inputs": [
        {
          "name": "lengthFt",
          "label": "Patio length",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 12,
          "min": 1,
          "max": 500
        },
        {
          "name": "widthFt",
          "label": "Patio width",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 12,
          "min": 1,
          "max": 500
        },
        {
          "name": "paverLIn",
          "label": "Paver length",
          "unit": "in",
          "type": "number",
          "group": "size",
          "default": 8,
          "min": 2,
          "max": 48
        },
        {
          "name": "paverWIn",
          "label": "Paver width",
          "unit": "in",
          "type": "number",
          "group": "size",
          "default": 4,
          "min": 2,
          "max": 48
        },
        {
          "name": "wastePct",
          "label": "Paver waste",
          "unit": "%",
          "type": "number",
          "group": "options",
          "default": 5,
          "min": 0,
          "max": 30,
          "help": "5% for straight edges; 10% or more with curves."
        },
        {
          "name": "baseDepthIn",
          "label": "Gravel base depth",
          "unit": "in",
          "type": "number",
          "group": "options",
          "default": 4,
          "min": 2,
          "max": 12,
          "help": "Patios commonly 4–6 in; driveways deeper."
        },
        {
          "name": "sandDepthIn",
          "label": "Bedding sand depth",
          "unit": "in",
          "type": "number",
          "group": "options",
          "default": 1,
          "min": 0.5,
          "max": 2
        },
        {
          "name": "compactPct",
          "label": "Extra for compaction",
          "unit": "%",
          "type": "number",
          "group": "options",
          "default": 15,
          "min": 0,
          "max": 40
        },
        {
          "name": "sandBagCuFt",
          "label": "Sand bag size",
          "unit": "cu ft",
          "type": "number",
          "group": "options",
          "default": 0.5,
          "min": 0.1,
          "max": 2
        },
        {
          "name": "pricePaver",
          "label": "Paver",
          "unit": "$/ea",
          "type": "number",
          "group": "prices",
          "default": 0.75,
          "min": 0,
          "priceKey": "paver"
        },
        {
          "name": "priceGravel",
          "label": "Gravel base, bulk",
          "unit": "$/cu yd",
          "type": "number",
          "group": "prices",
          "default": 55,
          "min": 0,
          "priceKey": "gravel_yard"
        },
        {
          "name": "priceSand",
          "label": "Paver sand",
          "unit": "$/bag",
          "type": "number",
          "group": "prices",
          "default": 6,
          "min": 0,
          "priceKey": "paver_sand"
        },
        {
          "name": "spreadPct",
          "label": "Estimate range (±)",
          "unit": "%",
          "type": "number",
          "group": "prices",
          "default": 15,
          "min": 0,
          "max": 100
        }
      ],
      "formulas": [
        {
          "output": "areaSqFt",
          "text": "Area = length × width",
          "expr": "lengthFt*widthFt"
        },
        {
          "output": "pavers",
          "text": "Pavers = ⌈ area × 144 ÷ (paver L × paver W) × (1 + waste ÷ 100) ⌉",
          "expr": "ceil(areaSqFt*144/(paverLIn*paverWIn)*(1+wastePct/100))"
        },
        {
          "output": "baseYd",
          "text": "Base (cu yd) = area × base depth ÷ 12 × (1 + compaction ÷ 100) ÷ 27, ordered to the next ½ yd",
          "expr": "ceil(areaSqFt*baseDepthIn/12*(1+compactPct/100)/27*2)/2"
        },
        {
          "output": "sandBags",
          "text": "Sand bags = ⌈ area × sand depth ÷ 12 ÷ bag size ⌉",
          "expr": "ceil(areaSqFt*sandDepthIn/12/sandBagCuFt)"
        },
        {
          "output": "cost",
          "text": "Cost = Σ (quantity × unit price); range = cost × (1 ± range % ÷ 100)",
          "expr": "sum(qty*unitPrice)*(1±spreadPct/100)"
        }
      ],
      "defaultResult": {
        "values": {
          "areaSqFt": 144,
          "pavers": 681,
          "baseCuYd": 2.044,
          "baseYd": 2.5,
          "sandCuFt": 12,
          "sandBags": 24
        },
        "groups": [
          {
            "title": "Materials",
            "subtotal": 792.25,
            "low": 673.41,
            "high": 911.09,
            "items": [
              {
                "key": "paver",
                "label": "Pavers, 8 × 4 in",
                "qty": 681,
                "unit": "ea",
                "unitPrice": 0.75,
                "cost": 510.75
              },
              {
                "key": "gravel_yard",
                "label": "Crushed stone base",
                "qty": 2.5,
                "unit": "cu yd",
                "unitPrice": 55,
                "cost": 137.5
              },
              {
                "key": "paver_sand",
                "label": "Paver sand, 0.5 cu ft",
                "qty": 24,
                "unit": "bag",
                "unitPrice": 6,
                "cost": 144
              }
            ]
          }
        ]
      }
    },
    {
      "id": "retaining-wall",
      "name": "Retaining Wall Calculator",
      "url": "https://estimahome.com/retaining-wall-calculator/",
      "category": "outdoor",
      "description": "Wall blocks, cap blocks, base gravel and drainage stone for a segmental block wall.",
      "related": [
        "https://estimahome.com/paver-calculator/",
        "https://estimahome.com/gravel-calculator/",
        "https://estimahome.com/fence-calculator/"
      ],
      "guides": [
        "https://estimahome.com/guides/retaining-wall-blocks-30-ft/"
      ],
      "inputs": [
        {
          "name": "lengthFt",
          "label": "Wall length",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 30,
          "min": 1,
          "max": 1000
        },
        {
          "name": "heightFt",
          "label": "Wall height, incl. buried course",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 2,
          "min": 0.5,
          "max": 4,
          "help": "Walls over about 3–4 ft usually need an engineer."
        },
        {
          "name": "blockLIn",
          "label": "Block face length",
          "unit": "in",
          "type": "number",
          "group": "options",
          "default": 16,
          "min": 4,
          "max": 36
        },
        {
          "name": "blockHIn",
          "label": "Block height",
          "unit": "in",
          "type": "number",
          "group": "options",
          "default": 6,
          "min": 2,
          "max": 12
        },
        {
          "name": "blockDIn",
          "label": "Block depth",
          "unit": "in",
          "type": "number",
          "group": "options",
          "default": 12,
          "min": 4,
          "max": 24
        },
        {
          "name": "capLIn",
          "label": "Cap length",
          "unit": "in",
          "type": "number",
          "group": "options",
          "default": 16,
          "min": 4,
          "max": 36
        },
        {
          "name": "baseDepthIn",
          "label": "Base gravel depth",
          "unit": "in",
          "type": "number",
          "group": "options",
          "default": 6,
          "min": 2,
          "max": 12
        },
        {
          "name": "trenchExtraIn",
          "label": "Trench width beyond block depth",
          "unit": "in",
          "type": "number",
          "group": "options",
          "default": 12,
          "min": 0,
          "max": 24
        },
        {
          "name": "drainWidthIn",
          "label": "Drainage stone behind wall",
          "unit": "in",
          "type": "number",
          "group": "options",
          "default": 12,
          "min": 0,
          "max": 24
        },
        {
          "name": "compactPct",
          "label": "Extra for compaction",
          "unit": "%",
          "type": "number",
          "group": "options",
          "default": 15,
          "min": 0,
          "max": 40
        },
        {
          "name": "wastePct",
          "label": "Block waste",
          "unit": "%",
          "type": "number",
          "group": "options",
          "default": 5,
          "min": 0,
          "max": 30
        },
        {
          "name": "priceBlock",
          "label": "Wall block",
          "unit": "$/ea",
          "type": "number",
          "group": "prices",
          "default": 6,
          "min": 0,
          "priceKey": "wall_block"
        },
        {
          "name": "priceCap",
          "label": "Cap block",
          "unit": "$/ea",
          "type": "number",
          "group": "prices",
          "default": 5,
          "min": 0,
          "priceKey": "wall_cap"
        },
        {
          "name": "priceGravel",
          "label": "Gravel, bulk",
          "unit": "$/cu yd",
          "type": "number",
          "group": "prices",
          "default": 55,
          "min": 0,
          "priceKey": "gravel_yard"
        },
        {
          "name": "spreadPct",
          "label": "Estimate range (±)",
          "unit": "%",
          "type": "number",
          "group": "prices",
          "default": 15,
          "min": 0,
          "max": 100
        }
      ],
      "formulas": [
        {
          "output": "perCourse",
          "text": "Blocks per course = ⌈ length × 12 ÷ block length ⌉",
          "expr": "ceil(lengthFt*12/blockLIn)"
        },
        {
          "output": "courses",
          "text": "Courses = ⌈ height × 12 ÷ block height ⌉",
          "expr": "ceil(heightFt*12/blockHIn)"
        },
        {
          "output": "blocks",
          "text": "Blocks = ⌈ blocks per course × courses × (1 + waste ÷ 100) ⌉",
          "expr": "ceil(perCourse*courses*(1+wastePct/100))"
        },
        {
          "output": "caps",
          "text": "Caps = ⌈ length × 12 ÷ cap length ⌉",
          "expr": "ceil(lengthFt*12/capLIn)"
        },
        {
          "output": "baseYd",
          "text": "Base gravel (cu yd) = length × (block depth + trench extra) ÷ 12 × base depth ÷ 12 × (1 + compaction ÷ 100) ÷ 27",
          "expr": "lengthFt*(blockDIn+trenchExtraIn)/12*baseDepthIn/12*(1+compactPct/100)/27"
        },
        {
          "output": "drainYd",
          "text": "Drainage stone (cu yd) = length × drain width ÷ 12 × height ÷ 27",
          "expr": "lengthFt*drainWidthIn/12*heightFt/27"
        },
        {
          "output": "gravelOrderYd",
          "text": "Gravel order = base + drainage, rounded up to ½ yd",
          "expr": "ceil((baseYd+drainYd)*2)/2"
        },
        {
          "output": "cost",
          "text": "Cost = Σ (quantity × unit price); range = cost × (1 ± range % ÷ 100)",
          "expr": "sum(qty*unitPrice)*(1±spreadPct/100)"
        }
      ],
      "defaultResult": {
        "values": {
          "perCourse": 23,
          "courses": 4,
          "blocks": 97,
          "caps": 23,
          "baseYd": 1.278,
          "drainYd": 2.222,
          "gravelOrderYd": 3.5
        },
        "groups": [
          {
            "title": "Materials",
            "subtotal": 889.5,
            "low": 756.08,
            "high": 1022.93,
            "items": [
              {
                "key": "wall_block",
                "label": "Wall block, 16 × 6 × 12 in",
                "qty": 97,
                "unit": "ea",
                "unitPrice": 6,
                "cost": 582
              },
              {
                "key": "wall_cap",
                "label": "Cap block",
                "qty": 23,
                "unit": "ea",
                "unitPrice": 5,
                "cost": 115
              },
              {
                "key": "gravel_yard",
                "label": "Crushed stone, base and drainage",
                "qty": 3.5,
                "unit": "cu yd",
                "unitPrice": 55,
                "cost": 192.5
              }
            ]
          }
        ]
      }
    },
    {
      "id": "mulch",
      "name": "Mulch Calculator",
      "url": "https://estimahome.com/mulch-calculator/",
      "category": "landscape",
      "description": "Cubic yards of mulch for a bed, and how many 2 cu ft bags that is versus bulk.",
      "related": [
        "https://estimahome.com/topsoil-calculator/",
        "https://estimahome.com/gravel-calculator/"
      ],
      "guides": [
        "https://estimahome.com/guides/how-many-bags-of-mulch/"
      ],
      "inputs": [
        {
          "name": "lengthFt",
          "label": "Bed or area length",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 20,
          "min": 0.5,
          "max": 1000
        },
        {
          "name": "widthFt",
          "label": "Bed or area width",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 10,
          "min": 0.5,
          "max": 1000
        },
        {
          "name": "depthIn",
          "label": "Depth",
          "unit": "in",
          "type": "number",
          "group": "size",
          "default": 3,
          "min": 0.5,
          "max": 36
        },
        {
          "name": "extraPct",
          "label": "Extra for settling",
          "unit": "%",
          "type": "number",
          "group": "options",
          "default": 0,
          "min": 0,
          "max": 50
        },
        {
          "name": "bagCuFt",
          "label": "Bag size",
          "unit": "cu ft",
          "type": "number",
          "group": "options",
          "default": 2,
          "min": 0.1,
          "max": 5,
          "help": "Most bagged mulch is 2 cu ft."
        },
        {
          "name": "tonsPerYd",
          "label": "Weight per cubic yard",
          "unit": "tons",
          "type": "number",
          "group": "options",
          "default": 0.4,
          "min": 0.1,
          "max": 3,
          "help": "Ask your supplier; moisture changes it."
        },
        {
          "name": "bulkIncrementYd",
          "label": "Bulk sold in increments of",
          "unit": "cu yd",
          "type": "select",
          "group": "options",
          "default": "0.5",
          "options": [
            "0.25",
            "0.5",
            "1"
          ]
        },
        {
          "name": "priceBag",
          "label": "Bag",
          "unit": "$/bag",
          "type": "number",
          "group": "prices",
          "default": 4.5,
          "min": 0,
          "priceKey": "mulch_bag"
        },
        {
          "name": "priceYard",
          "label": "Bulk",
          "unit": "$/cu yd",
          "type": "number",
          "group": "prices",
          "default": 40,
          "min": 0,
          "priceKey": "mulch_yard"
        },
        {
          "name": "priceDelivery",
          "label": "Bulk delivery",
          "unit": "$",
          "type": "number",
          "group": "prices",
          "default": 75,
          "min": 0,
          "priceKey": "bulk_delivery"
        },
        {
          "name": "spreadPct",
          "label": "Estimate range (±)",
          "unit": "%",
          "type": "number",
          "group": "prices",
          "default": 15,
          "min": 0,
          "max": 100
        }
      ],
      "formulas": [
        {
          "output": "cuFt",
          "text": "Volume (cu ft) = length × width × depth ÷ 12 × (1 + extra ÷ 100)",
          "expr": "lengthFt*widthFt*depthIn/12*(1+extraPct/100)"
        },
        {
          "output": "cuYd",
          "text": "Cubic yards = cubic feet ÷ 27",
          "expr": "cuFt/27"
        },
        {
          "output": "bags",
          "text": "Bags = ⌈ cubic feet ÷ bag size ⌉",
          "expr": "ceil(cuFt/bagCuFt)"
        },
        {
          "output": "bulkYd",
          "text": "Bulk order = cubic yards rounded up to the sale increment",
          "expr": "ceil(cuYd/bulkIncrementYd)*bulkIncrementYd"
        },
        {
          "output": "tons",
          "text": "Weight (tons) = cubic yards × tons per cubic yard",
          "expr": "cuYd*tonsPerYd"
        },
        {
          "output": "cost",
          "text": "Bag cost = bags × bag price; bulk cost = bulk yards × price per yard + delivery; range = cost × (1 ± range % ÷ 100)",
          "expr": "qty*unitPrice*(1±spreadPct/100)"
        }
      ],
      "defaultResult": {
        "values": {
          "areaSqFt": 200,
          "cuFt": 50,
          "cuYd": 1.85,
          "bags": 25,
          "bulkYd": 2,
          "tons": 0.74
        },
        "groups": [
          {
            "title": "Option A: bagged",
            "subtotal": 112.5,
            "low": 95.63,
            "high": 129.38,
            "items": [
              {
                "key": "mulch_bag",
                "label": "Mulch, 2 cu ft bags",
                "qty": 25,
                "unit": "bag",
                "unitPrice": 4.5,
                "cost": 112.5
              }
            ]
          },
          {
            "title": "Option B: bulk delivery",
            "subtotal": 155,
            "low": 131.75,
            "high": 178.25,
            "items": [
              {
                "key": "mulch_yard",
                "label": "Mulch, bulk",
                "qty": 2,
                "unit": "cu yd",
                "unitPrice": 40,
                "cost": 80
              },
              {
                "key": "bulk_delivery",
                "label": "Delivery",
                "qty": 1,
                "unit": "trip",
                "unitPrice": 75,
                "cost": 75
              }
            ]
          }
        ]
      }
    },
    {
      "id": "gravel",
      "name": "Gravel Calculator",
      "url": "https://estimahome.com/gravel-calculator/",
      "category": "landscape",
      "description": "Cubic yards and tons of gravel or crushed stone for a path, pad or driveway.",
      "related": [
        "https://estimahome.com/paver-calculator/",
        "https://estimahome.com/retaining-wall-calculator/",
        "https://estimahome.com/mulch-calculator/"
      ],
      "guides": [
        "https://estimahome.com/guides/gravel-driveway-50-ft/"
      ],
      "inputs": [
        {
          "name": "lengthFt",
          "label": "Bed or area length",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 20,
          "min": 0.5,
          "max": 1000
        },
        {
          "name": "widthFt",
          "label": "Bed or area width",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 10,
          "min": 0.5,
          "max": 1000
        },
        {
          "name": "depthIn",
          "label": "Depth",
          "unit": "in",
          "type": "number",
          "group": "size",
          "default": 4,
          "min": 0.5,
          "max": 36
        },
        {
          "name": "extraPct",
          "label": "Extra for settling",
          "unit": "%",
          "type": "number",
          "group": "options",
          "default": 0,
          "min": 0,
          "max": 50
        },
        {
          "name": "bagCuFt",
          "label": "Bag size",
          "unit": "cu ft",
          "type": "number",
          "group": "options",
          "default": 0.5,
          "min": 0.1,
          "max": 5,
          "help": "Bagged gravel is often 0.5 cu ft (about 50 lb)."
        },
        {
          "name": "tonsPerYd",
          "label": "Weight per cubic yard",
          "unit": "tons",
          "type": "number",
          "group": "options",
          "default": 1.4,
          "min": 0.1,
          "max": 3,
          "help": "Ask your supplier; moisture changes it."
        },
        {
          "name": "bulkIncrementYd",
          "label": "Bulk sold in increments of",
          "unit": "cu yd",
          "type": "select",
          "group": "options",
          "default": "0.5",
          "options": [
            "0.25",
            "0.5",
            "1"
          ]
        },
        {
          "name": "priceBag",
          "label": "Bag",
          "unit": "$/bag",
          "type": "number",
          "group": "prices",
          "default": 6,
          "min": 0,
          "priceKey": "gravel_bag"
        },
        {
          "name": "priceYard",
          "label": "Bulk",
          "unit": "$/cu yd",
          "type": "number",
          "group": "prices",
          "default": 55,
          "min": 0,
          "priceKey": "gravel_yard"
        },
        {
          "name": "priceDelivery",
          "label": "Bulk delivery",
          "unit": "$",
          "type": "number",
          "group": "prices",
          "default": 75,
          "min": 0,
          "priceKey": "bulk_delivery"
        },
        {
          "name": "spreadPct",
          "label": "Estimate range (±)",
          "unit": "%",
          "type": "number",
          "group": "prices",
          "default": 15,
          "min": 0,
          "max": 100
        }
      ],
      "formulas": [
        {
          "output": "cuFt",
          "text": "Volume (cu ft) = length × width × depth ÷ 12 × (1 + extra ÷ 100)",
          "expr": "lengthFt*widthFt*depthIn/12*(1+extraPct/100)"
        },
        {
          "output": "cuYd",
          "text": "Cubic yards = cubic feet ÷ 27",
          "expr": "cuFt/27"
        },
        {
          "output": "bags",
          "text": "Bags = ⌈ cubic feet ÷ bag size ⌉",
          "expr": "ceil(cuFt/bagCuFt)"
        },
        {
          "output": "bulkYd",
          "text": "Bulk order = cubic yards rounded up to the sale increment",
          "expr": "ceil(cuYd/bulkIncrementYd)*bulkIncrementYd"
        },
        {
          "output": "tons",
          "text": "Weight (tons) = cubic yards × tons per cubic yard",
          "expr": "cuYd*tonsPerYd"
        },
        {
          "output": "cost",
          "text": "Bag cost = bags × bag price; bulk cost = bulk yards × price per yard + delivery; range = cost × (1 ± range % ÷ 100)",
          "expr": "qty*unitPrice*(1±spreadPct/100)"
        }
      ],
      "defaultResult": {
        "values": {
          "areaSqFt": 200,
          "cuFt": 66.67,
          "cuYd": 2.47,
          "bags": 134,
          "bulkYd": 2.5,
          "tons": 3.46
        },
        "groups": [
          {
            "title": "Option A: bagged",
            "subtotal": 804,
            "low": 683.4,
            "high": 924.6,
            "items": [
              {
                "key": "gravel_bag",
                "label": "Gravel, 0.5 cu ft bags",
                "qty": 134,
                "unit": "bag",
                "unitPrice": 6,
                "cost": 804
              }
            ]
          },
          {
            "title": "Option B: bulk delivery",
            "subtotal": 212.5,
            "low": 180.63,
            "high": 244.37,
            "items": [
              {
                "key": "gravel_yard",
                "label": "Gravel, bulk",
                "qty": 2.5,
                "unit": "cu yd",
                "unitPrice": 55,
                "cost": 137.5
              },
              {
                "key": "bulk_delivery",
                "label": "Delivery",
                "qty": 1,
                "unit": "trip",
                "unitPrice": 75,
                "cost": 75
              }
            ]
          }
        ]
      }
    },
    {
      "id": "topsoil",
      "name": "Topsoil Calculator",
      "url": "https://estimahome.com/topsoil-calculator/",
      "category": "landscape",
      "description": "Cubic yards of topsoil to fill a bed or level a lawn, bagged or bulk.",
      "related": [
        "https://estimahome.com/mulch-calculator/",
        "https://estimahome.com/gravel-calculator/"
      ],
      "guides": [
        "https://estimahome.com/guides/raised-bed-soil-4x8/"
      ],
      "inputs": [
        {
          "name": "lengthFt",
          "label": "Bed or area length",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 20,
          "min": 0.5,
          "max": 1000
        },
        {
          "name": "widthFt",
          "label": "Bed or area width",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 10,
          "min": 0.5,
          "max": 1000
        },
        {
          "name": "depthIn",
          "label": "Depth",
          "unit": "in",
          "type": "number",
          "group": "size",
          "default": 4,
          "min": 0.5,
          "max": 36
        },
        {
          "name": "extraPct",
          "label": "Extra for settling",
          "unit": "%",
          "type": "number",
          "group": "options",
          "default": 0,
          "min": 0,
          "max": 50
        },
        {
          "name": "bagCuFt",
          "label": "Bag size",
          "unit": "cu ft",
          "type": "number",
          "group": "options",
          "default": 0.75,
          "min": 0.1,
          "max": 5,
          "help": "Bagged topsoil is often 0.75 or 1 cu ft."
        },
        {
          "name": "tonsPerYd",
          "label": "Weight per cubic yard",
          "unit": "tons",
          "type": "number",
          "group": "options",
          "default": 1.1,
          "min": 0.1,
          "max": 3,
          "help": "Ask your supplier; moisture changes it."
        },
        {
          "name": "bulkIncrementYd",
          "label": "Bulk sold in increments of",
          "unit": "cu yd",
          "type": "select",
          "group": "options",
          "default": "0.5",
          "options": [
            "0.25",
            "0.5",
            "1"
          ]
        },
        {
          "name": "priceBag",
          "label": "Bag",
          "unit": "$/bag",
          "type": "number",
          "group": "prices",
          "default": 3.5,
          "min": 0,
          "priceKey": "topsoil_bag"
        },
        {
          "name": "priceYard",
          "label": "Bulk",
          "unit": "$/cu yd",
          "type": "number",
          "group": "prices",
          "default": 40,
          "min": 0,
          "priceKey": "topsoil_yard"
        },
        {
          "name": "priceDelivery",
          "label": "Bulk delivery",
          "unit": "$",
          "type": "number",
          "group": "prices",
          "default": 75,
          "min": 0,
          "priceKey": "bulk_delivery"
        },
        {
          "name": "spreadPct",
          "label": "Estimate range (±)",
          "unit": "%",
          "type": "number",
          "group": "prices",
          "default": 15,
          "min": 0,
          "max": 100
        }
      ],
      "formulas": [
        {
          "output": "cuFt",
          "text": "Volume (cu ft) = length × width × depth ÷ 12 × (1 + extra ÷ 100)",
          "expr": "lengthFt*widthFt*depthIn/12*(1+extraPct/100)"
        },
        {
          "output": "cuYd",
          "text": "Cubic yards = cubic feet ÷ 27",
          "expr": "cuFt/27"
        },
        {
          "output": "bags",
          "text": "Bags = ⌈ cubic feet ÷ bag size ⌉",
          "expr": "ceil(cuFt/bagCuFt)"
        },
        {
          "output": "bulkYd",
          "text": "Bulk order = cubic yards rounded up to the sale increment",
          "expr": "ceil(cuYd/bulkIncrementYd)*bulkIncrementYd"
        },
        {
          "output": "tons",
          "text": "Weight (tons) = cubic yards × tons per cubic yard",
          "expr": "cuYd*tonsPerYd"
        },
        {
          "output": "cost",
          "text": "Bag cost = bags × bag price; bulk cost = bulk yards × price per yard + delivery; range = cost × (1 ± range % ÷ 100)",
          "expr": "qty*unitPrice*(1±spreadPct/100)"
        }
      ],
      "defaultResult": {
        "values": {
          "areaSqFt": 200,
          "cuFt": 66.67,
          "cuYd": 2.47,
          "bags": 89,
          "bulkYd": 2.5,
          "tons": 2.72
        },
        "groups": [
          {
            "title": "Option A: bagged",
            "subtotal": 311.5,
            "low": 264.77,
            "high": 358.23,
            "items": [
              {
                "key": "topsoil_bag",
                "label": "Topsoil, 0.75 cu ft bags",
                "qty": 89,
                "unit": "bag",
                "unitPrice": 3.5,
                "cost": 311.5
              }
            ]
          },
          {
            "title": "Option B: bulk delivery",
            "subtotal": 175,
            "low": 148.75,
            "high": 201.25,
            "items": [
              {
                "key": "topsoil_yard",
                "label": "Topsoil, bulk",
                "qty": 2.5,
                "unit": "cu yd",
                "unitPrice": 40,
                "cost": 100
              },
              {
                "key": "bulk_delivery",
                "label": "Delivery",
                "qty": 1,
                "unit": "trip",
                "unitPrice": 75,
                "cost": 75
              }
            ]
          }
        ]
      }
    },
    {
      "id": "drywall",
      "name": "Drywall Calculator",
      "url": "https://estimahome.com/drywall-calculator/",
      "category": "interior",
      "description": "Drywall sheets, joint compound, tape and screws for a room.",
      "related": [
        "https://estimahome.com/paint-calculator/",
        "https://estimahome.com/insulation-calculator/"
      ],
      "guides": [
        "https://estimahome.com/guides/drywall-sheets-12x12-room/"
      ],
      "inputs": [
        {
          "name": "lengthFt",
          "label": "Room length",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 12,
          "min": 1,
          "max": 200
        },
        {
          "name": "widthFt",
          "label": "Room width",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 12,
          "min": 1,
          "max": 200
        },
        {
          "name": "heightFt",
          "label": "Wall height",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 8,
          "min": 1,
          "max": 30
        },
        {
          "name": "includeCeiling",
          "label": "Hang the ceiling",
          "unit": "",
          "type": "select",
          "group": "size",
          "default": "1",
          "options": [
            "0",
            "1"
          ]
        },
        {
          "name": "doors",
          "label": "Doors",
          "unit": "ea",
          "type": "number",
          "group": "size",
          "default": 1,
          "min": 0,
          "max": 50
        },
        {
          "name": "windows",
          "label": "Windows",
          "unit": "ea",
          "type": "number",
          "group": "size",
          "default": 1,
          "min": 0,
          "max": 50
        },
        {
          "name": "sheet",
          "label": "Sheet size",
          "unit": "",
          "type": "select",
          "group": "options",
          "default": "32",
          "options": [
            "32",
            "48"
          ]
        },
        {
          "name": "wastePct",
          "label": "Waste",
          "unit": "%",
          "type": "number",
          "group": "options",
          "default": 10,
          "min": 0,
          "max": 50
        },
        {
          "name": "doorAreaSqFt",
          "label": "Area per door",
          "unit": "sq ft",
          "type": "number",
          "group": "options",
          "default": 21,
          "min": 0,
          "max": 100
        },
        {
          "name": "windowAreaSqFt",
          "label": "Area per window",
          "unit": "sq ft",
          "type": "number",
          "group": "options",
          "default": 15,
          "min": 0,
          "max": 100
        },
        {
          "name": "compoundGalPer1000",
          "label": "Joint compound per 1,000 sq ft",
          "unit": "gal",
          "type": "number",
          "group": "options",
          "default": 9.4,
          "min": 1,
          "max": 40,
          "help": "Ready-mixed, three coats. Check your product sheet."
        },
        {
          "name": "bucketGal",
          "label": "Compound bucket size",
          "unit": "gal",
          "type": "number",
          "group": "options",
          "default": 4.5,
          "min": 0.5,
          "max": 10
        },
        {
          "name": "tapeFtPer1000",
          "label": "Tape per 1,000 sq ft",
          "unit": "ft",
          "type": "number",
          "group": "options",
          "default": 370,
          "min": 50,
          "max": 1000
        },
        {
          "name": "tapeRollFt",
          "label": "Tape roll length",
          "unit": "ft",
          "type": "number",
          "group": "options",
          "default": 250,
          "min": 50,
          "max": 1000
        },
        {
          "name": "screwsPerSheet",
          "label": "Screws per 4 × 8 sheet",
          "unit": "ea",
          "type": "number",
          "group": "options",
          "default": 32,
          "min": 10,
          "max": 80,
          "help": "Scaled for 4 × 12 sheets."
        },
        {
          "name": "screwsPerBox",
          "label": "Screws per box",
          "unit": "ea",
          "type": "number",
          "group": "options",
          "default": 1000,
          "min": 50,
          "max": 10000
        },
        {
          "name": "priceSheet",
          "label": "Drywall sheet",
          "unit": "$/sheet",
          "type": "number",
          "group": "prices",
          "default": 16,
          "min": 0,
          "priceKey": "drywall_sheet",
          "help": "Use the price for the sheet size above."
        },
        {
          "name": "priceBucket",
          "label": "Joint compound bucket",
          "unit": "$/ea",
          "type": "number",
          "group": "prices",
          "default": 20,
          "min": 0,
          "priceKey": "joint_compound"
        },
        {
          "name": "priceTape",
          "label": "Paper tape roll",
          "unit": "$/roll",
          "type": "number",
          "group": "prices",
          "default": 5,
          "min": 0,
          "priceKey": "drywall_tape"
        },
        {
          "name": "priceScrewBox",
          "label": "Box of drywall screws",
          "unit": "$/box",
          "type": "number",
          "group": "prices",
          "default": 22,
          "min": 0,
          "priceKey": "drywall_screws"
        },
        {
          "name": "spreadPct",
          "label": "Estimate range (±)",
          "unit": "%",
          "type": "number",
          "group": "prices",
          "default": 15,
          "min": 0,
          "max": 100
        }
      ],
      "formulas": [
        {
          "output": "wallAreaSqFt",
          "text": "Wall area = 2 × (length + width) × height − doors × door area − windows × window area",
          "expr": "max(0,2*(lengthFt+widthFt)*heightFt-doors*doorAreaSqFt-windows*windowAreaSqFt)"
        },
        {
          "output": "totalAreaSqFt",
          "text": "Total area = wall area + ceiling (length × width, if hung)",
          "expr": "wallAreaSqFt+(includeCeiling?lengthFt*widthFt:0)"
        },
        {
          "output": "sheets",
          "text": "Sheets = ⌈ total area × (1 + waste ÷ 100) ÷ sheet area ⌉",
          "expr": "ceil(totalAreaSqFt*(1+wastePct/100)/sheet)"
        },
        {
          "output": "buckets",
          "text": "Compound buckets = ⌈ total area ÷ 1,000 × compound rate ÷ bucket size ⌉",
          "expr": "ceil(totalAreaSqFt/1000*compoundGalPer1000/bucketGal)"
        },
        {
          "output": "tapeRolls",
          "text": "Tape rolls = ⌈ total area ÷ 1,000 × tape rate ÷ roll length ⌉",
          "expr": "ceil(totalAreaSqFt/1000*tapeFtPer1000/tapeRollFt)"
        },
        {
          "output": "screws",
          "text": "Screws = sheets × screws per sheet × (sheet area ÷ 32); boxes = ⌈ screws ÷ per box ⌉",
          "expr": "ceil(sheets*screwsPerSheet*sheet/32)"
        },
        {
          "output": "cost",
          "text": "Cost = Σ (quantity × unit price); range = cost × (1 ± range % ÷ 100)",
          "expr": "sum(qty*unitPrice)*(1±spreadPct/100)"
        }
      ],
      "defaultResult": {
        "values": {
          "wallAreaSqFt": 348,
          "ceilingAreaSqFt": 144,
          "totalAreaSqFt": 492,
          "sheets": 17,
          "compoundGal": 4.62,
          "buckets": 2,
          "tapeFt": 182,
          "tapeRolls": 1,
          "screws": 544,
          "screwBoxes": 1
        },
        "groups": [
          {
            "title": "Materials",
            "subtotal": 339,
            "low": 288.15,
            "high": 389.85,
            "items": [
              {
                "key": "drywall_sheet",
                "label": "Drywall, 4 × 8 ft, 1/2 in",
                "qty": 17,
                "unit": "sheet",
                "unitPrice": 16,
                "cost": 272
              },
              {
                "key": "joint_compound",
                "label": "Joint compound, 4.5 gal",
                "qty": 2,
                "unit": "ea",
                "unitPrice": 20,
                "cost": 40
              },
              {
                "key": "drywall_tape",
                "label": "Paper tape, 250 ft",
                "qty": 1,
                "unit": "roll",
                "unitPrice": 5,
                "cost": 5
              },
              {
                "key": "drywall_screws",
                "label": "Drywall screws (544 pcs)",
                "qty": 1,
                "unit": "box",
                "unitPrice": 22,
                "cost": 22
              }
            ]
          }
        ]
      }
    },
    {
      "id": "paint",
      "name": "Paint Calculator",
      "url": "https://estimahome.com/paint-calculator/",
      "category": "interior",
      "description": "Gallons of wall paint, ceiling paint and primer for a room.",
      "related": [
        "https://estimahome.com/drywall-calculator/",
        "https://estimahome.com/wallpaper-calculator/"
      ],
      "guides": [
        "https://estimahome.com/guides/paint-12x12-room/"
      ],
      "inputs": [
        {
          "name": "lengthFt",
          "label": "Room length",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 12,
          "min": 1,
          "max": 200
        },
        {
          "name": "widthFt",
          "label": "Room width",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 12,
          "min": 1,
          "max": 200
        },
        {
          "name": "heightFt",
          "label": "Wall height",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 8,
          "min": 1,
          "max": 40
        },
        {
          "name": "doors",
          "label": "Doors",
          "unit": "ea",
          "type": "number",
          "group": "size",
          "default": 1,
          "min": 0,
          "max": 50
        },
        {
          "name": "windows",
          "label": "Windows",
          "unit": "ea",
          "type": "number",
          "group": "size",
          "default": 2,
          "min": 0,
          "max": 50
        },
        {
          "name": "coats",
          "label": "Coats",
          "unit": "",
          "type": "select",
          "group": "options",
          "default": "2",
          "options": [
            "1",
            "2",
            "3"
          ]
        },
        {
          "name": "includeCeiling",
          "label": "Paint the ceiling",
          "unit": "",
          "type": "select",
          "group": "options",
          "default": "0",
          "options": [
            "0",
            "1"
          ]
        },
        {
          "name": "primer",
          "label": "One coat of primer",
          "unit": "",
          "type": "select",
          "group": "options",
          "default": "0",
          "options": [
            "0",
            "1"
          ]
        },
        {
          "name": "coverageSqFt",
          "label": "Paint coverage",
          "unit": "sq ft/gal",
          "type": "number",
          "group": "options",
          "default": 350,
          "min": 50,
          "max": 800,
          "help": "From the can label."
        },
        {
          "name": "primerCoverageSqFt",
          "label": "Primer coverage",
          "unit": "sq ft/gal",
          "type": "number",
          "group": "options",
          "default": 300,
          "min": 50,
          "max": 800
        },
        {
          "name": "doorAreaSqFt",
          "label": "Area per door",
          "unit": "sq ft",
          "type": "number",
          "group": "options",
          "default": 21,
          "min": 0,
          "max": 100,
          "help": "3 × 7 ft door."
        },
        {
          "name": "windowAreaSqFt",
          "label": "Area per window",
          "unit": "sq ft",
          "type": "number",
          "group": "options",
          "default": 15,
          "min": 0,
          "max": 100,
          "help": "3 × 5 ft window."
        },
        {
          "name": "pricePaint",
          "label": "Paint",
          "unit": "$/gal",
          "type": "number",
          "group": "prices",
          "default": 42,
          "min": 0,
          "priceKey": "paint_gallon"
        },
        {
          "name": "pricePrimer",
          "label": "Primer",
          "unit": "$/gal",
          "type": "number",
          "group": "prices",
          "default": 28,
          "min": 0,
          "priceKey": "primer_gallon"
        },
        {
          "name": "priceSupplies",
          "label": "Rollers, tape, tray",
          "unit": "$",
          "type": "number",
          "group": "prices",
          "default": 35,
          "min": 0,
          "priceKey": "paint_supplies"
        },
        {
          "name": "spreadPct",
          "label": "Estimate range (±)",
          "unit": "%",
          "type": "number",
          "group": "prices",
          "default": 15,
          "min": 0,
          "max": 100
        }
      ],
      "formulas": [
        {
          "output": "wallAreaSqFt",
          "text": "Wall area = 2 × (length + width) × height − doors × door area − windows × window area",
          "expr": "max(0,2*(lengthFt+widthFt)*heightFt-doors*doorAreaSqFt-windows*windowAreaSqFt)"
        },
        {
          "output": "ceilingAreaSqFt",
          "text": "Ceiling area = length × width",
          "expr": "includeCeiling ? lengthFt*widthFt : 0"
        },
        {
          "output": "paintGallons",
          "text": "Paint (gal) = (wall area + ceiling area) × coats ÷ coverage",
          "expr": "(wallAreaSqFt+ceilingAreaSqFt)*coats/coverageSqFt"
        },
        {
          "output": "paintGallonsToBuy",
          "text": "Gallons to buy = ⌈ paint gallons ⌉",
          "expr": "ceil(paintGallons)"
        },
        {
          "output": "primerGallons",
          "text": "Primer (gal) = ⌈ (wall area + ceiling area) ÷ primer coverage ⌉",
          "expr": "primer ? ceil((wallAreaSqFt+ceilingAreaSqFt)/primerCoverageSqFt) : 0"
        },
        {
          "output": "cost",
          "text": "Cost = Σ (quantity × unit price); range = cost × (1 ± range % ÷ 100)",
          "expr": "sum(qty*unitPrice)*(1±spreadPct/100)"
        }
      ],
      "defaultResult": {
        "values": {
          "grossWallSqFt": 384,
          "wallAreaSqFt": 333,
          "ceilingAreaSqFt": 0,
          "paintArea": 333,
          "paintGallons": 1.9,
          "paintGallonsToBuy": 2,
          "primerGallons": 0
        },
        "groups": [
          {
            "title": "Materials",
            "subtotal": 119,
            "low": 101.15,
            "high": 136.85,
            "items": [
              {
                "key": "paint_gallon",
                "label": "Paint, 2 coats",
                "qty": 2,
                "unit": "gal",
                "unitPrice": 42,
                "cost": 84
              },
              {
                "key": "paint_supplies",
                "label": "Rollers, brushes, tape, tray",
                "qty": 1,
                "unit": "set",
                "unitPrice": 35,
                "cost": 35
              }
            ]
          }
        ]
      }
    },
    {
      "id": "wallpaper",
      "name": "Wallpaper Calculator",
      "url": "https://estimahome.com/wallpaper-calculator/",
      "category": "interior",
      "description": "Rolls of wallpaper by the strip method, with pattern repeat and trim allowance.",
      "related": [
        "https://estimahome.com/paint-calculator/",
        "https://estimahome.com/drywall-calculator/"
      ],
      "guides": [
        "https://estimahome.com/guides/wallpaper-rolls-12x12-room/"
      ],
      "inputs": [
        {
          "name": "lengthFt",
          "label": "Room length",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 12,
          "min": 1,
          "max": 200
        },
        {
          "name": "widthFt",
          "label": "Room width",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 12,
          "min": 1,
          "max": 200
        },
        {
          "name": "heightFt",
          "label": "Wall height",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 8,
          "min": 1,
          "max": 30
        },
        {
          "name": "skipFt",
          "label": "Width of openings to skip",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 3,
          "min": 0,
          "max": 200,
          "help": "Doors and full-height openings only. Leave windows in."
        },
        {
          "name": "rollWidthIn",
          "label": "Roll width",
          "unit": "in",
          "type": "number",
          "group": "options",
          "default": 20.5,
          "min": 10,
          "max": 60,
          "help": "US double rolls are usually 20.5 in × 33 ft."
        },
        {
          "name": "rollLengthFt",
          "label": "Roll length",
          "unit": "ft",
          "type": "number",
          "group": "options",
          "default": 33,
          "min": 5,
          "max": 100
        },
        {
          "name": "repeatIn",
          "label": "Pattern repeat",
          "unit": "in",
          "type": "number",
          "group": "options",
          "default": 0,
          "min": 0,
          "max": 60,
          "help": "Printed on the label. 0 for random match."
        },
        {
          "name": "trimIn",
          "label": "Trim allowance per strip",
          "unit": "in",
          "type": "number",
          "group": "options",
          "default": 4,
          "min": 0,
          "max": 12
        },
        {
          "name": "priceRoll",
          "label": "Wallpaper roll",
          "unit": "$/roll",
          "type": "number",
          "group": "prices",
          "default": 60,
          "min": 0,
          "priceKey": "wallpaper_roll"
        },
        {
          "name": "priceTools",
          "label": "Smoother, knife, seam roller",
          "unit": "$",
          "type": "number",
          "group": "prices",
          "default": 20,
          "min": 0,
          "priceKey": "wallpaper_tools"
        },
        {
          "name": "spreadPct",
          "label": "Estimate range (±)",
          "unit": "%",
          "type": "number",
          "group": "prices",
          "default": 15,
          "min": 0,
          "max": 100
        }
      ],
      "formulas": [
        {
          "output": "strips",
          "text": "Strips = ⌈ (2 × (length + width) − openings) × 12 ÷ roll width ⌉",
          "expr": "ceil((2*(lengthFt+widthFt)-skipFt)*12/rollWidthIn)"
        },
        {
          "output": "stripLengthIn",
          "text": "Strip length = wall height × 12 + pattern repeat + trim",
          "expr": "heightFt*12+repeatIn+trimIn"
        },
        {
          "output": "stripsPerRoll",
          "text": "Strips per roll = ⌊ roll length × 12 ÷ strip length ⌋",
          "expr": "floor(rollLengthFt*12/stripLengthIn)"
        },
        {
          "output": "rolls",
          "text": "Rolls = ⌈ strips ÷ strips per roll ⌉",
          "expr": "ceil(strips/stripsPerRoll)"
        },
        {
          "output": "cost",
          "text": "Cost = Σ (quantity × unit price); range = cost × (1 ± range % ÷ 100)",
          "expr": "sum(qty*unitPrice)*(1±spreadPct/100)"
        }
      ],
      "defaultResult": {
        "values": {
          "perimeterFt": 48,
          "coverFt": 45,
          "strips": 27,
          "stripLengthIn": 100,
          "stripsPerRoll": 3,
          "rolls": 9
        },
        "groups": [
          {
            "title": "Materials",
            "subtotal": 560,
            "low": 476,
            "high": 644,
            "items": [
              {
                "key": "wallpaper_roll",
                "label": "Wallpaper, 20.5 in × 33 ft",
                "qty": 9,
                "unit": "roll",
                "unitPrice": 60,
                "cost": 540
              },
              {
                "key": "wallpaper_tools",
                "label": "Smoother, knife, seam roller",
                "qty": 1,
                "unit": "set",
                "unitPrice": 20,
                "cost": 20
              }
            ]
          }
        ]
      }
    },
    {
      "id": "insulation",
      "name": "Insulation Calculator",
      "url": "https://estimahome.com/insulation-calculator/",
      "category": "interior",
      "description": "Packages of fiberglass batts or rolls for walls, floors or an attic, adjusted for framing.",
      "related": [
        "https://estimahome.com/drywall-calculator/",
        "https://estimahome.com/roofing-calculator/"
      ],
      "guides": [
        "https://estimahome.com/guides/r13-insulation-1000-sq-ft-wall/"
      ],
      "inputs": [
        {
          "name": "lengthFt",
          "label": "Wall length (or attic length)",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 40,
          "min": 1,
          "max": 1000
        },
        {
          "name": "heightFt",
          "label": "Wall height (or attic width)",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 8,
          "min": 1,
          "max": 200
        },
        {
          "name": "openingsSqFt",
          "label": "Windows and doors to subtract",
          "unit": "sq ft",
          "type": "number",
          "group": "size",
          "default": 30,
          "min": 0,
          "max": 10000
        },
        {
          "name": "rValue",
          "label": "R-value",
          "unit": "",
          "type": "select",
          "group": "size",
          "default": "13",
          "options": [
            "13",
            "15",
            "19",
            "21",
            "30",
            "38",
            "49"
          ]
        },
        {
          "name": "spacingIn",
          "label": "Stud or joist spacing, on center",
          "unit": "in",
          "type": "select",
          "group": "options",
          "default": "16",
          "options": [
            "16",
            "24"
          ]
        },
        {
          "name": "frameIn",
          "label": "Framing member thickness",
          "unit": "in",
          "type": "number",
          "group": "options",
          "default": 1.5,
          "min": 0.5,
          "max": 4
        },
        {
          "name": "packageSqFt",
          "label": "Coverage per package",
          "unit": "sq ft",
          "type": "number",
          "group": "options",
          "default": 40,
          "min": 1,
          "max": 200,
          "help": "Printed on the bag; it varies by R-value and width."
        },
        {
          "name": "wastePct",
          "label": "Waste",
          "unit": "%",
          "type": "number",
          "group": "options",
          "default": 5,
          "min": 0,
          "max": 30
        },
        {
          "name": "pricePackage",
          "label": "Package",
          "unit": "$/pkg",
          "type": "number",
          "group": "prices",
          "default": 30,
          "min": 0,
          "priceKey": "insulation_package"
        },
        {
          "name": "spreadPct",
          "label": "Estimate range (±)",
          "unit": "%",
          "type": "number",
          "group": "prices",
          "default": 15,
          "min": 0,
          "max": 100
        }
      ],
      "formulas": [
        {
          "output": "netSqFt",
          "text": "Net area = length × height − openings",
          "expr": "max(0,lengthFt*heightFt-openingsSqFt)"
        },
        {
          "output": "cavityFraction",
          "text": "Cavity fraction = (spacing − framing thickness) ÷ spacing",
          "expr": "(spacingIn-frameIn)/spacingIn"
        },
        {
          "output": "insulatedSqFt",
          "text": "Insulated area = net area × cavity fraction",
          "expr": "netSqFt*cavityFraction"
        },
        {
          "output": "packages",
          "text": "Packages = ⌈ insulated area × (1 + waste ÷ 100) ÷ coverage per package ⌉",
          "expr": "ceil(insulatedSqFt*(1+wastePct/100)/packageSqFt)"
        },
        {
          "output": "cost",
          "text": "Cost = Σ (quantity × unit price); range = cost × (1 ± range % ÷ 100)",
          "expr": "sum(qty*unitPrice)*(1±spreadPct/100)"
        }
      ],
      "defaultResult": {
        "values": {
          "netSqFt": 290,
          "cavityFraction": 0.9063,
          "insulatedSqFt": 262.81,
          "packages": 7
        },
        "groups": [
          {
            "title": "Materials",
            "subtotal": 210,
            "low": 178.5,
            "high": 241.5,
            "items": [
              {
                "key": "insulation_package",
                "label": "Fiberglass R-13, 15 in wide",
                "qty": 7,
                "unit": "pkg",
                "unitPrice": 30,
                "cost": 210
              }
            ]
          }
        ]
      }
    },
    {
      "id": "flooring",
      "name": "Flooring Calculator",
      "url": "https://estimahome.com/flooring-calculator/",
      "category": "floors",
      "description": "Boxes of laminate, vinyl plank or hardwood with waste, plus underlayment and trim.",
      "related": [
        "https://estimahome.com/tile-calculator/",
        "https://estimahome.com/paint-calculator/"
      ],
      "guides": [
        "https://estimahome.com/guides/laminate-flooring-cost-12x15/"
      ],
      "inputs": [
        {
          "name": "lengthFt",
          "label": "Room length",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 15,
          "min": 1,
          "max": 200
        },
        {
          "name": "widthFt",
          "label": "Room width",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 12,
          "min": 1,
          "max": 200
        },
        {
          "name": "type",
          "label": "Flooring type",
          "unit": "",
          "type": "select",
          "group": "size",
          "default": "laminate",
          "options": [
            "laminate",
            "lvp",
            "hardwood"
          ]
        },
        {
          "name": "wastePct",
          "label": "Waste",
          "unit": "%",
          "type": "number",
          "group": "options",
          "default": 10,
          "min": 0,
          "max": 40,
          "help": "10% straight lay; 15% diagonal; 20% herringbone."
        },
        {
          "name": "boxSqFt",
          "label": "Coverage per box",
          "unit": "sq ft",
          "type": "number",
          "group": "options",
          "default": 22,
          "min": 1,
          "max": 100,
          "help": "Printed on the box."
        },
        {
          "name": "underlayment",
          "label": "Separate underlayment",
          "unit": "",
          "type": "select",
          "group": "options",
          "default": "1",
          "options": [
            "0",
            "1"
          ],
          "help": "Skip if the planks have a pad attached."
        },
        {
          "name": "underlaymentRollSqFt",
          "label": "Underlayment roll coverage",
          "unit": "sq ft",
          "type": "number",
          "group": "options",
          "default": 100,
          "min": 10,
          "max": 1000
        },
        {
          "name": "trim",
          "label": "Quarter round at walls",
          "unit": "",
          "type": "select",
          "group": "options",
          "default": "1",
          "options": [
            "0",
            "1"
          ]
        },
        {
          "name": "priceSqFt",
          "label": "Flooring",
          "unit": "$/sq ft",
          "type": "number",
          "group": "prices",
          "default": 3,
          "min": 0,
          "priceKey": "flooring_sqft"
        },
        {
          "name": "priceUnderlayment",
          "label": "Underlayment roll",
          "unit": "$/roll",
          "type": "number",
          "group": "prices",
          "default": 30,
          "min": 0,
          "priceKey": "underlayment_roll"
        },
        {
          "name": "priceTrim",
          "label": "Quarter round, 8 ft",
          "unit": "$/ea",
          "type": "number",
          "group": "prices",
          "default": 8,
          "min": 0,
          "priceKey": "quarter_round"
        },
        {
          "name": "spreadPct",
          "label": "Estimate range (±)",
          "unit": "%",
          "type": "number",
          "group": "prices",
          "default": 15,
          "min": 0,
          "max": 100
        }
      ],
      "formulas": [
        {
          "output": "areaSqFt",
          "text": "Floor area = length × width",
          "expr": "lengthFt*widthFt"
        },
        {
          "output": "boxes",
          "text": "Boxes = ⌈ area × (1 + waste ÷ 100) ÷ coverage per box ⌉",
          "expr": "ceil(areaSqFt*(1+wastePct/100)/boxSqFt)"
        },
        {
          "output": "purchasedSqFt",
          "text": "Flooring bought = boxes × coverage per box (priced per sq ft)",
          "expr": "boxes*boxSqFt"
        },
        {
          "output": "underlaymentRolls",
          "text": "Underlayment rolls = ⌈ area ÷ roll coverage ⌉",
          "expr": "ceil(areaSqFt/underlaymentRollSqFt)"
        },
        {
          "output": "trimPieces",
          "text": "Quarter round = ⌈ 2 × (length + width) × 1.1 ÷ 8 ⌉ pieces",
          "expr": "ceil(2*(lengthFt+widthFt)*1.1/8)"
        },
        {
          "output": "cost",
          "text": "Cost = Σ (quantity × unit price); range = cost × (1 ± range % ÷ 100)",
          "expr": "sum(qty*unitPrice)*(1±spreadPct/100)"
        }
      ],
      "defaultResult": {
        "values": {
          "areaSqFt": 180,
          "boxes": 9,
          "purchasedSqFt": 198,
          "underlaymentRolls": 2,
          "perimeterFt": 54,
          "trimPieces": 8
        },
        "groups": [
          {
            "title": "Materials",
            "subtotal": 718,
            "low": 610.3,
            "high": 825.7,
            "items": [
              {
                "key": "flooring_sqft",
                "label": "Laminate flooring, 9 boxes",
                "qty": 198,
                "unit": "sq ft",
                "unitPrice": 3,
                "cost": 594
              },
              {
                "key": "underlayment_roll",
                "label": "Underlayment, 100 sq ft roll",
                "qty": 2,
                "unit": "roll",
                "unitPrice": 30,
                "cost": 60
              },
              {
                "key": "quarter_round",
                "label": "Quarter round, 8 ft",
                "qty": 8,
                "unit": "ea",
                "unitPrice": 8,
                "cost": 64
              }
            ]
          }
        ]
      }
    },
    {
      "id": "tile",
      "name": "Tile Calculator",
      "url": "https://estimahome.com/tile-calculator/",
      "category": "floors",
      "description": "Tile count and boxes, thinset bags and grout for a floor or wall.",
      "related": [
        "https://estimahome.com/flooring-calculator/",
        "https://estimahome.com/drywall-calculator/"
      ],
      "guides": [
        "https://estimahome.com/guides/bathroom-floor-tile-cost/"
      ],
      "inputs": [
        {
          "name": "lengthFt",
          "label": "Area length",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 10,
          "min": 0.5,
          "max": 200
        },
        {
          "name": "widthFt",
          "label": "Area width (or wall height)",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 12,
          "min": 0.5,
          "max": 200
        },
        {
          "name": "tileLIn",
          "label": "Tile length",
          "unit": "in",
          "type": "number",
          "group": "size",
          "default": 24,
          "min": 1,
          "max": 96
        },
        {
          "name": "tileWIn",
          "label": "Tile width",
          "unit": "in",
          "type": "number",
          "group": "size",
          "default": 12,
          "min": 1,
          "max": 96
        },
        {
          "name": "jointIn",
          "label": "Grout joint",
          "unit": "in",
          "type": "number",
          "group": "options",
          "default": 0.125,
          "min": 0,
          "max": 1
        },
        {
          "name": "tileThickIn",
          "label": "Tile thickness",
          "unit": "in",
          "type": "number",
          "group": "options",
          "default": 0.375,
          "min": 0.125,
          "max": 1.5
        },
        {
          "name": "wastePct",
          "label": "Waste",
          "unit": "%",
          "type": "number",
          "group": "options",
          "default": 10,
          "min": 0,
          "max": 40,
          "help": "Use 15% for diagonal layouts or lots of cuts."
        },
        {
          "name": "boxSqFt",
          "label": "Coverage per box",
          "unit": "sq ft",
          "type": "number",
          "group": "options",
          "default": 16,
          "min": 1,
          "max": 100
        },
        {
          "name": "thinsetSqFtPerBag",
          "label": "Thinset coverage per 50 lb bag",
          "unit": "sq ft",
          "type": "number",
          "group": "options",
          "default": 45,
          "min": 10,
          "max": 150,
          "help": "Depends on trowel notch; see the bag chart."
        },
        {
          "name": "groutDensity",
          "label": "Grout density",
          "unit": "lb/cu ft",
          "type": "number",
          "group": "options",
          "default": 112,
          "min": 50,
          "max": 160
        },
        {
          "name": "groutBagLb",
          "label": "Grout bag size",
          "unit": "lb",
          "type": "number",
          "group": "options",
          "default": 25,
          "min": 1,
          "max": 50
        },
        {
          "name": "priceSqFt",
          "label": "Tile",
          "unit": "$/sq ft",
          "type": "number",
          "group": "prices",
          "default": 3,
          "min": 0,
          "priceKey": "tile_sqft"
        },
        {
          "name": "priceThinset",
          "label": "Thinset, 50 lb",
          "unit": "$/bag",
          "type": "number",
          "group": "prices",
          "default": 22,
          "min": 0,
          "priceKey": "thinset_bag"
        },
        {
          "name": "priceGrout",
          "label": "Grout bag",
          "unit": "$/bag",
          "type": "number",
          "group": "prices",
          "default": 25,
          "min": 0,
          "priceKey": "grout_bag"
        },
        {
          "name": "spreadPct",
          "label": "Estimate range (±)",
          "unit": "%",
          "type": "number",
          "group": "prices",
          "default": 15,
          "min": 0,
          "max": 100
        }
      ],
      "formulas": [
        {
          "output": "areaSqFt",
          "text": "Area = length × width",
          "expr": "lengthFt*widthFt"
        },
        {
          "output": "tiles",
          "text": "Tiles = ⌈ area × 144 ÷ ((tile L + joint) × (tile W + joint)) × (1 + waste ÷ 100) ⌉",
          "expr": "ceil(areaSqFt*144/((tileLIn+jointIn)*(tileWIn+jointIn))*(1+wastePct/100))"
        },
        {
          "output": "boxes",
          "text": "Boxes = ⌈ area × (1 + waste ÷ 100) ÷ coverage per box ⌉",
          "expr": "ceil(areaSqFt*(1+wastePct/100)/boxSqFt)"
        },
        {
          "output": "thinsetBags",
          "text": "Thinset bags = ⌈ area ÷ coverage per bag ⌉",
          "expr": "ceil(areaSqFt/thinsetSqFtPerBag)"
        },
        {
          "output": "groutLb",
          "text": "Grout (lb) = area × 144 × (1 ÷ tile L + 1 ÷ tile W) × joint × thickness ÷ 1,728 × density × (1 + waste ÷ 100)",
          "expr": "areaSqFt*144*(1/tileLIn+1/tileWIn)*jointIn*tileThickIn/1728*groutDensity*(1+wastePct/100)"
        },
        {
          "output": "groutBags",
          "text": "Grout bags = ⌈ grout lb ÷ bag size ⌉",
          "expr": "ceil(groutLb/groutBagLb)"
        },
        {
          "output": "cost",
          "text": "Cost = Σ (quantity × unit price); range = cost × (1 ± range % ÷ 100)",
          "expr": "sum(qty*unitPrice)*(1±spreadPct/100)"
        }
      ],
      "defaultResult": {
        "values": {
          "areaSqFt": 120,
          "tilesExact": 59.07,
          "tiles": 65,
          "boxes": 9,
          "purchasedSqFt": 144,
          "thinsetBags": 3,
          "groutLb": 7.22,
          "groutBags": 1
        },
        "groups": [
          {
            "title": "Materials",
            "subtotal": 523,
            "low": 444.55,
            "high": 601.45,
            "items": [
              {
                "key": "tile_sqft",
                "label": "Tile 24 × 12 in, 9 boxes",
                "qty": 144,
                "unit": "sq ft",
                "unitPrice": 3,
                "cost": 432
              },
              {
                "key": "thinset_bag",
                "label": "Thinset mortar, 50 lb",
                "qty": 3,
                "unit": "bag",
                "unitPrice": 22,
                "cost": 66
              },
              {
                "key": "grout_bag",
                "label": "Sanded grout, 25 lb",
                "qty": 1,
                "unit": "bag",
                "unitPrice": 25,
                "cost": 25
              }
            ]
          }
        ]
      }
    },
    {
      "id": "roofing",
      "name": "Roofing Calculator",
      "url": "https://estimahome.com/roofing-calculator/",
      "category": "roofing",
      "description": "Roof squares from footprint and pitch, then shingle bundles, underlayment, ridge cap and drip edge.",
      "related": [
        "https://estimahome.com/insulation-calculator/",
        "https://estimahome.com/deck-calculator/"
      ],
      "guides": [
        "https://estimahome.com/guides/shingles-2000-sq-ft-roof/"
      ],
      "inputs": [
        {
          "name": "lengthFt",
          "label": "Roof length, incl. overhang",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 40,
          "min": 4,
          "max": 500,
          "help": "Along the ridge."
        },
        {
          "name": "widthFt",
          "label": "Roof width, incl. overhang",
          "unit": "ft",
          "type": "number",
          "group": "size",
          "default": 30,
          "min": 4,
          "max": 300,
          "help": "Eave to eave."
        },
        {
          "name": "pitch",
          "label": "Pitch (rise per 12 in)",
          "unit": "/12",
          "type": "select",
          "group": "size",
          "default": "6",
          "options": [
            "2",
            "3",
            "4",
            "5",
            "6",
            "7",
            "8",
            "9",
            "10",
            "12"
          ]
        },
        {
          "name": "style",
          "label": "Roof style",
          "unit": "",
          "type": "select",
          "group": "size",
          "default": "gable",
          "options": [
            "gable",
            "hip"
          ]
        },
        {
          "name": "wastePct",
          "label": "Shingle waste",
          "unit": "%",
          "type": "number",
          "group": "options",
          "default": 10,
          "min": 0,
          "max": 30,
          "help": "About 10% for gable, 15% for hip or cut-up roofs."
        },
        {
          "name": "bundlesPerSquare",
          "label": "Bundles per square",
          "unit": "ea",
          "type": "number",
          "group": "options",
          "default": 3,
          "min": 1,
          "max": 6,
          "help": "Most architectural shingles: 3."
        },
        {
          "name": "underlaymentRollSqFt",
          "label": "Underlayment roll coverage",
          "unit": "sq ft",
          "type": "number",
          "group": "options",
          "default": 1000,
          "min": 100,
          "max": 2000,
          "help": "Synthetic rolls are often 1,000 sq ft; #15 felt is 400."
        },
        {
          "name": "ridgeLfPerBundle",
          "label": "Ridge cap per bundle",
          "unit": "lin ft",
          "type": "number",
          "group": "options",
          "default": 30,
          "min": 10,
          "max": 60
        },
        {
          "name": "priceBundle",
          "label": "Shingle bundle",
          "unit": "$/bundle",
          "type": "number",
          "group": "prices",
          "default": 38,
          "min": 0,
          "priceKey": "shingle_bundle"
        },
        {
          "name": "priceUnderlayment",
          "label": "Underlayment roll",
          "unit": "$/roll",
          "type": "number",
          "group": "prices",
          "default": 95,
          "min": 0,
          "priceKey": "roof_underlayment"
        },
        {
          "name": "priceRidge",
          "label": "Ridge cap bundle",
          "unit": "$/bundle",
          "type": "number",
          "group": "prices",
          "default": 60,
          "min": 0,
          "priceKey": "ridge_cap"
        },
        {
          "name": "priceDrip",
          "label": "Drip edge, 10 ft",
          "unit": "$/ea",
          "type": "number",
          "group": "prices",
          "default": 9,
          "min": 0,
          "priceKey": "drip_edge"
        },
        {
          "name": "spreadPct",
          "label": "Estimate range (±)",
          "unit": "%",
          "type": "number",
          "group": "prices",
          "default": 15,
          "min": 0,
          "max": 100
        }
      ],
      "formulas": [
        {
          "output": "pitchFactor",
          "text": "Pitch factor = √(1 + (rise ÷ 12)²)",
          "expr": "sqrt(1+(pitch/12)^2)"
        },
        {
          "output": "roofAreaSqFt",
          "text": "Roof area = length × width × pitch factor",
          "expr": "lengthFt*widthFt*pitchFactor"
        },
        {
          "output": "squares",
          "text": "Squares = roof area ÷ 100",
          "expr": "roofAreaSqFt/100"
        },
        {
          "output": "bundles",
          "text": "Bundles = ⌈ squares × (1 + waste ÷ 100) × bundles per square ⌉",
          "expr": "ceil(squares*(1+wastePct/100)*bundlesPerSquare)"
        },
        {
          "output": "underlaymentRolls",
          "text": "Underlayment rolls = ⌈ roof area × 1.1 ÷ roll coverage ⌉ (10% for laps)",
          "expr": "ceil(roofAreaSqFt*1.1/underlaymentRollSqFt)"
        },
        {
          "output": "ridgeLf",
          "text": "Gable ridge = length. Hip: ridge = length − width, plus 4 hips of √(2 × (width ÷ 2)² + (width ÷ 2 × rise ÷ 12)²)",
          "expr": "style=='hip' ? (lengthFt-widthFt)+4*sqrt(2*(widthFt/2)^2+(widthFt/2*pitch/12)^2) : lengthFt"
        },
        {
          "output": "ridgeBundles",
          "text": "Ridge cap bundles = ⌈ ridge + hips ÷ lin ft per bundle ⌉",
          "expr": "ceil(ridgeLf/ridgeLfPerBundle)"
        },
        {
          "output": "dripEdgePieces",
          "text": "Drip edge = ⌈ (eaves + rakes) ÷ 10 ⌉. Gable: 2 × length + 4 × (width ÷ 2) × pitch factor. Hip: 2 × (length + width).",
          "expr": "ceil((style=='hip' ? 2*(lengthFt+widthFt) : 2*lengthFt+4*(widthFt/2)*pitchFactor)/10)"
        },
        {
          "output": "cost",
          "text": "Cost = Σ (quantity × unit price); range = cost × (1 ± range % ÷ 100)",
          "expr": "sum(qty*unitPrice)*(1±spreadPct/100)"
        }
      ],
      "defaultResult": {
        "values": {
          "pitchFactor": 1.118,
          "roofAreaSqFt": 1341.64,
          "squares": 13.42,
          "bundles": 45,
          "underlaymentRolls": 2,
          "ridgeLf": 40,
          "ridgeBundles": 2,
          "edgeLf": 147.08,
          "dripEdgePieces": 15
        },
        "groups": [
          {
            "title": "Materials",
            "subtotal": 2155,
            "low": 1831.75,
            "high": 2478.25,
            "items": [
              {
                "key": "shingle_bundle",
                "label": "Architectural shingles",
                "qty": 45,
                "unit": "bundle",
                "unitPrice": 38,
                "cost": 1710
              },
              {
                "key": "roof_underlayment",
                "label": "Synthetic underlayment, 1000 sq ft",
                "qty": 2,
                "unit": "roll",
                "unitPrice": 95,
                "cost": 190
              },
              {
                "key": "ridge_cap",
                "label": "Hip and ridge cap shingles",
                "qty": 2,
                "unit": "bundle",
                "unitPrice": 60,
                "cost": 120
              },
              {
                "key": "drip_edge",
                "label": "Drip edge, 10 ft",
                "qty": 15,
                "unit": "ea",
                "unitPrice": 9,
                "cost": 135
              }
            ]
          }
        ]
      }
    }
  ]
}