Bespoke Interiors | Since 2019

Transform Your Space Into

We craft refined residential and commercial interiors
with timeless aesthetics, thoughtful functionality,
and exceptional attention to detail.

View Projects

Trusted by Homeowners Who Value Interior Excellence

From first concept to final styling, we deliver a concierge-level experience home designed for busy professionals and premium homeowners.

See Signature Projects
Featured In Design Weekly Luxe Habitat Urban Living
4.9/5 Google Rating
8+ yrs Luxury Expertise
78% Referral Clients
0

Projects Delivered

0

Client Satisfaction

0

Luxury Expertise

0

Google Rating

Why Choose Us

The BlueLadder Difference

Six reasons premium homeowners and businesses trust us with their most important spaces.

Award-Winning Design

Recognized by Design Weekly and Luxe Habitat for interiors that balance elegance with everyday functionality.

On-Time Handover

78% of our projects are delivered on schedule. Dedicated project managers ensure zero delays and clear milestones.

Transparent Pricing

No hidden charges. You get a detailed line-item proposal before a single rupee is spent.

Fast 3D Visualization

See your space before it's built. Full 3D renders and walkthroughs help you decide with confidence, fast.

Dedicated Team

One senior designer and one project manager assigned exclusively to your project from concept to completion.

Post-Handover Support

90-day aftercare support after your project is complete. We stand behind every detail, long after you move in.

Room-Wise Catalog

Choose Products by Room, Scope, and Starting Price

Move from inspiration to action with productized packages similar to marketplace-style interior platforms.

Kitchen

Modular Kitchen Pro

₹2,49,000 starting

  • Layout and workflow optimization
  • Premium shutters and hardware
  • Countertop and lighting plan
  • Installation and QA checks
View Package
Bedroom

Bedroom and Wardrobe Set

₹1,79,000 starting

  • Wardrobe planning and joinery
  • Accent wall and lighting concept
  • Bed-back and side storage detailing
  • Material board and supervision
View Package
Living

Living and Dining Signature

₹2,29,000 starting

  • TV wall and focal panel design
  • False ceiling and layered lighting
  • Dining zone spatial optimization
  • Furniture and decor guidance
View Package
Full Home

End-to-End Full Home

₹8,99,000 starting

  • Complete concept to execution
  • Room-by-room package bundling
  • Dedicated project manager
  • Timeline, budget, and quality control
View Package
Open Full Package Catalog
Luxury Workflow

A White-Glove Process That Eliminates Stress

Every project is managed with clear milestones, transparent budgeting, and uncompromising quality control.

01

Discovery

We map your lifestyle, priorities, and investment goals in a strategy consultation.

02

Concept Design

Layouts, moodboards, and 3D visuals define the design direction before execution begins.

03

Execution

Dedicated project managers supervise quality, vendors, and timelines end-to-end.

04

Handover

Final styling, quality walkthrough, and post-handover support for complete peace of mind.

Selected Work

Portfolio of Signature Spaces

Explore our recent transformations across homes and lifestyle spaces.

Modern luxury living room with beige sofa and ambient lights

Calm Living Suite

Living Room | Pune

Luxury bedroom with layered textures and warm neutral palette

Serene Master Room

Bedroom | Bangalore

Contemporary modular kitchen with premium matte cabinetry

Luxe Culinary Zone

Kitchen | Mumbai

Elegant living area with curved seating and premium decor

Urban Art Lounge

Living Room | Delhi

Premium bedroom with layered lighting and custom headboard

Velvet Comfort Suite

Bedroom | Hyderabad

Minimal modern kitchen with island and statement finishes

Minimal Kitchen Studio

Kitchen | Chennai

Before & After Signature Transformation

Drag the slider to compare renovation impact.

After renovation: a polished premium living room
Before renovation: a dated living room layout
Before After
Design Inspiration

Curated Looks Across Living, Bedroom, and Kitchen

A richer visual wall to help clients instantly connect with their preferred style direction.

Luxury living room with architectural arches and warm shelving lights Living
Bold designer living room with expressive colors and statement sofa Statement Living
Dark-toned luxury bedroom with layered ambient lighting Bedroom
Contemporary bedroom with art wall and custom wardrobe Bedroom Styling
Fresh modular kitchen with modern color accents and efficient layout Kitchen
Premium navy kitchen with marble island and pendant lighting Premium Kitchen
Case Study Outcomes

Measured Results From Real Client Transformations

A snapshot of ROI-style outcomes reported by clients after BlueLadder design and execution upgrades.

South Mumbai Penthouse

Stronger Property Positioning in Premium Market

Full living and kitchen redesign improved buyer interest and listing confidence within the first month.

+27% Perceived valuation uplift
32% More resale enquiries
Bangalore Family Residence

Optimized Layout, Reduced Upgrade Costs Over Time

Storage planning and modular systems reduced post-handover modification needs significantly.

21% Lower annual upgrade spend
46 days Faster from design to move-in
Hyderabad Executive Office

Improved Team Experience and Client Perception

Workplace redesign elevated brand presence and improved workflow efficiency in client-facing teams.

+41% Client-reported productivity gain
+34% Positive visitor feedback score
About BlueLadder

Designing Spaces That Feel Personal, Polished, and Timeless

BlueLadder Interiors is a full-service design studio delivering high-end interiors for apartments, villas, and commercial spaces. Our process combines thoughtful planning, premium materials, and detail-oriented execution.

From moodboard to styling, we work closely with clients to create spaces that reflect their lifestyle while maximizing functionality and long-term value.

End-to-end execution Design, material planning, site coordination, and final styling move through one accountable team.
Premium detail focus Every finish, storage line, and lighting layer is considered for both visual quality and daily use.
Functional luxury Layouts are shaped around how you live, so the result feels elevated without becoming impractical.
Timeless direction We avoid short-lived trends and build a lasting design language that ages with confidence.
Luxury interior living room with curated seating and neutral palette
Built around how you live We shape each space around routines, priorities, and long-term usability so the design feels refined from day one and practical for years.
14+ Years Experience
380+ Projects Delivered
78% Client Satisfaction
Client Stories

What Our Clients Say

Honest feedback from homeowners and business owners who trusted us with their spaces.

Portrait of client Priya Nair

"BlueLadder transformed our apartment into a refined and functional home. Every corner feels intentional, elegant, and truly us."

Priya Nair

Portrait of client Arjun Mehta

"Their modular kitchen design gave us more storage than we imagined, without compromising style. The execution quality was top-tier."

Arjun Mehta

Portrait of client Rhea Kapoor

"Professional team, transparent communication, and stunning final result. Our office now feels premium and more productive."

Rhea Kapoor

Portrait of client Vikram Shah

"From concept to handover, the team handled everything seamlessly. Their eye for details and material selection was exceptional."

Vikram Shah

Packages

Flexible Plans for Every Project Scale

Choose a package based on your requirements, or contact us for a tailored quote.

🏷 Save 20%

Basic

₹1,874₹1,499 / room

Offer ends this Sunday

  • Concept layout and moodboard
  • Material and color guidance
  • 2 revision rounds
  • Email support
Choose Basic

Luxury

₹6,999 / turnkey

  • Complete bespoke design
  • Premium material curation
  • White-glove execution
  • Priority support and styling
Choose Luxury

Ready to Build a Home That Reflects Your Success?

Book a strategic design consultation and get a tailored scope, timeline guidance, and transparent budget pathway.

Priority access: 6 slots available this week
Estimate Your Budget
Instant Estimator

Preview Your Interior Investment Range

Use this quick tool for a high-level estimate before booking a strategy call with our design experts.

1200 sq ft
Common Questions

Everything Premium Clients Usually Ask

Quick answers to pricing, timelines, and execution so you can decide with confidence.

It is a directional range based on project type, area, finish level, and furnishing scope. Final quotes are provided after design brief and measurements.

Yes. We provide end-to-end management including contractors, material procurement, quality checks, and schedule control.

Most projects complete in 8-12 weeks after design sign-off. Fast-track and express options are available for select scopes.

Absolutely. Many clients start with a priority room, then scale to full-home design in structured phases.

Book your design strategy call now. 6 slots left this week.
Get Cost Range
`; const element = document.createElement('div'); element.innerHTML = htmlContent; const opt = { margin: 10, filename: fileName, image: { type: 'jpeg', quality: 0.98 }, html2canvas: { scale: 2 }, jsPDF: { orientation: 'portrait', unit: 'mm', format: 'a4' } }; html2pdf().set(opt).from(element).save(); }; const applyPlannerSnapshotToUi = (snapshot) => { plannerData = { ...snapshot.data, recommendation: snapshot.recommendation }; plannerResultTitle.textContent = snapshot.recommendation.title; plannerResultCopy.textContent = snapshot.recommendation.copy; plannerResultPackage.textContent = snapshot.recommendation.packageName; plannerResultEstimate.textContent = snapshot.recommendation.estimateRange; plannerResultTimeline.textContent = snapshot.recommendation.deliveryBand; plannerResultConsult.textContent = snapshot.recommendation.consultRoute; plannerSummaryList.innerHTML = ''; [ ['Project Type', snapshot.data.property], ['Scope', snapshot.data.scope], ['City', snapshot.data.city], ['Budget Band', snapshot.data.budget], ['Estimate Band', snapshot.recommendation.estimateRange], ['Timeline', `${snapshot.data.timeline} | likely ${snapshot.recommendation.deliveryBand}`], ['Contact', `${snapshot.data.name} (${snapshot.data.phone})`] ].forEach(([label, value]) => { const item = document.createElement('li'); item.textContent = `${label}: ${value}`; plannerSummaryList.appendChild(item); }); plannerForm.hidden = true; plannerResult.hidden = false; plannerStepCount.textContent = 'Plan unlocked'; plannerStepLabel.textContent = 'Recommended next step'; plannerProgressFill.style.width = '100%'; }; const showPlannerResult = () => { plannerData = getPlannerSelections(); const recommendation = derivePlannerRecommendation(plannerData); const snapshot = { createdAt: new Date().toISOString(), data: plannerData, recommendation }; savePlannerSnapshot(snapshot); applyPlannerSnapshotToUi(snapshot); trackEvent('planner_completed', { property_type: plannerData.property, project_scope: plannerData.scope, city: plannerData.city, budget_band: plannerData.budget, timeline: plannerData.timeline, package_recommendation: recommendation.packageName, estimated_range: recommendation.estimateRange, delivery_band: recommendation.deliveryBand, callback_route: recommendation.consultRoute }); routeLeadOperationally({ source: 'planner_result', name: plannerData.name, phone: plannerData.phone, email: plannerData.email, project_type: plannerData.property, project_scope: plannerData.scope, city: plannerData.city, budget_band: plannerData.budget, timeline: plannerData.timeline, package_recommendation: recommendation.packageName, estimate_range: recommendation.estimateRange, consultation_route: recommendation.consultRoute, submitted_at: snapshot.createdAt }).then((result) => { // Store planner lead ID for later merging with consultation booking if (result.status === 'sent' && result.lead_id) { setPlannerLeadId(result.lead_id); } if (result.status !== 'sent') { showToast('Planner saved. CRM sync queued in fallback storage.'); } }); }; const resetPlannerToStart = () => { plannerCurrentStep = 0; plannerResult.hidden = true; plannerForm.hidden = false; updatePlannerUi(); }; const mapPlannerProjectToConsult = (data) => { if (data.scope === 'Kitchen Only') { return 'Modular Kitchen'; } if (data.property === 'Office') { return 'Office Space'; } if (data.property === 'Renovation') { return 'Renovation'; } if (data.property === 'Villa') { return 'Villa Interior'; } return 'Apartment Interior'; }; const openPlannerModal = (source = 'direct') => { plannerModal.classList.add('active'); plannerModal.setAttribute('aria-hidden', 'false'); document.body.style.overflow = 'hidden'; updatePlannerUi(); const savedSnapshot = getSavedPlannerSnapshot(); plannerSaved.hidden = !savedSnapshot; trackEvent('planner_start', { source, cta_variant: activeCtaVariant.toUpperCase() }); }; const closePlannerModal = () => { if (plannerModal.contains(document.activeElement)) { document.activeElement.blur(); } plannerModal.classList.remove('active'); plannerModal.setAttribute('aria-hidden', 'true'); document.body.style.overflow = consultModal.classList.contains('active') ? 'hidden' : ''; }; openPlannerButtons.forEach((button) => { button.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); openPlannerModal(button.dataset.trackCta || 'planner_cta'); closeMobileMenu(); }); }); plannerClose.addEventListener('click', closePlannerModal); plannerModal.addEventListener('click', (event) => { if (event.target === plannerModal) { closePlannerModal(); } }); plannerForm.querySelectorAll('.planner-choice input').forEach((input) => { input.addEventListener('change', syncPlannerChoiceCards); }); plannerPhone.addEventListener('input', () => { plannerPhone.setCustomValidity(''); }); plannerBack.addEventListener('click', () => { if (plannerCurrentStep === 0) { return; } plannerCurrentStep -= 1; updatePlannerUi(); }); plannerNext.addEventListener('click', () => { if (!validatePlannerStep()) { return; } if (plannerCurrentStep === plannerSteps.length - 1) { showPlannerResult(); showToast('Your project recommendation is ready.'); return; } plannerCurrentStep += 1; updatePlannerUi(); }); plannerRestart.addEventListener('click', resetPlannerToStart); plannerLoadSaved.addEventListener('click', () => { const snapshot = getSavedPlannerSnapshot(); if (!snapshot) { showToast('No saved quote summary was found.'); return; } applyPlannerSnapshotToUi(snapshot); trackEvent('planner_saved_loaded', { saved_at: snapshot.createdAt }); showToast('Saved quote summary loaded.'); }); plannerDownloadSummary.addEventListener('click', () => { const snapshot = getSavedPlannerSnapshot(); if (!snapshot) { showToast('Complete the planner once to download your summary.'); return; } try { generatePlannerPDF(snapshot); showToast('Quote summary downloaded as PDF!'); } catch (error) { console.error('PDF generation failed:', error); showToast('Failed to generate PDF. Please try again.'); } trackEvent('planner_summary_downloaded', { saved_at: snapshot.createdAt, format: 'pdf' }); }); plannerWhatsAppSummary.addEventListener('click', () => { const snapshot = getSavedPlannerSnapshot(); if (!snapshot) { showToast('Complete the planner first to share your summary.'); return; } const whatsappMessage = buildPlannerWhatsappText(snapshot.data, snapshot.recommendation); openWhatsappLead(whatsappMessage); trackEvent('planner_summary_whatsapp', { city: snapshot.data.city, package_recommendation: snapshot.recommendation.packageName }); }); plannerContinueConsult.addEventListener('click', () => { if (!plannerData) { return; } const recommendation = plannerData.recommendation || derivePlannerRecommendation(plannerData); consultName.value = plannerData.name; consultPhone.value = plannerData.phone; consultProject.value = mapPlannerProjectToConsult(plannerData); consultEstimateRange.value = recommendation.estimateRange; consultMessage.value = `Planner brief: ${plannerData.property} in ${plannerData.city}. Scope: ${plannerData.scope}. Budget band: ${plannerData.budget}. Timeline: ${plannerData.timeline}. Recommended pathway: ${recommendation.packageName}.`; trackEvent('planner_consult_continue', { city: plannerData.city, project_scope: plannerData.scope, package_recommendation: recommendation.packageName }); closePlannerModal(); openConsultationModal('planner_result_continue'); }); // Auto-open planner disabled � was locking mobile scroll (body overflow:hidden stuck) conversionDismiss.addEventListener('click', () => { railDismissed = true; sessionStorage.setItem('conversionRailDismissed', '1'); conversionRail.classList.remove('show'); trackEvent('conversion_rail_dismissed', { source: 'dismiss_button' }); showToast('Quick action bar hidden for this session.'); }); [consultPhone, contactPhone, plannerPhone].filter(Boolean).forEach((input) => { input.addEventListener('input', () => { input.setCustomValidity(''); }); }); consultClose.addEventListener('click', closeConsultationModal); consultModal.addEventListener('click', (event) => { if (event.target === consultModal) { closeConsultationModal(); } }); consultForm.addEventListener('submit', async (event) => { event.preventDefault(); // Prevent double-submission if (!acquireSubmissionLock()) { showToast('Form submission in progress... please wait.'); return; } if (!consultForm.checkValidity()) { releaseSubmissionLock(); consultForm.reportValidity(); return; } if (!isValidPhone(consultPhone.value)) { releaseSubmissionLock(); consultPhone.setCustomValidity('Please enter a valid phone number.'); consultPhone.reportValidity(); return; } if (!consultDate.value || consultDate.value < getTodayDateKey() || isSundayDate(consultDate.value)) { releaseSubmissionLock(); showToast('Please select a valid consultation date.'); return; } if (!consultSlot.value) { releaseSubmissionLock(); showToast('Please pick an available consultation slot.'); return; } const selectedSlotLabel = consultSlot.dataset.label || consultSlot.value; // Merge with planner data if available const plannerLeadId = getPlannerLeadId(); const savedSnapshot = getSavedPlannerSnapshot(); const plannerInfo = savedSnapshot ? savedSnapshot.data : null; const consultLeadPayload = { source: 'consult_form', name: consultName.value.trim(), phone: consultPhone.value.trim(), email: consultEmail.value.trim(), project_type: consultProject.value || plannerInfo?.property || null, project_scope: plannerInfo?.scope || null, city: plannerInfo?.city || null, budget_band: plannerInfo?.budget || null, timeline: plannerInfo?.timeline || null, consultation_date: consultDate.value, consultation_slot: selectedSlotLabel, estimate_range: consultEstimateRange.value || plannerInfo?.estimateRange || null, message: consultMessage.value.trim() || null, notes: consultMessage.value.trim() || null, submitted_at: new Date().toISOString() }; trackEvent('form_submit', { form_id: 'consultForm', project_type: consultProject.value || null, consultation_date: consultDate.value, consultation_slot: selectedSlotLabel, estimate_range: consultEstimateRange.value || null }); const routeResult = await routeLeadOperationally(consultLeadPayload); releaseSubmissionLock(); if (routeResult.status === 'sent') { showToast('? Slot locked! Check email for confirmation link. Your designer will call within 24h.'); if (routeResult.whatsapp_number) { // Could open WhatsApp here if desired } } else { showToast('? Slot reserved! Our team will call you tomorrow.'); } consultForm.reset(); consultDate.value = getBookableStartDate(); await renderConsultSlots(consultDate.value); closeConsultationModal(); }); const updateHeaderScrolledState = () => { header.classList.toggle('scrolled', window.scrollY > 8); }; let scrollTicking = false; window.addEventListener('scroll', () => { if (!scrollTicking) { window.requestAnimationFrame(() => { updateScrollProgress(); updateHeaderScrolledState(); if (!railDismissed) { conversionRail.classList.toggle('show', window.scrollY > 640); } scrollTicking = false; }); scrollTicking = true; } }, { passive: true }); // Apply correct navbar shape immediately on page load/refresh. updateHeaderScrolledState(); if (railDismissed) { conversionRail.classList.remove('show'); } // Menu toggle is handled by inline onclick="toggleMobileNav()" on the button. // The listener below closes the menu when tapping outside it. document.addEventListener('click', (e) => { if (menu.classList.contains('open') && !menu.contains(e.target) && !menuToggle.contains(e.target)) { closeMobileMenu(); } }); window.addEventListener('resize', () => { if (window.innerWidth > 860 && menu.classList.contains('open')) { closeMobileMenu(); } }); navLinks.forEach((link) => { link.addEventListener('click', () => { closeMobileMenu(); }); }); const filterButtons = document.querySelectorAll('.filter-btn'); const portfolioItems = document.querySelectorAll('.portfolio-item'); filterButtons.forEach((button) => { button.addEventListener('click', () => { filterButtons.forEach((btn) => btn.classList.remove('active')); button.classList.add('active'); const filter = button.dataset.filter; portfolioItems.forEach((item) => { const isMatch = filter === 'all' || item.dataset.category === filter; item.classList.toggle('hide', !isMatch); }); }); }); const lightbox = document.getElementById('lightbox'); const lightboxImage = document.getElementById('lightboxImage'); const lightboxCaption = document.getElementById('lightboxCaption'); const lightboxClose = document.getElementById('lightboxClose'); document.querySelectorAll('.lightbox-btn').forEach((button) => { button.addEventListener('click', (event) => { const card = event.target.closest('.portfolio-item'); const image = card.querySelector('img'); const title = card.querySelector('h3').textContent; const subtitle = card.querySelector('p').textContent; lightboxImage.src = image.src; lightboxImage.alt = image.alt; lightboxCaption.textContent = `${title} - ${subtitle}`; lightbox.classList.add('active'); lightbox.setAttribute('aria-hidden', 'false'); }); }); const closeLightbox = () => { lightbox.classList.remove('active'); lightbox.setAttribute('aria-hidden', 'true'); lightboxImage.src = ''; }; lightboxClose.addEventListener('click', closeLightbox); lightbox.addEventListener('click', (event) => { if (event.target === lightbox) { closeLightbox(); } }); document.addEventListener('keydown', (event) => { if (event.key === 'Escape' && plannerModal.classList.contains('active')) { closePlannerModal(); } if (event.key === 'Escape' && lightbox.classList.contains('active')) { closeLightbox(); } if (event.key === 'Escape' && consultModal.classList.contains('active')) { closeConsultationModal(); } }); const beforeAfterRange = document.getElementById('beforeAfterRange'); const beforeLayer = document.getElementById('beforeLayer'); const beforeAfterHandle = document.getElementById('beforeAfterHandle'); const updateBeforeAfter = (value) => { const numericValue = Number(value); beforeLayer.style.clipPath = `inset(0 ${100 - numericValue}% 0 0)`; beforeAfterHandle.style.left = `${numericValue}%`; }; beforeAfterRange.addEventListener('input', (event) => { updateBeforeAfter(event.target.value); }); updateBeforeAfter(beforeAfterRange.value); const estimatorForm = document.getElementById('estimatorForm'); const estSpace = document.getElementById('estSpace'); const estArea = document.getElementById('estArea'); const estAreaValue = document.getElementById('estAreaValue'); const estFinish = document.getElementById('estFinish'); const estFurnishing = document.getElementById('estFurnishing'); const estTimeline = document.getElementById('estTimeline'); const estimateRange = document.getElementById('estimateRange'); const estimateSummary = document.getElementById('estimateSummary'); const estimatorLeadBtn = document.getElementById('estimatorLeadBtn'); const formatCurrency = (value) => new Intl.NumberFormat('en-IN', { style: 'currency', currency: 'INR', maximumFractionDigits: 0 }).format(value); const calculateEstimate = () => { const baseRates = { living: 56, bedroom: 49, kitchen: 82, office: 64, villa: 97 }; const finishMultiplier = { standard: 1, premium: 1.25, luxury: 1.55 }; const furnishingMultiplier = { essential: 1, semi: 1.2, full: 1.45 }; const timelineMultiplier = { standard: 1, fast: 1.14, express: 1.29 }; const area = Number(estArea.value); const core = area * baseRates[estSpace.value]; const estimate = core * finishMultiplier[estFinish.value] * furnishingMultiplier[estFurnishing.value] * timelineMultiplier[estTimeline.value]; const min = Math.round(estimate * 0.92 / 100) * 100; const max = Math.round(estimate * 1.12 / 100) * 100; estAreaValue.textContent = `${area} sq ft`; estimateRange.textContent = `${formatCurrency(min)} - ${formatCurrency(max)}`; estimateSummary.textContent = `For a ${area} sq ft ${estSpace.options[estSpace.selectedIndex].text} with ${estFinish.options[estFinish.selectedIndex].text} finish and ${estFurnishing.options[estFurnishing.selectedIndex].text} scope.`; }; const markEstimatorStarted = () => { if (estimatorStarted) { return; } estimatorStarted = true; trackEvent('estimator_start', { initial_space_type: estSpace.value, initial_area: Number(estArea.value) }); }; ['input', 'change'].forEach((eventName) => { estimatorForm.addEventListener(eventName, () => { markEstimatorStarted(); calculateEstimate(); }); }); estimatorForm.addEventListener('submit', (event) => { event.preventDefault(); markEstimatorStarted(); calculateEstimate(); trackEvent('estimator_submit', { space_type: estSpace.value, area: Number(estArea.value), finish: estFinish.value, furnishing: estFurnishing.value, timeline: estTimeline.value, estimate_range: estimateRange.textContent }); }); calculateEstimate(); estimatorLeadBtn.addEventListener('click', () => { const selectedLabel = estSpace.options[estSpace.selectedIndex].text; consultEstimateRange.value = estimateRange.textContent; Array.from(consultProject.options).forEach((option) => { if (option.text.toLowerCase().includes(selectedLabel.split(' ')[0].toLowerCase())) { consultProject.value = option.text; } }); consultMessage.value = `Hi, I would like to discuss a ${selectedLabel} project. My current estimator range is ${estimateRange.textContent}.`; openConsultationModal('estimator_lock_plan'); }); const faqItems = document.querySelectorAll('.faq-item'); faqItems.forEach((item) => { const button = item.querySelector('.faq-question'); button.addEventListener('click', () => { const willOpen = !item.classList.contains('active'); faqItems.forEach((entry) => { entry.classList.remove('active'); entry.querySelector('.faq-question').setAttribute('aria-expanded', 'false'); }); if (willOpen) { item.classList.add('active'); button.setAttribute('aria-expanded', 'true'); } }); }); const testimonialTrack = document.getElementById('testimonialTrack'); const testimonialCards = testimonialTrack.querySelectorAll('.testimonial-card'); const prevTestimonial = document.getElementById('prevTestimonial'); const nextTestimonial = document.getElementById('nextTestimonial'); const testimonialDots = document.getElementById('testimonialDots'); let slideIndex = 0; let autoSlideInterval; const createDots = () => { testimonialCards.forEach((_, index) => { const dot = document.createElement('button'); dot.className = 'dot'; dot.setAttribute('aria-label', `Go to testimonial ${index + 1}`); dot.addEventListener('click', () => { slideIndex = index; updateSlider(); resetAutoSlide(); }); testimonialDots.appendChild(dot); }); }; const updateSlider = () => { testimonialTrack.style.transform = `translateX(-${slideIndex * 100}%)`; const dots = testimonialDots.querySelectorAll('.dot'); dots.forEach((dot, index) => { dot.classList.toggle('active', index === slideIndex); }); }; const showNextSlide = () => { slideIndex = (slideIndex + 1) % testimonialCards.length; updateSlider(); }; const showPrevSlide = () => { slideIndex = (slideIndex - 1 + testimonialCards.length) % testimonialCards.length; updateSlider(); }; const resetAutoSlide = () => { clearInterval(autoSlideInterval); autoSlideInterval = setInterval(showNextSlide, 4500); }; nextTestimonial.addEventListener('click', () => { showNextSlide(); resetAutoSlide(); }); prevTestimonial.addEventListener('click', () => { showPrevSlide(); resetAutoSlide(); }); createDots(); updateSlider(); resetAutoSlide(); // -- Live Chat Popup -------------------------------------- const chatToggle = document.getElementById('chatToggle'); const chatPopup = document.getElementById('chatPopup'); const chatBody = document.getElementById('chatBody'); const chatInput = document.getElementById('chatInput'); const chatSend = document.getElementById('chatSend'); const chatClose = document.getElementById('chatClose'); const chatBadge = document.getElementById('chatBadge'); const chatTyping = document.getElementById('chatTyping'); const chatChips = document.getElementById('chatChips'); const chatLeadGate = document.getElementById('chatLeadGate'); const chatLeadName = document.getElementById('chatLeadName'); const chatLeadEmail = document.getElementById('chatLeadEmail'); const chatLeadPhone = document.getElementById('chatLeadPhone'); const chatLeadError = document.getElementById('chatLeadError'); const chatLeadSubmit = document.getElementById('chatLeadSubmit'); const chatFooter = document.querySelector('.chat-popup-footer'); const CHAT_LEAD_KEY = 'chatLeadCaptured'; let chatOpen = false; let chatAutoIdx = 0; const chatNowLabel = () => new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); const scrollChatBottom = () => requestAnimationFrame(() => { chatBody.scrollTop = chatBody.scrollHeight; }); const appendChatMsg = (text, outbound = false) => { const wrap = document.createElement('div'); wrap.className = 'chat-msg-wrap' + (outbound ? ' outbound' : ''); const bubble = document.createElement('p'); bubble.className = 'chat-message' + (outbound ? ' outbound' : ''); bubble.textContent = text; const time = document.createElement('span'); time.className = 'chat-msg-time'; time.textContent = chatNowLabel(); wrap.appendChild(bubble); wrap.appendChild(time); chatBody.insertBefore(wrap, chatTyping); scrollChatBottom(); }; // -- Smart keyword-matched auto-replies -------------------- const chatReplyMap = [ { keywords: ['full home', 'full-home', 'entire home', 'whole home', 'whole house', 'entire house', 'turnkey'], replies: [ 'Great choice! Full-home transformations are our specialty. Shall I send you a free design brief?', 'Full-home projects are where we truly shine — every room planned together for a cohesive result.', 'We\'d love to walk you through our end-to-end full home process. Want to schedule a free call?' ] }, { keywords: ['kitchen', 'modular', 'cooking', 'countertop'], replies: [ 'Love that! Our kitchens blend premium aesthetics with everyday practicality. Want to see recent projects?', 'A beautifully designed kitchen sets the tone for your whole home. I can share our lookbook — interested?', 'Modular kitchens deserve great design too. Let me show you what we\'ve done for clients like you.' ] }, { keywords: ['bedroom', 'wardrobe', 'bed', 'closet', 'master'], replies: [ 'A beautifully designed bedroom changes your mornings entirely. I can share our lookbook — interested?', 'Our bedroom packages include wardrobe planning, accent walls, and layered lighting. Want the details?', 'Great choice! Bedrooms are personal sanctuaries — we design them to reflect exactly who you are.' ] }, { keywords: ['office', 'workspace', 'commercial', 'co-working', 'corporate'], replies: [ 'Modern offices deserve great design too. We create spaces that boost focus and impress clients.', 'An aesthetically designed office brings energy and professionalism to every working day.', 'We\'d love to design your workspace. Let me connect you with our commercial interiors team!' ] }, { keywords: ['renovation', 'renovate', 'upgrade', 'remodel', 'refurbish', 'redo'], replies: [ 'Oh! Let me know if you want to discuss your renovation project. We have some amazing ideas for that!', 'Renovations are a chance to start fresh — we\'ll help you plan every detail within your budget.', 'Great timing! We\'re currently taking on select renovation projects. Want to check availability?' ] }, { keywords: ['living', 'dining', 'lounge', 'hall', 'drawing room'], replies: [ 'Living spaces set the first impression of your home. We\'d love to create something stunning for you!', 'Our living & dining packages include TV walls, false ceilings, and full furniture guidance.', 'A well-designed living room brings the whole family together. Want to see our recent work?' ] } ]; const chatGeneralReplies = [ 'Thanks for sharing! Our consultant will follow up shortly. You can also book a free call — it only takes 2 minutes.', 'That sounds exciting! Our team would love to understand your vision better. Shall we set up a quick call?', 'Great! We work with clients across India to create spaces they\'ll love for years. Want to explore options?', 'Every space has a story — let us help you tell yours. Book a quick free consultation to get started!' ]; let chatGeneralIdx = 0; const getAutoReply = (userText) => { const t = userText.toLowerCase(); for (const group of chatReplyMap) { if (group.keywords.some(kw => t.includes(kw))) { return group.replies[Math.floor(Math.random() * group.replies.length)]; } } const reply = chatGeneralReplies[chatGeneralIdx % chatGeneralReplies.length]; chatGeneralIdx++; return reply; }; const sendChatMessage = (text) => { text = (text !== undefined ? text : chatInput.value).trim(); if (!text) return; chatSend.disabled = true; appendChatMsg(text, true); chatInput.value = ''; chatChips.hidden = true; trackEvent('chat_message_sent', { message_length: text.length }); chatTyping.classList.add('visible'); scrollChatBottom(); const delay = 1000 + Math.random() * 700; setTimeout(() => { chatTyping.classList.remove('visible'); const reply = getAutoReply(text); appendChatMsg(reply, false); chatSend.disabled = false; chatInput.focus(); }, delay); }; const showChatGate = () => { // Reset form state so re-openers don't see stale errors chatLeadName.value = ''; chatLeadEmail.value = ''; chatLeadPhone.value = ''; chatLeadEmail.classList.remove('invalid'); chatLeadPhone.classList.remove('invalid'); chatLeadError.hidden = true; chatLeadError.textContent = ''; chatLeadSubmit.disabled = false; chatLeadSubmit.textContent = 'Start Chatting \u2192'; chatLeadGate.hidden = false; chatBody.hidden = true; chatChips.hidden = true; chatFooter.hidden = true; setTimeout(() => chatLeadName.focus(), 320); }; const showChatInterface = () => { chatLeadGate.hidden = true; chatBody.hidden = false; chatChips.hidden = false; chatFooter.hidden = false; setTimeout(() => { chatInput.focus(); scrollChatBottom(); }, 300); }; const toggleChat = () => { chatOpen = !chatOpen; chatPopup.classList.toggle('open', chatOpen); chatToggle.setAttribute('aria-expanded', String(chatOpen)); chatPopup.setAttribute('aria-hidden', String(!chatOpen)); if (chatOpen) { chatBadge.style.display = 'none'; const alreadyCaptured = localStorage.getItem(CHAT_LEAD_KEY) === '1'; if (alreadyCaptured) { showChatInterface(); } else { showChatGate(); } trackEvent('chat_opened', { source: 'chat_button' }); } }; chatToggle.addEventListener('click', toggleChat); chatClose.addEventListener('click', () => { if (chatOpen) toggleChat(); }); chatLeadSubmit.addEventListener('click', async () => { const email = chatLeadEmail.value.trim(); const phone = chatLeadPhone.value.trim(); const name = chatLeadName.value.trim(); const emailOk = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); const phoneOk = /^[0-9+\-\s]{7,15}$/.test(phone); chatLeadEmail.classList.toggle('invalid', !emailOk); chatLeadPhone.classList.toggle('invalid', !phoneOk); if (!emailOk || !phoneOk) { chatLeadError.hidden = false; chatLeadError.textContent = !emailOk ? 'Please enter a valid email address.' : 'Please enter a valid phone number (7\u201315 digits).'; return; } chatLeadError.hidden = true; chatLeadSubmit.disabled = true; chatLeadSubmit.textContent = 'Saving\u2026'; try { const res = await fetch('/Interior/api/submit-lead.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: name || 'Chat Visitor', phone, email, source: 'chat_widget', message: 'Lead captured via chat widget' }) }); const data = await res.json(); if (data.success) { localStorage.setItem(CHAT_LEAD_KEY, '1'); showChatInterface(); } else { throw new Error(data.message || 'Submission failed'); } } catch (err) { chatLeadError.hidden = false; chatLeadError.textContent = 'Something went wrong. Please try again.'; chatLeadSubmit.disabled = false; chatLeadSubmit.textContent = 'Start Chatting \u2192'; } }); // Allow Enter key on gate inputs [chatLeadName, chatLeadEmail, chatLeadPhone].forEach(input => { input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); chatLeadSubmit.click(); } }); }); chatChips.addEventListener('click', (e) => { const chip = e.target.closest('.chat-chip'); if (chip) sendChatMessage(chip.dataset.chip); }); chatSend.addEventListener('click', () => sendChatMessage()); chatInput.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendChatMessage(); } }); // -- Intersection Observer reveals (all variants) --------- const allRevealSelectors = '.reveal, .reveal-left, .reveal-right, .reveal-zoom'; const reveals = document.querySelectorAll(allRevealSelectors); const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { entry.target.classList.add('visible'); observer.unobserve(entry.target); } }); }, { threshold: 0.12 } ); reveals.forEach((element, index) => { element.style.setProperty('--reveal-delay', `${(index % 5) * 90}ms`); observer.observe(element); }); const contactForm = document.getElementById('contactForm'); if (contactForm) { contactForm.addEventListener('submit', async (event) => { event.preventDefault(); if (!contactForm.checkValidity()) { contactForm.reportValidity(); return; } if (!isValidPhone(contactPhone.value)) { contactPhone.setCustomValidity('Please enter a valid phone number.'); contactPhone.reportValidity(); return; } trackEvent('form_submit', { form_id: 'contactForm', budget: budgetField.value || null, callback_time: callbackTime.value || null }); const contactLeadPayload = { source: 'contact_form', name: document.getElementById('name').value.trim(), phone: contactPhone.value.trim(), email: null, message: document.getElementById('message').value.trim() || null, budget_band: budgetField.value || null, timeline: callbackTime.value || null, status: 'new', submitted_at: new Date().toISOString() }; const routeResult = await routeLeadOperationally(contactLeadPayload); if (routeResult.status === 'sent') { showToast('Thank you. Your inquiry was submitted successfully.'); } else { showToast('Thank you. Your inquiry was submitted and queued for processing.'); } contactForm.reset(); }); } document.getElementById('year').textContent = new Date().getFullYear(); updateScrollProgress(); // Form double-submission protection (re-enable after 3s to avoid permanent lockout) document.querySelectorAll('form').forEach(form => { form.addEventListener('submit', () => { const buttons = form.querySelectorAll('button[type="submit"]'); buttons.forEach(btn => { btn.disabled = true; btn.style.opacity = '0.6'; setTimeout(() => { btn.disabled = false; btn.style.opacity = ''; }, 3000); }); }); }); // Accessibility: Announce form errors to screen readers const announceError = (message) => { const announcement = document.createElement('div'); announcement.setAttribute('role', 'alert'); announcement.setAttribute('aria-live', 'polite'); announcement.style.position = 'absolute'; announcement.style.left = '-10000px'; announcement.textContent = message; document.body.appendChild(announcement); setTimeout(() => announcement.remove(), 3000); }; // Enhance form error announcements document.querySelectorAll('form').forEach(form => { form.addEventListener('invalid', (e) => { const field = e.target; if (field.validity.valueMissing) { announceError(`${field.name} is required.`); } else if (field.validity.typeMismatch) { announceError(`${field.name} is invalid.`); } }, true); }); // Native