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:
Concept Agent
2026-05-15 21:27:36 +02:00
parent 2aeb4285f9
commit 68d29ae532
20 changed files with 323 additions and 67 deletions
+3 -3
View File
@@ -14,7 +14,7 @@
</div>
</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="page-hero-content">
<span class="hero-eyebrow">Waterloo, NY &mdash; Finger Lakes Region</span>
@@ -29,7 +29,7 @@
<div class="container">
<div class="service-detail-row">
<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 class="service-detail-content">
<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>
</div>
<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>
Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

-1
View File
@@ -5,7 +5,6 @@
</a>
<ul class="nav-links">
<li><a href="/">Home</a></li>
<li><a href="/our-work/">Our Work</a></li>
<li><a href="/about/">About</a></li>
<li>
<div class="nav-dropdown">
+18 -13
View File
@@ -1,32 +1,37 @@
const mobileToggle = document.querySelector('.mobile-menu-toggle');
const navLinks = document.querySelector('.nav-links');
const navContact = document.querySelector('.nav-contact');
if (mobileToggle) {
mobileToggle.addEventListener('click', () => {
navLinks.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) {
e.preventDefault();
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');
}
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');
if (form) {
Binary file not shown.
+2 -2
View File
@@ -14,7 +14,7 @@
</div>
</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="page-hero-content">
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls &amp; 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>
<a href="/contact/" class="btn btn-primary">Book Now</a>
</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>
</section>
+2 -2
View File
@@ -14,7 +14,7 @@
</div>
</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="page-hero-content">
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls &amp; 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>
<a href="/contact/" class="btn btn-primary">Book Now</a>
</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>
</section>
+2 -2
View File
@@ -14,7 +14,7 @@
</div>
</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="page-hero-content">
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls &amp; 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>
<a href="/contact/" class="btn btn-primary">Book Now</a>
</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>
</section>
+2 -2
View File
@@ -14,7 +14,7 @@
</div>
</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="page-hero-content">
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls &amp; 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>
<a href="/contact/" class="btn btn-primary">Book Now</a>
</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>
</section>
+2 -2
View File
@@ -14,7 +14,7 @@
</div>
</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="page-hero-content">
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls &amp; 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>
<a href="/contact/" class="btn btn-primary">Book Now</a>
</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>
</section>
+11 -1
View File
@@ -3,7 +3,10 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lahr Carpet Cleaning | Professional Carpet &amp; Upholstery Cleaning in Waterloo, NY</title>
<title>Lahr Carpet Cleaning | Residential &amp; 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="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
</head>
@@ -277,6 +280,13 @@
</div>
</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>
<script src="/assets/js/components.js"></script>
+1 -1
View File
@@ -14,7 +14,7 @@
</div>
</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="page-hero-content">
<span class="hero-eyebrow">Seneca, Ontario, Schuyler &amp; Yates Counties</span>
+2 -2
View File
@@ -14,7 +14,7 @@
</div>
</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="page-hero-content">
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls &amp; 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>
<a href="/contact/" class="btn btn-primary">Book Now</a>
</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>
</section>
+2 -2
View File
@@ -14,7 +14,7 @@
</div>
</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="page-hero-content">
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls &amp; 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>
<a href="/contact/" class="btn btn-primary">Book Now</a>
</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>
</section>
+2 -2
View File
@@ -14,7 +14,7 @@
</div>
</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="page-hero-content">
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls &amp; 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>
<a href="/contact/" class="btn btn-primary">Book Now</a>
</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>
</section>
+2 -2
View File
@@ -14,7 +14,7 @@
</div>
</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="page-hero-content">
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls &amp; 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>
<a href="/contact/" class="btn btn-primary">Book Now</a>
</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>
</section>
+2 -2
View File
@@ -14,7 +14,7 @@
</div>
</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="page-hero-content">
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls &amp; 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>
<a href="/contact/" class="btn btn-primary">Book Now</a>
</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>
</section>
+2 -2
View File
@@ -14,7 +14,7 @@
</div>
</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="page-hero-content">
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls &amp; 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>
<a href="/contact/" class="btn btn-primary">Book Now</a>
</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>
</section>
+16 -18
View File
@@ -18,34 +18,32 @@ os.makedirs(TMP_DIR, exist_ok=True)
CLIPS = [
"v3-shot-02", # 1 - wine spill on sofa
"shot-05-extraction-couch",# 2 - extraction couch
"shot-03-technician", # 3 - technician
"v3-shot-03", # 4 - dirty stained carpet close-up
"v3-shot-06", # 5 - living room clean carpet pan
"v3-shot-07", # 6 - restaurant carpet glide
"v3-shot-05", # 7 - office lobby carpet pan
"v2-shot-05-clean-stairs", # 8 - clean bright staircase
"v2-shot-07-restaurant", # 9 - restaurant carpet
"v2-shot-06-office", # 10 - bright office carpet
"shot-01-wide-room", # 11 - wide room establishing
"shot-05-clean-reveal", # 12 - clean reveal
"shot-04-extraction-carpet",# 13 - final reveal
"v3-shot-03", # 3 - dirty stained carpet close-up
"v3-shot-06", # 4 - living room clean carpet pan
"v3-shot-07", # 5 - restaurant carpet glide
"v3-shot-05", # 6 - office lobby carpet pan
"v2-shot-05-clean-stairs", # 7 - clean bright staircase
"v2-shot-07-restaurant", # 8 - restaurant carpet
"v2-shot-06-office", # 9 - bright office carpet
"shot-01-wide-room", # 10 - wide room establishing
"shot-05-clean-reveal", # 11 - clean reveal
"shot-04-extraction-carpet",# 12 - final reveal
]
# Duration for each clip
DURATIONS = [
4.5, # 1 wine spill — cut at 4.5s
5.0, # 2
5.0, # 3
4.0, # 4 --- building pace ---
4.0, # 3 --- building pace ---
4.0, # 4
4.0, # 5
4.0, # 6
3.5, # 6
3.5, # 7
3.5, # 8
2.5, # 9 --- rapid ---
2.5, # 8 --- rapid ---
2.5, # 9
2.5, # 10
2.5, # 11
2.5, # 12
6.0, # 13 final reveal — hold
6.0, # 12 final reveal — hold
]
paced_clips = []
+244
View File
@@ -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)