Fix mobile nav dropdowns, unique hero images per page, Google Maps, meta tags, reel update
- Fix mobile nav: all 3 dropdowns now get click handlers (was only first) - Remove Our Work from nav - Add Google Maps embed to homepage footer - Update title and meta description/keywords/canonical - Unique hero image per page (14 pages updated) - Remove technician clip from hero reel - Add .cpanel.yml for cPanel Git deployment - Add hero image generation script (ComfyUI SDXL) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+3
-3
@@ -14,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-technician.jpg'); background-size: cover; background-position: center;">
|
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-about.jpg'); background-size: cover; background-position: center;">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="page-hero-content">
|
<div class="page-hero-content">
|
||||||
<span class="hero-eyebrow">Waterloo, NY — Finger Lakes Region</span>
|
<span class="hero-eyebrow">Waterloo, NY — Finger Lakes Region</span>
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="service-detail-row">
|
<div class="service-detail-row">
|
||||||
<div class="service-detail-image">
|
<div class="service-detail-image">
|
||||||
<img src="/assets/images/hero/hero-technician.jpg" alt="Lahr Carpet Cleaning technician at work" style="width:100%;height:500px;object-fit:cover;border-radius:6px;">
|
<img src="/assets/images/hero/hero-about.jpg" alt="Lahr Carpet Cleaning technician at work" style="width:100%;height:500px;object-fit:cover;border-radius:6px;">
|
||||||
</div>
|
</div>
|
||||||
<div class="service-detail-content">
|
<div class="service-detail-content">
|
||||||
<span class="section-label">Who We Are</span>
|
<span class="section-label">Who We Are</span>
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
<p class="service-detail-desc">We serve homes, rental properties, offices, and commercial spaces throughout Seneca, Ontario, Schuyler, and Yates counties. If you are not sure we cover your area, give us a call at <a href="tel:315-719-1218" class="text-accent">315-719-1218</a>.</p>
|
<p class="service-detail-desc">We serve homes, rental properties, offices, and commercial spaces throughout Seneca, Ontario, Schuyler, and Yates counties. If you are not sure we cover your area, give us a call at <a href="tel:315-719-1218" class="text-accent">315-719-1218</a>.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="service-detail-image">
|
<div class="service-detail-image">
|
||||||
<img src="/assets/images/hero/hero-living-room.jpg" alt="Clean living room carpet in Finger Lakes home" style="width:100%;height:420px;object-fit:cover;border-radius:6px;">
|
<img src="/assets/images/hero/hero-about.jpg" alt="Clean living room carpet in Finger Lakes home" style="width:100%;height:420px;object-fit:cover;border-radius:6px;">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 150 KiB |
@@ -5,7 +5,6 @@
|
|||||||
</a>
|
</a>
|
||||||
<ul class="nav-links">
|
<ul class="nav-links">
|
||||||
<li><a href="/">Home</a></li>
|
<li><a href="/">Home</a></li>
|
||||||
<li><a href="/our-work/">Our Work</a></li>
|
|
||||||
<li><a href="/about/">About</a></li>
|
<li><a href="/about/">About</a></li>
|
||||||
<li>
|
<li>
|
||||||
<div class="nav-dropdown">
|
<div class="nav-dropdown">
|
||||||
|
|||||||
+26
-21
@@ -1,32 +1,37 @@
|
|||||||
const mobileToggle = document.querySelector('.mobile-menu-toggle');
|
const mobileToggle = document.querySelector('.mobile-menu-toggle');
|
||||||
const navLinks = document.querySelector('.nav-links');
|
const navLinks = document.querySelector('.nav-links');
|
||||||
const navContact = document.querySelector('.nav-contact');
|
const navContact = document.querySelector('.nav-contact');
|
||||||
mobileToggle.addEventListener('click', () => {
|
if (mobileToggle) {
|
||||||
navLinks.classList.toggle('active');
|
mobileToggle.addEventListener('click', () => {
|
||||||
navContact.classList.toggle('active');
|
navLinks.classList.toggle('active');
|
||||||
mobileToggle.classList.toggle('active');
|
navContact.classList.toggle('active');
|
||||||
});
|
mobileToggle.classList.toggle('active');
|
||||||
const dropdownLabel = document.querySelector('.dropdown-label');
|
});
|
||||||
const navDropdown = document.querySelector('.nav-dropdown');
|
}
|
||||||
if (dropdownLabel && navDropdown) {
|
|
||||||
dropdownLabel.addEventListener('click', function(e) {
|
// Mobile dropdowns — wire ALL dropdown labels, not just the first
|
||||||
|
document.querySelectorAll('.nav-dropdown').forEach(function(dropdown) {
|
||||||
|
var label = dropdown.querySelector('.dropdown-label');
|
||||||
|
if (!label) return;
|
||||||
|
label.addEventListener('click', function(e) {
|
||||||
if (window.innerWidth <= 768) {
|
if (window.innerWidth <= 768) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
navDropdown.classList.toggle('open');
|
// Close all other open dropdowns
|
||||||
|
document.querySelectorAll('.nav-dropdown.open').forEach(function(other) {
|
||||||
|
if (other !== dropdown) other.classList.remove('open');
|
||||||
|
});
|
||||||
|
dropdown.classList.toggle('open');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
document.addEventListener('click', function(e) {
|
});
|
||||||
if (!navDropdown.contains(e.target)) {
|
|
||||||
navDropdown.classList.remove('open');
|
document.addEventListener('click', function(e) {
|
||||||
}
|
if (!e.target.closest('.nav-dropdown')) {
|
||||||
});
|
document.querySelectorAll('.nav-dropdown.open').forEach(function(d) {
|
||||||
}
|
d.classList.remove('open');
|
||||||
document.querySelectorAll('.dropdown-toggle').forEach(toggle => {
|
});
|
||||||
toggle.addEventListener('click', function() {
|
}
|
||||||
this.classList.toggle('active');
|
|
||||||
this.nextElementSibling.classList.toggle('active');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
const form = document.getElementById('quoteForm');
|
const form = document.getElementById('quoteForm');
|
||||||
if (form) {
|
if (form) {
|
||||||
|
|||||||
Binary file not shown.
@@ -14,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-before-after.jpg')">
|
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-hotels.jpg')">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="page-hero-content">
|
<div class="page-hero-content">
|
||||||
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
<p>Bed and breakfasts, small inns, and boutique hotels all receive the same professional extraction service we bring to larger commercial accounts. No property is too small to benefit from clean carpet.</p>
|
<p>Bed and breakfasts, small inns, and boutique hotels all receive the same professional extraction service we bring to larger commercial accounts. No property is too small to benefit from clean carpet.</p>
|
||||||
<a href="/contact/" class="btn btn-primary">Book Now</a>
|
<a href="/contact/" class="btn btn-primary">Book Now</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-living-room.jpg')"></div>
|
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-hotels.jpg')"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-technician.jpg')">
|
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-offices.jpg')">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="page-hero-content">
|
<div class="page-hero-content">
|
||||||
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
<p>We use truck-mounted hot water extraction that pulls deep soil out of office carpet rather than pushing it around. The result lasts longer and dries faster than portable equipment. Your office is ready for the morning crew, not still damp when they arrive.</p>
|
<p>We use truck-mounted hot water extraction that pulls deep soil out of office carpet rather than pushing it around. The result lasts longer and dries faster than portable equipment. Your office is ready for the morning crew, not still damp when they arrive.</p>
|
||||||
<a href="/contact/" class="btn btn-primary">Book Now</a>
|
<a href="/contact/" class="btn btn-primary">Book Now</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-clean-result.jpg')"></div>
|
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-offices.jpg')"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-technician.jpg')">
|
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-property-management.jpg')">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="page-hero-content">
|
<div class="page-hero-content">
|
||||||
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
<p>We work with both residential and commercial property managers. From single-family rental homes to multi-unit apartment buildings and commercial office suites, we bring the same professional extraction process and reliable communication to every job in your portfolio.</p>
|
<p>We work with both residential and commercial property managers. From single-family rental homes to multi-unit apartment buildings and commercial office suites, we bring the same professional extraction process and reliable communication to every job in your portfolio.</p>
|
||||||
<a href="/contact/" class="btn btn-primary">Book Now</a>
|
<a href="/contact/" class="btn btn-primary">Book Now</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-stairs.jpg')"></div>
|
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-property-management.jpg')"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-before-after.jpg')">
|
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-retail.jpg')">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="page-hero-content">
|
<div class="page-hero-content">
|
||||||
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
<p>We serve businesses from Waterloo to Seneca Falls and throughout the wine country corridor. Our scheduling is flexible enough to work around tasting room hours, gallery events, and seasonal traffic spikes.</p>
|
<p>We serve businesses from Waterloo to Seneca Falls and throughout the wine country corridor. Our scheduling is flexible enough to work around tasting room hours, gallery events, and seasonal traffic spikes.</p>
|
||||||
<a href="/contact/" class="btn btn-primary">Book Now</a>
|
<a href="/contact/" class="btn btn-primary">Book Now</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-before-after.jpg')"></div>
|
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-retail.jpg')"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-living-room.jpg')">
|
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-vacation-rentals.jpg')">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="page-hero-content">
|
<div class="page-hero-content">
|
||||||
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
<p>We communicate clearly, arrive when scheduled, and send confirmation when the job is done. Set up a recurring program and take carpet cleaning off your mental list entirely.</p>
|
<p>We communicate clearly, arrive when scheduled, and send confirmation when the job is done. Set up a recurring program and take carpet cleaning off your mental list entirely.</p>
|
||||||
<a href="/contact/" class="btn btn-primary">Book Now</a>
|
<a href="/contact/" class="btn btn-primary">Book Now</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-clean-result.jpg')"></div>
|
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-vacation-rentals.jpg')"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
+11
-1
@@ -3,7 +3,10 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Lahr Carpet Cleaning | Professional Carpet & Upholstery Cleaning in Waterloo, NY</title>
|
<title>Lahr Carpet Cleaning | Residential & Commercial Carpet Cleaning — Finger Lakes, NY</title>
|
||||||
|
<meta name="description" content="Professional carpet and upholstery cleaning for homes and businesses across the Finger Lakes region. Serving Waterloo, Geneva, Seneca Falls and surrounding areas. Book a free estimate today.">
|
||||||
|
<meta name="keywords" content="carpet cleaning Waterloo NY, carpet cleaning Geneva NY, Finger Lakes carpet cleaning, upholstery cleaning, commercial carpet cleaning, stain removal Seneca Falls">
|
||||||
|
<link rel="canonical" href="https://lahrcarpetcleaning.com/">
|
||||||
<link rel="stylesheet" href="/assets/css/styles.css?v=5">
|
<link rel="stylesheet" href="/assets/css/styles.css?v=5">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||||
</head>
|
</head>
|
||||||
@@ -277,6 +280,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section style="background:#0a0a0b; padding:60px 0 0;">
|
||||||
|
<div class="container">
|
||||||
|
<h2 class="heading-4" style="color:#ffffff; margin-bottom:24px;">Find Us</h2>
|
||||||
|
</div>
|
||||||
|
<iframe src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d187279.35321875248!2d-76.9450219507772!3d42.82554615383044!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x89d0c9afbcb0b94b%3A0x332522dd2236c530!2sLahr%20Carpet%20Cleaning!5e0!3m2!1sen!2snl!4v1778870326338!5m2!1sen!2snl" width="100%" height="450" style="border:0;display:block;" allowfullscreen="" loading="lazy" referrerpolicy="no-referrer-when-downgrade"></iframe>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div id="site-footer"></div>
|
<div id="site-footer"></div>
|
||||||
|
|
||||||
<script src="/assets/js/components.js"></script>
|
<script src="/assets/js/components.js"></script>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-living-room.jpg'); background-size: cover; background-position: center;">
|
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-service-area.jpg'); background-size: cover; background-position: center;">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="page-hero-content">
|
<div class="page-hero-content">
|
||||||
<span class="hero-eyebrow">Seneca, Ontario, Schuyler & Yates Counties</span>
|
<span class="hero-eyebrow">Seneca, Ontario, Schuyler & Yates Counties</span>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-technician.jpg')">
|
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-add-ons.jpg')">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="page-hero-content">
|
<div class="page-hero-content">
|
||||||
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
<p>These treatments are optional. Not every home needs them. We will tell you honestly at the time of booking whether an add-on makes sense for your situation.</p>
|
<p>These treatments are optional. Not every home needs them. We will tell you honestly at the time of booking whether an add-on makes sense for your situation.</p>
|
||||||
<a href="/contact/" class="btn btn-primary">Book Now</a>
|
<a href="/contact/" class="btn btn-primary">Book Now</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-technician.jpg')"></div>
|
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-add-ons.jpg')"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-living-room.jpg')">
|
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-area-rugs.jpg')">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="page-hero-content">
|
<div class="page-hero-content">
|
||||||
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
<p>If your rug smells, looks flat, or has visible staining, professional cleaning will make a clear difference. A rug that looks too far gone often surprises its owner after a thorough clean.</p>
|
<p>If your rug smells, looks flat, or has visible staining, professional cleaning will make a clear difference. A rug that looks too far gone often surprises its owner after a thorough clean.</p>
|
||||||
<a href="/contact/" class="btn btn-primary">Book Now</a>
|
<a href="/contact/" class="btn btn-primary">Book Now</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-living-room.jpg')"></div>
|
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-area-rugs.jpg')"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-living-room.jpg')">
|
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-carpet-cleaning.jpg')">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="page-hero-content">
|
<div class="page-hero-content">
|
||||||
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
<p>Every job follows the same approach: assess the carpet, pre-treat problem areas, extract thoroughly, and leave the space cleaner than we found it. That consistency is what keeps customers coming back.</p>
|
<p>Every job follows the same approach: assess the carpet, pre-treat problem areas, extract thoroughly, and leave the space cleaner than we found it. That consistency is what keeps customers coming back.</p>
|
||||||
<a href="/contact/" class="btn btn-primary">Book Now</a>
|
<a href="/contact/" class="btn btn-primary">Book Now</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-technician.jpg')"></div>
|
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-carpet-cleaning.jpg')"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-technician.jpg')">
|
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-commercial.jpg')">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="page-hero-content">
|
<div class="page-hero-content">
|
||||||
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
<p>Every job uses professional hot water extraction equipment. No rental-grade machines, no shortcuts. You get clean carpets the first time, with a before and after walk-through so you know exactly what was done.</p>
|
<p>Every job uses professional hot water extraction equipment. No rental-grade machines, no shortcuts. You get clean carpets the first time, with a before and after walk-through so you know exactly what was done.</p>
|
||||||
<a href="/contact/" class="btn btn-primary">Book Now</a>
|
<a href="/contact/" class="btn btn-primary">Book Now</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-technician.jpg')"></div>
|
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-commercial.jpg')"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-before-after.jpg')">
|
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-floors.jpg')">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="page-hero-content">
|
<div class="page-hero-content">
|
||||||
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
<p>If your floors look dull or your grout lines have darkened despite regular mopping, professional cleaning will show you the difference. Book the service and see what the floor actually looks like clean.</p>
|
<p>If your floors look dull or your grout lines have darkened despite regular mopping, professional cleaning will show you the difference. Book the service and see what the floor actually looks like clean.</p>
|
||||||
<a href="/contact/" class="btn btn-primary">Book Now</a>
|
<a href="/contact/" class="btn btn-primary">Book Now</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-before-after.jpg')"></div>
|
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-floors.jpg')"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-living-room.jpg')">
|
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-upholstery.jpg')">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="page-hero-content">
|
<div class="page-hero-content">
|
||||||
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
<p>If your furniture smells, looks dull, or shows visible soil on the armrests and seat cushions, cleaning will make a visible difference. Book the appointment and see the result for yourself.</p>
|
<p>If your furniture smells, looks dull, or shows visible soil on the armrests and seat cushions, cleaning will make a visible difference. Book the appointment and see the result for yourself.</p>
|
||||||
<a href="/contact/" class="btn btn-primary">Book Now</a>
|
<a href="/contact/" class="btn btn-primary">Book Now</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-living-room.jpg')"></div>
|
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-upholstery.jpg')"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
+16
-18
@@ -18,34 +18,32 @@ os.makedirs(TMP_DIR, exist_ok=True)
|
|||||||
CLIPS = [
|
CLIPS = [
|
||||||
"v3-shot-02", # 1 - wine spill on sofa
|
"v3-shot-02", # 1 - wine spill on sofa
|
||||||
"shot-05-extraction-couch",# 2 - extraction couch
|
"shot-05-extraction-couch",# 2 - extraction couch
|
||||||
"shot-03-technician", # 3 - technician
|
"v3-shot-03", # 3 - dirty stained carpet close-up
|
||||||
"v3-shot-03", # 4 - dirty stained carpet close-up
|
"v3-shot-06", # 4 - living room clean carpet pan
|
||||||
"v3-shot-06", # 5 - living room clean carpet pan
|
"v3-shot-07", # 5 - restaurant carpet glide
|
||||||
"v3-shot-07", # 6 - restaurant carpet glide
|
"v3-shot-05", # 6 - office lobby carpet pan
|
||||||
"v3-shot-05", # 7 - office lobby carpet pan
|
"v2-shot-05-clean-stairs", # 7 - clean bright staircase
|
||||||
"v2-shot-05-clean-stairs", # 8 - clean bright staircase
|
"v2-shot-07-restaurant", # 8 - restaurant carpet
|
||||||
"v2-shot-07-restaurant", # 9 - restaurant carpet
|
"v2-shot-06-office", # 9 - bright office carpet
|
||||||
"v2-shot-06-office", # 10 - bright office carpet
|
"shot-01-wide-room", # 10 - wide room establishing
|
||||||
"shot-01-wide-room", # 11 - wide room establishing
|
"shot-05-clean-reveal", # 11 - clean reveal
|
||||||
"shot-05-clean-reveal", # 12 - clean reveal
|
"shot-04-extraction-carpet",# 12 - final reveal
|
||||||
"shot-04-extraction-carpet",# 13 - final reveal
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Duration for each clip
|
# Duration for each clip
|
||||||
DURATIONS = [
|
DURATIONS = [
|
||||||
4.5, # 1 wine spill — cut at 4.5s
|
4.5, # 1 wine spill — cut at 4.5s
|
||||||
5.0, # 2
|
5.0, # 2
|
||||||
5.0, # 3
|
4.0, # 3 --- building pace ---
|
||||||
4.0, # 4 --- building pace ---
|
4.0, # 4
|
||||||
4.0, # 5
|
4.0, # 5
|
||||||
4.0, # 6
|
3.5, # 6
|
||||||
3.5, # 7
|
3.5, # 7
|
||||||
3.5, # 8
|
2.5, # 8 --- rapid ---
|
||||||
2.5, # 9 --- rapid ---
|
2.5, # 9
|
||||||
2.5, # 10
|
2.5, # 10
|
||||||
2.5, # 11
|
2.5, # 11
|
||||||
2.5, # 12
|
6.0, # 12 final reveal — hold
|
||||||
6.0, # 13 final reveal — hold
|
|
||||||
]
|
]
|
||||||
|
|
||||||
paced_clips = []
|
paced_clips = []
|
||||||
|
|||||||
@@ -0,0 +1,244 @@
|
|||||||
|
"""
|
||||||
|
Generate unique hero images for every page via ComfyUI SDXL.
|
||||||
|
No people. No machines. Residential = warm home scenes. Commercial = professional spaces.
|
||||||
|
Run in tmux: python3 tools/gen-hero-images.py
|
||||||
|
"""
|
||||||
|
import json, time, urllib.request, os, random
|
||||||
|
|
||||||
|
COMFY = "http://localhost:8188"
|
||||||
|
OUT_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "assets", "images", "hero")
|
||||||
|
CKPT = "sd_xl_base_1.0.safetensors"
|
||||||
|
|
||||||
|
NEG = (
|
||||||
|
"people, person, human, hands, feet, boots, shoes, worker, cleaner, technician, "
|
||||||
|
"machine, vacuum cleaner, equipment, steam, city skyline, apartment building exterior, "
|
||||||
|
"urban, dirty, stain, text, watermark, logo, blurry, low quality, cartoon, anime, "
|
||||||
|
"illustration, render, CGI"
|
||||||
|
)
|
||||||
|
|
||||||
|
IMAGES = [
|
||||||
|
{
|
||||||
|
"filename": "hero-carpet-cleaning.jpg",
|
||||||
|
"positive": (
|
||||||
|
"wide shot of a bright residential living room in an upstate New York home, "
|
||||||
|
"thick plush beige carpet throughout, warm afternoon sunlight through large windows, "
|
||||||
|
"comfortable couch and coffee table, Finger Lakes farmhouse style interior, "
|
||||||
|
"no people, ultra-realistic architectural photography, 16:9 wide"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "hero-stairs.jpg",
|
||||||
|
"positive": (
|
||||||
|
"wide shot of a clean carpeted staircase inside a suburban American home, "
|
||||||
|
"light grey carpet runner on dark wood stairs, white painted banister, "
|
||||||
|
"bright natural light from above, no people, no equipment, "
|
||||||
|
"ultra-realistic interior photography, 16:9"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "hero-upholstery.jpg",
|
||||||
|
"positive": (
|
||||||
|
"bright cozy living room in a residential home, large comfortable fabric sofa "
|
||||||
|
"in warm neutral tones, clean armchair beside it, plush carpet beneath, "
|
||||||
|
"afternoon light, Finger Lakes countryside visible through window, "
|
||||||
|
"no people, ultra-realistic interior photography, 16:9"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "hero-floors.jpg",
|
||||||
|
"positive": (
|
||||||
|
"wide shot of a clean hardwood floor hallway in a spacious suburban American home, "
|
||||||
|
"light oak floors gleaming, white walls, natural light streaming through windows, "
|
||||||
|
"no people, no equipment, ultra-realistic interior photography, 16:9"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "hero-area-rugs.jpg",
|
||||||
|
"positive": (
|
||||||
|
"beautiful oriental area rug centered in a bright residential living room, "
|
||||||
|
"rich warm tones of deep red and gold, hardwood floor surrounding the rug, "
|
||||||
|
"cozy farmhouse interior, natural light, Finger Lakes region home decor, "
|
||||||
|
"no people, ultra-realistic interior photography, 16:9"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "hero-add-ons.jpg",
|
||||||
|
"positive": (
|
||||||
|
"bright clean residential bedroom interior, freshly cleaned light beige carpet, "
|
||||||
|
"white walls, large window with sheer curtains, simple wooden bed frame, "
|
||||||
|
"crisp natural morning light, Finger Lakes home style, "
|
||||||
|
"no people, no machines, ultra-realistic interior photography, 16:9"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "hero-commercial.jpg",
|
||||||
|
"positive": (
|
||||||
|
"wide shot of a modern commercial office building lobby interior, "
|
||||||
|
"clean dark grey commercial carpet throughout, professional corporate space, "
|
||||||
|
"glass entrance doors, white walls, recessed lighting, "
|
||||||
|
"no people, no equipment, architectural photography, ultra-realistic, 16:9"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "hero-offices.jpg",
|
||||||
|
"positive": (
|
||||||
|
"modern open-plan corporate office interior, clean grey carpet tiles, "
|
||||||
|
"rows of empty desks, glass partitions, professional overhead lighting, "
|
||||||
|
"large windows with daylight, no people, no equipment, "
|
||||||
|
"architectural photography, ultra-realistic, 16:9"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "hero-vacation-rentals.jpg",
|
||||||
|
"positive": (
|
||||||
|
"bright cozy vacation rental cottage living room interior, Finger Lakes region style, "
|
||||||
|
"clean beige carpet, wooden ceiling beams, stone fireplace, comfortable furniture, "
|
||||||
|
"large window with lake view in distance, warm inviting atmosphere, "
|
||||||
|
"no people, ultra-realistic interior photography, 16:9"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "hero-hotels.jpg",
|
||||||
|
"positive": (
|
||||||
|
"long elegant hotel corridor interior, clean patterned burgundy carpet runner, "
|
||||||
|
"warm wall sconce lighting along white walls, numbered wooden room doors, "
|
||||||
|
"soft warm glow, upscale hospitality interior, no people, no equipment, "
|
||||||
|
"professional architectural photography, ultra-realistic, 16:9"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "hero-retail.jpg",
|
||||||
|
"positive": (
|
||||||
|
"upscale retail showroom interior, clean light grey carpet flooring, "
|
||||||
|
"modern minimalist display fixtures, bright track lighting overhead, "
|
||||||
|
"white walls, large storefront windows with natural light, "
|
||||||
|
"no people, no equipment, architectural photography, ultra-realistic, 16:9"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "hero-property-management.jpg",
|
||||||
|
"positive": (
|
||||||
|
"clean move-in ready apartment unit living room, fresh neutral carpet, "
|
||||||
|
"white walls, bright windows, empty space ready for tenants, "
|
||||||
|
"no furniture, no people, no equipment, "
|
||||||
|
"real estate photography style, ultra-realistic, 16:9"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "hero-about.jpg",
|
||||||
|
"positive": (
|
||||||
|
"warm exterior view of a classic upstate New York suburban home, "
|
||||||
|
"green lawn, mature trees, clear blue sky, Finger Lakes region, "
|
||||||
|
"inviting residential property, no people, no vehicles, "
|
||||||
|
"professional real estate photography, ultra-realistic, 16:9"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "hero-service-area.jpg",
|
||||||
|
"positive": (
|
||||||
|
"scenic Finger Lakes region landscape, rolling green hills, vineyards in distance, "
|
||||||
|
"calm lake water reflecting sky, late afternoon golden hour light, "
|
||||||
|
"upstate New York countryside, no people, no vehicles, "
|
||||||
|
"professional landscape photography, ultra-realistic, 16:9"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "hero-living-room.jpg",
|
||||||
|
"positive": (
|
||||||
|
"spacious bright residential living room in a Finger Lakes area home, "
|
||||||
|
"plush clean light grey carpet, white walls, large sectional sofa, "
|
||||||
|
"afternoon sunlight through bay windows, warm cozy family atmosphere, "
|
||||||
|
"no people, ultra-realistic interior photography, 16:9"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "hero-clean-result.jpg",
|
||||||
|
"positive": (
|
||||||
|
"close-up wide angle of immaculate freshly cleaned residential carpet, "
|
||||||
|
"uniform plush beige pile, bright natural light raking across the surface, "
|
||||||
|
"showing deep clean texture, no people, no machines, "
|
||||||
|
"ultra-realistic macro photography, 16:9"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def build_workflow(positive, seed=None):
|
||||||
|
if seed is None:
|
||||||
|
seed = random.randint(0, 2**32)
|
||||||
|
return {
|
||||||
|
"3": {"class_type": "KSampler", "inputs": {
|
||||||
|
"cfg": 7.5, "denoise": 1.0,
|
||||||
|
"latent_image": ["5", 0], "model": ["4", 0],
|
||||||
|
"negative": ["7", 0], "positive": ["6", 0],
|
||||||
|
"sampler_name": "dpmpp_2m", "scheduler": "karras",
|
||||||
|
"seed": seed, "steps": 30,
|
||||||
|
}},
|
||||||
|
"4": {"class_type": "CheckpointLoaderSimple", "inputs": {"ckpt_name": CKPT}},
|
||||||
|
"5": {"class_type": "EmptyLatentImage", "inputs": {"batch_size": 1, "height": 576, "width": 1024}},
|
||||||
|
"6": {"class_type": "CLIPTextEncode", "inputs": {"clip": ["4", 1], "text": positive}},
|
||||||
|
"7": {"class_type": "CLIPTextEncode", "inputs": {"clip": ["4", 1], "text": NEG}},
|
||||||
|
"8": {"class_type": "VAEDecode", "inputs": {"samples": ["3", 0], "vae": ["4", 2]}},
|
||||||
|
"9": {"class_type": "SaveImage", "inputs": {"filename_prefix": "hero_gen", "images": ["8", 0]}},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def queue_prompt(workflow):
|
||||||
|
data = json.dumps({"prompt": workflow}).encode()
|
||||||
|
req = urllib.request.Request(f"{COMFY}/prompt", data=data,
|
||||||
|
headers={"Content-Type": "application/json"})
|
||||||
|
with urllib.request.urlopen(req) as resp:
|
||||||
|
return json.loads(resp.read())["prompt_id"]
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_result(prompt_id, timeout=900):
|
||||||
|
start = time.time()
|
||||||
|
while time.time() - start < timeout:
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(f"{COMFY}/history/{prompt_id}") as resp:
|
||||||
|
hist = json.loads(resp.read())
|
||||||
|
if prompt_id in hist:
|
||||||
|
outputs = hist[prompt_id].get("outputs", {})
|
||||||
|
for node_out in outputs.values():
|
||||||
|
if "images" in node_out:
|
||||||
|
return node_out["images"]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
time.sleep(5)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def download_image(img_info, out_path):
|
||||||
|
fname = img_info["filename"]
|
||||||
|
subfolder = img_info.get("subfolder", "")
|
||||||
|
img_type = img_info.get("type", "output")
|
||||||
|
url = f"{COMFY}/view?filename={fname}&subfolder={subfolder}&type={img_type}"
|
||||||
|
with urllib.request.urlopen(url) as resp:
|
||||||
|
data = resp.read()
|
||||||
|
try:
|
||||||
|
from PIL import Image
|
||||||
|
import io
|
||||||
|
img = Image.open(io.BytesIO(data)).convert("RGB")
|
||||||
|
img.save(out_path, "JPEG", quality=92)
|
||||||
|
print(f" OK: {os.path.basename(out_path)} ({os.path.getsize(out_path)//1024}KB)", flush=True)
|
||||||
|
except ImportError:
|
||||||
|
png_path = out_path.replace(".jpg", ".png")
|
||||||
|
with open(png_path, "wb") as f:
|
||||||
|
f.write(data)
|
||||||
|
print(f" OK (PNG): {png_path}", flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
total = len(IMAGES)
|
||||||
|
for i, spec in enumerate(IMAGES):
|
||||||
|
out_path = os.path.join(OUT_DIR, spec["filename"])
|
||||||
|
print(f"\n[{i+1}/{total}] {spec['filename']}", flush=True)
|
||||||
|
workflow = build_workflow(spec["positive"])
|
||||||
|
prompt_id = queue_prompt(workflow)
|
||||||
|
print(f" queued {prompt_id[:8]}...", flush=True)
|
||||||
|
images = wait_for_result(prompt_id)
|
||||||
|
if images:
|
||||||
|
download_image(images[0], out_path)
|
||||||
|
else:
|
||||||
|
print(f" FAILED (timeout)", flush=True)
|
||||||
|
|
||||||
|
print("\nAll done.", flush=True)
|
||||||
Reference in New Issue
Block a user