document.addEventListener('DOMContentLoaded', function() { const questions = [ { question: "What is the width of the deck (left to right)?", type: "number", key: "width" }, { question: "What is the depth of the deck (distance from the house outward)?", type: "number", key: "depth" }, { question: "Is the deck a simple rectangle, or does it have angles/irregularities?", type: "select", options: ["Simple rectangle", "Angles/irregularities"], key: "shape" }, { question: "Is the deck tying into the house with a ledger?", type: "yesno", key: "hasLedger" }, { question: "Will deck boards run parallel or perpendicular to the house?", type: "select", options: ["Parallel", "Perpendicular"], key: "boardOrientation" }, { question: "Are we picture framing the deck?", type: "yesno", key: "hasPictureFrame", followups: [ { question: "What color is the picture frame?", type: "select", options: ["Black", "White", "Gray", "Brown", "Custom"], key: "frameColor" }, { question: "What color is the field decking?", type: "select", options: ["Black", "White", "Gray", "Brown", "Custom"], key: "fieldColor" } ] }, { question: "Are there steps?", type: "yesno", key: "hasSteps", followups: [ { question: "How many sets of steps?", type: "number", key: "stepSets" }, { question: "What is the width of each step run?", type: "number", key: "stepWidth" }, { question: "What is the deck height off the ground?", type: "number", key: "deckHeight" }, { question: "Is there a landing?", type: "yesno", key: "hasLanding", followups: [ { question: "What size is the landing?", type: "text", key: "landingSize" } ] } ] }, { question: "Are we installing railing on the deck?", type: "yesno", key: "hasRailing", followups: [ { question: "Which sides of the deck will have railing? (Select all that apply)", type: "multiselect", options: ["Front", "Left", "Right", "Back"], key: "railingSides" } ] }, { question: "Do we need ADA handrail?", type: "yesno", key: "hasAdaRail", followups: [ { question: "Choose ADA handrail length", type: "select", options: ["8'8\"", "16'"], key: "adaRailLength" } ] }, { question: "Will the deck be skirted?", type: "yesno", key: "hasSkirting" }, { question: "Are we installing fascia?", type: "yesno", key: "hasFascia", followups: [ { question: "What color is the fascia?", type: "select", options: ["Black", "White", "Gray", "Brown", "Custom"], key: "fasciaColor" } ] }, { question: "Any customer preferences, township requirements, or special framing considerations?", type: "textarea", key: "notes" } ]; let currentQuestion = 0; let answers = {}; const questionContainer = document.getElementById('current-question'); const answerInput = document.getElementById('answer-input'); const nextBtn = document.getElementById('next-btn'); function renderQuestion(index) { const q = questions[index]; questionContainer.textContent = q.question; answerInput.innerHTML = ''; switch(q.type) { case 'number': answerInput.innerHTML = ` `; break; case 'yesno': answerInput.innerHTML = `
`; // Add click handlers for yes/no buttons document.querySelectorAll('.yesno-btn').forEach(btn => { btn.addEventListener('click', function() { document.querySelectorAll('.yesno-btn').forEach(b => b.classList.remove('bg-blue-500', 'text-white', 'border-blue-500')); this.classList.add('bg-blue-500', 'text-white', 'border-blue-500'); }); }); break; case 'select': answerInput.innerHTML = ` `; break; case 'multiselect': answerInput.innerHTML = `
${q.options.map(opt => ` `).join('')}
`; break; case 'textarea': answerInput.innerHTML = ` `; break; } } function collectAnswer() { const q = questions[currentQuestion]; let answer; switch(q.type) { case 'number': answer = parseFloat(document.getElementById('answer').value) || 0; break; case 'select': case 'textarea': answer = document.getElementById('answer').value; break; case 'yesno': const selectedBtn = document.querySelector('.yesno-btn.bg-blue-500'); answer = selectedBtn ? selectedBtn.dataset.value : null; break; case 'multiselect': answer = Array.from(document.querySelectorAll('input[type="checkbox"]:checked')) .map(cb => cb.value); break; } // Skip validation for notes question (it's optional) if (q.key === 'notes') { answers[q.key] = answer || ''; return true; } // Validate other questions if (answer === null || answer === undefined || (Array.isArray(answer) && answer.length === 0)) { return false; } // Store answer answers[q.key] = answer; return true; } nextBtn.addEventListener('click', function() { if (!collectAnswer()) { alert('Please provide an answer before continuing'); return; } currentQuestion++; if (currentQuestion < questions.length) { renderQuestion(currentQuestion); } else { // All questions answered - show material list section document.getElementById('question-container').classList.add('hidden'); document.getElementById('results-section').classList.remove('hidden'); } }); // Initialize first question renderQuestion(0); // Show stairs details if checkbox is checked on load if (document.getElementById('stairs').checked) { document.getElementById('stairs-details').classList.remove('hidden'); } // Toggle stairs details document.getElementById('stairs').addEventListener('change', function() { document.getElementById('stairs-details').classList.toggle('hidden', !this.checked); }); // Toggle railing details document.getElementById('railing').addEventListener('change', function() { // Add any railing-specific toggle logic here }); // Handle calculate button click document.getElementById('calculate-btn').addEventListener('click', function() { // First make sure all questions are answered while (currentQuestion < questions.length) { if (!collectAnswer()) { alert('Please answer all questions before calculating materials'); document.getElementById('question-container').classList.remove('hidden'); document.getElementById('results-section').classList.add('hidden'); renderQuestion(currentQuestion); return; } currentQuestion++; } // Generate and show results generateMaterialList(); document.getElementById('question-container').classList.add('hidden'); document.getElementById('results-section').classList.remove('hidden'); document.getElementById('results-section').scrollIntoView({ behavior: 'smooth' }); // Reset for next calculation currentQuestion = 0; answers = {}; renderQuestion(0); // Log answers for debugging console.log('User answers:', answers); }); function generateMaterialList() { // Get basic deck dimensions const width = answers.width || 10; // Default to 10ft if not provided const depth = answers.depth || 10; // Default to 10ft if not provided const deckArea = width * depth; // Get special features const boardType = document.getElementById('board-type').value; const hasStairs = document.getElementById('stairs').checked; const stepCount = parseInt(document.getElementById('step-count').value) || 0; const hasRailing = document.getElementById('railing').checked; const hasRoof = document.getElementById('roof').checked; // Calculate deck boards based on board type and dimensions const boardWidth = 5.5; // inches const boardSpacing = 0.25; // inches const effectiveWidth = boardWidth + boardSpacing; // Calculate number of boards needed along depth const boardsAlongDepth = Math.ceil((depth * 12) / effectiveWidth); // Calculate board lengths needed based on width let deckBoards; if (boardType === 'trex-grooved') { // Trex grooved comes in 12', 16', 20' lengths - optimize for least waste deckBoards = calculateOptimalBoards(width, boardsAlongDepth, [12, 16, 20]); } else if (boardType === 'trex-square') { // Trex square only in 20' deckBoards = calculateOptimalBoards(width, boardsAlongDepth, [20]); } else { // Wood/other composite - assume 12' boards deckBoards = calculateOptimalBoards(width, boardsAlongDepth, [12]); } // Calculate framing materials const joistSpacing = 16; // inches const joists = Math.ceil((width * 12) / joistSpacing) + 1; // +1 for rim joist const beams = Math.ceil(width / 8); const posts = Math.ceil(width / 6) * 2; // Posts on both sides const concrete = posts * 3; // 3 bags per post // Calculate fasteners const screwSpacing = 12; // inches along board length const screwsPerBoard = Math.ceil((depth * 12) / screwSpacing) * 2; // 2 screws per joist const totalScrews = screwsPerBoard * boardsAlongDepth; const fastenerBoxes = Math.ceil(totalScrews / 250); // 250 screws per box // Update results display document.getElementById('deck-boards').textContent = `${deckBoards.total} ${boardType.includes('trex') ? 'Trex' : boardType === 'wood' ? 'Wood' : 'Composite'} boards`; document.getElementById('joists').textContent = `${joists} (2×8 @ 16" OC)`; document.getElementById('beams').textContent = `${beams} (4×6)`; document.getElementById('posts').textContent = `${posts} (6×6)`; document.getElementById('concrete').textContent = `${concrete} bags`; document.getElementById('fasteners').textContent = `${fastenerBoxes} boxes`; // Add special features if checked if (hasStairs) { document.getElementById('deck-boards').textContent += ` + ${stepCount} stair treads`; document.getElementById('joists').textContent += ` + ${stepCount} stair stringers`; } if (hasRailing) { const railingLength = width * 2; // Assuming 2 sides const railingPosts = Math.ceil(railingLength / 6) * 2; // 6' max spacing, 2 per section document.getElementById('deck-boards').textContent += ` + ${Math.ceil(railingLength / 6)} railing sections`; document.getElementById('posts').textContent += ` + ${railingPosts} railing posts`; } if (hasRoof) { document.getElementById('joists').textContent += ` + ${Math.ceil(width / 2)} roof rafters`; document.getElementById('posts').textContent += ` + ${Math.ceil(width / 8) * 2} roof support posts`; } } function calculateOptimalBoards(width, count, availableLengths) { availableLengths.sort((a, b) => b - a); // Sort descending const result = { total: 0, lengths: {} }; for (let i = 0; i < count; i++) { let remaining = width; let currentCut = {}; while (remaining > 0) { for (const len of availableLengths) { if (len <= remaining) { currentCut[len] = (currentCut[len] || 0) + 1; remaining -= len; break; } } } // Aggregate the lengths for (const [len, qty] of Object.entries(currentCut)) { result.lengths[len] = (result.lengths[len] || 0) + qty; result.total += qty; } } return result; } });