Sky Drains - Modern Trenchless Repair Estimator
SKY DRAINS
The Modern Solution for Trenchless Sewer Repair
### 2. `style.css` (The Design)
```css
/* --- ROOT VARIABLES & GLOBAL STYLES --- */
:root {
--bg-dark-primary: #0a0f1e; /* Deep blue-black */
--bg-dark-secondary: #1a2a45; /* Dark blue-gray */
--primary-accent: #87CEEB; /* Soft Sky Blue */
--primary-accent-dark: #6495ED; /* Deeper Cornflower Blue */
--glass-bg: rgba(20, 30, 55, 0.7); /* More solid, darker glass */
--glass-border: rgba(135, 206, 235, 0.2); /* Soft blue border */
--text-primary: #FFFFFF; /* Pure white for max contrast */
--text-secondary: #B0C4DE; /* Light steel blue */
--text-heading: #FFFFFF;
--error-red: #ff6b6b;
--success-green: #90EE90;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Poppins', sans-serif;
font-weight: 400;
background-color: var(--bg-dark-primary);
color: var(--text-primary);
line-height: 1.6;
overflow-x: hidden;
}
.background-animation {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
background: linear-gradient(230deg, var(--bg-dark-primary), var(--bg-dark-secondary), var(--bg-dark-primary));
background-size: 400% 400%;
animation: gradientShift 18s ease infinite;
}
@keyframes gradientShift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
h1, h2, h3 {
font-family: 'Poppins', sans-serif;
font-weight: 600;
color: var(--text-heading);
margin-bottom: 0.5rem;
}
h2 {
font-size: 1.8rem;
color: var(--primary-accent);
display: flex;
align-items: center;
border-bottom: 1px solid var(--glass-border);
padding-bottom: 1rem;
margin-bottom: 1.5rem;
}
.step-num {
font-size: 1.2rem;
font-weight: 700;
color: var(--bg-dark-primary);
background-color: var(--primary-accent);
border-radius: 50%;
width: 35px;
height: 35px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-right: 1rem;
}
.step-sub {
font-size: 0.95rem;
color: var(--text-secondary);
margin-bottom: 1.5rem;
max-width: 600px;
font-weight: 300;
}
/* --- MAIN ESTIMATOR CONTAINER --- */
.estimator-container {
max-width: 800px;
margin: 3rem auto;
padding: 2.5rem;
background: var(--glass-bg);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid var(--glass-border);
border-radius: 16px;
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease;
}
.main-header {
text-align: center;
margin-bottom: 2.5rem;
}
.main-header .logo-svg {
width: 60px;
height: 60px;
color: var(--primary-accent);
margin-bottom: 0.5rem;
}
.main-header h1 {
font-size: 2.5rem;
font-weight: 700;
color: var(--text-heading);
letter-spacing: 1px;
}
.main-header p {
font-size: 1.1rem;
font-weight: 300;
color: var(--primary-accent);
}
/* --- FORM STEPS --- */
.form-step {
display: none;
animation: fadeIn 0.5s ease-in-out;
}
.form-step.active {
display: block;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* --- CARD OPTIONS (Service & Price) --- */
.card-option-group {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
}
.card-option-group input[type="radio"] {
display: none;
}
.card-option {
display: block;
background: rgba(255, 255, 255, 0.03);
border: 1px solid var(--glass-border);
border-radius: 12px;
padding: 1.5rem;
cursor: pointer;
transition: all 0.2s ease-out;
position: relative;
}
.card-option:hover {
background: rgba(255, 255, 255, 0.08);
transform: translateY(-3px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
}
.card-option-group input[type="radio"]:checked + .card-option {
background: rgba(135, 206, 235, 0.1);
border: 2px solid var(--primary-accent);
padding: calc(1.5rem - 1px);
}
.card-option h3 {
color: var(--text-heading);
font-size: 1.2rem;
font-weight: 600;
}
.card-option p {
color: var(--text-secondary);
font-size: 0.9rem;
font-weight: 300;
}
.price-card {
text-align: center;
}
.price-tag {
display: block;
font-family: 'Poppins', sans-serif;
font-size: 2.2rem;
font-weight: 700;
color: var(--success-green);
margin-bottom: 0.5rem;
}
/* --- INPUT FIELDS & GROUPS --- */
.input-group {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.5rem;
margin-bottom: 1.5rem;
}
/* ::: NEW: 3-Column Grid ::: */
.input-group.three-col {
grid-template-columns: 2fr 1fr 1.2fr;
gap: 1rem;
}
.input-field {
margin-bottom: 1.5rem;
position: relative;
}
/* ::: NEW: Margin-Bottom Utility ::: */
.input-field.no-mb {
margin-bottom: 0;
}
#step-2 .input-field {
margin-bottom: 0;
}
.input-field label {
display: block;
font-weight: 600;
margin-bottom: 0.5rem;
color: var(--text-secondary);
font-size: 0.9rem;
}
.field-description {
font-size: 0.85rem;
font-weight: 300;
color: var(--text-secondary);
margin-top: -0.25rem;
margin-bottom: 0.5rem;
}
input[type="number"],
input[type="tel"],
input[type="text"],
input[type="email"],
select {
width: 100%;
padding: 0.8rem 1rem;
font-size: 1rem;
font-family: 'Poppins', sans-serif;
font-weight: 400;
color: var(--text-primary);
background: rgba(0, 0, 0, 0.5);
border: 1px solid var(--glass-border);
border-radius: 8px;
transition: all 0.2s ease;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
}
select {
background-image: url('data:image/svg+xml;utf8,
');
background-repeat: no-repeat;
background-position: right 0.7rem top 50%;
background-size: 1.5rem;
padding-right: 2.5rem;
}
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type="number"] {
-moz-appearance: textfield;
}
input[type="number"]:focus,
input[type="tel"]:focus,
input[type="text"]:focus,
input[type="email"]:focus,
select:focus {
outline: none;
border-color: var(--primary-accent);
box-shadow: 0 0 0 2px var(--primary-accent);
}
input[readonly] {
background-color: rgba(0, 0, 0, 0.2);
color: var(--text-secondary);
cursor: not-allowed;
}
.input-field.error input,
.input-field.error select,
.input-field.error .time-picker-group {
border-color: var(--error-red);
box-shadow: 0 0 0 2px var(--error-red);
}
.checkbox-group.error label {
color: var(--error-red);
}
.input-field.error #card-element {
border-color: var(--error-red);
box-shadow: 0 0 0 2px var(--error-red);
}
.input-unit {
position: absolute;
right: 1rem;
top: 50%;
transform: translateY(5px);
color: var(--text-secondary);
font-weight: 600;
}
/* --- SEGMENTED CONTROL --- */
.segmented-control {
display: flex;
width: 100%;
border: 1px solid var(--glass-border);
border-radius: 10px;
overflow: hidden;
}
.segmented-control input[type="radio"] { display: none; }
.segmented-control label {
flex: 1;
padding: 0.8rem 1rem;
text-align: center;
cursor: pointer;
background: rgba(0, 0, 0, 0.3);
color: var(--text-secondary);
transition: all 0.2s ease;
margin: 0;
font-weight: 400;
}
.segmented-control label:not(:last-child) {
border-right: 1px solid var(--glass-border);
}
.segmented-control input[type="radio"]:checked + label {
background: var(--primary-accent);
color: var(--bg-dark-primary);
font-weight: 600;
}
/* --- BUTTONS --- */
.cta-button {
display: block;
width: 100%;
padding: 1rem;
font-family: 'Poppins', sans-serif;
font-size: 1.1rem;
font-weight: 700;
color: var(--bg-dark-primary);
background: var(--primary-accent);
border: none;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 1px;
margin-top: 1.5rem;
}
.cta-button:hover:not(:disabled) {
background: var(--text-primary);
color: var(--bg-dark-primary);
transform: translateY(-2px);
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.2);
}
.cta-button:disabled {
background: var(--bg-dark-secondary);
color: var(--text-secondary);
cursor: not-allowed;
opacity: 0.6;
box-shadow: none;
transform: none;
}
.button-group {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
margin-top: 1.5rem;
}
.button-group .cta-button {
flex-grow: 1;
width: auto;
margin-top: 0;
}
.button-group .back-button {
background: none;
border: none;
color: var(--text-secondary);
cursor: pointer;
font-family: 'Poppins', sans-serif;
font-weight: 600;
font-size: 0.9rem;
padding: 1rem;
border-radius: 10px;
transition: all 0.2s ease;
flex-grow: 0;
flex-shrink: 0;
text-transform: uppercase;
letter-spacing: 1px;
}
.button-group .back-button:hover {
color: var(--primary-accent);
background: rgba(255, 255, 255, 0.05);
}
/* --- VERIFICATION & FINAL STEP --- */
.hidden { display: none !important; }
#code-entry-section { margin-top: 2rem; }
hr {
border: none;
height: 1px;
background-color: var(--glass-border);
margin-bottom: 2rem;
}
.error-text {
color: var(--error-red);
text-align: center;
margin-top: 1rem;
font-weight: 600;
}
.form-step > .error-text {
margin-top: -0.5rem;
margin-bottom: 1.5rem;
text-align: left;
}
/* --- MOCK PHONE CONSOLE --- */
#mock-phone-console {
background: #000000;
border: 1px solid var(--glass-border);
border-radius: 12px;
margin: 2rem 0;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
.console-header {
background: rgba(255, 255, 255, 0.1);
padding: 0.5rem 1rem;
font-weight: 600;
border-bottom: 1px solid var(--glass-border);
border-radius: 12px 12px 0 0;
color: var(--primary-accent);
}
.console-body {
padding: 1.5rem;
height: 200px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.console-message {
background: var(--primary-accent-dark);
color: white;
padding: 0.8rem 1rem;
border-radius: 20px 20px 20px 5px;
align-self: flex-start;
max-width: 80%;
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from { opacity: 0; transform: translateX(-20px); }
to { opacity: 1; transform: translateX(0); }
}
/* --- LENDING/SCHEDULE FORM STYLES --- */
.form-section {
border-bottom: 1px solid var(--glass-border);
padding-bottom: 1.5rem;
margin-bottom: 1.5rem;
}
.form-section:last-of-type {
border-bottom: none;
padding-bottom: 0;
}
.form-section h3 {
color: var(--text-heading);
font-weight: 600;
margin-bottom: 1rem;
}
.form-section > label {
display: block;
font-weight: 600;
margin-bottom: 0.5rem;
color: var(--text-secondary);
font-size: 0.9rem;
}
.radio-group {
display: flex;
align-items: center;
margin-bottom: 0.75rem;
}
.radio-group label {
margin-left: 0.75rem;
font-weight: 400;
color: var(--text-primary);
}
input[type="radio"] {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border: 2px solid var(--primary-accent);
border-radius: 50%;
cursor: pointer;
transition: all 0.2s ease;
flex-shrink: 0;
}
input[type="radio"]:checked {
background-color: var(--primary-accent);
border: 2px solid var(--primary-accent);
box-shadow: inset 0 0 0 4px var(--bg-dark-primary);
}
.other-group {
display: flex;
align-items: center;
}
.other-group .other-text-input {
flex-grow: 1;
margin-left: 0.5rem;
}
.other-text-input:disabled {
background: rgba(0, 0, 0, 0.2);
opacity: 0.5;
}
.terms-section .terms-text {
font-size: 0.8rem;
font-weight: 300;
color: var(--text-secondary);
background: rgba(0, 0, 0, 0.2);
border: 1px solid var(--glass-border);
border-radius: 8px;
padding: 1rem;
height: 150px;
overflow-y: auto;
margin-bottom: 1rem;
}
.checkbox-group {
display: flex;
align-items: flex-start;
padding: 0.5rem;
border-radius: 8px;
transition: all 0.2s ease;
}
.checkbox-group.error {
background-color: rgba(255, 107, 107, 0.1);
border: 1px solid var(--error-red);
}
.checkbox-group label {
margin-left: 0.75rem;
font-size: 0.9rem;
color: var(--text-secondary);
line-height: 1.4;
}
input[type="checkbox"] {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border: 2px solid var(--primary-accent);
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
margin-top: 1px;
flex-shrink: 0;
}
input[type="checkbox"]:checked {
background-color: var(--primary-accent);
background-image: url('data:image/svg+xml;utf8,
');
background-size: 18px;
background-position: center;
background-repeat: no-repeat;
}
/* --- E-SIGNATURE STYLES --- */
.signature-display-box {
width: 100%;
height: 150px;
background: rgba(0, 0, 0, 0.2);
border: 1px dashed var(--glass-border);
border-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
font-size: 3.5rem;
color: var(--text-primary);
margin-bottom: 1.5rem;
padding: 1rem;
transition: font-family 0.3s ease;
overflow: hidden;
text-align: center;
}
.font-dancing { font-family: 'Dancing Script', cursive; font-size: 3rem; }
.font-great-vibes { font-family: 'Great Vibes', cursive; font-size: 3.5rem; }
.font-sacramento { font-family: 'Sacramento', cursive; font-size: 4rem; }
.segmented-control.signature-fonts label {
transition: font-family 0.3s ease;
}
/* --- TIME PICKER STYLES --- */
.time-picker-group {
display: flex;
align-items: center;
gap: 0.5rem;
background: rgba(0, 0, 0, 0.5);
border: 1px solid var(--glass-border);
border-radius: 8px;
padding: 0.8rem 1rem;
transition: all 0.2s ease;
}
.time-picker-group:focus-within {
border-color: var(--primary-accent);
box-shadow: 0 0 0 2px var(--primary-accent);
}
.time-picker-group input[type="number"] {
width: 3.5rem;
text-align: center;
background: none;
border: none;
padding: 0;
box-shadow: none;
font-size: 1.1rem;
}
.time-picker-group input[type="number"]:focus {
box-shadow: none;
}
.time-picker-group span {
font-size: 1.1rem;
font-weight: 600;
color: var(--text-secondary);
}
.time-picker-group select {
width: auto;
background: none;
border: none;
padding: 0;
box-shadow: none;
font-size: 1.1rem;
font-weight: 600;
background-position: right 0 top 50%;
padding-right: 1.8rem;
}
/* --- PAYMENT FORM STYLES --- */
.payment-amount-display {
font-size: 1.5rem;
font-weight: 600;
color: var(--text-primary);
text-align: center;
margin-bottom: 1.5rem;
}
.payment-amount-display span {
color: var(--success-green);
}
#card-element {
background: rgba(0, 0, 0, 0.5);
border: 1px solid var(--glass-border);
border-radius: 8px;
padding: 1rem;
transition: all 0.2s ease;
}
#card-element--focus {
border-color: var(--primary-accent);
box-shadow: 0 0 0 2px var(--primary-accent);
}
#card-errors {
text-align: left;
margin-top: 1rem;
}
/* --- FOOTER --- */
.main-footer {
text-align: center;
padding: 2rem;
font-size: 0.9rem;
color: var(--text-secondary);
font-weight: 300;
}
/* --- RESPONSIVENESS --- */
@media (max-width: 850px) {
.estimator-container {
margin: 1rem;
padding: 1.5rem;
}
}
@media (max-width: 600px) {
.input-group,
.input-group.three-col { /* ::: UPDATED ::: */
grid-template-columns: 1fr;
gap: 1rem;
}
#step-2 .input-group {
gap: 1.5rem;
}
.card-option-group {
grid-template-columns: 1fr;
}
.segmented-control {
flex-direction: column;
}
.segmented-control label:not(:last-child) {
border-right: none;
border-bottom: 1px solid var(--glass-border);
}
.signature-display-box {
font-size: 2.5rem;
}
.font-dancing { font-size: 2.2rem; }
.font-great-vibes { font-size: 2.5rem; }
.font-sacramento { font-size: 3rem; }
.time-picker-group {
flex-wrap: wrap;
}
.time-picker-group input[type="number"] {
width: 3rem;
}
.time-picker-group select {
margin-left: auto;
}
}
}
{
type: uploaded file
fileName: script.js
fullContent:
// Wait for the DOM to be fully loaded before executing
document.addEventListener('DOMContentLoaded', () => {
// --- I. STATE & CONSTANTS ---
const state = {
service: null, linearFeet: 20, potholes: 1, warranty: null, permit: true,
finalPrices: { finance: 0, cashPermit: 0, cashNoPermit: 0 },
chosenPriceType: null, chosenPriceValue: 0,
verification: { code: null, tries: 0, maxTries: 3 },
coApplicant: false, applicantName: '', coApplicantName: '',
cashApplicantName: '',
downPaymentAmount: 0
};
const marketPricing = {
Liner: { base: 12562.50, perFoot: 250.00 },
Epoxy: { base: 11984.40, perFoot: 320.00 },
Burst: { base: 12732.60, perFoot: 142.00 },
potholeCost: 2500.00, permitCost: 250.00,
cashPermitDiscount: 1000.00, cashNoPermitDiscountPercent: 0.18
};
const warrantyData = {
Liner: [{ text: '10-Year', value: '10yrLinerWarranty', cost: 0 },{ text: '15-Year', value: '15yrLinerWarranty', cost: 1500.00 },{ text: '30-Year', value: '30yrLinerWarranty', cost: 2000.00 }],
Epoxy: [{ text: '10-Year', value: '10yrEpoxyWarranty', cost: 0 },{ text: '12-Year', value: '12yrEpoxyWarranty', cost: 1500.00 },{ text: '15-Year', value: '15yrEpoxyWarranty', cost: 2000.00 }],
Burst: [{ text: '30-Year', value: '30yrBurstWarranty', cost: 0 },{ text: '50-Year', value: '50yrBurstWarranty', cost: 1500.00 },{ text: '75-Year', value: '75yrBurstWarranty', cost: 2000.00 }]
};
// --- II. DOM ELEMENT SELECTORS ---
const form = document.getElementById('sky-drain-estimator');
const steps = document.querySelectorAll('.form-step');
// Step 1-5 (Core)
const serviceRadios = document.querySelectorAll('input[name="service"]');
const linearFeetInput = document.getElementById('linearFeet');
const potholesInput = document.getElementById('potholes');
const step2NextBtn = document.getElementById('step2-next-btn');
const step2BackBtn = document.getElementById('step2-back-btn');
const warrantyContainer = document.getElementById('warranty-options-container');
const permitRadios = document.querySelectorAll('input[name="permit"]');
const calculateBtn = document.getElementById('calculate-btn');
const step3BackBtn = document.getElementById('step3-back-btn');
const priceRadios = document.querySelectorAll('input[name="finalPrice"]');
const financeTotalEl = document.getElementById('finance-total');
const cashPermitTotalEl = document.getElementById('cash-permit-total');
const cashNoPermitTotalEl = document.getElementById('cash-no-permit-total');
const confirmQuoteBtn = document.getElementById('confirm-quote-btn');
const step4BackBtn = document.getElementById('step4-back-btn');
const phoneInput = document.getElementById('phoneNumber');
const sendCodeBtn = document.getElementById('send-code-btn');
const codeEntrySection = document.getElementById('code-entry-section');
const verificationCodeInput = document.getElementById('verificationCode');
const verifyCodeBtn = document.getElementById('verify-code-btn');
const errorMessage = document.getElementById('error-message');
// Step 6 (Cash Thanks)
const consoleMessages = document.getElementById('console-messages');
const scheduleJobBtn = document.getElementById('schedule-job-btn');
// --- LENDING FLOW SELECTORS (7-11) ---
const personalPriceInput = document.getElementById('personalPrice');
const firstNameInput = document.getElementById('firstName');
const lastNameInput = document.getElementById('lastName');
const applicantNameInTerms = document.getElementById('applicant-name-in-terms');
const ownerOtherRadio = document.getElementById('owner-other');
const ownerOtherText = document.getElementById('owner-other-text');
const ownerRadios = document.querySelectorAll('input[name="propertyOwner"]');
const coApplicantRadios = document.querySelectorAll('input[name="addCoApplicant"]');
const termsAgreeCheckbox = document.getElementById('terms-agree');
const step7Error = document.getElementById('step-7-error');
const step7NextBtn = document.getElementById('step7-next-btn');
const signatureDisplay = document.getElementById('signature-display');
const signatureFontRadios = document.querySelectorAll('input[name="sigFont"]');
const step8BackBtn = document.getElementById('step8-back-btn');
const step8NextBtn = document.getElementById('step8-next-btn');
const coFirstNameInput = document.getElementById('co-firstName');
const coLastNameInput = document.getElementById('co-lastName');
const coApplicantNameInTerms = document.getElementById('co-applicant-name-in-terms');
const coTermsAgreeCheckbox = document.getElementById('co-terms-agree');
const step9Error = document.getElementById('step-9-error');
const step9BackBtn = document.getElementById('step9-back-btn');
const step9NextBtn = document.getElementById('step9-next-btn');
const coSignatureDisplay = document.getElementById('co-signature-display');
const coSignatureFontRadios = document.querySelectorAll('input[name="coSigFont"]');
const step10BackBtn = document.getElementById('step10-back-btn');
const submitApplicationBtn = document.getElementById('submit-application-btn');
const finishBtn = document.getElementById('finish-btn');
// --- CASH SCHEDULE FLOW SELECTORS (CASH 1-7) ---
const cashPersonalPrice = document.getElementById('cash-personalPrice');
const cashFirstName = document.getElementById('cash-firstName');
const cashLastName = document.getElementById('cash-lastName');
const stepCash1Error = document.getElementById('step-cash-1-error');
const stepCash1NextBtn = document.getElementById('step-cash-1-next-btn');
const stepCash2Error = document.getElementById('step-cash-2-error');
const stepCash2BackBtn = document.getElementById('step-cash-2-back-btn');
const stepCash2NextBtn = document.getElementById('step-cash-2-next-btn');
const stepCash3Error = document.getElementById('step-cash-3-error');
const stepCash3BackBtn = document.getElementById('step-cash-3-back-btn');
const stepCash3NextBtn = document.getElementById('step-cash-3-next-btn');
const cashSignatureDisplay = document.getElementById('cash-signature-display');
const cashSignatureFontRadios = document.querySelectorAll('input[name="cashSigFont"]');
const stepCash4BackBtn = document.getElementById('step-cash-4-back-btn');
const stepCash4NextBtn = document.getElementById('step-cash-4-next-btn');
const finishScheduleBtn = document.getElementById('finish-schedule-btn');
const downPaymentAmountEl = document.getElementById('down-payment-amount');
const paymentBtnAmountEl = document.getElementById('payment-btn-amount');
const cardElementDiv = document.getElementById('card-element');
const cardErrors = document.getElementById('card-errors');
const stepCash5BackBtn = document.getElementById('step-cash-5-back-btn');
const submitPaymentBtn = document.getElementById('submit-payment-btn');
const paymentSigAmountEl = document.getElementById('payment-sig-amount');
const cashPaymentSignatureDisplay = document.getElementById('cash-payment-signature-display');
const cashPaymentSignatureFontRadios = document.querySelectorAll('input[name="cashPaymentSigFont"]');
const stepCash6BackBtn = document.getElementById('step-cash-6-back-btn');
const submitScheduleBtn = document.getElementById('submit-schedule-btn');
// Footer
document.getElementById('current-year').textContent = new Date().getFullYear();
// --- STRIPE INITIALIZATION ---
const stripe = Stripe('pk_test_51HqIeVEgS3rWeosm2C8L7YlYF04o9e8c4q4y9YqJj8kM8YJ8Yj8Yj8Yj8Yj8Yj8Yj8Yj8Yj8Yj8Yj8Yj8Y'); // Public test key
const elements = stripe.elements();
const cardElement = elements.create('card', {
style: {
base: {
color: '#FFFFFF',
fontFamily: '"Poppins", sans-serif',
fontSmoothing: 'antialiased',
fontSize: '16px',
'::placeholder': { color: '#B0C4DE' }
},
invalid: { color: '#ff6b6b', iconColor: '#ff6b6b' }
}
});
cardElement.mount('#card-element');
cardElement.on('change', (event) => {
if (event.error) {
cardErrors.textContent = event.error.message;
cardErrors.classList.remove('hidden');
} else {
cardErrors.textContent = '';
cardErrors.classList.add('hidden');
}
});
// --- III. HELPER FUNCTIONS ---
function showStep(stepId) {
steps.forEach(step => step.classList.remove('active'));
document.querySelector(stepId).classList.add('active');
window.scrollTo(0, 0);
}
function formatCurrency(amount) {
return new Intl.NumberFormat('en-US', {
style: 'currency', currency: 'USD', minimumFractionDigits: 2, maximumFractionDigits: 2
}).format(amount);
}
function updateWarrantyOptions() {
if (!state.service) return;
const options = warrantyData[state.service];
warrantyContainer.innerHTML = '';
options.forEach((opt, index) => {
const input = document.createElement('input');
input.type = 'radio'; input.id = `warranty-${opt.value}`; input.name = 'warranty';
input.value = opt.value; input.required = true;
if (index === 0) { input.checked = true; state.warranty = opt.value; }
const label = document.createElement('label');
label.htmlFor = `warranty-${opt.value}`; label.textContent = opt.text;
warrantyContainer.appendChild(input); warrantyContainer.appendChild(label);
});
// Add listeners to newly created radio buttons
warrantyContainer.querySelectorAll('input[name="warranty"]').forEach(radio => {
radio.addEventListener('change', (e) => { state.warranty = e.target.value; });
});
}
function calculateQuote() {
const { service, linearFeet, potholes, warranty, permit } = state;
const pricing = marketPricing[service];
let basePrice1, basePrice2, basePrice3, basePrice4;
if (linearFeet <= 20) basePrice1 = pricing.base;
else basePrice1 = ((linearFeet - 20) * pricing.perFoot) + pricing.base;
if (potholes <= 1) basePrice2 = basePrice1;
else basePrice2 = ((potholes - 1) * marketPricing.potholeCost) + basePrice1;
const warrantyCost = warrantyData[service].find(w => w.value === warranty)?.cost || 0;
basePrice3 = basePrice2 + warrantyCost;
if (permit) basePrice4 = basePrice3;
else basePrice4 = basePrice3 - marketPricing.permitCost;
state.finalPrices.finance = basePrice4;
state.finalPrices.cashPermit = basePrice4 - marketPricing.cashPermitDiscount;
state.finalPrices.cashNoPermit = basePrice4 - (basePrice4 * marketPricing.cashNoPermitDiscountPercent);
}
function displayResults() {
const { finance, cashPermit, cashNoPermit } = state.finalPrices;
financeTotalEl.textContent = formatCurrency(finance);
cashPermitTotalEl.textContent = formatCurrency(cashPermit);
cashNoPermitTotalEl.textContent = formatCurrency(cashNoPermit);
document.getElementById('price-one').dataset.price = finance;
document.getElementById('price-two').dataset.price = cashPermit;
document.getElementById('price-three').dataset.price = cashNoPermit;
showStep('#step-4');
}
function logToMockPhone(message, delay = 0) {
setTimeout(() => {
const msgElement = document.createElement('div');
msgElement.className = 'console-message';
msgElement.textContent = message;
consoleMessages.appendChild(msgElement);
consoleMessages.scrollTop = consoleMessages.scrollHeight;
}, delay);
}
function handleSuccessfulVerification() {
let myOrder = `Your Sky Drains Personal Price: ${formatCurrency(state.chosenPriceValue)}`;
if (state.chosenPriceType === 'One') {
// LENDING FLOW
personalPriceInput.value = formatCurrency(state.chosenPriceValue);
showStep('#step-7-applicant');
} else {
// CASH FLOW
cashPersonalPrice.value = formatCurrency(state.chosenPriceValue);
logToMockPhone(myOrder, 500);
logToMockPhone('Your estimate QR code (valid for 3 days): [QR Code Generated]', 1500);
logToMockPhone("Click 'Schedule Your Repair' below to continue.", 2500);
showStep('#step-6-cash-thanks');
}
}
function validateStep(stepSelector, errorSelector) {
let isValid = true;
const errorEl = document.querySelector(errorSelector);
const fields = document.querySelectorAll(`${stepSelector} [required]`);
fields.forEach(field => {
if (!field.closest('.form-step.active')) return;
const fieldContainer = field.closest('.input-field') || field.closest('.checkbox-group') || field.closest('.radio-group');
let fieldValid = true;
if (field.type === 'checkbox') fieldValid = field.checked;
else if (field.type === 'radio') {
const radioGroup = document.querySelectorAll(`input[name="${field.name}"]`);
fieldValid = Array.from(radioGroup).some(radio => radio.checked);
} else fieldValid = field.value.trim() !== '';
if (!fieldValid) {
isValid = false;
if (fieldContainer) fieldContainer.classList.add('error');
} else {
if (fieldContainer) fieldContainer.classList.remove('error');
}
});
if (!isValid) errorEl.classList.remove('hidden');
else errorEl.classList.add('hidden');
return isValid;
}
// --- LENDING SIGNATURE FUNCTIONS ---
function updateApplicantSignature() {
const firstName = firstNameInput.value || '';
const lastName = lastNameInput.value || '';
state.applicantName = `${firstName} ${lastName}`;
const displayName = state.applicantName.trim() ? state.applicantName : 'Your Name';
signatureDisplay.textContent = displayName;
applicantNameInTerms.textContent = displayName;
}
function updateCoApplicantSignature() {
const firstName = coFirstNameInput.value || '';
const lastName = coLastNameInput.value || '';
state.coApplicantName = `${firstName} ${lastName}`;
const displayName = state.coApplicantName.trim() ? state.coApplicantName : 'Co-Applicant Name';
coSignatureDisplay.textContent = displayName;
coApplicantNameInTerms.textContent = displayName;
}
function submitApplication() {
console.log("--- LENDING APPLICATION SUBMITTED (SIMULATED) ---");
showStep('#step-11-thanks');
}
// --- CASH SCHEDULE SIGNATURE FUNCTIONS ---
function updateScheduleSignature() {
const firstName = cashFirstName.value || '';
const lastName = cashLastName.value || '';
state.cashApplicantName = `${firstName} ${lastName}`;
const displayName = state.cashApplicantName.trim() ? state.cashApplicantName : 'Your Name';
cashSignatureDisplay.textContent = displayName;
cashPaymentSignatureDisplay.textContent = displayName;
}
function submitSchedule() {
console.log("--- SCHEDULE SUBMITTED (SIMULATED) ---");
showStep('#step-cash-7-thanks');
}
// --- IV. EVENT LISTENERS ---
// Step 1: Service
serviceRadios.forEach(radio => { radio.addEventListener('change', (e) => { state.service = e.target.value; updateWarrantyOptions(); showStep('#step-2'); }); });
// Step 2: Details
linearFeetInput.addEventListener('change', () => { state.linearFeet = Math.max(20, parseInt(linearFeetInput.value) || 20); if (linearFeetInput.value && parseInt(linearFeetInput.value) < 20) linearFeetInput.value = 20; });
potholesInput.addEventListener('change', () => { state.potholes = Math.max(1, parseInt(potholesInput.value) || 1); if (potholesInput.value && parseInt(potholesInput.value) < 1) potholesInput.value = 1; });
step2NextBtn.addEventListener('click', () => { state.linearFeet = Math.max(20, parseInt(linearFeetInput.value) || 20); state.potholes = Math.max(1, parseInt(potholesInput.value) || 1); showStep('#step-3'); });
step2BackBtn.addEventListener('click', () => showStep('#step-1'));
// Step 3: Options
permitRadios.forEach(radio => { radio.addEventListener('change', (e) => { state.permit = (e.target.value === 'Yes'); }); });
calculateBtn.addEventListener('click', () => { if (!state.service || !state.warranty) { alert('Please make sure all selections are made.'); return; } calculateQuote(); displayResults(); });
step3BackBtn.addEventListener('click', () => showStep('#step-2'));
// Step 4: Results
priceRadios.forEach(radio => { radio.addEventListener('change', (e) => { state.chosenPriceType = e.target.value; state.chosenPriceValue = parseFloat(e.target.dataset.price); confirmQuoteBtn.disabled = false; }); });
confirmQuoteBtn.addEventListener('click', () => showStep('#step-5'));
step4BackBtn.addEventListener('click', () => showStep('#step-3'));
// Step 5: Verification
sendCodeBtn.addEventListener('click', () => {
const phone = phoneInput.value;
if (phone.length < 10) { alert('Please enter a valid phone number.'); return; }
state.verification.code = Math.floor(1000 + Math.random() * 9000).toString();
state.verification.tries = 0;
console.log(`--- SIMULATION: Sending code ${state.verification.code} to ${phone} ---`);
alert(`A (simulated) verification code has been sent to your phone: ${state.verification.code}`);
sendCodeBtn.disabled = true; sendCodeBtn.textContent = 'Code Sent';
codeEntrySection.classList.remove('hidden'); errorMessage.classList.add('hidden');
});
verifyCodeBtn.addEventListener('click', () => {
const inputCode = verificationCodeInput.value;
if (inputCode === state.verification.code) {
errorMessage.classList.add('hidden');
handleSuccessfulVerification();
} else {
state.verification.tries++;
if (state.verification.tries >= state.verification.maxTries) {
errorMessage.textContent = 'Too many attempts. Please call us to confirm your quote.';
errorMessage.classList.remove('hidden');
verifyCodeBtn.disabled = true; verificationCodeInput.disabled = true;
} else {
errorMessage.textContent = `Invalid code. ${state.verification.maxTries - state.verification.tries} tries remaining.`;
errorMessage.classList.remove('hidden');
}
}
});
// Step 6: Cash Thanks
scheduleJobBtn.addEventListener('click', () => { showStep('#step-cash-1-details'); });
// --- LENDING FLOW (STEPS 7-11) ---
firstNameInput.addEventListener('input', updateApplicantSignature);
lastNameInput.addEventListener('input', updateApplicantSignature);
ownerRadios.forEach(radio => { radio.addEventListener('change', () => { if (ownerOtherRadio.checked) { ownerOtherText.disabled = false; ownerOtherText.focus(); } else { ownerOtherText.disabled = true; ownerOtherText.value = ''; } }); });
coApplicantRadios.forEach(radio => { radio.addEventListener('change', (e) => { state.coApplicant = (e.target.value === 'Yes'); }); });
step7NextBtn.addEventListener('click', () => { if (validateStep('#step-7-applicant', '#step-7-error')) { showStep('#step-8-applicant-signature'); } });
signatureFontRadios.forEach(radio => { radio.addEventListener('change', (e) => { signatureDisplay.classList.remove('font-dancing', 'font-great-vibes', 'font-sacramento'); signatureDisplay.classList.add(e.target.value); }); });
step8BackBtn.addEventListener('click', () => showStep('#step-7-applicant'));
step8NextBtn.addEventListener('click', () => { if (state.coApplicant) { showStep('#step-9-co-applicant'); } else { submitApplication(); } });
coFirstNameInput.addEventListener('input', updateCoApplicantSignature);
coLastNameInput.addEventListener('input', updateCoApplicantSignature);
step9BackBtn.addEventListener('click', () => showStep('#step-8-applicant-signature'));
step9NextBtn.addEventListener('click', () => { if (validateStep('#step-9-co-applicant', '#step-9-error')) { showStep('#step-10-co-applicant-signature'); } });
coSignatureFontRadios.forEach(radio => { radio.addEventListener('change', (e) => { coSignatureDisplay.classList.remove('font-dancing', 'font-great-vibes', 'font-sacramento'); coSignatureDisplay.classList.add(e.target.value); }); });
step10BackBtn.addEventListener('click', () => showStep('#step-9-co-applicant'));
submitApplicationBtn.addEventListener('click', () => { submitApplication(); });
finishBtn.addEventListener('click', () => { location.reload(); });
// --- CASH SCHEDULE FLOW (STEPS CASH 1-7) ---
// Step Cash-1: Details
cashFirstName.addEventListener('input', updateScheduleSignature);
cashLastName.addEventListener('input', updateScheduleSignature);
stepCash1NextBtn.addEventListener('click', () => {
// ::: BUG FIX: Changed selector from #step-7-applicant to #step-cash-1-details :::
if (validateStep('#step-cash-1-details', '#step-cash-1-error')) {
showStep('#step-cash-2-agreements');
}
});
// Step Cash-2: Agreements
stepCash2BackBtn.addEventListener('click', () => showStep('#step-cash-1-details'));
stepCash2NextBtn.addEventListener('click', () => { if (validateStep('#step-cash-2-agreements', '#step-cash-2-error')) { showStep('#step-cash-3-schedule'); } });
// Step Cash-3: Schedule
stepCash3BackBtn.addEventListener('click', () => showStep('#step-cash-2-agreements'));
stepCash3NextBtn.addEventListener('click', () => {
const hr = document.getElementById('cash-time-hr').value;
const min = document.getElementById('cash-time-min').value;
if (validateStep('#step-cash-3-schedule', '#step-cash-3-error') && hr && min) { showStep('#step-cash-4-job-signature'); }
else { document.getElementById('step-cash-3-error').classList.remove('hidden'); }
});
// Step Cash-4: Job Signature
cashSignatureFontRadios.forEach(radio => {
radio.addEventListener('change', (e) => {
cashSignatureDisplay.classList.remove('font-dancing', 'font-great-vibes', 'font-sacramento');
cashSignatureDisplay.classList.add(e.target.value);
});
});
stepCash4BackBtn.addEventListener('click', () => showStep('#step-cash-3-schedule'));
stepCash4NextBtn.addEventListener('click', () => {
state.downPaymentAmount = state.chosenPriceValue * 0.10;
const formattedAmount = formatCurrency(state.downPaymentAmount);
downPaymentAmountEl.textContent = formattedAmount;
paymentBtnAmountEl.textContent = formattedAmount;
paymentSigAmountEl.textContent = formattedAmount;
showStep('#step-cash-5-payment');
});
// Step Cash-5: Payment
stepCash5BackBtn.addEventListener('click', () => showStep('#step-cash-4-job-signature'));
submitPaymentBtn.addEventListener('click', async (e) => {
e.preventDefault();
submitPaymentBtn.disabled = true;
submitPaymentBtn.textContent = 'Processing...';
// --- SIMULATION ---
setTimeout(() => {
console.log("--- PAYMENT SIMULATED SUCCESSFULLY ---");
cardErrors.classList.add('hidden');
showStep('#step-cash-6-payment-signature');
submitPaymentBtn.disabled = false;
submitPaymentBtn.innerHTML = `Pay
${formatCurrency(state.downPaymentAmount)}`;
}, 1500);
});
// Step Cash-6: Payment Signature
cashPaymentSignatureFontRadios.forEach(radio => {
radio.addEventListener('change', (e) => {
cashPaymentSignatureDisplay.classList.remove('font-dancing', 'font-great-vibes', 'font-sacramento');
cashPaymentSignatureDisplay.classList.add(e.target.value);
});
});
stepCash6BackBtn.addEventListener('click', () => showStep('#step-cash-5-payment'));
submitScheduleBtn.addEventListener('click', () => {
submitSchedule();
});
// Step Cash-7: Thanks
finishScheduleBtn.addEventListener('click', () => { location.reload(); });
});
}