2328 Eastwood Ct, Spearfish, SD 57783

2328 Eastwood Ct, Spearfish, SD 57783

$1,495,000 $463/sq ft
3 Beds
4.0 Baths
3,231 Sq Ft
Listed 2 weeks ago FSBO-PRO #FSBO0017451
FSBO.PRO Protection

All inquiries and showings are handled securely through our platform.

About This Property

HOME FOR SALE BY OWNER WITH ATTACHED SHOP

The Woodlands • Spearfish, South Dakota

Welcome to one of Spearfish’s most desirable neighborhoods — The Woodlands. Built in 2023, this custom single-level ranch-style home offers 3,231 square feet of living space, plus an impressive 2,640 square feet of combined garage and shop space.

The home features a distinctive timber-framed entrance, thoughtful finishes, functional spaces, and plenty of room for entertaining.

PRIMARY SUITE

The spacious primary suite features:

* Large en-suite bathroom
* Standalone soaking tub
* Custom tile shower
* Expansive walk-in closet
* Private laundry area

BEDROOMS & FLEXIBLE LIVING SPACE

The home offers 2–3 additional bedrooms, several with their own en-suite bathrooms. One bedroom is currently set up as a game room, providing flexibility to use the space as a bedroom, media room, office, or additional living area.

A convenient Jack-and-Jill-style laundry arrangement connects the additional bedroom spaces.

GOURMET KITCHEN & ENTERTAINING

The heart of the home is the spacious kitchen, featuring:

* Custom Alder cabinetry
* Full-height tile backsplash
* Cambria quartz countertops
* Double ovens
* Induction cooktop
* Full-size refrigerator
* Full-size freezer
* Ice maker
* Beverage refrigerator
* Walk-in pantry

The kitchen opens to the large dining and entertaining areas, creating an ideal space for gatherings.

FUNCTIONAL SPACES

Additional features include:

* Spacious mudroom with custom locker system
* Dedicated home gym
* Large dining room
* Beverage bar
* Generous storage
* Three-stall garage
* Attached shop
* 2,640 sq. ft. of combined garage and shop space

OUTDOOR LIVING

Enjoy the Black Hills lifestyle with extensive outdoor entertaining space, including:

* Large stamped-concrete patios
* Built-in outdoor kitchen
* Hot tub
* Garden
* Multiple outdoor entertaining areas

Located in The Woodlands, this home offers an exceptional combination of space, functionality, and location in one of Spearfish’s most sought-after neighborhoods.

FOR SALE BY OWNER

A rare opportunity to own a 2023-built, 3,231 sq. ft. single-level ranch-style home with a timber-framed entrance and 2,640 sq. ft. of combined garage and shop space in The Woodlands.

**Private showings available by appointment.**
Call Andy 605-490-7953 or Jamie 605-580-0602
vandeestjamie@gmail.com

Property Type Single Family
Year Built 2023
Lot Size 2.50 Acres

Features & Amenities

Interior Features

  • Basement (not specified)
  • Garage

Location

2328 Eastwood Ct, Spearfish, SD 57783, Spearfish, SD 57783

Property Owner

PO

Property Owner

Property Owner

Login to Contact

Contact the Owner

Why Buy This Home?

  • Listed directly by the owner – save on fees
  • Well maintained and move-in ready
  • Direct communication with the owner
  • Flexible showing schedule
  • Great location and neighborhood

Estimated Monthly Payment

*non-binding estimate only

$9,552/mo
Principal & Interest $7,957
Property Taxes $1,495
Home Insurance $100
View Full Estimate Calculator

Mobile Number Not Matched

The mobile number you entered does not match the phone number associated with this property listing.

Please check your number Make sure you are using the mobile number registered with the property.
// ============================================================ // Schedule Showing Modal + Seller Availability // ============================================================ let sellerAvailabilitySlots = []; let availabilityMode = 'flexible'; let availabilityLoaded = false; let showingDatePicker = null; /** * Return local browser date as YYYY-MM-DD. */ function getLocalDateString(date = new Date()) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } /** * Convert HH:MM or HH:MM:SS to total minutes. */ function timeToMinutes(time) { if (!time) { return 0; } const parts = time.split(':'); return ( parseInt(parts[0], 10) * 60 + parseInt(parts[1], 10) ); } /** * Convert 24-hour time to AM/PM display. */ function formatTime(time) { if (!time) { return ''; } const parts = time.split(':'); let hour = parseInt(parts[0], 10); const minute = parts[1] || '00'; const suffix = hour >= 12 ? 'PM' : 'AM'; hour = hour % 12; if (hour === 0) { hour = 12; } return `${hour}:${minute} ${suffix}`; } /** * Display a seller availability slot as: * 9:10 PM - 9:40 PM */ function formatSlotTime(startTime, endTime) { return `${formatTime(startTime)} - ${formatTime(endTime)}`; } /** * Open Schedule Showing Modal. */ function openShowingModal() { document.getElementById('showingModal').classList.add('active'); document.body.style.overflow = 'hidden'; } /** * Close Schedule Showing Modal. */ function closeShowingModal() { document.getElementById('showingModal').classList.remove('active'); document.body.style.overflow = 'auto'; } /** * Load future active seller availability for this listing. * * If future seller slots exist: * - only seller dates are allowed * - only seller times for selected date are shown * * If no future seller slots exist: * - flexible date/time selection is enabled */ async function loadSellerAvailability() { const dateInput = document.getElementById('showingDate'); const timeSelect = document.getElementById('showingTime'); const hint = document.getElementById('availabilityHint'); if (!dateInput || !timeSelect) { return; } sellerAvailabilitySlots = []; availabilityMode = 'flexible'; availabilityLoaded = false; dateInput.disabled = true; timeSelect.disabled = true; dateInput.value = ''; timeSelect.innerHTML = ''; if (hint) { hint.textContent = 'Checking seller availability...'; } try { const formData = new FormData(); formData.append('action', 'get_available_slots'); formData.append('listing_id', '17451'); const response = await fetch( 'https://fsbo.pro/api/availability-handler.php', { method: 'POST', body: formData } ); if (!response.ok) { throw new Error('Availability request failed.'); } const data = await response.json(); if (!data.success) { throw new Error( data.message || 'Unable to load seller availability.' ); } sellerAvailabilitySlots = Array.isArray(data.slots) ? data.slots : []; /* * Remove any slot that has already started. * This is especially important when the selected * date is today. */ const now = new Date(); const today = getLocalDateString(now); const currentMinutes = now.getHours() * 60 + now.getMinutes(); sellerAvailabilitySlots = sellerAvailabilitySlots.filter(slot => { if (slot.available_date > today) { return true; } if (slot.available_date < today) { return false; } return timeToMinutes(slot.start_time) > currentMinutes; }); if (sellerAvailabilitySlots.length > 0) { availabilityMode = 'seller'; setupSellerAvailability(); } else { availabilityMode = 'flexible'; setupFlexibleAvailability(); } availabilityLoaded = true; } catch (error) { console.error('Seller availability error:', error); /* * If the availability API cannot be reached, keep the * form usable by falling back to flexible showing mode. */ availabilityMode = 'flexible'; sellerAvailabilitySlots = []; setupFlexibleAvailability(); if (hint) { hint.textContent = 'Seller availability could not be loaded. You may request a future date and time.'; } } } /** * Setup date selection when seller has future availability. */ function setupSellerAvailability() { const dateInput = document.getElementById('showingDate'); const timeSelect = document.getElementById('showingTime'); const hint = document.getElementById('availabilityHint'); if (!dateInput || !timeSelect) { return; } /* * Build unique available dates. Only these dates will be * enabled in the calendar. Every other date is disabled. */ const availableDates = [ ...new Set( sellerAvailabilitySlots .map(slot => slot.available_date) .filter(Boolean) ) ].sort(); if (availableDates.length === 0) { setupFlexibleAvailability(); return; } dateInput.disabled = false; /* Remove the native browser date picker and create our custom picker. */ if (showingDatePicker) { showingDatePicker.destroy(); showingDatePicker = null; } /* * IMPORTANT: * Native cannot disable arbitrary dates. * Flatpickr can, so only sellerAvailability dates are enabled. */ if (typeof flatpickr !== 'undefined') { showingDatePicker = flatpickr(dateInput, { dateFormat: 'Y-m-d', altInput: true, altFormat: 'd-m-Y', allowInput: false, disableMobile: true, enable: availableDates, defaultDate: availableDates[0], minDate: availableDates[0], maxDate: availableDates[availableDates.length - 1], onChange: function(selectedDates, dateStr) { if (!availableDates.includes(dateStr)) { this.setDate(availableDates[0], true); return; } populateSellerTimes(dateStr); } }); } else { /* Fallback if the Flatpickr library fails to load. */ dateInput.type = 'date'; dateInput.min = availableDates[0]; dateInput.max = availableDates[availableDates.length - 1]; dateInput.value = availableDates[0]; dateInput.onchange = function() { if (!availableDates.includes(this.value)) { alert('Please select one of the dates when the seller is available.'); this.value = availableDates[0]; } populateSellerTimes(this.value); }; } if (hint) { hint.innerHTML = 'Seller availability: Only dates with seller availability are selectable. Please choose an available time slot.'; } populateSellerTimes(availableDates[0]); } /** * Populate seller time slots for the selected date. */ function populateSellerTimes(selectedDate) { const timeSelect = document.getElementById('showingTime'); if (!timeSelect) { return; } timeSelect.innerHTML = ''; timeSelect.disabled = true; if (!selectedDate) { timeSelect.innerHTML = ''; return; } let slots = sellerAvailabilitySlots.filter(slot => slot.available_date === selectedDate ); /* * On today's date, only slots whose START TIME is still * in the future can be selected. */ const now = new Date(); const today = getLocalDateString(now); const currentMinutes = now.getHours() * 60 + now.getMinutes(); slots = slots.filter(slot => { if (slot.available_date > today) { return true; } if (slot.available_date < today) { return false; } return timeToMinutes(slot.start_time) > currentMinutes; }); if (slots.length === 0) { timeSelect.innerHTML = ` `; return; } timeSelect.innerHTML = ''; slots.forEach(slot => { const option = document.createElement('option'); /* * Submit the seller slot START TIME. * Example: * 21:10:00 - 21:40:00 * submitted value = 21:10 */ option.value = slot.start_time.substring(0, 5); option.textContent = formatSlotTime( slot.start_time, slot.end_time ); option.dataset.slotId = slot.id || ''; timeSelect.appendChild(option); }); timeSelect.disabled = false; } /** * Setup flexible showing mode when there are no future * active seller availability slots. */ function setupFlexibleAvailability() { const dateInput = document.getElementById('showingDate'); const timeSelect = document.getElementById('showingTime'); const hint = document.getElementById('availabilityHint'); if (!dateInput || !timeSelect) { return; } const today = getLocalDateString(); dateInput.disabled = false; /* Destroy seller-only calendar before switching to flexible mode. */ if (showingDatePicker) { showingDatePicker.destroy(); showingDatePicker = null; } if (typeof flatpickr !== 'undefined') { showingDatePicker = flatpickr(dateInput, { dateFormat: 'Y-m-d', altInput: true, altFormat: 'd-m-Y', allowInput: false, disableMobile: true, minDate: today, defaultDate: today, onChange: function(selectedDates, dateStr) { populateFlexibleTimes(dateStr); } }); } else { /* Fallback to the native date input. */ dateInput.type = 'date'; dateInput.min = today; dateInput.removeAttribute('max'); dateInput.value = today; dateInput.onchange = function() { populateFlexibleTimes(this.value); }; } if (hint) { hint.innerHTML = 'Flexible showing: The seller has no future availability scheduled. You may request any future date and time.'; } populateFlexibleTimes(today); } /** * Populate flexible showing times. * * Times are generated every 30 minutes. * For today, only times after the current time are shown. * For future dates, all 30-minute times are available. */ function populateFlexibleTimes(selectedDate) { const timeSelect = document.getElementById('showingTime'); if (!timeSelect) { return; } timeSelect.innerHTML = ''; timeSelect.disabled = true; if (!selectedDate) { return; } const now = new Date(); const today = getLocalDateString(now); const currentMinutes = now.getHours() * 60 + now.getMinutes(); for (let minutes = 0; minutes < 24 * 60; minutes += 30) { /* * For today, do not offer a past/current time. */ if ( selectedDate === today && minutes <= currentMinutes ) { continue; } const hour = Math.floor(minutes / 60); const minute = minutes % 60; const value = String(hour).padStart(2, '0') + ':' + String(minute).padStart(2, '0'); const option = document.createElement('option'); option.value = value; option.textContent = formatTime(value); timeSelect.appendChild(option); } timeSelect.disabled = timeSelect.options.length <= 1; } /** * Validate selected showing date/time before submission. * This is frontend validation only. The API must ALSO validate * the request server-side. */ function validateShowingSelection() { const dateInput = document.getElementById('showingDate'); const timeSelect = document.getElementById('showingTime'); if (!dateInput || !timeSelect) { return false; } const selectedDate = dateInput.value; const selectedTime = timeSelect.value; if (!selectedDate) { alert('Please select a showing date.'); return false; } if (!selectedTime) { alert('Please select a showing time.'); return false; } const now = new Date(); const today = getLocalDateString(now); const currentMinutes = now.getHours() * 60 + now.getMinutes(); /* Never allow a past date. */ if (selectedDate < today) { alert('Please select today or a future date.'); return false; } /* Never allow current/past time today. */ if ( selectedDate === today && timeToMinutes(selectedTime) <= currentMinutes ) { alert('Please select a time after the current time.'); return false; } /* * Seller availability mode: * the selected date/time must match a future seller slot. */ if (availabilityMode === 'seller') { const matchingSlot = sellerAvailabilitySlots.find(slot => { return ( slot.available_date === selectedDate && selectedTime === slot.start_time.substring(0, 5) ); }); if (!matchingSlot) { alert( 'The selected date/time is not available. Please choose one of the seller\'s available showing slots.' ); return false; } } return true; } // ============================================================ // Submit Showing Request // ============================================================ function submitShowingRequest(e) { e.preventDefault(); /* * Validate before sending the request. */ if (!validateShowingSelection()) { return; } const form = e.target; const formData = new FormData(form); formData.append('action', 'schedule_showing'); formData.append('listing_id', '17451'); formData.append( 'property_address', '2328 Eastwood Ct, Spearfish, SD 57783' ); const btn = form.querySelector('button[type="submit"]'); const originalHTML = btn.innerHTML; /* Show loader */ btn.disabled = true; btn.classList.add('loading'); btn.innerHTML = ` Submitting... `; fetch('https://fsbo.pro/api/showing-handler.php', { method: 'POST', body: formData }) .then(r => r.json()) .then(data => { if (data.success) { /* Show success message */ const successMsg = document.createElement('div'); successMsg.style.cssText = 'background: linear-gradient(135deg, #d4edda, #c3e6cb); color: #155724; padding: 20px; border-radius: 12px; margin-bottom: 20px; text-align: center; animation: slideDown 0.4s ease;'; successMsg.innerHTML = `

Showing Request Sent!

${data.message}

`; form.parentNode.insertBefore(successMsg, form); form.reset(); /* * Restore showing controls after reset so that opening * the modal again reloads current seller availability. */ const dateInput = document.getElementById('showingDate'); const timeSelect = document.getElementById('showingTime'); if (dateInput) { if (showingDatePicker) { showingDatePicker.clear(); } else { dateInput.value = ''; } } if (timeSelect) { timeSelect.innerHTML = ''; timeSelect.disabled = true; } setTimeout(() => { closeShowingModal(); setTimeout(() => successMsg.remove(), 300); }, 3000); } else { alert( data.message || 'Error submitting request. Please try again.' ); } /* Restore button */ btn.disabled = false; btn.classList.remove('loading'); btn.innerHTML = originalHTML; }) .catch(() => { alert('Error submitting request. Please try again.'); btn.disabled = false; btn.classList.remove('loading'); btn.innerHTML = originalHTML; }); } // Calculator Modal function openCalculatorModal() { document.getElementById('calculatorModal').classList.add('active'); document.body.style.overflow = 'hidden'; calculatePayment(); // Recalculate on open } function closeCalculatorModal() { document.getElementById('calculatorModal').classList.remove('active'); document.body.style.overflow = 'auto'; } // Result Modal Function // // IMPORTANT: // This modal intentionally uses normal string concatenation instead of // JavaScript template literals. This prevents literal ${title}, ${icon} // and ${message} from appearing in the browser if another script/template // processor interferes with backtick/template-literal parsing. function showResultModal(type, title, message) { // Remove an existing result modal first. const oldModal = document.getElementById('resultModal'); if (oldModal) { oldModal.remove(); } const modal = document.createElement('div'); modal.className = 'custom-modal active'; modal.id = 'resultModal'; const isError = type === 'error'; const headerBackground = isError ? 'linear-gradient(135deg, #dc3545, #c82333)' : 'linear-gradient(135deg, #28a745, #218838)'; const icon = isError ? '' + '' + '' + '' + '' : '' + '' + '' + '' + ''; // Escape user/API text before inserting it into innerHTML. function escapeHtml(value) { return String(value == null ? '' : value) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } const safeTitle = escapeHtml(title || 'Message'); const safeMessage = escapeHtml(message || ''); modal.innerHTML = '' + ''; document.body.appendChild(modal); } function closeResultModal() { const modal = document.getElementById('resultModal'); if (modal) { modal.remove(); } } // Calculator functions function calculatePayment() { const price = parseFloat(document.getElementById('calcPrice').value) || 0; const downPayment = parseFloat(document.getElementById('calcDownPayment').value) || 0; const interestRate = parseFloat(document.getElementById('calcInterest').value) || 0; const term = parseInt(document.getElementById('calcTerm').value) || 30; const annualTax = parseFloat(document.getElementById('calcTax').value) || 0; const annualInsurance = parseFloat(document.getElementById('calcInsurance').value) || 0; const monthlyHOA = parseFloat(document.getElementById('calcHOA').value) || 0; const loanAmount = price - downPayment; const monthlyRate = (interestRate / 100) / 12; const months = term * 12; // Calculate monthly P&I let monthlyPI = 0; if (monthlyRate > 0) { monthlyPI = loanAmount * (monthlyRate * Math.pow(1 + monthlyRate, months)) / (Math.pow(1 + monthlyRate, months) - 1); } else { monthlyPI = loanAmount / months; } const monthlyTax = annualTax / 12; const monthlyInsurance = annualInsurance / 12; const totalMonthly = monthlyPI + monthlyTax + monthlyInsurance + monthlyHOA; // Update display document.getElementById('calcTotalPayment').innerHTML = `$${Math.round(totalMonthly).toLocaleString()}/mo`; document.getElementById('calcPI').textContent = `$${Math.round(monthlyPI).toLocaleString()}`; document.getElementById('calcTaxMonthly').textContent = `$${Math.round(monthlyTax).toLocaleString()}`; document.getElementById('calcInsuranceMonthly').textContent = `$${Math.round(monthlyInsurance).toLocaleString()}`; document.getElementById('calcHOAMonthly').textContent = `$${Math.round(monthlyHOA).toLocaleString()}`; document.getElementById('calcLoanAmount').textContent = `$${Math.round(loanAmount).toLocaleString()}`; document.getElementById('calcTotalInterest').textContent = `$${Math.round((monthlyPI * months) - loanAmount).toLocaleString()}`; document.getElementById('calcTotalCost').textContent = `$${Math.round(monthlyPI * months).toLocaleString()}`; // Update sidebar display document.getElementById('totalPayment').innerHTML = `$${Math.round(totalMonthly).toLocaleString()}/mo`; document.getElementById('piAmount').textContent = `$${Math.round(monthlyPI).toLocaleString()}`; document.getElementById('taxAmount').textContent = `$${Math.round(monthlyTax).toLocaleString()}`; document.getElementById('insuranceAmount').textContent = `$${Math.round(monthlyInsurance).toLocaleString()}`; } function updateDownPaymentPercent() { const price = parseFloat(document.getElementById('calcPrice').value) || 0; const downPayment = parseFloat(document.getElementById('calcDownPayment').value) || 0; const percent = (downPayment / price) * 100; document.getElementById('calcDownPercent').value = Math.round(percent); calculatePayment(); } function updateDownPaymentAmount() { const price = parseFloat(document.getElementById('calcPrice').value) || 0; const percent = parseFloat(document.getElementById('calcDownPercent').value) || 0; const downPayment = (price * percent) / 100; document.getElementById('calcDownPayment').value = Math.round(downPayment); calculatePayment(); } // Close modals on escape key document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { closeShowingModal(); closeCalculatorModal(); } }); // Contact form submission function submitContactForm(e) { e.preventDefault(); const form = e.target; const formData = new FormData(form); formData.append('action', 'submit_contact'); formData.append('listing_id', 'VnNaN0VlQVp5TUlhSmQrbUlzSTZTdz09'); const btn = form.querySelector('button[type="submit"]'); const btnIcon = btn.querySelector('svg'); const btnText = btn.childNodes[btn.childNodes.length - 1]; // Save original state const originalIcon = btnIcon.outerHTML; const originalText = btnText.textContent; // Show loader btn.disabled = true; btn.classList.add('loading'); btnIcon.outerHTML = ` `; btnText.textContent = ' SENDING...'; fetch('https://fsbo.pro/api/messaging-handler.php', { method: 'POST', body: formData }) .then(r => r.json()) .then(data => { if (data.success) { // Show success message const successMsg = document.createElement('div'); successMsg.style.cssText = 'background: linear-gradient(135deg, #d4edda, #c3e6cb); color: #155724; padding: 16px 20px; border-radius: 12px; margin-bottom: 16px; border: 1px solid #c3e6cb; animation: slideDown 0.4s ease; box-shadow: 0 4px 12px rgba(21,87,36,0.15); display: flex; align-items: center; gap: 12px;'; successMsg.innerHTML = `
Message Sent Successfully! ${data.message}
`; form.parentNode.insertBefore(successMsg, form); form.reset(); setTimeout(() => { successMsg.style.animation = 'slideUp 0.3s ease'; setTimeout(() => successMsg.remove(), 300); }, 5000); } else { showResultModal('error', 'Error', data.message || 'Error sending message. Please try again.'); } // Restore button btn.disabled = false; btn.classList.remove('loading'); btn.innerHTML = originalIcon + originalText; }) .catch(() => { showResultModal('error', 'Error', 'Error sending message. Please try again.'); btn.disabled = false; btn.classList.remove('loading'); btn.innerHTML = originalIcon + originalText; }); } // Initialize Google Maps (if coordinates available) function initMap() { const propertyLocation = { lat: 44.49171000, lng: -103.81925000 }; const map = new google.maps.Map(document.getElementById('propertyMap'), { zoom: 15, center: propertyLocation, mapTypeControl: false, streetViewControl: false, fullscreenControl: true }); new google.maps.Marker({ position: propertyLocation, map: map, title: '2328 Eastwood Ct, Spearfish, SD 57783' }); } // Load Google Maps API const script = document.createElement('script'); script.src = 'https://maps.googleapis.com/maps/api/js?key=AIzaSyAFkzYDINVUGTnNI4HK-ADcOAyQUgXD8Hw&callback=initMap'; script.async = true; script.defer = true; document.head.appendChild(script);