Compare commits
10 Commits
1b99352327
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 637159f9c1 | |||
| 3cf84b07b2 | |||
| 264281abdd | |||
| aae53ae0ee | |||
| d626ad2e90 | |||
| 6891eaa18e | |||
| 53aacf9999 | |||
| 2e9329b1f4 | |||
| e464ac20f0 | |||
| 50be7cd947 |
@@ -1,7 +1,7 @@
|
||||
---
|
||||
deployment:
|
||||
tasks:
|
||||
- export DEPLOYPATH=/home/dev1communitypro/public_html/lahrcarpetcleaning.dev1.communityproud.com/
|
||||
- export DEPLOYPATH=/home/lahrcarpetcleani/public_html/
|
||||
- /bin/cp -r assets "$DEPLOYPATH"
|
||||
- /bin/cp -r about "$DEPLOYPATH"
|
||||
- /bin/cp -r commercial "$DEPLOYPATH"
|
||||
|
||||
@@ -6,3 +6,6 @@ build/
|
||||
*.log
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.claude/
|
||||
.planning/
|
||||
.tools/
|
||||
|
||||
@@ -1,14 +1,36 @@
|
||||
Options -Indexes
|
||||
RewriteEngine On
|
||||
|
||||
# Deny sensitive files
|
||||
<FilesMatch "\.(py|yml|yaml|md|log|sh|env|conf|dockerfile)$">
|
||||
# Security headers
|
||||
<IfModule mod_headers.c>
|
||||
Header always set X-Frame-Options "SAMEORIGIN"
|
||||
Header always set X-Content-Type-Options "nosniff"
|
||||
Header always set X-XSS-Protection "1; mode=block"
|
||||
Header always set Referrer-Policy "strict-origin-when-cross-origin"
|
||||
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
|
||||
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data:; media-src 'self'; connect-src 'self'; frame-ancestors 'none';"
|
||||
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
|
||||
</IfModule>
|
||||
|
||||
# Block server version
|
||||
ServerSignature Off
|
||||
|
||||
# Deny sensitive file types
|
||||
<FilesMatch "\.(py|pyc|yml|yaml|md|log|sh|env|conf|dockerfile|bak|backup|sql|key|pem|planning|cpanel)$">
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
</FilesMatch>
|
||||
|
||||
# Deny tools directory
|
||||
RewriteRule ^tools/ - [F,L]
|
||||
# Block root-level dev files
|
||||
<FilesMatch "^(Dockerfile|docker-compose\.yml|README\.md|\.cpanel\.yml)$">
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
</FilesMatch>
|
||||
|
||||
# Block tools and hidden directories
|
||||
RewriteRule ^\.tools/ - [F,L]
|
||||
RewriteRule ^\.planning/ - [F,L]
|
||||
RewriteRule ^\.claude/ - [F,L]
|
||||
|
||||
ErrorDocument 404 /404.html
|
||||
ErrorDocument 500 /500.html
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
# SITEMAP_CANONICAL.md — lahrcarpetcleaning.com
|
||||
|
||||
## Nav Structure
|
||||
Home · Residential · Commercial · Our Work · About · Company
|
||||
|
||||
---
|
||||
|
||||
## Pages
|
||||
|
||||
### / — Home
|
||||
Status: EXISTS
|
||||
File: index.html
|
||||
|
||||
---
|
||||
|
||||
### /residential/ — Residential Services (landing)
|
||||
Status: TODO — new page
|
||||
Nav: Residential (parent)
|
||||
Purpose: Overview of all residential services with cards linking to each sub-page.
|
||||
|
||||
#### /residential/carpet-cleaning/
|
||||
Status: EXISTS (currently at /services/carpet-cleaning/) — needs folder rename
|
||||
Hero image: hero-technician.jpg
|
||||
|
||||
#### /residential/stairs/
|
||||
Status: EXISTS (currently at /services/stairs/) — needs folder rename
|
||||
Hero image: hero-stairs.jpg
|
||||
|
||||
#### /residential/upholstery/
|
||||
Status: EXISTS (currently at /services/upholstery/) — needs folder rename
|
||||
Hero image: hero-clean-result.jpg
|
||||
|
||||
#### /residential/floors/
|
||||
Status: EXISTS (currently at /services/floors/) — needs folder rename
|
||||
Hero image: hero-living-room.jpg
|
||||
|
||||
#### /residential/area-rugs/
|
||||
Status: EXISTS (currently at /services/area-rugs/) — needs folder rename
|
||||
Hero image: hero-clean-result.jpg
|
||||
|
||||
#### /residential/add-ons/
|
||||
Status: EXISTS (currently at /services/add-ons/) — needs folder rename
|
||||
Hero image: hero-before-after.jpg
|
||||
|
||||
---
|
||||
|
||||
### /commercial/ — Commercial Cleaning
|
||||
Status: EXISTS (currently at /services/commercial/) — needs folder move to root
|
||||
Hero image: AdobeStock commercial images (keep as-is)
|
||||
|
||||
---
|
||||
|
||||
### /our-work/ — Our Work (Before/After Gallery)
|
||||
Status: TODO — new page
|
||||
Nav: Our Work (standalone)
|
||||
Purpose: Grid of before/after job photos. Real client work images.
|
||||
Images: assets/images/our-work/ (folder needed)
|
||||
|
||||
---
|
||||
|
||||
### /about/ — About
|
||||
Status: EXISTS
|
||||
Nav: About (standalone)
|
||||
|
||||
---
|
||||
|
||||
### /company/ — Company (dropdown parent, no landing needed)
|
||||
Nav label: Company
|
||||
|
||||
#### /contact/ — Contact
|
||||
Status: EXISTS
|
||||
|
||||
#### /service-area/ — Service Area
|
||||
Status: TODO — new page
|
||||
Purpose: Towns served — Waterloo, Seneca Falls, Geneva, Canandaigua, Auburn, Finger Lakes region.
|
||||
SEO: local landing content per town area.
|
||||
|
||||
#### /reviews/ — Reviews
|
||||
Status: TODO — new page
|
||||
Purpose: Google review embeds / static testimonials.
|
||||
|
||||
---
|
||||
|
||||
## Folder Rename Plan
|
||||
|
||||
| Current path | New path |
|
||||
|----------------------------|-------------------------------|
|
||||
| /services/carpet-cleaning/ | /residential/carpet-cleaning/ |
|
||||
| /services/stairs/ | /residential/stairs/ |
|
||||
| /services/upholstery/ | /residential/upholstery/ |
|
||||
| /services/floors/ | /residential/floors/ |
|
||||
| /services/area-rugs/ | /residential/area-rugs/ |
|
||||
| /services/add-ons/ | /residential/add-ons/ |
|
||||
| /services/commercial/ | /commercial/ |
|
||||
|
||||
All internal nav links and footer links update site-wide on rename.
|
||||
|
||||
---
|
||||
|
||||
## sitemap.xml URLs (target)
|
||||
|
||||
/
|
||||
/residential/
|
||||
/residential/carpet-cleaning/
|
||||
/residential/stairs/
|
||||
/residential/upholstery/
|
||||
/residential/floors/
|
||||
/residential/area-rugs/
|
||||
/residential/add-ons/
|
||||
/commercial/
|
||||
/our-work/
|
||||
/about/
|
||||
/contact/
|
||||
/service-area/
|
||||
/reviews/
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
- No pricing on any page (pricing flyer is internal reference only)
|
||||
- All hero images: Imagen 4 generated (assets/images/hero/)
|
||||
- Service area focus: Upstate NY — Waterloo, Seneca Falls, Geneva, Finger Lakes region
|
||||
- Phone: 315-719-1218 | Email: lahrcarpet@gmail.com
|
||||
- Address: 1076 Waterloo/Geneva Road, Waterloo, NY
|
||||
|
Before Width: | Height: | Size: 169 KiB |
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com404.html">
|
||||
<meta property="og:title" content="Page Not Found | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet and upholstery cleaning in the Finger Lakes, NY.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/og-image.jpg">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Page Not Found | Lahr Carpet Cleaning</title>
|
||||
<link rel="stylesheet" href="/assets/css/styles.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com500.html">
|
||||
<meta property="og:title" content="Server Error | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet and upholstery cleaning in the Finger Lakes, NY.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/og-image.jpg">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Server Error | Lahr Carpet Cleaning</title>
|
||||
<link rel="stylesheet" href="/assets/css/styles.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
|
||||
@@ -3,9 +3,23 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/about/">
|
||||
<meta property="og:title" content="About Lahr Carpet Cleaning | Waterloo NY">
|
||||
<meta property="og:description" content="Professional carpet and upholstery cleaning in the Finger Lakes, NY.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-living-room.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>About Lahr Carpet Cleaning | Waterloo NY</title>
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
<link rel="canonical" href="https://lahrcarpetcleaning.com/about/">
|
||||
<script type="application/ld+json">{"@context":"https://schema.org","@type":"LocalBusiness","name":"Lahr Carpet Cleaning","url":"https://lahrcarpetcleaning.com","telephone":"+13157191218","image":"https://lahrcarpetcleaning.com/assets/images/hero/hero-carpet-cleaning.webp","address":{"@type":"PostalAddress","streetAddress":"1076 Waterloo/Geneva Road","addressLocality":"Waterloo","addressRegion":"NY","postalCode":"13165","addressCountry":"US"},"description":"Professional carpet and upholstery cleaning for homes and businesses across the Finger Lakes region of Upstate New York.","areaServed":[{"@type":"City","name":"Waterloo"},{"@type":"City","name":"Geneva"},{"@type":"City","name":"Seneca Falls"},{"@type":"City","name":"Canandaigua"},{"@type":"City","name":"Penn Yan"}]}</script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="header" id="header">
|
||||
@@ -14,13 +28,13 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-about.webp'); background-size: cover; background-position: center;">
|
||||
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-living-room.webp'); background-size: cover; background-position: center;">
|
||||
<div class="container">
|
||||
<div class="page-hero-content">
|
||||
<span class="hero-eyebrow">Waterloo, NY — Finger Lakes Region</span>
|
||||
<h1>About <span class="text-accent">Lahr</span> Carpet Cleaning</h1>
|
||||
<p>Local, reliable carpet care that treats your home with respect.</p>
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -29,7 +43,7 @@
|
||||
<div class="container">
|
||||
<div class="service-detail-row">
|
||||
<div class="service-detail-image">
|
||||
<img src="/assets/images/hero/hero-about.webp" alt="Lahr Carpet Cleaning technician at work" style="width:100%;height:500px;object-fit:cover;border-radius:6px;">
|
||||
<img src="/assets/images/hero/hero-living-room.webp" 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 +112,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-about.webp" 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-living-room.webp" alt="Clean living room carpet in Finger Lakes home" style="width:100%;height:420px;object-fit:cover;border-radius:6px;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -108,7 +122,7 @@
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Ready to See the </strong><span class="text-accent"><strong>Difference</strong></span><strong>?</strong></h2>
|
||||
<p class="paragraph-4">Call us or fill out our contact form. Free estimates included with every booking.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Call Now</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -205,6 +205,11 @@ h1,h2,h3,h4 {
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
.logo-highlight { color: var(--color-accent); }
|
||||
.logo-img {
|
||||
height: 44px;
|
||||
width: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
@@ -1079,6 +1084,7 @@ h1,h2,h3,h4 {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
.footer-logo .logo-img { height: 50px; }
|
||||
.footer-brand p {
|
||||
color: var(--color-text-2);
|
||||
font-size: 0.875rem;
|
||||
|
||||
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 848 B |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 870 B |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 81 KiB |
@@ -1,7 +1,7 @@
|
||||
(function () {
|
||||
var NAV = `<nav class="navbar">
|
||||
<a href="/" class="logo">
|
||||
<span class="logo-text">Lahr <span class="logo-highlight">Carpet</span></span>
|
||||
<img src="/assets/images/logo.webp" alt="Lahr Carpet Cleaning" class="logo-img" width="160" height="67">
|
||||
</a>
|
||||
<ul class="nav-links">
|
||||
<li><a href="/">Home</a></li>
|
||||
@@ -47,7 +47,8 @@
|
||||
<div class="nav-contact">
|
||||
<a href="https://www.facebook.com/profile.php?id=61559554960851" class="social-icon" aria-label="Facebook" target="_blank"><i class="fab fa-facebook-f"></i></a>
|
||||
<a href="https://www.instagram.com/lahrautospa/" class="social-icon" aria-label="Instagram" target="_blank"><i class="fab fa-instagram"></i></a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary"><i class="fas fa-phone"></i></a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost"><i class="fas fa-phone"></i></a>
|
||||
<a href="https://bookedin.com/book/lahr-carpet-cleaning-and-upholstery" class="btn btn-primary" target="_blank" rel="noopener noreferrer"><i class="fas fa-calendar-check"></i> Book Online</a>
|
||||
</div>
|
||||
<button class="mobile-menu-toggle" aria-label="Toggle menu">
|
||||
<span></span><span></span><span></span>
|
||||
@@ -59,7 +60,7 @@
|
||||
<div class="footer-grid">
|
||||
<div class="footer-brand">
|
||||
<a href="/" class="footer-logo">
|
||||
<span class="logo-text">Lahr <span class="logo-highlight">Carpet</span></span>
|
||||
<img src="/assets/images/logo.webp" alt="Lahr Carpet Cleaning" class="logo-img" width="160" height="67">
|
||||
</a>
|
||||
<p>We clean carpets, upholstery, rugs, and hard floors for homes and businesses in Waterloo, Geneva, and the Finger Lakes region.</p>
|
||||
</div>
|
||||
@@ -94,6 +95,7 @@
|
||||
<li><i class="fas fa-phone"></i> <a href="tel:315-719-1218">315-719-1218</a></li>
|
||||
<li><i class="fas fa-envelope"></i> <a href="mailto:lahrcarpet@gmail.com">lahrcarpet@gmail.com</a></li>
|
||||
<li><i class="fas fa-location-dot"></i> 1076 Waterloo/Geneva Road, Waterloo, NY</li>
|
||||
<li><i class="fas fa-calendar-check"></i> <a href="https://bookedin.com/book/lahr-carpet-cleaning-and-upholstery" target="_blank" rel="noopener noreferrer">Book Online</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -133,10 +135,17 @@
|
||||
</div>
|
||||
</section>`;
|
||||
|
||||
function inject() {
|
||||
var navEl = document.getElementById('site-nav');
|
||||
var footerEl = document.getElementById('site-footer');
|
||||
var processEl = document.getElementById('site-process');
|
||||
if (navEl) navEl.innerHTML = NAV;
|
||||
if (footerEl) footerEl.innerHTML = FOOTER;
|
||||
if (processEl) processEl.outerHTML = PROCESS;
|
||||
}
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', inject);
|
||||
} else {
|
||||
inject();
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/commercial/hotels-inns/">
|
||||
<meta property="og:title" content="Hotel and Inn Carpet Cleaning | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet and upholstery cleaning in the Finger Lakes, NY.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-hotels.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Hotel and Inn Carpet Cleaning | Lahr Carpet Cleaning</title>
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
@@ -20,7 +32,7 @@
|
||||
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
||||
<h1>Hotel and Inn Carpet Cleaning in the Finger Lakes</h1>
|
||||
<p>Guests write reviews based on what they see and smell. Worn, stained carpet is the detail that costs you stars.</p>
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -65,7 +77,7 @@
|
||||
<h2>We Understand the Pace of Wine Country Hospitality</h2>
|
||||
<p>Finger Lakes lodging properties face compressed busy seasons and demanding guests who compare properties online before they arrive. Your property needs to look its best during peak periods, not just between them. We work with innkeepers and property managers to build cleaning programs that keep up with the season.</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="tel:315-719-1218" class="btn btn-primary">Call Now</a>
|
||||
</div>
|
||||
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-hotels.webp')"></div>
|
||||
</div>
|
||||
@@ -78,7 +90,7 @@
|
||||
<h2>Protect Your Property Rating With Clean Carpet</h2>
|
||||
<p>Book a cleaning program built around your occupancy schedule.</p>
|
||||
<div class="cta-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/commercial/offices/">
|
||||
<meta property="og:title" content="Office Carpet Cleaning Waterloo NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet and upholstery cleaning in the Finger Lakes, NY.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-offices.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Office Carpet Cleaning Waterloo NY | Lahr Carpet Cleaning</title>
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
@@ -20,7 +32,7 @@
|
||||
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
||||
<h1>Office Carpet Cleaning in Waterloo, NY</h1>
|
||||
<p>Worn office carpet tells clients you do not care about the details. We clean after hours so your space looks sharp every morning.</p>
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -65,7 +77,7 @@
|
||||
<h2>No Disruption. No Excuses. Just Clean Carpet.</h2>
|
||||
<p>Office managers need vendors who show up on schedule, communicate clearly, and do not require hand-holding. We have built our commercial business in Waterloo and the surrounding area on exactly that kind of reliability.</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="tel:315-719-1218" class="btn btn-primary">Call Now</a>
|
||||
</div>
|
||||
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-offices.webp')"></div>
|
||||
</div>
|
||||
@@ -78,7 +90,7 @@
|
||||
<h2>Your Office Should Look As Professional As You Are</h2>
|
||||
<p>Book after-hours cleaning and give your clients the right first impression.</p>
|
||||
<div class="cta-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/commercial/property-management/">
|
||||
<meta property="og:title" content="Carpet Cleaning for Property Managers | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet and upholstery cleaning in the Finger Lakes, NY.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-property-management.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Carpet Cleaning for Property Managers | Lahr Carpet Cleaning</title>
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
@@ -20,7 +32,7 @@
|
||||
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
||||
<h1>Carpet Cleaning for Property Managers in Upstate NY</h1>
|
||||
<p>Managing multiple units means you need carpet vendors who show up, do it right, and get out of your way.</p>
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -65,7 +77,7 @@
|
||||
<h2>We Work at the Speed Your Turnovers Demand</h2>
|
||||
<p>A unit sitting empty costs money. We understand that turnover timelines are tight and that carpet cleaning cannot be the bottleneck that delays a new lease. Lahr Carpet Cleaning prioritizes commercial accounts with committed scheduling so your units move through the process on time.</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="tel:315-719-1218" class="btn btn-primary">Call Now</a>
|
||||
</div>
|
||||
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-property-management.webp')"></div>
|
||||
</div>
|
||||
@@ -78,7 +90,7 @@
|
||||
<h2>Stop Chasing Carpet Vendors Between Tenants</h2>
|
||||
<p>Set up a portfolio relationship with one reliable local cleaner and move on.</p>
|
||||
<div class="cta-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/commercial/retail-showrooms/">
|
||||
<meta property="og:title" content="Retail and Showroom Carpet Cleaning | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet and upholstery cleaning in the Finger Lakes, NY.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-retail.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Retail and Showroom Carpet Cleaning | Lahr Carpet Cleaning</title>
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
@@ -20,7 +32,7 @@
|
||||
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
||||
<h1>Retail and Showroom Carpet Cleaning in the Finger Lakes</h1>
|
||||
<p>Worn showroom carpet undermines your product before a customer reaches for it. We restore what foot traffic takes away.</p>
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -65,7 +77,7 @@
|
||||
<h2>The Finger Lakes Market Expects a Premium Experience</h2>
|
||||
<p>Visitors to this region come to taste fine wine, browse galleries, and shop in spaces that feel curated. A poorly maintained floor undercuts everything else you have built. Retail and showroom clients in this market hold their venues to a higher standard because their customers do.</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="tel:315-719-1218" class="btn btn-primary">Call Now</a>
|
||||
</div>
|
||||
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-retail.webp')"></div>
|
||||
</div>
|
||||
@@ -78,7 +90,7 @@
|
||||
<h2>Let Your Showroom Floor Reflect the Quality You Sell</h2>
|
||||
<p>Book after-hours cleaning and give every customer the right first step.</p>
|
||||
<div class="cta-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/commercial/vacation-rentals/">
|
||||
<meta property="og:title" content="Carpet Cleaning for Vacation Rentals | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet and upholstery cleaning in the Finger Lakes, NY.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-vacation-rentals.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Carpet Cleaning for Vacation Rentals | Lahr Carpet Cleaning</title>
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
@@ -20,7 +32,7 @@
|
||||
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
||||
<h1>Carpet Cleaning for Vacation Rentals in the Finger Lakes</h1>
|
||||
<p>One stained carpet can cost you a five-star review. We turn over your property fast so the next guests see it at its best.</p>
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -65,7 +77,7 @@
|
||||
<h2>A Cleaning Partner You Can Actually Count On</h2>
|
||||
<p>Managing a short-term rental from a distance is stressful enough. Your cleaning vendors need to show up on time and do the job right without you standing there. Lahr Carpet Cleaning has worked with Finger Lakes hosts long enough to understand what that reliability means.</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="tel:315-719-1218" class="btn btn-primary">Call Now</a>
|
||||
</div>
|
||||
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-vacation-rentals.webp')"></div>
|
||||
</div>
|
||||
@@ -78,7 +90,7 @@
|
||||
<h2>Keep Your Rental Property Guest-Ready</h2>
|
||||
<p>Book between-guest carpet cleaning and protect the rating your property deserves.</p>
|
||||
<div class="cta-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/contact/">
|
||||
<meta property="og:title" content="Contact | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet and upholstery cleaning in the Finger Lakes, NY.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-clean-result.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Contact | Lahr Carpet Cleaning</title>
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
@@ -181,7 +193,51 @@
|
||||
justify-content: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.book-online-banner {
|
||||
background: linear-gradient(135deg, rgba(79,195,247,0.15), rgba(79,195,247,0.05));
|
||||
border: 1px solid rgba(79,195,247,0.3);
|
||||
border-radius: 12px;
|
||||
padding: 28px 32px;
|
||||
margin-bottom: 28px;
|
||||
text-align: center;
|
||||
}
|
||||
.book-online-banner h3 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
color: #fff;
|
||||
}
|
||||
.book-online-banner p {
|
||||
color: rgba(255,255,255,0.65);
|
||||
font-size: 0.95rem;
|
||||
margin-bottom: 18px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.book-online-banner .btn {
|
||||
font-size: 1rem;
|
||||
padding: 14px 32px;
|
||||
}
|
||||
.form-divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 28px;
|
||||
color: rgba(255,255,255,0.35);
|
||||
font-size: 0.82rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
font-weight: 600;
|
||||
}
|
||||
.form-divider::before,
|
||||
.form-divider::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: rgba(255,255,255,0.1);
|
||||
}
|
||||
</style>
|
||||
<link rel="canonical" href="https://lahrcarpetcleaning.com/contact/">
|
||||
<script type="application/ld+json">{"@context":"https://schema.org","@type":"LocalBusiness","name":"Lahr Carpet Cleaning","url":"https://lahrcarpetcleaning.com","telephone":"+13157191218","image":"https://lahrcarpetcleaning.com/assets/images/hero/hero-carpet-cleaning.webp","address":{"@type":"PostalAddress","streetAddress":"1076 Waterloo/Geneva Road","addressLocality":"Waterloo","addressRegion":"NY","postalCode":"13165","addressCountry":"US"},"description":"Professional carpet and upholstery cleaning for homes and businesses across the Finger Lakes region of Upstate New York.","areaServed":[{"@type":"City","name":"Waterloo"},{"@type":"City","name":"Geneva"},{"@type":"City","name":"Seneca Falls"},{"@type":"City","name":"Canandaigua"},{"@type":"City","name":"Penn Yan"}]}</script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="header" id="header">
|
||||
@@ -238,6 +294,12 @@
|
||||
</div>
|
||||
|
||||
<div class="contact-page-form">
|
||||
<div class="book-online-banner">
|
||||
<h3><i class="fas fa-calendar-check"></i> Book Online Instantly</h3>
|
||||
<p>Skip the form and schedule your cleaning directly. Pick your date and time and you are all set.</p>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Call Now</a>
|
||||
</div>
|
||||
<div class="form-divider">or send us a message</div>
|
||||
<h2>Book Your Cleaning</h2>
|
||||
<p class="form-sub">Fill out the form and we will be in touch to confirm your appointment.</p>
|
||||
<form id="contactForm" action="#" method="POST">
|
||||
|
||||
@@ -3,12 +3,25 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Lahr Carpet Cleaning | Residential & Commercial Carpet Cleaning — Finger Lakes, NY</title>
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/">
|
||||
<meta property="og:title" content="Lahr Carpet Cleaning | Residential & Commercial Carpet Cleaning, Finger Lakes NY">
|
||||
<meta property="og: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 property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-carpet-cleaning.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<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=6">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
<script type="application/ld+json">{"@context":"https://schema.org","@type":"LocalBusiness","name":"Lahr Carpet Cleaning","url":"https://lahrcarpetcleaning.com","telephone":"+13157191218","image":"https://lahrcarpetcleaning.com/assets/images/hero/hero-carpet-cleaning.webp","address":{"@type":"PostalAddress","streetAddress":"1076 Waterloo/Geneva Road","addressLocality":"Waterloo","addressRegion":"NY","postalCode":"13165","addressCountry":"US"},"description":"Professional carpet and upholstery cleaning for homes and businesses across the Finger Lakes region of Upstate New York.","areaServed":[{"@type":"City","name":"Waterloo"},{"@type":"City","name":"Geneva"},{"@type":"City","name":"Seneca Falls"},{"@type":"City","name":"Canandaigua"},{"@type":"City","name":"Penn Yan"}]}</script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="header" id="header">
|
||||
@@ -29,7 +42,7 @@
|
||||
<h1 class="hero-title">Deeply<br>Clean<br><span class="text-accent">Carpets.</span></h1>
|
||||
<p class="hero-sub">Professional carpet and upholstery cleaning for homes and businesses across Upstate New York.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -52,10 +65,10 @@
|
||||
</div>
|
||||
<i class="fas fa-arrow-right cta-strip-arrow"></i>
|
||||
</a>
|
||||
<a href="/contact/" class="cta-strip-item cta-strip-primary">
|
||||
<a href="tel:315-719-1218" class="cta-strip-item cta-strip-primary">
|
||||
<div class="cta-strip-icon"><i class="fas fa-calendar-check"></i></div>
|
||||
<div class="cta-strip-text">
|
||||
<span class="cta-strip-label">Book Now</span>
|
||||
<span class="cta-strip-label">Call Now</span>
|
||||
<span class="cta-strip-sub">Schedule today</span>
|
||||
</div>
|
||||
<i class="fas fa-arrow-right cta-strip-arrow"></i>
|
||||
@@ -180,7 +193,7 @@
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Book Your </strong><span class="text-accent"><strong>Cleaning</strong></span><strong> Today!</strong></h2>
|
||||
<p class="paragraph-4">Get a free estimate for your home or business. We serve Waterloo, Geneva, and the Finger Lakes region.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Call Now</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -4,6 +4,42 @@ server {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data:; media-src 'self'; connect-src 'self'; frame-ancestors 'none';" always;
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
|
||||
# Remove server version fingerprint
|
||||
server_tokens off;
|
||||
|
||||
# Block dot files and hidden directories
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
return 404;
|
||||
}
|
||||
|
||||
# Block tools directory (hidden, but belt-and-suspenders)
|
||||
location ^~ /.tools/ {
|
||||
deny all;
|
||||
return 404;
|
||||
}
|
||||
|
||||
# Block sensitive file types
|
||||
location ~* \.(env|log|sh|py|pyc|md|yml|yaml|conf|dockerfile|dockerignore|bak|backup|sql|key|pem|json|planning|cpanel)$ {
|
||||
deny all;
|
||||
return 404;
|
||||
}
|
||||
|
||||
# Block README and Dockerfile at root
|
||||
location ~* ^/(README|Dockerfile|docker-compose\.yml|\.cpanel\.yml)$ {
|
||||
deny all;
|
||||
return 404;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ $uri/index.html =404;
|
||||
}
|
||||
@@ -11,16 +47,8 @@ server {
|
||||
error_page 404 /404.html;
|
||||
error_page 500 502 503 504 /500.html;
|
||||
|
||||
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf)$ {
|
||||
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|webp|woff|woff2|ttf|mp4|webm)$ {
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
}
|
||||
|
||||
location ~* \.(env|Dockerfile|dockerignore|yml|yaml|md|planning)$ {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/locations/canandaigua-ny/">
|
||||
<meta property="og:title" content="Carpet Cleaning in Canandaigua, NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet, upholstery, and floor cleaning in Canandaigua, NY. Lahr Carpet Cleaning serves Ontario County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-clean-result.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Carpet Cleaning in Canandaigua, NY | Lahr Carpet Cleaning</title>
|
||||
<meta name="description" content="Professional carpet, upholstery, and floor cleaning in Canandaigua, NY. Lahr Carpet Cleaning serves Ontario County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
@@ -20,7 +32,7 @@
|
||||
<h1 class="hero-title">Canandaigua,<br><span class="text-accent">NY</span></h1>
|
||||
<p>Lakefront homes, rentals, and businesses along Canandaigua Lake.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,7 +97,7 @@
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Serving </strong><span class="text-accent"><strong>Canandaigua</strong></span></h2>
|
||||
<p class="paragraph-4">Call 315-719-1218 or submit the form for a free estimate in Canandaigua, NY.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Get a Free Estimate</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Get a Free Estimate</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/locations/clifton-springs-ny/">
|
||||
<meta property="og:title" content="Carpet Cleaning in Clifton Springs, NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet, upholstery, and floor cleaning in Clifton Springs, NY. Lahr Carpet Cleaning serves Ontario County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-clean-result.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Carpet Cleaning in Clifton Springs, NY | Lahr Carpet Cleaning</title>
|
||||
<meta name="description" content="Professional carpet, upholstery, and floor cleaning in Clifton Springs, NY. Lahr Carpet Cleaning serves Ontario County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
@@ -20,7 +32,7 @@
|
||||
<h1 class="hero-title">Clifton Springs,<br><span class="text-accent">NY</span></h1>
|
||||
<p>Residential and commercial cleaning throughout Clifton Springs.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,7 +97,7 @@
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Serving </strong><span class="text-accent"><strong>Clifton Springs</strong></span></h2>
|
||||
<p class="paragraph-4">Call 315-719-1218 or submit the form for a free estimate in Clifton Springs, NY.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Get a Free Estimate</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Get a Free Estimate</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/locations/clyde-ny/">
|
||||
<meta property="og:title" content="Carpet Cleaning in Clyde, NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet, upholstery, and floor cleaning in Clyde, NY. Lahr Carpet Cleaning serves Wayne County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-clean-result.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Carpet Cleaning in Clyde, NY | Lahr Carpet Cleaning</title>
|
||||
<meta name="description" content="Professional carpet, upholstery, and floor cleaning in Clyde, NY. Lahr Carpet Cleaning serves Wayne County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
@@ -20,7 +32,7 @@
|
||||
<h1 class="hero-title">Clyde,<br><span class="text-accent">NY</span></h1>
|
||||
<p>Carpet, upholstery, and floor cleaning for homes and businesses in Clyde.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,7 +97,7 @@
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Serving </strong><span class="text-accent"><strong>Clyde</strong></span></h2>
|
||||
<p class="paragraph-4">Call 315-719-1218 or submit the form for a free estimate in Clyde, NY.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Get a Free Estimate</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Get a Free Estimate</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/locations/east-bloomfield-ny/">
|
||||
<meta property="og:title" content="Carpet Cleaning in East Bloomfield, NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet, upholstery, and floor cleaning in East Bloomfield, NY. Lahr Carpet Cleaning serves Ontario County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-clean-result.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Carpet Cleaning in East Bloomfield, NY | Lahr Carpet Cleaning</title>
|
||||
<meta name="description" content="Professional carpet, upholstery, and floor cleaning in East Bloomfield, NY. Lahr Carpet Cleaning serves Ontario County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
@@ -20,7 +32,7 @@
|
||||
<h1 class="hero-title">East Bloomfield,<br><span class="text-accent">NY</span></h1>
|
||||
<p>Serving homes and properties in East Bloomfield and surrounding areas.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,7 +97,7 @@
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Serving </strong><span class="text-accent"><strong>East Bloomfield</strong></span></h2>
|
||||
<p class="paragraph-4">Call 315-719-1218 or submit the form for a free estimate in East Bloomfield, NY.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Get a Free Estimate</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Get a Free Estimate</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/locations/farmington-ny/">
|
||||
<meta property="og:title" content="Carpet Cleaning in Farmington, NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet, upholstery, and floor cleaning in Farmington, NY. Lahr Carpet Cleaning serves Ontario County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-carpet-cleaning.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Carpet Cleaning in Farmington, NY | Lahr Carpet Cleaning</title>
|
||||
<meta name="description" content="Professional carpet, upholstery, and floor cleaning in Farmington, NY. Lahr Carpet Cleaning serves Ontario County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
@@ -20,7 +32,7 @@
|
||||
<h1 class="hero-title">Farmington,<br><span class="text-accent">NY</span></h1>
|
||||
<p>Residential and commercial carpet cleaning throughout Farmington.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,7 +97,7 @@
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Serving </strong><span class="text-accent"><strong>Farmington</strong></span></h2>
|
||||
<p class="paragraph-4">Call 315-719-1218 or submit the form for a free estimate in Farmington, NY.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Get a Free Estimate</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Get a Free Estimate</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/locations/finger-lakes-ny/">
|
||||
<meta property="og:title" content="Carpet Cleaning in Finger Lakes, NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet, upholstery, and floor cleaning in Finger Lakes, NY. Lahr Carpet Cleaning serves Region and the Finger Lakes region. Call 315-719-1218.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-living-room.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Carpet Cleaning in Finger Lakes, NY | Lahr Carpet Cleaning</title>
|
||||
<meta name="description" content="Professional carpet, upholstery, and floor cleaning in Finger Lakes, NY. Lahr Carpet Cleaning serves Region and the Finger Lakes region. Call 315-719-1218.">
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
@@ -20,7 +32,7 @@
|
||||
<h1 class="hero-title">Finger Lakes,<br><span class="text-accent">NY</span></h1>
|
||||
<p>Serving vacation rentals, wineries, and homes across the Finger Lakes region.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,7 +97,7 @@
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Serving </strong><span class="text-accent"><strong>Finger Lakes</strong></span></h2>
|
||||
<p class="paragraph-4">Call 315-719-1218 or submit the form for a free estimate in Finger Lakes, NY.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Get a Free Estimate</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Get a Free Estimate</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/locations/geneva-ny/">
|
||||
<meta property="og:title" content="Carpet Cleaning in Geneva, NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet, upholstery, and floor cleaning in Geneva, NY. Lahr Carpet Cleaning serves Ontario County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-clean-result.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Carpet Cleaning in Geneva, NY | Lahr Carpet Cleaning</title>
|
||||
<meta name="description" content="Professional carpet, upholstery, and floor cleaning in Geneva, NY. Lahr Carpet Cleaning serves Ontario County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
@@ -20,7 +32,7 @@
|
||||
<h1 class="hero-title">Geneva,<br><span class="text-accent">NY</span></h1>
|
||||
<p>Full residential and commercial services throughout Geneva.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,7 +97,7 @@
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Serving </strong><span class="text-accent"><strong>Geneva</strong></span></h2>
|
||||
<p class="paragraph-4">Call 315-719-1218 or submit the form for a free estimate in Geneva, NY.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Get a Free Estimate</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Get a Free Estimate</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/locations/gorham-ny/">
|
||||
<meta property="og:title" content="Carpet Cleaning in Gorham, NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet, upholstery, and floor cleaning in Gorham, NY. Lahr Carpet Cleaning serves Ontario County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-clean-result.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Carpet Cleaning in Gorham, NY | Lahr Carpet Cleaning</title>
|
||||
<meta name="description" content="Professional carpet, upholstery, and floor cleaning in Gorham, NY. Lahr Carpet Cleaning serves Ontario County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
@@ -20,7 +32,7 @@
|
||||
<h1 class="hero-title">Gorham,<br><span class="text-accent">NY</span></h1>
|
||||
<p>Carpet and floor cleaning for homes and properties in Gorham.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,7 +97,7 @@
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Serving </strong><span class="text-accent"><strong>Gorham</strong></span></h2>
|
||||
<p class="paragraph-4">Call 315-719-1218 or submit the form for a free estimate in Gorham, NY.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Get a Free Estimate</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Get a Free Estimate</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/locations/himrod-ny/">
|
||||
<meta property="og:title" content="Carpet Cleaning in Himrod, NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet, upholstery, and floor cleaning in Himrod, NY. Lahr Carpet Cleaning serves Yates County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-clean-result.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Carpet Cleaning in Himrod, NY | Lahr Carpet Cleaning</title>
|
||||
<meta name="description" content="Professional carpet, upholstery, and floor cleaning in Himrod, NY. Lahr Carpet Cleaning serves Yates County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
@@ -20,7 +32,7 @@
|
||||
<h1 class="hero-title">Himrod,<br><span class="text-accent">NY</span></h1>
|
||||
<p>Carpet and floor cleaning for homes and rentals in the Himrod area.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,7 +97,7 @@
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Serving </strong><span class="text-accent"><strong>Himrod</strong></span></h2>
|
||||
<p class="paragraph-4">Call 315-719-1218 or submit the form for a free estimate in Himrod, NY.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Get a Free Estimate</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Get a Free Estimate</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -3,10 +3,24 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/locations/">
|
||||
<meta property="og:title" content="Service Areas | Lahr Carpet Cleaning | Finger Lakes, NY">
|
||||
<meta property="og:description" content="Lahr Carpet Cleaning serves Waterloo, Geneva, Seneca Falls, Canandaigua, Penn Yan, and 16 more cities across the Finger Lakes region. Call 315-719-1218.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-carpet-cleaning.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Service Areas | Lahr Carpet Cleaning | Finger Lakes, NY</title>
|
||||
<meta name="description" content="Lahr Carpet Cleaning serves Waterloo, Geneva, Seneca Falls, Canandaigua, Penn Yan, and 16 more cities across the Finger Lakes region. Call 315-719-1218.">
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
<link rel="canonical" href="https://lahrcarpetcleaning.com/locations/">
|
||||
<script type="application/ld+json">{"@context":"https://schema.org","@type":"LocalBusiness","name":"Lahr Carpet Cleaning","url":"https://lahrcarpetcleaning.com","telephone":"+13157191218","image":"https://lahrcarpetcleaning.com/assets/images/hero/hero-carpet-cleaning.webp","address":{"@type":"PostalAddress","streetAddress":"1076 Waterloo/Geneva Road","addressLocality":"Waterloo","addressRegion":"NY","postalCode":"13165","addressCountry":"US"},"description":"Professional carpet and upholstery cleaning for homes and businesses across the Finger Lakes region of Upstate New York.","areaServed":[{"@type":"City","name":"Waterloo"},{"@type":"City","name":"Geneva"},{"@type":"City","name":"Seneca Falls"},{"@type":"City","name":"Canandaigua"},{"@type":"City","name":"Penn Yan"}]}</script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="header" id="header">
|
||||
@@ -20,7 +34,7 @@
|
||||
<h1 class="hero-title">Service<br><span class="text-accent">Areas</span></h1>
|
||||
<p>We clean carpets, upholstery, rugs, and hard floors across 21 cities in Upstate New York. Select your city below.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -190,7 +204,7 @@
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Not sure if we cover your area?</strong></h2>
|
||||
<p class="paragraph-4">Call 315-719-1218 or submit the form and we will confirm availability for your address.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Get a Free Estimate</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Get a Free Estimate</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/locations/lodi-ny/">
|
||||
<meta property="og:title" content="Carpet Cleaning in Lodi, NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet, upholstery, and floor cleaning in Lodi, NY. Lahr Carpet Cleaning serves Seneca County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-carpet-cleaning.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Carpet Cleaning in Lodi, NY | Lahr Carpet Cleaning</title>
|
||||
<meta name="description" content="Professional carpet, upholstery, and floor cleaning in Lodi, NY. Lahr Carpet Cleaning serves Seneca County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
@@ -20,7 +32,7 @@
|
||||
<h1 class="hero-title">Lodi,<br><span class="text-accent">NY</span></h1>
|
||||
<p>Serving homes and vacation properties in Lodi and surrounding areas.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,7 +97,7 @@
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Serving </strong><span class="text-accent"><strong>Lodi</strong></span></h2>
|
||||
<p class="paragraph-4">Call 315-719-1218 or submit the form for a free estimate in Lodi, NY.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Get a Free Estimate</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Get a Free Estimate</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/locations/manchester-ny/">
|
||||
<meta property="og:title" content="Carpet Cleaning in Manchester, NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet, upholstery, and floor cleaning in Manchester, NY. Lahr Carpet Cleaning serves Ontario County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-stairs.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Carpet Cleaning in Manchester, NY | Lahr Carpet Cleaning</title>
|
||||
<meta name="description" content="Professional carpet, upholstery, and floor cleaning in Manchester, NY. Lahr Carpet Cleaning serves Ontario County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
@@ -20,7 +32,7 @@
|
||||
<h1 class="hero-title">Manchester,<br><span class="text-accent">NY</span></h1>
|
||||
<p>Residential and commercial cleaning services in Manchester, NY.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,7 +97,7 @@
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Serving </strong><span class="text-accent"><strong>Manchester</strong></span></h2>
|
||||
<p class="paragraph-4">Call 315-719-1218 or submit the form for a free estimate in Manchester, NY.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Get a Free Estimate</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Get a Free Estimate</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/locations/naples-ny/">
|
||||
<meta property="og:title" content="Carpet Cleaning in Naples, NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet, upholstery, and floor cleaning in Naples, NY. Lahr Carpet Cleaning serves Ontario County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-carpet-cleaning.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Carpet Cleaning in Naples, NY | Lahr Carpet Cleaning</title>
|
||||
<meta name="description" content="Professional carpet, upholstery, and floor cleaning in Naples, NY. Lahr Carpet Cleaning serves Ontario County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
@@ -20,7 +32,7 @@
|
||||
<h1 class="hero-title">Naples,<br><span class="text-accent">NY</span></h1>
|
||||
<p>Serving homes, wineries, and vacation rentals in the Naples area.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,7 +97,7 @@
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Serving </strong><span class="text-accent"><strong>Naples</strong></span></h2>
|
||||
<p class="paragraph-4">Call 315-719-1218 or submit the form for a free estimate in Naples, NY.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Get a Free Estimate</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Get a Free Estimate</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/locations/newark-ny/">
|
||||
<meta property="og:title" content="Carpet Cleaning in Newark, NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet, upholstery, and floor cleaning in Newark, NY. Lahr Carpet Cleaning serves Wayne County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-living-room.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Carpet Cleaning in Newark, NY | Lahr Carpet Cleaning</title>
|
||||
<meta name="description" content="Professional carpet, upholstery, and floor cleaning in Newark, NY. Lahr Carpet Cleaning serves Wayne County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
@@ -20,7 +32,7 @@
|
||||
<h1 class="hero-title">Newark,<br><span class="text-accent">NY</span></h1>
|
||||
<p>Carpet and upholstery cleaning for homes and businesses in Newark.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,7 +97,7 @@
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Serving </strong><span class="text-accent"><strong>Newark</strong></span></h2>
|
||||
<p class="paragraph-4">Call 315-719-1218 or submit the form for a free estimate in Newark, NY.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Get a Free Estimate</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Get a Free Estimate</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/locations/ovid-ny/">
|
||||
<meta property="og:title" content="Carpet Cleaning in Ovid, NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet, upholstery, and floor cleaning in Ovid, NY. Lahr Carpet Cleaning serves Seneca County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-living-room.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Carpet Cleaning in Ovid, NY | Lahr Carpet Cleaning</title>
|
||||
<meta name="description" content="Professional carpet, upholstery, and floor cleaning in Ovid, NY. Lahr Carpet Cleaning serves Seneca County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
@@ -20,7 +32,7 @@
|
||||
<h1 class="hero-title">Ovid,<br><span class="text-accent">NY</span></h1>
|
||||
<p>Serving homes and rental properties throughout Ovid.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,7 +97,7 @@
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Serving </strong><span class="text-accent"><strong>Ovid</strong></span></h2>
|
||||
<p class="paragraph-4">Call 315-719-1218 or submit the form for a free estimate in Ovid, NY.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Get a Free Estimate</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Get a Free Estimate</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/locations/penn-yan-ny/">
|
||||
<meta property="og:title" content="Carpet Cleaning in Penn Yan, NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet, upholstery, and floor cleaning in Penn Yan, NY. Lahr Carpet Cleaning serves Yates County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-stairs.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Carpet Cleaning in Penn Yan, NY | Lahr Carpet Cleaning</title>
|
||||
<meta name="description" content="Professional carpet, upholstery, and floor cleaning in Penn Yan, NY. Lahr Carpet Cleaning serves Yates County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
@@ -20,7 +32,7 @@
|
||||
<h1 class="hero-title">Penn Yan,<br><span class="text-accent">NY</span></h1>
|
||||
<p>Homes, wineries, and short-term rentals in the Penn Yan area.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,7 +97,7 @@
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Serving </strong><span class="text-accent"><strong>Penn Yan</strong></span></h2>
|
||||
<p class="paragraph-4">Call 315-719-1218 or submit the form for a free estimate in Penn Yan, NY.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Get a Free Estimate</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Get a Free Estimate</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/locations/phelps-ny/">
|
||||
<meta property="og:title" content="Carpet Cleaning in Phelps, NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet, upholstery, and floor cleaning in Phelps, NY. Lahr Carpet Cleaning serves Ontario County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-stairs.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Carpet Cleaning in Phelps, NY | Lahr Carpet Cleaning</title>
|
||||
<meta name="description" content="Professional carpet, upholstery, and floor cleaning in Phelps, NY. Lahr Carpet Cleaning serves Ontario County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
@@ -20,7 +32,7 @@
|
||||
<h1 class="hero-title">Phelps,<br><span class="text-accent">NY</span></h1>
|
||||
<p>Residential carpet and upholstery cleaning throughout Phelps.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,7 +97,7 @@
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Serving </strong><span class="text-accent"><strong>Phelps</strong></span></h2>
|
||||
<p class="paragraph-4">Call 315-719-1218 or submit the form for a free estimate in Phelps, NY.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Get a Free Estimate</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Get a Free Estimate</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/locations/rushville-ny/">
|
||||
<meta property="og:title" content="Carpet Cleaning in Rushville, NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet, upholstery, and floor cleaning in Rushville, NY. Lahr Carpet Cleaning serves Yates County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-stairs.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Carpet Cleaning in Rushville, NY | Lahr Carpet Cleaning</title>
|
||||
<meta name="description" content="Professional carpet, upholstery, and floor cleaning in Rushville, NY. Lahr Carpet Cleaning serves Yates County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
@@ -20,7 +32,7 @@
|
||||
<h1 class="hero-title">Rushville,<br><span class="text-accent">NY</span></h1>
|
||||
<p>Carpet and upholstery cleaning for homes and rentals in Rushville.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,7 +97,7 @@
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Serving </strong><span class="text-accent"><strong>Rushville</strong></span></h2>
|
||||
<p class="paragraph-4">Call 315-719-1218 or submit the form for a free estimate in Rushville, NY.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Get a Free Estimate</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Get a Free Estimate</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/locations/seneca-falls-ny/">
|
||||
<meta property="og:title" content="Carpet Cleaning in Seneca Falls, NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet, upholstery, and floor cleaning in Seneca Falls, NY. Lahr Carpet Cleaning serves Seneca County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-carpet-cleaning.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Carpet Cleaning in Seneca Falls, NY | Lahr Carpet Cleaning</title>
|
||||
<meta name="description" content="Professional carpet, upholstery, and floor cleaning in Seneca Falls, NY. Lahr Carpet Cleaning serves Seneca County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
@@ -20,7 +32,7 @@
|
||||
<h1 class="hero-title">Seneca Falls,<br><span class="text-accent">NY</span></h1>
|
||||
<p>Serving homes, vacation rentals, and businesses in Seneca Falls.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,7 +97,7 @@
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Serving </strong><span class="text-accent"><strong>Seneca Falls</strong></span></h2>
|
||||
<p class="paragraph-4">Call 315-719-1218 or submit the form for a free estimate in Seneca Falls, NY.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Get a Free Estimate</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Get a Free Estimate</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/locations/shortsville-ny/">
|
||||
<meta property="og:title" content="Carpet Cleaning in Shortsville, NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet, upholstery, and floor cleaning in Shortsville, NY. Lahr Carpet Cleaning serves Ontario County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-living-room.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Carpet Cleaning in Shortsville, NY | Lahr Carpet Cleaning</title>
|
||||
<meta name="description" content="Professional carpet, upholstery, and floor cleaning in Shortsville, NY. Lahr Carpet Cleaning serves Ontario County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
@@ -20,7 +32,7 @@
|
||||
<h1 class="hero-title">Shortsville,<br><span class="text-accent">NY</span></h1>
|
||||
<p>Home and business cleaning services in Shortsville, NY.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,7 +97,7 @@
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Serving </strong><span class="text-accent"><strong>Shortsville</strong></span></h2>
|
||||
<p class="paragraph-4">Call 315-719-1218 or submit the form for a free estimate in Shortsville, NY.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Get a Free Estimate</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Get a Free Estimate</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/locations/victor-ny/">
|
||||
<meta property="og:title" content="Carpet Cleaning in Victor, NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet, upholstery, and floor cleaning in Victor, NY. Lahr Carpet Cleaning serves Ontario County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-clean-result.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Carpet Cleaning in Victor, NY | Lahr Carpet Cleaning</title>
|
||||
<meta name="description" content="Professional carpet, upholstery, and floor cleaning in Victor, NY. Lahr Carpet Cleaning serves Ontario County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
@@ -20,7 +32,7 @@
|
||||
<h1 class="hero-title">Victor,<br><span class="text-accent">NY</span></h1>
|
||||
<p>Residential and commercial carpet cleaning throughout Victor.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,7 +97,7 @@
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Serving </strong><span class="text-accent"><strong>Victor</strong></span></h2>
|
||||
<p class="paragraph-4">Call 315-719-1218 or submit the form for a free estimate in Victor, NY.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Get a Free Estimate</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Get a Free Estimate</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/locations/waterloo-ny/">
|
||||
<meta property="og:title" content="Carpet Cleaning in Waterloo, NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet, upholstery, and floor cleaning in Waterloo, NY. Lahr Carpet Cleaning serves Seneca County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-living-room.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Carpet Cleaning in Waterloo, NY | Lahr Carpet Cleaning</title>
|
||||
<meta name="description" content="Professional carpet, upholstery, and floor cleaning in Waterloo, NY. Lahr Carpet Cleaning serves Seneca County and the Finger Lakes region. Call 315-719-1218.">
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
@@ -20,7 +32,7 @@
|
||||
<h1 class="hero-title">Waterloo,<br><span class="text-accent">NY</span></h1>
|
||||
<p>Our home base. Fastest response times in the area.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,7 +97,7 @@
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Serving </strong><span class="text-accent"><strong>Waterloo</strong></span></h2>
|
||||
<p class="paragraph-4">Call 315-719-1218 or submit the form for a free estimate in Waterloo, NY.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Get a Free Estimate</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Get a Free Estimate</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -3,9 +3,23 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/reviews/">
|
||||
<meta property="og:title" content="Customer Reviews | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet and upholstery cleaning in the Finger Lakes, NY.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-clean-result.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Customer Reviews | Lahr Carpet Cleaning</title>
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
<link rel="canonical" href="https://lahrcarpetcleaning.com/reviews/">
|
||||
<script type="application/ld+json">{"@context":"https://schema.org","@type":"LocalBusiness","name":"Lahr Carpet Cleaning","url":"https://lahrcarpetcleaning.com","telephone":"+13157191218","image":"https://lahrcarpetcleaning.com/assets/images/hero/hero-carpet-cleaning.webp","address":{"@type":"PostalAddress","streetAddress":"1076 Waterloo/Geneva Road","addressLocality":"Waterloo","addressRegion":"NY","postalCode":"13165","addressCountry":"US"},"description":"Professional carpet and upholstery cleaning for homes and businesses across the Finger Lakes region of Upstate New York.","areaServed":[{"@type":"City","name":"Waterloo"},{"@type":"City","name":"Geneva"},{"@type":"City","name":"Seneca Falls"},{"@type":"City","name":"Canandaigua"},{"@type":"City","name":"Penn Yan"}]}</script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="header" id="header">
|
||||
@@ -20,7 +34,7 @@
|
||||
<span class="hero-eyebrow">Waterloo, NY — Finger Lakes Region</span>
|
||||
<h1>What Our <span class="text-accent">Customers</span> Say</h1>
|
||||
<p>Real reviews from your neighbors throughout the Finger Lakes.</p>
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -89,7 +103,7 @@
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Ready to Join Our </strong><span class="text-accent"><strong>Satisfied Customers</strong></span><strong>?</strong></h2>
|
||||
<p class="paragraph-4">Schedule your cleaning today and experience the Lahr difference firsthand.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Call Now</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -3,9 +3,23 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/service-area/">
|
||||
<meta property="og:title" content="Service Area | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet and upholstery cleaning in the Finger Lakes, NY.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-service-area.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Service Area | Lahr Carpet Cleaning</title>
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
<link rel="canonical" href="https://lahrcarpetcleaning.com/service-area/">
|
||||
<script type="application/ld+json">{"@context":"https://schema.org","@type":"LocalBusiness","name":"Lahr Carpet Cleaning","url":"https://lahrcarpetcleaning.com","telephone":"+13157191218","image":"https://lahrcarpetcleaning.com/assets/images/hero/hero-carpet-cleaning.webp","address":{"@type":"PostalAddress","streetAddress":"1076 Waterloo/Geneva Road","addressLocality":"Waterloo","addressRegion":"NY","postalCode":"13165","addressCountry":"US"},"description":"Professional carpet and upholstery cleaning for homes and businesses across the Finger Lakes region of Upstate New York.","areaServed":[{"@type":"City","name":"Waterloo"},{"@type":"City","name":"Geneva"},{"@type":"City","name":"Seneca Falls"},{"@type":"City","name":"Canandaigua"},{"@type":"City","name":"Penn Yan"}]}</script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="header" id="header">
|
||||
@@ -20,7 +34,7 @@
|
||||
<span class="hero-eyebrow">Seneca, Ontario, Schuyler & Yates Counties</span>
|
||||
<h1>Our <span class="text-accent">Service Area</span></h1>
|
||||
<p>Professional carpet and upholstery cleaning throughout the Finger Lakes region.</p>
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -139,7 +153,7 @@
|
||||
<p style="color:rgba(255,255,255,0.8);font-size:1.05rem;line-height:1.8;margin-bottom:32px;">Our coverage area continues to grow. If your address is not listed above, give us a call. We will let you know whether we can reach you and when we have availability.</p>
|
||||
<div style="display:flex;gap:16px;justify-content:center;flex-wrap:wrap;">
|
||||
<a href="tel:315-719-1218" class="btn btn-primary"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
<a href="/contact/" class="btn btn-ghost">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost">Call Now</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -148,7 +162,7 @@
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Ready to Schedule Your </strong><span class="text-accent"><strong>Cleaning</strong></span><strong>?</strong></h2>
|
||||
<p class="paragraph-4">We serve your neighborhood. Free estimates available on every appointment.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Call Now</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/services/add-ons/">
|
||||
<meta property="og:title" content="Carpet Cleaning Add-On Services | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet and upholstery cleaning in the Finger Lakes, NY.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-add-ons.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Carpet Cleaning Add-On Services | Lahr Carpet Cleaning</title>
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
@@ -20,7 +32,7 @@
|
||||
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
||||
<h1>Carpet Cleaning Add-On Services</h1>
|
||||
<p>Standard cleaning handles most situations. Some homes need more. These optional treatments are available at booking and extend the results of your service.</p>
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -65,7 +77,7 @@
|
||||
<h2>Tell Us What You Are Dealing With When You Book</h2>
|
||||
<p>When you contact us to schedule, describe the specific problems you want addressed. Pet odors, heavy soiling, or a request for protector are all things we factor into the job before arriving. Nothing is added without your knowledge.</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="tel:315-719-1218" class="btn btn-primary">Call Now</a>
|
||||
</div>
|
||||
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-add-ons.webp')"></div>
|
||||
</div>
|
||||
@@ -78,7 +90,7 @@
|
||||
<h2>Get the Full Treatment Your Home Needs</h2>
|
||||
<p>Serving Waterloo, Seneca Falls, and the Finger Lakes region.</p>
|
||||
<div class="cta-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/services/area-rugs/">
|
||||
<meta property="og:title" content="Area Rug Cleaning Waterloo NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet and upholstery cleaning in the Finger Lakes, NY.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-area-rugs.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Area Rug Cleaning Waterloo NY | Lahr Carpet Cleaning</title>
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
@@ -20,7 +32,7 @@
|
||||
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
||||
<h1>Area Rug Cleaning in Waterloo, NY</h1>
|
||||
<p>Area rugs trap allergens, pet dander, and dust mites deep in the fibers. Gentle but thorough cleaning removes what regular vacuuming cannot reach.</p>
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -65,7 +77,7 @@
|
||||
<h2>Wool, Synthetic, and Delicate Rugs Welcome</h2>
|
||||
<p>We clean area rugs of all common fiber types including wool, polypropylene, nylon, cotton, and blended synthetics. Each rug is inspected for fiber type and condition before cleaning begins. Delicate or hand-woven pieces receive a gentler approach.</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="tel:315-719-1218" class="btn btn-primary">Call Now</a>
|
||||
</div>
|
||||
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-area-rugs.webp')"></div>
|
||||
</div>
|
||||
@@ -78,7 +90,7 @@
|
||||
<h2>Give Your Rugs a Real Clean</h2>
|
||||
<p>Serving Waterloo, Seneca Falls, and the Finger Lakes region.</p>
|
||||
<div class="cta-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/services/carpet-cleaning/">
|
||||
<meta property="og:title" content="Residential Carpet Cleaning in Waterloo NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet and upholstery cleaning in the Finger Lakes, NY.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-carpet-cleaning.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Residential Carpet Cleaning in Waterloo NY | Lahr Carpet Cleaning</title>
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
@@ -20,7 +32,7 @@
|
||||
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
||||
<h1>Residential Carpet Cleaning in Waterloo, NY</h1>
|
||||
<p>Your carpets hold more dirt than you can see. Hot water extraction pulls out what vacuuming leaves behind.</p>
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -65,7 +77,7 @@
|
||||
<h2>Local Service You Can Count On</h2>
|
||||
<p>Lahr Carpet Cleaning serves Waterloo, Seneca Falls, and the surrounding Finger Lakes region. When you call, you reach the person doing the work. There are no subcontractors and no surprises.</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="tel:315-719-1218" class="btn btn-primary">Call Now</a>
|
||||
</div>
|
||||
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-carpet-cleaning.webp')"></div>
|
||||
</div>
|
||||
@@ -78,7 +90,7 @@
|
||||
<h2>Ready for Truly Clean Carpets?</h2>
|
||||
<p>Serving Waterloo, Seneca Falls, and the Finger Lakes region.</p>
|
||||
<div class="cta-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/services/commercial/">
|
||||
<meta property="og:title" content="Commercial Carpet Cleaning Waterloo NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet and upholstery cleaning in the Finger Lakes, NY.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-commercial.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Commercial Carpet Cleaning Waterloo NY | Lahr Carpet Cleaning</title>
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
@@ -20,7 +32,7 @@
|
||||
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
||||
<h1>Commercial Carpet Cleaning in Waterloo, NY</h1>
|
||||
<p>Your business floors see heavy traffic every day. We restore them without disrupting your operations.</p>
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -65,7 +77,7 @@
|
||||
<h2>Local Knowledge. Professional Results.</h2>
|
||||
<p>We have served the Waterloo and Finger Lakes commercial market for years. We understand the seasonal pace of this region and the demands placed on properties serving wine country visitors, year-round residents, and business clients alike.</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="tel:315-719-1218" class="btn btn-primary">Call Now</a>
|
||||
</div>
|
||||
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-commercial.webp')"></div>
|
||||
</div>
|
||||
@@ -78,7 +90,7 @@
|
||||
<h2>Ready to Protect Your Business Image?</h2>
|
||||
<p>Call or book online and we will schedule around your hours.</p>
|
||||
<div class="cta-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/services/floors/">
|
||||
<meta property="og:title" content="Floor Cleaning Services Waterloo NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet and upholstery cleaning in the Finger Lakes, NY.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-floors.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Floor Cleaning Services Waterloo NY | Lahr Carpet Cleaning</title>
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
@@ -20,7 +32,7 @@
|
||||
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
||||
<h1>Floor Cleaning Services in Waterloo, NY</h1>
|
||||
<p>Mopping pushes grime around grout lines without removing it. Professional agitation and extraction restores the color your floors used to have.</p>
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -65,7 +77,7 @@
|
||||
<h2>Professional Results on Multiple Hard Floor Types</h2>
|
||||
<p>We work on tile, ceramic, porcelain, vinyl, and LVP. Each surface type is assessed before cleaning begins to confirm the right approach. Grout sealing is available as an add-on after cleaning to help maintain the results.</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="tel:315-719-1218" class="btn btn-primary">Call Now</a>
|
||||
</div>
|
||||
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-floors.webp')"></div>
|
||||
</div>
|
||||
@@ -78,7 +90,7 @@
|
||||
<h2>Restore Your Hard Floors</h2>
|
||||
<p>Serving Waterloo, Seneca Falls, and the Finger Lakes region.</p>
|
||||
<div class="cta-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/services/stairs/">
|
||||
<meta property="og:title" content="Stair Carpet Cleaning Waterloo NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet and upholstery cleaning in the Finger Lakes, NY.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-stairs.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Stair Carpet Cleaning Waterloo NY | Lahr Carpet Cleaning</title>
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
@@ -20,7 +32,7 @@
|
||||
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
||||
<h1>Stair Carpet Cleaning in Waterloo, NY</h1>
|
||||
<p>Stairs collect more dirt per square inch than any flat surface in your home. Compact equipment reaches every tread and riser for a dramatic before-and-after result.</p>
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -65,7 +77,7 @@
|
||||
<h2>Every Step Gets Individual Attention</h2>
|
||||
<p>We work tread by tread from the top of the staircase down. Each step is vacuumed, pre-treated, and extracted before moving to the next. Risers and the edges where carpet meets the wall get cleaned too, not just the walking surface.</p>
|
||||
<p>The process takes longer per square foot than open room cleaning, but the result justifies it. Stairs that looked permanently stained often clean up completely with this approach.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary">Call Now</a>
|
||||
</div>
|
||||
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-stairs.webp')"></div>
|
||||
</div>
|
||||
@@ -78,7 +90,7 @@
|
||||
<h2>Clean Every Step in Your Home</h2>
|
||||
<p>Serving Waterloo, Seneca Falls, and the Finger Lakes region.</p>
|
||||
<div class="cta-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Lahr Carpet Cleaning">
|
||||
<meta property="og:url" content="https://lahrcarpetcleaning.com/services/upholstery/">
|
||||
<meta property="og:title" content="Upholstery Cleaning Waterloo NY | Lahr Carpet Cleaning">
|
||||
<meta property="og:description" content="Professional carpet and upholstery cleaning in the Finger Lakes, NY.">
|
||||
<meta property="og:image" content="https://lahrcarpetcleaning.com/assets/images/hero/hero-upholstery.webp">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<title>Upholstery Cleaning Waterloo NY | Lahr Carpet Cleaning</title>
|
||||
<link rel="stylesheet" href="/assets/css/styles.css?v=6">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
@@ -20,7 +32,7 @@
|
||||
<span class="hero-eyebrow"><i class="fas fa-map-marker-alt"></i> Waterloo, Seneca Falls & the Finger Lakes</span>
|
||||
<h1>Upholstery Cleaning in Waterloo, NY</h1>
|
||||
<p>Your sofa absorbs pet hair, body oils, and food odors every single day. Fabric-safe extraction removes what vacuuming and spot sprays leave behind.</p>
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -65,7 +77,7 @@
|
||||
<h2>Residential Upholstered Furniture of All Types</h2>
|
||||
<p>We service standard residential upholstered pieces: sofas, loveseats, sectionals, armchairs, recliners, and dining chairs with fabric seats. Each piece is vacuumed, spot pre-treated, and then extracted using equipment sized for furniture rather than floors.</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="tel:315-719-1218" class="btn btn-primary">Call Now</a>
|
||||
</div>
|
||||
<div class="why-choose-image" style="background-image: url('/assets/images/hero/hero-upholstery.webp')"></div>
|
||||
</div>
|
||||
@@ -78,7 +90,7 @@
|
||||
<h2>Fresher Furniture Starts Here</h2>
|
||||
<p>Serving Waterloo, Seneca Falls, and the Finger Lakes region.</p>
|
||||
<div class="cta-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-primary btn-large">Call Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
"""
|
||||
Build paced hero reel from the browser-ordered clip list.
|
||||
- Shot 1 (family entry): trimmed to 3s — cuts before mud pan
|
||||
- Shots 2-7: full 6s (establish the story)
|
||||
- Shots 8-12: trimmed to 3.5s (building pace)
|
||||
- Shots 13-15: trimmed to 2.5s (rapid)
|
||||
- Shot 16 (final reveal): full 6s (hold on clean result)
|
||||
"""
|
||||
import os, subprocess, shutil
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||
VID_DIR = os.path.join(BASE_DIR, "assets", "videos", "hero", "clips")
|
||||
REEL_OUT = os.path.join(BASE_DIR, "assets", "videos", "hero", "hero-reel.mp4")
|
||||
TMP_DIR = os.path.join(VID_DIR, "paced-tmp")
|
||||
os.makedirs(TMP_DIR, exist_ok=True)
|
||||
|
||||
# Clip order: wine spill > extraction > stain > technician, stairs later
|
||||
CLIPS = [
|
||||
"v3-shot-02", # 1 - wine spill on sofa
|
||||
"shot-05-extraction-couch",# 2 - extraction couch
|
||||
"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 = [
|
||||
3.0, # 1 wine spill — shortened to 3.0s
|
||||
5.0, # 2
|
||||
4.0, # 4 --- building pace ---
|
||||
4.0, # 5
|
||||
3.5, # 6
|
||||
3.5, # 7
|
||||
2.5, # 8 --- rapid ---
|
||||
2.5, # 9
|
||||
2.5, # 10
|
||||
2.5, # 11
|
||||
6.0, # 12 final reveal — hold
|
||||
]
|
||||
|
||||
paced_clips = []
|
||||
for i, (name, dur) in enumerate(zip(CLIPS, DURATIONS)):
|
||||
src = os.path.join(VID_DIR, f"{name}.mp4")
|
||||
out = os.path.join(TMP_DIR, f"{i:02d}-{name}.mp4")
|
||||
if not os.path.exists(src):
|
||||
print(f" MISSING: {src}")
|
||||
continue
|
||||
print(f"[{i+1:02d}] {name} → {dur}s")
|
||||
result = subprocess.run(
|
||||
["ffmpeg", "-y", "-i", src, "-t", str(dur),
|
||||
"-c:v", "libx264", "-crf", "22", "-preset", "fast", out],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(f" ffmpeg error: {result.stderr[-200:]}")
|
||||
else:
|
||||
paced_clips.append(out)
|
||||
|
||||
print(f"\n{len(paced_clips)}/{len(CLIPS)} clips trimmed")
|
||||
|
||||
concat_file = os.path.join(TMP_DIR, "concat.txt")
|
||||
with open(concat_file, "w") as f:
|
||||
for p in paced_clips:
|
||||
f.write(f"file '{p}'\n")
|
||||
|
||||
result = subprocess.run(
|
||||
["ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", concat_file,
|
||||
"-c:v", "libx264", "-crf", "22", "-preset", "fast", "-movflags", "+faststart", REEL_OUT],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
if result.returncode == 0:
|
||||
print(f"Reel saved: {REEL_OUT} ({os.path.getsize(REEL_OUT)//1024}KB)")
|
||||
# Clean up tmp
|
||||
shutil.rmtree(TMP_DIR)
|
||||
print("Done.")
|
||||
else:
|
||||
print(f"ffmpeg error: {result.stderr[-400:]}")
|
||||
@@ -1,84 +0,0 @@
|
||||
"""Local server: serves clip-browser.html + handles POST /build-reel to run ffmpeg."""
|
||||
import http.server, json, os, subprocess, urllib.parse
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||
VID_DIR = os.path.join(BASE_DIR, "assets", "videos", "hero", "clips")
|
||||
REEL_OUT = os.path.join(BASE_DIR, "assets", "videos", "hero", "hero-reel.mp4")
|
||||
TOOLS_DIR = os.path.dirname(__file__)
|
||||
|
||||
class Handler(http.server.BaseHTTPRequestHandler):
|
||||
def log_message(self, fmt, *args): pass
|
||||
|
||||
def do_GET(self):
|
||||
path = self.path.split('?')[0]
|
||||
if path in ('/', '/tools/clip-browser.html'):
|
||||
self._serve_file(os.path.join(TOOLS_DIR, 'clip-browser.html'), 'text/html')
|
||||
elif path.startswith('/assets/videos/'):
|
||||
rel = path.lstrip('/')
|
||||
fpath = os.path.join(BASE_DIR, rel)
|
||||
if os.path.exists(fpath):
|
||||
self._serve_file(fpath, 'video/mp4')
|
||||
else:
|
||||
self._404()
|
||||
else:
|
||||
self._404()
|
||||
|
||||
def do_POST(self):
|
||||
if self.path == '/tools/build-reel-api.py':
|
||||
length = int(self.headers.get('Content-Length', 0))
|
||||
body = json.loads(self.rfile.read(length))
|
||||
clips = body.get('clips', [])
|
||||
if not clips:
|
||||
self._json({'message': 'No clips provided.'}, 400)
|
||||
return
|
||||
concat_file = os.path.join(VID_DIR, 'concat-browser.txt')
|
||||
missing = []
|
||||
with open(concat_file, 'w') as f:
|
||||
for name in clips:
|
||||
p = os.path.join(VID_DIR, f'{name}.mp4')
|
||||
if not os.path.exists(p):
|
||||
missing.append(name)
|
||||
else:
|
||||
f.write(f"file '{p}'\n")
|
||||
if missing:
|
||||
self._json({'message': f'Missing clips: {missing}'}, 400)
|
||||
return
|
||||
result = subprocess.run(
|
||||
['ffmpeg', '-y', '-f', 'concat', '-safe', '0', '-i', concat_file,
|
||||
'-c:v', 'libx264', '-crf', '22', '-preset', 'fast',
|
||||
'-movflags', '+faststart', REEL_OUT],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
if result.returncode == 0:
|
||||
size = os.path.getsize(REEL_OUT) // 1024
|
||||
self._json({'message': f'Reel built: hero-reel.mp4 ({size}KB). Now rebuild Docker: docker compose up -d --build'})
|
||||
else:
|
||||
self._json({'message': f'ffmpeg error: {result.stderr[-300:]}'}, 500)
|
||||
else:
|
||||
self._404()
|
||||
|
||||
def _serve_file(self, path, ctype):
|
||||
with open(path, 'rb') as f:
|
||||
data = f.read()
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', ctype)
|
||||
self.send_header('Content-Length', len(data))
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
|
||||
def _json(self, obj, code=200):
|
||||
data = json.dumps(obj).encode()
|
||||
self.send_response(code)
|
||||
self.send_header('Content-Type', 'application/json')
|
||||
self.send_header('Content-Length', len(data))
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
|
||||
def _404(self):
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
|
||||
if __name__ == '__main__':
|
||||
port = 8088
|
||||
print(f'Clip browser running at http://localhost:{port}/')
|
||||
http.server.HTTPServer(('', port), Handler).serve_forever()
|
||||
@@ -1,230 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Lahr Clip Browser</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { background: #0a0a0b; color: #eee; font-family: Inter, sans-serif; padding: 24px; }
|
||||
h1 { font-size: 20px; color: #e8291b; margin-bottom: 6px; }
|
||||
p.sub { color: #888; font-size: 13px; margin-bottom: 24px; }
|
||||
.layout { display: grid; grid-template-columns: 1fr 340px; gap: 24px; }
|
||||
#clip-list { display: flex; flex-direction: column; gap: 10px; }
|
||||
.clip-item {
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
background: #161618; border: 1px solid #2a2a2e; border-radius: 8px;
|
||||
padding: 10px 12px; cursor: grab; user-select: none;
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
.clip-item:hover { border-color: #e8291b; }
|
||||
.clip-item.dragging { opacity: 0.4; }
|
||||
.clip-item.drag-over { border-color: #e8291b; background: #1f1012; }
|
||||
.drag-handle { color: #555; font-size: 18px; flex-shrink: 0; cursor: grab; }
|
||||
.clip-thumb { width: 120px; height: 68px; object-fit: cover; border-radius: 4px; flex-shrink: 0; background: #222; }
|
||||
.clip-info { flex: 1; min-width: 0; }
|
||||
.clip-name { font-size: 13px; font-weight: 600; color: #ddd; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.clip-size { font-size: 11px; color: #666; margin-top: 2px; }
|
||||
.clip-actions { display: flex; flex-direction: column; gap: 6px; flex-shrink: 0; }
|
||||
.btn-preview { background: #1e1e22; border: 1px solid #333; color: #ccc; padding: 5px 10px; border-radius: 4px; font-size: 11px; cursor: pointer; }
|
||||
.btn-preview:hover { border-color: #e8291b; color: #e8291b; }
|
||||
.btn-remove { background: transparent; border: 1px solid #333; color: #555; padding: 5px 10px; border-radius: 4px; font-size: 11px; cursor: pointer; }
|
||||
.btn-remove:hover { border-color: #e8291b; color: #e8291b; }
|
||||
|
||||
.sidebar { position: sticky; top: 24px; }
|
||||
.preview-box { background: #161618; border: 1px solid #2a2a2e; border-radius: 8px; padding: 16px; margin-bottom: 16px; }
|
||||
.preview-box h3 { font-size: 13px; color: #888; margin-bottom: 10px; text-transform: uppercase; letter-spacing: 0.05em; }
|
||||
#preview-video { width: 100%; border-radius: 6px; background: #000; }
|
||||
#preview-name { font-size: 12px; color: #666; margin-top: 8px; text-align: center; }
|
||||
|
||||
.reel-box { background: #161618; border: 1px solid #2a2a2e; border-radius: 8px; padding: 16px; }
|
||||
.reel-box h3 { font-size: 13px; color: #888; margin-bottom: 12px; text-transform: uppercase; letter-spacing: 0.05em; }
|
||||
#reel-order { font-size: 11px; color: #666; line-height: 1.8; margin-bottom: 14px; max-height: 220px; overflow-y: auto; }
|
||||
.btn-build { width: 100%; background: #e8291b; color: #fff; border: none; padding: 12px; border-radius: 6px; font-size: 14px; font-weight: 600; cursor: pointer; }
|
||||
.btn-build:hover { background: #c72216; }
|
||||
#build-output { margin-top: 12px; font-size: 11px; color: #888; background: #0d0d0f; border-radius: 4px; padding: 10px; display: none; white-space: pre-wrap; }
|
||||
|
||||
.unused-section { margin-top: 32px; }
|
||||
.unused-section h2 { font-size: 14px; color: #555; margin-bottom: 12px; }
|
||||
#unused-list { display: flex; flex-direction: column; gap: 8px; }
|
||||
.unused-item { display: flex; align-items: center; gap: 12px; background: #111; border: 1px solid #1e1e22; border-radius: 8px; padding: 8px 12px; }
|
||||
.unused-item .clip-name { font-size: 12px; color: #666; }
|
||||
.btn-add { background: #1e1e22; border: 1px solid #333; color: #888; padding: 5px 10px; border-radius: 4px; font-size: 11px; cursor: pointer; margin-left: auto; }
|
||||
.btn-add:hover { border-color: #4a9; color: #4a9; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Lahr Clip Browser</h1>
|
||||
<p class="sub">Drag clips to reorder. Click Preview to watch. Remove clips from reel. Build Reel when ready.</p>
|
||||
|
||||
<div class="layout">
|
||||
<div>
|
||||
<div id="clip-list"></div>
|
||||
<div class="unused-section">
|
||||
<h2>Available (not in reel)</h2>
|
||||
<div id="unused-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar">
|
||||
<div class="preview-box">
|
||||
<h3>Preview</h3>
|
||||
<video id="preview-video" controls></video>
|
||||
<div id="preview-name">Click Preview on any clip</div>
|
||||
</div>
|
||||
<div class="reel-box">
|
||||
<h3>Current Reel Order</h3>
|
||||
<div id="reel-order"></div>
|
||||
<button class="btn-build" onclick="buildReel()">Build Reel</button>
|
||||
<div id="build-output"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const BASE = '/assets/videos/hero/clips/';
|
||||
|
||||
const ALL_CLIPS = [
|
||||
{ name: 'v3-shot-01', label: 'v3 · Family enters door, pans to carpet', size: '5.2MB' },
|
||||
{ name: 'v3-shot-02', label: 'v3 · Wine spill on sofa close-up', size: '6.0MB' },
|
||||
{ name: 'v3-shot-03', label: 'v3 · Dirty stained carpet close-up', size: '3.3MB' },
|
||||
{ name: 'v3-shot-04', label: 'v3 · Clean bright sofa pullback', size: '5.6MB' },
|
||||
{ name: 'v3-shot-05', label: 'v3 · Office lobby carpet pan', size: '4.7MB' },
|
||||
{ name: 'v3-shot-06', label: 'v3 · Living room clean carpet pan', size: '4.9MB' },
|
||||
{ name: 'v3-shot-07', label: 'v3 · Restaurant carpet glide', size: '3.1MB' },
|
||||
{ name: 'v2-shot-01-door-entry', label: 'v2 · Door entry muddy boots', size: '2.2MB' },
|
||||
{ name: 'v2-shot-02-mud-on-carpet', label: 'v2 · Mud boots on carpet floor level', size: '4.1MB' },
|
||||
{ name: 'v2-shot-03-stain-on-chair', label: 'v2 · Stain on chair close-up', size: '3.3MB' },
|
||||
{ name: 'v2-shot-04-extraction-carpet', label: 'v2 · Extraction machine on carpet', size: '4.2MB' },
|
||||
{ name: 'v2-shot-05-clean-stairs', label: 'v2 · Clean bright staircase', size: '5.6MB' },
|
||||
{ name: 'v2-shot-06-office', label: 'v2 · Bright office carpet', size: '4.7MB' },
|
||||
{ name: 'v2-shot-07-restaurant', label: 'v2 · Restaurant carpet', size: '4.7MB' },
|
||||
{ name: 'shot-01-door-opens-trimmed', label: 'v1 · Door opens (trimmed 2.5s)', size: '525KB' },
|
||||
{ name: 'shot-01-wide-room', label: 'v1 · Wide room establishing', size: '2.6MB' },
|
||||
{ name: 'shot-02-pan-to-stains', label: 'v1 · Pan to stains / muddy shoes', size: '2.8MB' },
|
||||
{ name: 'shot-02-staircase', label: 'v1 · Staircase', size: '1.4MB' },
|
||||
{ name: 'shot-03-stain-closeup', label: 'v1 · Stain close-up', size: '4.7MB' },
|
||||
{ name: 'shot-03-technician', label: 'v1 · Technician', size: '1.6MB' },
|
||||
{ name: 'shot-04-extraction-carpet', label: 'v1 · Extraction carpet (clean reveal)', size: '6.2MB' },
|
||||
{ name: 'shot-04-extraction-closeup', label: 'v1 · Extraction close-up', size: '2.0MB' },
|
||||
{ name: 'shot-05-clean-reveal', label: 'v1 · Clean reveal', size: '2.2MB' },
|
||||
{ name: 'shot-05-extraction-couch', label: 'v1 · Extraction couch', size: '3.5MB' },
|
||||
{ name: 'shot-06-extraction-stairs', label: 'v1 · Extraction stairs', size: '5.4MB' },
|
||||
{ name: 'shot-07-office-entryway', label: 'v1 · Office entryway', size: '5.9MB' },
|
||||
{ name: 'shot-08-showroom', label: 'v1 · Showroom', size: '4.1MB' },
|
||||
{ name: 'shot-09-technician-unloading', label: 'v1 · Technician unloading van', size: '3.7MB' },
|
||||
{ name: 'shot-01-door-opens', label: 'v1 · Door opens (full 6s)', size: '1.5MB' },
|
||||
];
|
||||
|
||||
// Default reel = current v3 set
|
||||
let reelClips = ALL_CLIPS.slice(0, 7).map(c => c.name);
|
||||
|
||||
function getClip(name) { return ALL_CLIPS.find(c => c.name === name); }
|
||||
|
||||
function render() {
|
||||
const list = document.getElementById('clip-list');
|
||||
list.innerHTML = '';
|
||||
reelClips.forEach((name, idx) => {
|
||||
const clip = getClip(name);
|
||||
if (!clip) return;
|
||||
const div = document.createElement('div');
|
||||
div.className = 'clip-item';
|
||||
div.draggable = true;
|
||||
div.dataset.name = name;
|
||||
div.innerHTML = `
|
||||
<span class="drag-handle">⠿</span>
|
||||
<video class="clip-thumb" src="${BASE}${name}.mp4" muted preload="metadata"></video>
|
||||
<div class="clip-info">
|
||||
<div class="clip-name">${idx+1}. ${clip.label}</div>
|
||||
<div class="clip-size">${clip.size}</div>
|
||||
</div>
|
||||
<div class="clip-actions">
|
||||
<button class="btn-preview" onclick="preview('${name}', '${clip.label}')">Preview</button>
|
||||
<button class="btn-remove" onclick="remove('${name}')">Remove</button>
|
||||
</div>`;
|
||||
div.addEventListener('dragstart', dragStart);
|
||||
div.addEventListener('dragover', dragOver);
|
||||
div.addEventListener('drop', drop);
|
||||
div.addEventListener('dragend', dragEnd);
|
||||
list.appendChild(div);
|
||||
});
|
||||
|
||||
const unused = ALL_CLIPS.filter(c => !reelClips.includes(c.name));
|
||||
const ulist = document.getElementById('unused-list');
|
||||
ulist.innerHTML = '';
|
||||
unused.forEach(clip => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'unused-item';
|
||||
div.innerHTML = `
|
||||
<div class="clip-name">${clip.label} <span style="color:#444">(${clip.size})</span></div>
|
||||
<button class="btn-add" onclick="addToReel('${clip.name}')">+ Add</button>`;
|
||||
ulist.appendChild(div);
|
||||
});
|
||||
|
||||
const orderEl = document.getElementById('reel-order');
|
||||
orderEl.innerHTML = reelClips.map((n,i) => {
|
||||
const c = getClip(n);
|
||||
return `<div>${i+1}. ${c ? c.label : n}</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function preview(name, label) {
|
||||
const v = document.getElementById('preview-video');
|
||||
v.src = BASE + name + '.mp4';
|
||||
v.play();
|
||||
document.getElementById('preview-name').textContent = label;
|
||||
}
|
||||
|
||||
function remove(name) {
|
||||
reelClips = reelClips.filter(n => n !== name);
|
||||
render();
|
||||
}
|
||||
|
||||
function addToReel(name) {
|
||||
reelClips.push(name);
|
||||
render();
|
||||
}
|
||||
|
||||
let dragSrc = null;
|
||||
function dragStart(e) { dragSrc = this; this.classList.add('dragging'); }
|
||||
function dragOver(e) { e.preventDefault(); document.querySelectorAll('.clip-item').forEach(el => el.classList.remove('drag-over')); this.classList.add('drag-over'); }
|
||||
function drop(e) {
|
||||
e.preventDefault();
|
||||
if (dragSrc === this) return;
|
||||
const fromName = dragSrc.dataset.name;
|
||||
const toName = this.dataset.name;
|
||||
const fi = reelClips.indexOf(fromName);
|
||||
const ti = reelClips.indexOf(toName);
|
||||
reelClips.splice(fi, 1);
|
||||
reelClips.splice(ti, 0, fromName);
|
||||
render();
|
||||
}
|
||||
function dragEnd() { document.querySelectorAll('.clip-item').forEach(el => el.classList.remove('dragging','drag-over')); }
|
||||
|
||||
async function buildReel() {
|
||||
const out = document.getElementById('build-output');
|
||||
out.style.display = 'block';
|
||||
out.textContent = 'Sending to server...';
|
||||
|
||||
const resp = await fetch('/tools/build-reel-api.py', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ clips: reelClips })
|
||||
});
|
||||
|
||||
if (!resp.ok) {
|
||||
// Fallback: show the ffmpeg command to copy
|
||||
const lines = reelClips.map(n => `file 'assets/videos/hero/clips/${n}.mp4'`).join('\n');
|
||||
out.textContent = 'Server not available. Run this manually:\n\n' +
|
||||
'# 1. Save as concat.txt:\n' + lines + '\n\n' +
|
||||
'# 2. Run ffmpeg:\n' +
|
||||
'ffmpeg -y -f concat -safe 0 -i concat.txt -c:v libx264 -crf 22 -preset fast -movflags +faststart assets/videos/hero/hero-reel.mp4';
|
||||
} else {
|
||||
const data = await resp.json();
|
||||
out.textContent = data.message || 'Done.';
|
||||
}
|
||||
}
|
||||
|
||||
render();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,60 +0,0 @@
|
||||
"""Convert all service/hero JPGs to WebP at 60-70KB target, update all HTML refs."""
|
||||
import os, glob
|
||||
from PIL import Image
|
||||
|
||||
BASE = os.path.dirname(os.path.dirname(__file__))
|
||||
IMG_DIRS = [
|
||||
os.path.join(BASE, "assets", "images", "services"),
|
||||
os.path.join(BASE, "assets", "images", "hero"),
|
||||
]
|
||||
|
||||
def convert(jpg_path, max_width=900, quality=78):
|
||||
# Hero images get larger max_width
|
||||
if "/hero/" in jpg_path:
|
||||
max_width = 1400
|
||||
quality = 80
|
||||
webp_path = jpg_path.rsplit(".", 1)[0] + ".webp"
|
||||
img = Image.open(jpg_path).convert("RGB")
|
||||
w, h = img.size
|
||||
if w > max_width:
|
||||
img = img.resize((max_width, int(h * max_width / w)), Image.LANCZOS)
|
||||
img.save(webp_path, "WEBP", quality=quality, method=6)
|
||||
kb = os.path.getsize(webp_path) // 1024
|
||||
orig_kb = os.path.getsize(jpg_path) // 1024
|
||||
return webp_path, kb, orig_kb
|
||||
|
||||
converted = []
|
||||
for d in IMG_DIRS:
|
||||
for jpg in glob.glob(os.path.join(d, "*.jpg")):
|
||||
if any(x in jpg for x in ["gen.log", "regen"]):
|
||||
continue
|
||||
webp, kb, orig = convert(jpg)
|
||||
print(f" {os.path.basename(jpg)} {orig}KB -> {kb}KB")
|
||||
converted.append((jpg, webp))
|
||||
|
||||
print(f"\n{len(converted)} images converted")
|
||||
|
||||
# Update all HTML files to reference .webp instead of .jpg (for these image dirs only)
|
||||
html_files = []
|
||||
for root, dirs, files in os.walk(BASE):
|
||||
dirs[:] = [d for d in dirs if d not in [".git", "tools", "assets/videos"]]
|
||||
for f in files:
|
||||
if f.endswith(".html"):
|
||||
html_files.append(os.path.join(root, f))
|
||||
|
||||
updated = 0
|
||||
for html in html_files:
|
||||
with open(html, "r") as f:
|
||||
content = f.read()
|
||||
new = content
|
||||
for jpg, webp in converted:
|
||||
jpg_ref = "/assets/images/" + os.path.relpath(jpg, os.path.join(BASE, "assets", "images"))
|
||||
webp_ref = "/assets/images/" + os.path.relpath(webp, os.path.join(BASE, "assets", "images"))
|
||||
new = new.replace(jpg_ref, webp_ref)
|
||||
if new != content:
|
||||
with open(html, "w") as f:
|
||||
f.write(new)
|
||||
updated += 1
|
||||
|
||||
print(f"{updated} HTML files updated to .webp refs")
|
||||
print("Done.")
|
||||
@@ -1,292 +0,0 @@
|
||||
"""
|
||||
Generate all site images via FLUX.1 Schnell GGUF through ComfyUI.
|
||||
FLUX Schnell: 4 steps, cfg=1.0, no negative prompt, photorealistic.
|
||||
Run after ComfyUI restart: python3 tools/gen-images-flux.py
|
||||
"""
|
||||
import json, time, urllib.request, os, random, io
|
||||
|
||||
COMFY = "http://localhost:8188"
|
||||
HERO_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "assets", "images", "hero")
|
||||
SVC_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "assets", "images", "services")
|
||||
|
||||
IMAGES = [
|
||||
# --- HERO IMAGES ---
|
||||
{"filename": "hero-carpet-cleaning.jpg", "dir": HERO_DIR, "prompt": (
|
||||
"low-angle 35mm lens perspective looking across thick plush cream carpet in an upstate New York living room, "
|
||||
"carpet fibers razor sharp in foreground, couch and coffee table receding into shallow bokeh background, "
|
||||
"warm afternoon window light raking across carpet texture, Finger Lakes farmhouse interior, "
|
||||
"no people, ultra-realistic architectural photography, 16:9"
|
||||
)},
|
||||
{"filename": "hero-stairs.jpg", "dir": HERO_DIR, "prompt": (
|
||||
"dramatic low 35mm angle looking up a clean carpeted staircase from floor level, "
|
||||
"light grey carpet runner sharp and textured in foreground steps, wood banister receding diagonally, "
|
||||
"bright daylight flooding from above, shallow depth of field, "
|
||||
"no people, ultra-realistic interior photography, 16:9"
|
||||
)},
|
||||
{"filename": "hero-upholstery.jpg", "dir": HERO_DIR, "prompt": (
|
||||
"50mm lens low corner angle across a bright residential living room, "
|
||||
"plush linen fabric sofa arm sharp in near foreground, clean armchair and window receding with bokeh, "
|
||||
"afternoon countryside light through window, shallow depth of field, "
|
||||
"no people, ultra-realistic interior photography, 16:9"
|
||||
)},
|
||||
{"filename": "hero-floors.jpg", "dir": HERO_DIR, "prompt": (
|
||||
"low 24mm angle pressed to gleaming light oak hardwood floor, "
|
||||
"floor grain razor sharp in extreme foreground receding to hallway vanishing point, "
|
||||
"white walls, natural light streaming in, shallow depth of field, "
|
||||
"no people, ultra-realistic interior photography, 16:9"
|
||||
)},
|
||||
{"filename": "hero-area-rugs.jpg", "dir": HERO_DIR, "prompt": (
|
||||
"low 35mm angle looking across a hand-knotted oriental rug from floor level, "
|
||||
"rich red and gold rug fibers sharp in foreground, hardwood floor and room receding into bokeh, "
|
||||
"cozy farmhouse living room, warm natural light, shallow depth of field, "
|
||||
"no people, ultra-realistic interior photography, 16:9"
|
||||
)},
|
||||
{"filename": "hero-add-ons.jpg", "dir": HERO_DIR, "prompt": (
|
||||
"low 35mm angle across a clean beige bedroom carpet, "
|
||||
"carpet pile sharp and detailed in near foreground, wooden bed frame and sheer curtained window receding, "
|
||||
"crisp morning light, shallow depth of field, "
|
||||
"no people, no machines, ultra-realistic interior photography, 16:9"
|
||||
)},
|
||||
{"filename": "hero-commercial.jpg", "dir": HERO_DIR, "prompt": (
|
||||
"low 24mm wide-angle lens across a modern corporate lobby floor, "
|
||||
"dark charcoal commercial carpet sharp in extreme foreground receding to glass entrance doors, "
|
||||
"recessed ceiling lights creating depth, strong vanishing point perspective, "
|
||||
"no people, ultra-realistic architectural photography, 16:9"
|
||||
)},
|
||||
{"filename": "hero-offices.jpg", "dir": HERO_DIR, "prompt": (
|
||||
"low 24mm angle across clean grey carpet tiles in a modern open-plan office, "
|
||||
"carpet tile seams sharp in foreground receding to rows of empty desks and glass partitions, "
|
||||
"professional overhead lighting, strong linear perspective, "
|
||||
"no people, ultra-realistic architectural photography, 16:9"
|
||||
)},
|
||||
{"filename": "hero-vacation-rentals.jpg", "dir": HERO_DIR, "prompt": (
|
||||
"low 35mm angle across clean beige carpet in a Finger Lakes cottage living room, "
|
||||
"carpet fibers sharp in foreground, stone fireplace and lake-view window receding with bokeh, "
|
||||
"wooden ceiling beams, warm inviting light, shallow depth of field, "
|
||||
"no people, ultra-realistic interior photography, 16:9"
|
||||
)},
|
||||
{"filename": "hero-hotels.jpg", "dir": HERO_DIR, "prompt": (
|
||||
"low 24mm lens looking down a long hotel corridor from floor level, "
|
||||
"patterned burgundy carpet runner sharp in extreme foreground receding to vanishing point, "
|
||||
"warm wall sconces lining white walls, numbered doors converging in perspective, "
|
||||
"no people, ultra-realistic hospitality photography, 16:9"
|
||||
)},
|
||||
{"filename": "hero-retail.jpg", "dir": HERO_DIR, "prompt": (
|
||||
"low 35mm diagonal angle across clean light grey carpet in an upscale retail showroom, "
|
||||
"carpet surface sharp in foreground, minimalist display fixtures and storefront windows receding with bokeh, "
|
||||
"bright track lighting overhead, shallow depth of field, "
|
||||
"no people, ultra-realistic architectural photography, 16:9"
|
||||
)},
|
||||
{"filename": "hero-property-management.jpg", "dir": HERO_DIR, "prompt": (
|
||||
"low 35mm angle across fresh neutral carpet in an empty move-in ready apartment, "
|
||||
"carpet texture sharp in foreground, bare white walls and bright windows receding, "
|
||||
"clean real estate photography perspective, shallow depth of field, "
|
||||
"no people, ultra-realistic real estate photography, 16:9"
|
||||
)},
|
||||
{"filename": "hero-about.jpg", "dir": HERO_DIR, "prompt": (
|
||||
"low 35mm angle from lawn level looking up at a classic upstate New York suburban home, "
|
||||
"green grass blades sharp in extreme foreground, inviting house facade receding upward, "
|
||||
"mature trees and clear blue sky, warm summer afternoon, "
|
||||
"no people, ultra-realistic real estate photography, 16:9"
|
||||
)},
|
||||
{"filename": "hero-service-area.jpg", "dir": HERO_DIR, "prompt": (
|
||||
"low horizon 24mm wide-angle Finger Lakes landscape, "
|
||||
"green vineyard vines sharp in foreground receding to rolling hills and calm lake, "
|
||||
"golden hour light casting long shadows, strong depth and distance, "
|
||||
"no people, ultra-realistic landscape photography, 16:9"
|
||||
)},
|
||||
{"filename": "hero-living-room.jpg", "dir": HERO_DIR, "prompt": (
|
||||
"low 35mm corner angle across a spacious residential living room, "
|
||||
"plush light grey carpet sharp and textured in foreground, large sectional sofa and bay windows receding with bokeh, "
|
||||
"warm afternoon sunlight, shallow depth of field, "
|
||||
"no people, ultra-realistic interior photography, 16:9"
|
||||
)},
|
||||
{"filename": "hero-clean-result.jpg", "dir": HERO_DIR, "prompt": (
|
||||
"extreme low 50mm macro angle pressed to immaculate freshly cleaned residential carpet, "
|
||||
"individual carpet fibers razor sharp in foreground, pile receding into soft bokeh, "
|
||||
"raking natural light revealing deep clean texture and uniform pile height, "
|
||||
"no people, ultra-realistic macro carpet photography, 16:9"
|
||||
)},
|
||||
# --- SERVICE CARD IMAGES ---
|
||||
{"filename": "carpet-cleaning.jpg", "dir": SVC_DIR, "prompt": (
|
||||
"low 35mm angle looking across plush clean beige carpet in a residential living room, "
|
||||
"carpet fibers sharp in foreground, couch and window receding into bokeh, "
|
||||
"warm afternoon light, shallow depth of field, no people, ultra-realistic interior photography"
|
||||
)},
|
||||
{"filename": "stairs-cleaning.jpg", "dir": SVC_DIR, "prompt": (
|
||||
"low 35mm angle looking up clean grey carpeted stairs from bottom step, "
|
||||
"carpet texture sharp on nearest step, stairs receding diagonally upward, "
|
||||
"wood banister, bright light from above, no people, ultra-realistic interior photography"
|
||||
)},
|
||||
{"filename": "upholstery-cleaning.jpg", "dir": SVC_DIR, "prompt": (
|
||||
"low 50mm angle across a clean plush linen fabric sofa arm, "
|
||||
"fabric weave sharp in foreground, living room receding with bokeh, "
|
||||
"warm light, shallow depth of field, no people, ultra-realistic interior photography"
|
||||
)},
|
||||
{"filename": "floor-cleaning.jpg", "dir": SVC_DIR, "prompt": (
|
||||
"low 24mm angle pressed to gleaming light oak hardwood floor, "
|
||||
"wood grain razor sharp in extreme foreground receding down hallway, "
|
||||
"natural light, no people, ultra-realistic interior photography"
|
||||
)},
|
||||
{"filename": "area-rug-cleaning.jpg", "dir": SVC_DIR, "prompt": (
|
||||
"low 35mm angle across a vibrant clean oriental rug from floor level, "
|
||||
"rug fibers and pattern sharp in foreground, hardwood floor and room receding, "
|
||||
"warm light, shallow depth of field, no people, ultra-realistic interior photography"
|
||||
)},
|
||||
{"filename": "add-ons.jpg", "dir": SVC_DIR, "prompt": (
|
||||
"low 35mm angle across clean beige bedroom carpet, "
|
||||
"carpet pile sharp in foreground, bed frame and curtained window receding with bokeh, "
|
||||
"morning light, no people, ultra-realistic interior photography"
|
||||
)},
|
||||
{"filename": "commercial-overview.jpg", "dir": SVC_DIR, "prompt": (
|
||||
"low 24mm angle across dark commercial carpet in a corporate lobby, "
|
||||
"carpet surface sharp in foreground receding to glass entrance, "
|
||||
"strong vanishing point, no people, ultra-realistic architectural photography"
|
||||
)},
|
||||
{"filename": "vacation-rentals.jpg", "dir": SVC_DIR, "prompt": (
|
||||
"low 35mm angle across clean carpet in a Finger Lakes cottage living room, "
|
||||
"carpet sharp in foreground, stone fireplace and window receding with bokeh, "
|
||||
"rustic warm decor, no people, ultra-realistic interior photography"
|
||||
)},
|
||||
{"filename": "office-spaces.jpg", "dir": SVC_DIR, "prompt": (
|
||||
"low 24mm angle across grey carpet tiles in a modern open office, "
|
||||
"tile seams sharp in foreground, empty desks receding with linear perspective, "
|
||||
"professional lighting, no people, ultra-realistic architectural photography"
|
||||
)},
|
||||
{"filename": "hotels-inns.jpg", "dir": SVC_DIR, "prompt": (
|
||||
"low 24mm angle down a hotel corridor, patterned carpet runner sharp in foreground, "
|
||||
"corridor receding to vanishing point, warm wall sconces, "
|
||||
"no people, ultra-realistic hospitality photography"
|
||||
)},
|
||||
{"filename": "retail-showrooms.jpg", "dir": SVC_DIR, "prompt": (
|
||||
"low 35mm diagonal angle across light grey carpet in an upscale retail showroom, "
|
||||
"carpet sharp in foreground, display fixtures and track lighting receding with bokeh, "
|
||||
"no people, ultra-realistic architectural photography"
|
||||
)},
|
||||
{"filename": "property-management.jpg", "dir": SVC_DIR, "prompt": (
|
||||
"low 35mm angle across fresh neutral carpet in an empty clean apartment, "
|
||||
"carpet texture sharp in foreground, white walls and windows receding, "
|
||||
"no people, ultra-realistic real estate photography"
|
||||
)},
|
||||
]
|
||||
|
||||
|
||||
def build_workflow(prompt, seed=None):
|
||||
if seed is None:
|
||||
seed = random.randint(0, 2**32)
|
||||
return {
|
||||
"1": {
|
||||
"class_type": "UnetLoaderGGUF",
|
||||
"inputs": {"unet_name": "flux1-schnell-Q8_0.gguf"},
|
||||
},
|
||||
"2": {
|
||||
"class_type": "DualCLIPLoader",
|
||||
"inputs": {
|
||||
"clip_name1": "t5xxl_fp8_e4m3fn.safetensors",
|
||||
"clip_name2": "clip_l.safetensors",
|
||||
"type": "flux",
|
||||
},
|
||||
},
|
||||
"3": {
|
||||
"class_type": "VAELoader",
|
||||
"inputs": {"vae_name": "ae.safetensors"},
|
||||
},
|
||||
"4": {
|
||||
"class_type": "CLIPTextEncode",
|
||||
"inputs": {"clip": ["2", 0], "text": prompt},
|
||||
},
|
||||
"5": {
|
||||
"class_type": "EmptyLatentImage",
|
||||
"inputs": {"batch_size": 1, "height": 576, "width": 1024},
|
||||
},
|
||||
"6": {
|
||||
"class_type": "KSampler",
|
||||
"inputs": {
|
||||
"cfg": 1.0,
|
||||
"denoise": 1.0,
|
||||
"latent_image": ["5", 0],
|
||||
"model": ["1", 0],
|
||||
"negative": ["4", 0],
|
||||
"positive": ["4", 0],
|
||||
"sampler_name": "euler",
|
||||
"scheduler": "simple",
|
||||
"seed": seed,
|
||||
"steps": 4,
|
||||
},
|
||||
},
|
||||
"7": {
|
||||
"class_type": "VAEDecode",
|
||||
"inputs": {"samples": ["6", 0], "vae": ["3", 0]},
|
||||
},
|
||||
"8": {
|
||||
"class_type": "SaveImage",
|
||||
"inputs": {"filename_prefix": "flux_lahr", "images": ["7", 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=600):
|
||||
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:
|
||||
entry = hist[prompt_id]
|
||||
status = entry.get("status", {}).get("status_str", "")
|
||||
if status == "error":
|
||||
msgs = entry.get("status", {}).get("messages", [])
|
||||
print(f" COMFYUI ERROR: {msgs}", flush=True)
|
||||
return None
|
||||
for node_out in entry.get("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
|
||||
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(spec["dir"], spec["filename"])
|
||||
print(f"\n[{i+1}/{total}] {spec['filename']}", flush=True)
|
||||
workflow = build_workflow(spec["prompt"])
|
||||
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)
|
||||
@@ -1,226 +0,0 @@
|
||||
"""
|
||||
Lahr Carpet Cleaning — Location page generator.
|
||||
Creates /locations/<slug>/index.html for each city.
|
||||
Run: python3 tools/gen-locations.py
|
||||
"""
|
||||
import os
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||
LOC_DIR = os.path.join(BASE_DIR, "locations")
|
||||
|
||||
CITIES = [
|
||||
{"name": "Waterloo", "slug": "waterloo-ny", "county": "Seneca County", "note": "Our home base. Fastest response times in the area."},
|
||||
{"name": "Geneva", "slug": "geneva-ny", "county": "Ontario County", "note": "Full residential and commercial services throughout Geneva."},
|
||||
{"name": "Seneca Falls", "slug": "seneca-falls-ny", "county": "Seneca County", "note": "Serving homes, vacation rentals, and businesses in Seneca Falls."},
|
||||
{"name": "Canandaigua", "slug": "canandaigua-ny", "county": "Ontario County", "note": "Lakefront homes, rentals, and businesses along Canandaigua Lake."},
|
||||
{"name": "Penn Yan", "slug": "penn-yan-ny", "county": "Yates County", "note": "Homes, wineries, and short-term rentals in the Penn Yan area."},
|
||||
{"name": "Newark", "slug": "newark-ny", "county": "Wayne County", "note": "Carpet and upholstery cleaning for homes and businesses in Newark."},
|
||||
{"name": "Clifton Springs", "slug": "clifton-springs-ny", "county": "Ontario County", "note": "Residential and commercial cleaning throughout Clifton Springs."},
|
||||
{"name": "Lodi", "slug": "lodi-ny", "county": "Seneca County", "note": "Serving homes and vacation properties in Lodi and surrounding areas."},
|
||||
{"name": "Himrod", "slug": "himrod-ny", "county": "Yates County", "note": "Carpet and floor cleaning for homes and rentals in the Himrod area."},
|
||||
{"name": "Phelps", "slug": "phelps-ny", "county": "Ontario County", "note": "Residential carpet and upholstery cleaning throughout Phelps."},
|
||||
{"name": "Shortsville", "slug": "shortsville-ny", "county": "Ontario County", "note": "Home and business cleaning services in Shortsville, NY."},
|
||||
{"name": "Victor", "slug": "victor-ny", "county": "Ontario County", "note": "Residential and commercial carpet cleaning throughout Victor."},
|
||||
{"name": "Naples", "slug": "naples-ny", "county": "Ontario County", "note": "Serving homes, wineries, and vacation rentals in the Naples area."},
|
||||
{"name": "Gorham", "slug": "gorham-ny", "county": "Ontario County", "note": "Carpet and floor cleaning for homes and properties in Gorham."},
|
||||
{"name": "Manchester", "slug": "manchester-ny", "county": "Ontario County", "note": "Residential and commercial cleaning services in Manchester, NY."},
|
||||
{"name": "Ovid", "slug": "ovid-ny", "county": "Seneca County", "note": "Serving homes and rental properties throughout Ovid."},
|
||||
{"name": "Clyde", "slug": "clyde-ny", "county": "Wayne County", "note": "Carpet, upholstery, and floor cleaning for homes and businesses in Clyde."},
|
||||
{"name": "Farmington", "slug": "farmington-ny", "county": "Ontario County", "note": "Residential and commercial carpet cleaning throughout Farmington."},
|
||||
{"name": "East Bloomfield", "slug": "east-bloomfield-ny", "county": "Ontario County", "note": "Serving homes and properties in East Bloomfield and surrounding areas."},
|
||||
{"name": "Rushville", "slug": "rushville-ny", "county": "Yates County", "note": "Carpet and upholstery cleaning for homes and rentals in Rushville."},
|
||||
{"name": "Finger Lakes", "slug": "finger-lakes-ny", "county": "Region", "note": "Serving vacation rentals, wineries, and homes across the Finger Lakes region."},
|
||||
]
|
||||
|
||||
SERVICES = [
|
||||
{"name": "Carpet Cleaning", "slug": "/services/carpet-cleaning/", "img": "/assets/images/services/carpet-cleaning.jpg", "sub": "In-Home Service", "desc": "Hot water extraction removes deep-seated dirt, allergens, and stains from carpet fibers throughout your home."},
|
||||
{"name": "Stairs Cleaning", "slug": "/services/stairs/", "img": "/assets/images/services/stairs-cleaning.jpg", "sub": "Step by Step", "desc": "Stairs collect more dirt per square inch than any flat surface. We clean every tread, riser, and landing."},
|
||||
{"name": "Upholstery Cleaning","slug": "/services/upholstery/", "img": "/assets/images/services/upholstery-cleaning.jpg","sub": "Furniture Refresh", "desc": "Safe, effective cleaning for sofas, chairs, and mattresses. We work with all fabric types and leave no residue."},
|
||||
{"name": "Floor Cleaning", "slug": "/services/floors/", "img": "/assets/images/services/floor-cleaning.jpg", "sub": "Hard Surface Care", "desc": "Wood floor cleaning and tile and grout restoration that brings hard surfaces back to their original condition."},
|
||||
{"name": "Area Rug Cleaning", "slug": "/services/area-rugs/", "img": "/assets/images/services/area-rug-cleaning.jpg", "sub": "Delicate Care", "desc": "Gentle, specialized cleaning for oriental, Persian, and delicate rugs that restores color and removes embedded dirt."},
|
||||
{"name": "Add-On Services", "slug": "/services/add-ons/", "img": "/assets/images/services/add-ons.jpg", "sub": "Extra Care", "desc": "Furniture moving, pet hair removal, odor treatment, and heavily soiled area care available alongside any service."},
|
||||
]
|
||||
|
||||
HERO_IMAGES = [
|
||||
"/assets/images/hero/hero-living-room.jpg",
|
||||
"/assets/images/hero/hero-clean-result.jpg",
|
||||
"/assets/images/hero/hero-technician.jpg",
|
||||
"/assets/images/hero/hero-before-after.jpg",
|
||||
"/assets/images/hero/hero-stairs.jpg",
|
||||
]
|
||||
|
||||
|
||||
def service_card(svc, city_name):
|
||||
accent_word, rest = svc["name"].split(" ", 1) if " " in svc["name"] else (svc["name"], "")
|
||||
h3 = f'<span class="text-accent">{accent_word}</span> {rest}'.strip()
|
||||
return f""" <div class="service-card">
|
||||
<div class="service-image"><img src="{svc['img']}" alt="{svc['name']} in {city_name}, NY"></div>
|
||||
<h3 class="heading-3">{h3}</h3>
|
||||
<div class="text-block-4">{svc['sub']}</div>
|
||||
<p class="paragraph-2">{svc['desc']}</p>
|
||||
<a href="{svc['slug']}" class="btn btn-primary">Learn More</a>
|
||||
</div>"""
|
||||
|
||||
|
||||
def page_html(city, idx):
|
||||
hero_img = HERO_IMAGES[idx % len(HERO_IMAGES)]
|
||||
cards = "\n".join(service_card(s, city["name"]) for s in SERVICES)
|
||||
name = city["name"]
|
||||
county = city["county"]
|
||||
note = city["note"]
|
||||
slug = city["slug"]
|
||||
|
||||
return f"""<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Carpet Cleaning in {name}, NY | Lahr Carpet Cleaning</title>
|
||||
<meta name="description" content="Professional carpet, upholstery, and floor cleaning in {name}, NY. Lahr Carpet Cleaning serves {county} and the Finger Lakes region. Call 315-719-1218.">
|
||||
<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>
|
||||
<body>
|
||||
<header class="header" id="header">
|
||||
<div class="container"><div id="site-nav"></div></div>
|
||||
</header>
|
||||
|
||||
<section class="page-hero" style="background-image: url('{hero_img}');">
|
||||
<div class="container">
|
||||
<div class="page-hero-content">
|
||||
<span class="hero-eyebrow">{county} — Finger Lakes</span>
|
||||
<h1 class="hero-title">{name},<br><span class="text-accent">NY</span></h1>
|
||||
<p>{note}</p>
|
||||
<div class="hero-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="services-overview">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<span class="section-label">{name}, NY</span>
|
||||
<h2 class="section-title">Services in {name}</h2>
|
||||
<p class="section-subtitle">We serve {name} and the surrounding {county} communities. Call to confirm availability for your address.</p>
|
||||
</div>
|
||||
<div class="services-grid">
|
||||
{cards}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="cta-banner">
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Serving </strong><span class="text-accent"><strong>{name}</strong></span></h2>
|
||||
<p class="paragraph-4">Call 315-719-1218 or submit the form for a free estimate in {name}, NY.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Get a Free Estimate</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="/assets/js/components.js"></script>
|
||||
<script src="/assets/js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
def locations_index():
|
||||
city_cards = []
|
||||
for i, city in enumerate(CITIES):
|
||||
img = HERO_IMAGES[i % len(HERO_IMAGES)]
|
||||
name = city["name"]
|
||||
county = city["county"]
|
||||
note = city["note"]
|
||||
slug = city["slug"]
|
||||
if " " in name:
|
||||
accent_word, rest = name.split(" ", 1)
|
||||
h3 = f'<span class="text-accent">{accent_word}</span> {rest}, NY'
|
||||
else:
|
||||
h3 = f'<span class="text-accent">{name}</span>, NY'
|
||||
city_cards.append(f""" <div class="service-card">
|
||||
<div class="service-image"><img src="{img}" alt="{name} NY"></div>
|
||||
<h3 class="heading-3">{h3}</h3>
|
||||
<div class="text-block-4">{county}</div>
|
||||
<p class="paragraph-2">{note}</p>
|
||||
<a href="/locations/{slug}/" class="btn btn-primary">View Services</a>
|
||||
</div>""")
|
||||
|
||||
cards_html = "\n".join(city_cards)
|
||||
return f"""<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Service Areas | Lahr Carpet Cleaning | Finger Lakes, NY</title>
|
||||
<meta name="description" content="Lahr Carpet Cleaning serves Waterloo, Geneva, Seneca Falls, Canandaigua, Penn Yan, and 16 more cities across the Finger Lakes region. Call 315-719-1218.">
|
||||
<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>
|
||||
<body>
|
||||
<header class="header" id="header">
|
||||
<div class="container"><div id="site-nav"></div></div>
|
||||
</header>
|
||||
|
||||
<section class="page-hero" style="background-image: url('/assets/images/hero/hero-technician.jpg');">
|
||||
<div class="container">
|
||||
<div class="page-hero-content">
|
||||
<span class="hero-eyebrow">Finger Lakes Region</span>
|
||||
<h1 class="hero-title">Service<br><span class="text-accent">Areas</span></h1>
|
||||
<p>We clean carpets, upholstery, rugs, and hard floors across 21 cities in Upstate New York. Select your city below.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="/contact/" class="btn btn-primary btn-large">Book Now</a>
|
||||
<a href="tel:315-719-1218" class="btn btn-ghost btn-large"><i class="fas fa-phone"></i> 315-719-1218</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="services-overview">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<span class="section-label">Where We Work</span>
|
||||
<h2 class="section-title">Cities We Serve</h2>
|
||||
<p class="section-subtitle">Based in Waterloo, NY. We travel throughout Seneca, Ontario, Yates, Wayne, and Cayuga counties.</p>
|
||||
</div>
|
||||
<div class="services-grid">
|
||||
{cards_html}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="cta-banner">
|
||||
<div class="cta-content">
|
||||
<h2 class="heading-4"><strong>Not sure if we cover your area?</strong></h2>
|
||||
<p class="paragraph-4">Call 315-719-1218 or submit the form and we will confirm availability for your address.</p>
|
||||
<a href="/contact/" class="btn btn-primary">Get a Free Estimate</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="/assets/js/components.js"></script>
|
||||
<script src="/assets/js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Write locations index
|
||||
with open(os.path.join(LOC_DIR, "index.html"), "w") as f:
|
||||
f.write(locations_index())
|
||||
print("Wrote locations/index.html")
|
||||
|
||||
# Write each city page
|
||||
for i, city in enumerate(CITIES):
|
||||
city_dir = os.path.join(LOC_DIR, city["slug"])
|
||||
os.makedirs(city_dir, exist_ok=True)
|
||||
out_path = os.path.join(city_dir, "index.html")
|
||||
with open(out_path, "w") as f:
|
||||
f.write(page_html(city, i))
|
||||
print(f"Wrote locations/{city['slug']}/index.html")
|
||||
|
||||
print(f"\nDone. {len(CITIES)} city pages + index generated.")
|
||||
@@ -1,179 +0,0 @@
|
||||
"""
|
||||
Lahr Carpet Cleaning — Service card image generator.
|
||||
Generates 12 unique images for residential and commercial service cards.
|
||||
Saves to: assets/images/services/
|
||||
Run: python3 tools/gen-service-images.py
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
from google import genai
|
||||
from google.genai import types
|
||||
except ImportError:
|
||||
print("Installing google-genai...")
|
||||
os.system(f"{sys.executable} -m pip install google-genai --quiet")
|
||||
from google import genai
|
||||
from google.genai import types
|
||||
|
||||
API_KEY = os.environ.get("GEMINI_API_KEY", "AIzaSyB_1p8KvaT_rdNJGPs8HKk8bKsvUlcL6Kg")
|
||||
OUT_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "assets", "images", "services")
|
||||
os.makedirs(OUT_DIR, exist_ok=True)
|
||||
|
||||
client = genai.Client(api_key=API_KEY)
|
||||
|
||||
IMAGES = [
|
||||
# ── Residential ──────────────────────────────────────────────────────────
|
||||
{
|
||||
"name": "carpet-cleaning",
|
||||
"prompt": (
|
||||
"Wide shot of a large industrial stand-up hot water extraction machine being pushed across "
|
||||
"a plush beige residential carpet. The machine is a heavy commercial-grade upright extractor "
|
||||
"on wheels — tall, wide cleaning head at the base, long upright handle. "
|
||||
"The carpet behind it transitions from dirty and matted to clean, bright, and fluffy. "
|
||||
"Completely dry machine exterior, no steam, no water spraying anywhere. "
|
||||
"Warm natural interior light. Ultra-realistic professional photography."
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "stairs-cleaning",
|
||||
"prompt": (
|
||||
"Wide cinematic shot of a carpeted residential staircase. Each step has clean, "
|
||||
"bright plush carpet that looks freshly cleaned with visible extraction lines. "
|
||||
"Modern upstate New York home interior, natural light from above, "
|
||||
"warm wood banisters, no people, no equipment visible. "
|
||||
"Professional interior photography, ultra-realistic."
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "upholstery-cleaning",
|
||||
"prompt": (
|
||||
"Close-up of a clean grey linen sofa cushion showing bright, lifted fabric texture "
|
||||
"after professional upholstery cleaning. Half the cushion shows the before "
|
||||
"(slightly soiled, flat fabric) and half shows the cleaned result (bright, fluffy, refreshed). "
|
||||
"Natural window light, residential living room in background, no people, no equipment. "
|
||||
"Ultra-realistic product photography."
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "floor-cleaning",
|
||||
"prompt": (
|
||||
"Wide shot of a gleaming hardwood floor in a modern residential home after professional "
|
||||
"cleaning. The floor reflects soft natural window light, showing deep grain detail. "
|
||||
"Contemporary furniture in background, no people, no cleaning equipment visible. "
|
||||
"Professional interior photography, ultra-realistic."
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "area-rug-cleaning",
|
||||
"prompt": (
|
||||
"Overhead flat-lay shot of a large vibrant Persian or oriental area rug "
|
||||
"with rich red, navy, and cream geometric patterns, looking freshly cleaned — "
|
||||
"colors vivid, fibers lifted and bright. Hardwood floor beneath. "
|
||||
"No people, no equipment, no water. Professional product photography, ultra-realistic."
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "add-ons",
|
||||
"prompt": (
|
||||
"Close-up macro shot of clean carpet fibers being lifted by a professional "
|
||||
"grooming brush after hot water extraction cleaning. The fibers are bright, "
|
||||
"fluffy, and standing upright. Warm light catches the texture. "
|
||||
"No steam, no water, no people. Ultra-realistic macro photography."
|
||||
),
|
||||
},
|
||||
# ── Commercial ───────────────────────────────────────────────────────────
|
||||
{
|
||||
"name": "vacation-rentals",
|
||||
"prompt": (
|
||||
"Bright, airy vacation rental living room in the Finger Lakes region of upstate New York. "
|
||||
"Spotlessly clean cream carpet, contemporary furniture, large windows with lake views, "
|
||||
"warm natural afternoon light. Inviting and fresh. No people, no equipment. "
|
||||
"Professional real estate photography, ultra-realistic."
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "office-spaces",
|
||||
"prompt": (
|
||||
"Wide shot of a clean modern corporate office with freshly cleaned dark charcoal carpet "
|
||||
"throughout. Open plan workspace, glass partitions, professional lighting. "
|
||||
"Carpet shows neat vacuum lines indicating recent professional cleaning. "
|
||||
"No people, no equipment. Professional architectural photography, ultra-realistic."
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "hotels-inns",
|
||||
"prompt": (
|
||||
"Elegant hotel corridor in a boutique upstate New York inn. Clean, plush patterned "
|
||||
"carpet runner down the hallway with fresh vacuum lines. Warm sconce lighting, "
|
||||
"wood paneling, framed art on walls. No people, no equipment. "
|
||||
"Professional hospitality photography, ultra-realistic."
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "retail-showrooms",
|
||||
"prompt": (
|
||||
"Wide shot of an upscale retail showroom or winery tasting room in the Finger Lakes. "
|
||||
"Clean, rich carpet throughout, warm lighting, product displays on shelves. "
|
||||
"Carpet looks freshly extracted — bright and spotless. "
|
||||
"No people, no equipment. Professional commercial interior photography, ultra-realistic."
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "property-management",
|
||||
"prompt": (
|
||||
"View across three clean apartment living rooms in sequence, each showing "
|
||||
"spotlessly clean beige carpet with fresh vacuum lines after professional cleaning. "
|
||||
"Bright, neutral interiors ready for new tenants. Natural light, no furniture, "
|
||||
"no people, no equipment. Professional real estate photography, ultra-realistic."
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "commercial-overview",
|
||||
"prompt": (
|
||||
"Professional carpet cleaning technician in a plain black shirt, shown from the side, "
|
||||
"pushing a large industrial stand-up hot water extraction machine through a bright commercial "
|
||||
"building lobby. The machine is a heavy commercial-grade upright extractor on wheels — "
|
||||
"tall, wide cleaning head, long handle. Clean carpet visible. No steam, no water spraying, "
|
||||
"no face visible. Professional editorial photography, ultra-realistic."
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def generate():
|
||||
saved = []
|
||||
total = len(IMAGES)
|
||||
for i, item in enumerate(IMAGES, 1):
|
||||
out_path = os.path.join(OUT_DIR, f"{item['name']}.jpg")
|
||||
print(f"[{i}/{total}] Generating {item['name']}...")
|
||||
try:
|
||||
resp = client.models.generate_images(
|
||||
model="imagen-4.0-generate-001",
|
||||
prompt=item["prompt"],
|
||||
config=types.GenerateImagesConfig(
|
||||
number_of_images=1,
|
||||
aspect_ratio="4:3",
|
||||
output_mime_type="image/jpeg",
|
||||
safety_filter_level="block_low_and_above",
|
||||
),
|
||||
)
|
||||
if resp.generated_images:
|
||||
img_bytes = resp.generated_images[0].image.image_bytes
|
||||
with open(out_path, "wb") as f:
|
||||
f.write(img_bytes)
|
||||
print(f" Saved {out_path} ({len(img_bytes)//1024}KB)")
|
||||
saved.append(item["name"])
|
||||
else:
|
||||
print(f" No image returned for {item['name']}")
|
||||
except Exception as e:
|
||||
print(f" Error on {item['name']}: {e}")
|
||||
|
||||
return saved
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
saved = generate()
|
||||
print(f"\nDone. {len(saved)}/{len(IMAGES)} images saved to assets/images/services/")
|
||||
if saved:
|
||||
print("Generated:", ", ".join(saved))
|
||||
@@ -1,223 +0,0 @@
|
||||
"""
|
||||
Generate carpet cleaning reel clips via Wan 2.2 TI2V (text+image to video) through ComfyUI.
|
||||
Uses FLUX-generated hero stills as input frames, animates each into a 3-5 second clip.
|
||||
Run after gen-images-flux.py completes and images are converted to webp.
|
||||
|
||||
Usage:
|
||||
python3 tools/gen-video-wan.py
|
||||
|
||||
Output: assets/videos/clips/*.mp4 — stitch with ffmpeg into final reel.
|
||||
"""
|
||||
import json, time, urllib.request, os, random, io
|
||||
|
||||
COMFY = "http://localhost:8188"
|
||||
HERO_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "assets", "images", "hero")
|
||||
OUT_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "assets", "videos", "clips")
|
||||
os.makedirs(OUT_DIR, exist_ok=True)
|
||||
|
||||
WAN_MODEL = "Wan2.2-TI2V-5B-Q4_K_M.gguf"
|
||||
|
||||
# Each clip: input still + motion prompt → 3-5 sec animated clip
|
||||
# Order matches the reel sequence
|
||||
CLIPS = [
|
||||
{
|
||||
"filename": "clip-01-carpet.mp4",
|
||||
"image": "hero-carpet-cleaning.webp",
|
||||
"prompt": "slow dolly forward across clean plush carpet, gentle camera push toward the far wall, warm afternoon light, cinematic, smooth motion",
|
||||
"frames": 49, # ~4 seconds at ~12fps
|
||||
},
|
||||
{
|
||||
"filename": "clip-02-stairs.mp4",
|
||||
"image": "hero-stairs.webp",
|
||||
"prompt": "slow pan upward along clean carpeted staircase, camera tilts up following the banister, soft natural light, cinematic motion",
|
||||
"frames": 49,
|
||||
},
|
||||
{
|
||||
"filename": "clip-03-upholstery.mp4",
|
||||
"image": "hero-upholstery.webp",
|
||||
"prompt": "gentle push in toward clean linen sofa, shallow depth of field, warm light, slow cinematic camera movement",
|
||||
"frames": 49,
|
||||
},
|
||||
{
|
||||
"filename": "clip-04-commercial.mp4",
|
||||
"image": "hero-commercial.webp",
|
||||
"prompt": "slow tracking shot moving forward down a clean corporate lobby, receding vanishing point, professional lighting, cinematic",
|
||||
"frames": 49,
|
||||
},
|
||||
{
|
||||
"filename": "clip-05-floors.mp4",
|
||||
"image": "hero-floors.webp",
|
||||
"prompt": "floor-level drift forward along gleaming hardwood, camera slides smoothly down the hallway, natural light",
|
||||
"frames": 49,
|
||||
},
|
||||
{
|
||||
"filename": "clip-06-clean-result.mp4",
|
||||
"image": "hero-clean-result.webp",
|
||||
"prompt": "slow rack focus across clean carpet fibers, foreground to background, raking natural light, macro detail, cinematic",
|
||||
"frames": 49,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def load_image_as_base64(image_path):
|
||||
import base64
|
||||
with open(image_path, "rb") as f:
|
||||
return base64.b64encode(f.read()).decode("utf-8")
|
||||
|
||||
|
||||
def upload_image(image_path):
|
||||
"""Upload image to ComfyUI and return the filename it assigned."""
|
||||
import base64
|
||||
fname = os.path.basename(image_path)
|
||||
with open(image_path, "rb") as f:
|
||||
img_data = f.read()
|
||||
boundary = "----FormBoundary" + str(random.randint(100000, 999999))
|
||||
body = (
|
||||
f"--{boundary}\r\n"
|
||||
f'Content-Disposition: form-data; name="image"; filename="{fname}"\r\n'
|
||||
f"Content-Type: image/webp\r\n\r\n"
|
||||
).encode() + img_data + f"\r\n--{boundary}--\r\n".encode()
|
||||
req = urllib.request.Request(
|
||||
f"{COMFY}/upload/image",
|
||||
data=body,
|
||||
headers={"Content-Type": f"multipart/form-data; boundary={boundary}"},
|
||||
)
|
||||
with urllib.request.urlopen(req) as resp:
|
||||
result = json.loads(resp.read())
|
||||
return result["name"]
|
||||
|
||||
|
||||
def build_workflow(image_name, prompt, frames, seed=None):
|
||||
if seed is None:
|
||||
seed = random.randint(0, 2**32)
|
||||
return {
|
||||
"1": {
|
||||
"class_type": "UnetLoaderGGUF",
|
||||
"inputs": {"unet_name": WAN_MODEL},
|
||||
},
|
||||
"2": {
|
||||
"class_type": "CLIPLoader",
|
||||
"inputs": {
|
||||
"clip_name": "umt5_xxl_fp8_e4m3fn_scaled.safetensors",
|
||||
"type": "wan",
|
||||
},
|
||||
},
|
||||
"3": {
|
||||
"class_type": "VAELoader",
|
||||
"inputs": {"vae_name": "wan_2.1_vae.safetensors"},
|
||||
},
|
||||
"4": {
|
||||
"class_type": "LoadImage",
|
||||
"inputs": {"image": image_name},
|
||||
},
|
||||
"5": {
|
||||
"class_type": "CLIPTextEncode",
|
||||
"inputs": {"clip": ["2", 0], "text": prompt},
|
||||
},
|
||||
"6": {
|
||||
"class_type": "CLIPTextEncode",
|
||||
"inputs": {"clip": ["2", 0], "text": "blur, low quality, distortion, text, watermark, people, faces"},
|
||||
},
|
||||
"7": {
|
||||
"class_type": "WanImageToVideo",
|
||||
"inputs": {
|
||||
"model": ["1", 0],
|
||||
"clip": ["2", 0],
|
||||
"vae": ["3", 0],
|
||||
"image": ["4", 0],
|
||||
"positive": ["5", 0],
|
||||
"negative": ["6", 0],
|
||||
"width": 832,
|
||||
"height": 480,
|
||||
"length": frames,
|
||||
"batch_size": 1,
|
||||
"seed": seed,
|
||||
"steps": 20,
|
||||
"cfg": 6.0,
|
||||
"sampler_name": "uni_pc",
|
||||
"scheduler": "simple",
|
||||
"denoise": 1.0,
|
||||
},
|
||||
},
|
||||
"8": {
|
||||
"class_type": "SaveAnimatedWEBP",
|
||||
"inputs": {
|
||||
"images": ["7", 0],
|
||||
"filename_prefix": "wan_lahr",
|
||||
"fps": 16,
|
||||
"lossless": False,
|
||||
"quality": 85,
|
||||
"method": "default",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
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=1800):
|
||||
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:
|
||||
entry = hist[prompt_id]
|
||||
if entry.get("status", {}).get("status_str") == "error":
|
||||
print(f" ERROR: {entry['status'].get('messages', '')}", flush=True)
|
||||
return None
|
||||
for node_out in entry.get("outputs", {}).values():
|
||||
if "images" in node_out:
|
||||
return node_out["images"]
|
||||
if "gifs" in node_out:
|
||||
return node_out["gifs"]
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(8)
|
||||
print(" waiting...", flush=True)
|
||||
return None
|
||||
|
||||
|
||||
def download_video(vid_info, out_path):
|
||||
fname = vid_info["filename"]
|
||||
subfolder = vid_info.get("subfolder", "")
|
||||
img_type = vid_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()
|
||||
with open(out_path, "wb") as f:
|
||||
f.write(data)
|
||||
print(f" saved: {os.path.basename(out_path)} ({len(data)//1024}KB)", flush=True)
|
||||
|
||||
|
||||
total = len(CLIPS)
|
||||
for i, clip in enumerate(CLIPS):
|
||||
print(f"\n[{i+1}/{total}] {clip['filename']}", flush=True)
|
||||
image_path = os.path.join(HERO_DIR, clip["image"])
|
||||
if not os.path.exists(image_path):
|
||||
print(f" SKIP: {image_path} not found", flush=True)
|
||||
continue
|
||||
print(f" uploading {clip['image']}...", flush=True)
|
||||
image_name = upload_image(image_path)
|
||||
workflow = build_workflow(image_name, clip["prompt"], clip["frames"])
|
||||
prompt_id = queue_prompt(workflow)
|
||||
print(f" queued {prompt_id[:8]}...", flush=True)
|
||||
results = wait_for_result(prompt_id)
|
||||
if results:
|
||||
out_path = os.path.join(OUT_DIR, clip["filename"])
|
||||
# rename .webp output to .mp4 for compatibility — or save as webp animation
|
||||
out_path_webp = out_path.replace(".mp4", ".webp")
|
||||
download_video(results[0], out_path_webp)
|
||||
else:
|
||||
print(f" FAILED", flush=True)
|
||||
|
||||
print("\nAll clips done. Stitch with:")
|
||||
print(f" ffmpeg -f concat -safe 0 -i tools/clip-list.txt -c copy assets/videos/hero-reel-flux.mp4")
|
||||
@@ -1,220 +0,0 @@
|
||||
"""
|
||||
Lahr Carpet Cleaning — Veo hero video generator.
|
||||
5 shots x 4s = 20s reel. Concatenated by ffmpeg into hero-reel.mp4.
|
||||
Saves clips to: assets/videos/hero/clips/
|
||||
Saves final to: assets/videos/hero/hero-reel.mp4
|
||||
Run: python3 tools/gen-video.py
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import subprocess
|
||||
|
||||
try:
|
||||
from google import genai
|
||||
from google.genai import types
|
||||
except ImportError:
|
||||
print("Installing google-genai...")
|
||||
os.system(f"{sys.executable} -m pip install google-genai --quiet")
|
||||
from google import genai
|
||||
from google.genai import types
|
||||
|
||||
API_KEY = os.environ.get("GEMINI_API_KEY", "AIzaSyB_1p8KvaT_rdNJGPs8HKk8bKsvUlcL6Kg")
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||
OUT_DIR = os.path.join(BASE_DIR, "assets", "videos", "hero", "clips")
|
||||
REEL_OUT = os.path.join(BASE_DIR, "assets", "videos", "hero", "hero-reel.mp4")
|
||||
os.makedirs(OUT_DIR, exist_ok=True)
|
||||
|
||||
client = genai.Client(api_key=API_KEY)
|
||||
|
||||
SHOTS = [
|
||||
{
|
||||
"name": "shot-01-door-opens",
|
||||
"prompt": (
|
||||
"Cinematic low-angle wide shot. A solid wood front door of an upstate New York home opens "
|
||||
"inward smoothly. Bright golden afternoon sunlight pours through the doorway onto a carpeted "
|
||||
"entryway floor. Camera is at floor level, looking toward the door. The door swings open "
|
||||
"fully revealing light. No people visible. Photorealistic, warm inviting light, slow motion."
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "shot-02-pan-to-stains",
|
||||
"prompt": (
|
||||
"Slow cinematic camera pan from the front door entryway across a residential living room carpet "
|
||||
"in an upstate New York home. The carpet shows visible dirt tracks, pet stains, and soiling "
|
||||
"from daily use. Natural light. No people. Camera moves fluidly across the room revealing "
|
||||
"the stained carpet. Photorealistic."
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "shot-03-stain-closeup",
|
||||
"prompt": (
|
||||
"Close-up shot of a stained beige carpet with visible pet stains, mud, and dark soiling. "
|
||||
"Camera slowly pushes in on the dirty area. Dramatic side lighting emphasises the stain depth "
|
||||
"and texture. Slow motion. Ultra-realistic macro photography style."
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "shot-04-extraction-carpet",
|
||||
"prompt": (
|
||||
"Cinematic slow-motion wide shot: a large industrial stand-up hot water extraction machine "
|
||||
"being pushed steadily forward across a beige residential carpet. The machine is a tall "
|
||||
"professional-grade upright extractor — heavy-duty, commercial size, on wheels, with a wide "
|
||||
"cleaning head at the base and an upright handle. No steam, no spraying water, no visible "
|
||||
"liquid anywhere on the machine exterior. The carpet behind the machine transitions from dirty "
|
||||
"and matted to bright, clean, and fluffy as it passes. Warm natural room light. Photorealistic."
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "shot-05-extraction-couch",
|
||||
"prompt": (
|
||||
"Close-up cinematic shot of a professional technician's gloved hand holding a small flat "
|
||||
"upholstery cleaning attachment tool, pressing it firmly against a dirty grey sofa cushion "
|
||||
"and sliding it slowly across the fabric. The fabric visibly brightens and lifts as the tool "
|
||||
"moves. No water pours out — suction draws moisture into the tool. Slow motion, natural light. "
|
||||
"Photorealistic."
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "shot-06-extraction-stairs",
|
||||
"prompt": (
|
||||
"Cinematic shot of a professional technician's hands using a compact portable upright carpet "
|
||||
"cleaner on a carpeted staircase — pushing the machine up a stair tread step by step. Each "
|
||||
"tread brightens and looks freshly cleaned as the machine passes. No water pours out. Clean "
|
||||
"bright carpet revealed on each step. Slow motion, warm interior light. Photorealistic."
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "shot-07-office-entryway",
|
||||
"prompt": (
|
||||
"Wide cinematic shot of a clean professional office building entryway with commercial grade "
|
||||
"carpet. Modern corporate interior, glass doors, professional lighting. No people. Camera "
|
||||
"slowly pushes forward through the entry. Photorealistic."
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "shot-08-showroom",
|
||||
"prompt": (
|
||||
"Wide cinematic shot of an upscale retail showroom or winery tasting room in the Finger Lakes "
|
||||
"region. Rich carpet throughout, warm interior lighting, product displays. No people. Camera "
|
||||
"glides forward through the space. Photorealistic, luxurious atmosphere."
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "shot-09-technician-unloading",
|
||||
"prompt": (
|
||||
"Wide shot of a professional carpet cleaning technician wearing a plain black shirt with no logo, "
|
||||
"rolling a large industrial stand-up hot water extraction machine out of a white service van "
|
||||
"parked in a residential driveway in upstate New York. The machine is a heavy commercial-grade "
|
||||
"upright extractor on wheels — tall, industrial size. Autumn trees in background, bright day. "
|
||||
"Technician shown from side or behind, no face visible. Photorealistic."
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
MODELS = [
|
||||
"veo-2.0-generate-001",
|
||||
"veo-3.0-generate-001",
|
||||
]
|
||||
|
||||
def poll(operation, timeout=420):
|
||||
elapsed = 0
|
||||
while not operation.done:
|
||||
if elapsed >= timeout:
|
||||
print(" Timed out.")
|
||||
return None
|
||||
print(f" Waiting... ({elapsed}s)")
|
||||
time.sleep(15)
|
||||
elapsed += 15
|
||||
operation = client.operations.get(operation)
|
||||
return operation
|
||||
|
||||
def download_video(video, out_path):
|
||||
video_bytes = None
|
||||
try:
|
||||
video_bytes = client.files.download(file=video)
|
||||
except Exception:
|
||||
pass
|
||||
if video_bytes:
|
||||
with open(out_path, "wb") as f:
|
||||
f.write(video_bytes)
|
||||
return True
|
||||
if hasattr(video, "uri") and video.uri:
|
||||
import urllib.request
|
||||
uri = video.uri + ("&" if "?" in video.uri else "?") + f"key={API_KEY}"
|
||||
print(f" Fetching via URI...")
|
||||
urllib.request.urlretrieve(uri, out_path)
|
||||
return True
|
||||
return False
|
||||
|
||||
def generate():
|
||||
saved = []
|
||||
for item in SHOTS:
|
||||
out_path = os.path.join(OUT_DIR, f"{item['name']}.mp4")
|
||||
print(f"\n[{SHOTS.index(item)+1}/{len(SHOTS)}] Generating {item['name']}...")
|
||||
|
||||
done = False
|
||||
for model in MODELS:
|
||||
try:
|
||||
print(f" Model: {model}")
|
||||
op = client.models.generate_videos(
|
||||
model=model,
|
||||
prompt=item["prompt"],
|
||||
config=types.GenerateVideosConfig(
|
||||
aspect_ratio="16:9",
|
||||
resolution="720p",
|
||||
duration_seconds=6,
|
||||
number_of_videos=1,
|
||||
),
|
||||
)
|
||||
op = poll(op)
|
||||
if op is None:
|
||||
continue
|
||||
if op.response and op.response.generated_videos:
|
||||
vid = op.response.generated_videos[0].video
|
||||
if download_video(vid, out_path):
|
||||
size_kb = os.path.getsize(out_path) // 1024
|
||||
print(f" Saved {out_path} ({size_kb}KB)")
|
||||
saved.append(out_path)
|
||||
done = True
|
||||
break
|
||||
else:
|
||||
print(f" Download failed for {model}")
|
||||
else:
|
||||
print(f" No video from {model}")
|
||||
except Exception as e:
|
||||
print(f" Error with {model}: {e}")
|
||||
|
||||
if not done:
|
||||
print(f" FAILED: {item['name']}")
|
||||
|
||||
return saved
|
||||
|
||||
def concat(clips):
|
||||
if len(clips) < 2:
|
||||
print("Not enough clips to concatenate.")
|
||||
return
|
||||
list_file = os.path.join(OUT_DIR, "concat.txt")
|
||||
with open(list_file, "w") as f:
|
||||
for c in clips:
|
||||
f.write(f"file '{c}'\n")
|
||||
print(f"\nConcatenating {len(clips)} clips into hero-reel.mp4...")
|
||||
result = subprocess.run(
|
||||
["ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", list_file,
|
||||
"-c:v", "libx264", "-crf", "22", "-preset", "fast",
|
||||
"-movflags", "+faststart", REEL_OUT],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
if result.returncode == 0:
|
||||
size_kb = os.path.getsize(REEL_OUT) // 1024
|
||||
print(f" Saved {REEL_OUT} ({size_kb}KB)")
|
||||
else:
|
||||
print(f" ffmpeg error: {result.stderr[-300:]}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
clips = generate()
|
||||
if clips:
|
||||
concat(clips)
|
||||
print(f"\nDone. {len(clips)}/5 clips generated.")
|
||||
if len(clips) == 5:
|
||||
print("Hero reel ready: assets/videos/hero/hero-reel.mp4")
|
||||
@@ -1,151 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Image Gen Pipeline</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { background: #0d0d0d; color: #e0e0e0; font-family: monospace; padding: 32px; }
|
||||
h1 { font-size: 14px; color: #aaa; margin-bottom: 28px; letter-spacing: 1px; text-transform: uppercase; }
|
||||
h2 { font-size: 11px; color: #666; letter-spacing: 1px; text-transform: uppercase; margin-bottom: 16px; }
|
||||
|
||||
.pipeline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
margin-bottom: 40px;
|
||||
flex-wrap: wrap;
|
||||
gap: 0;
|
||||
}
|
||||
.node {
|
||||
background: #1a1a1a;
|
||||
border: 1px solid #333;
|
||||
border-radius: 6px;
|
||||
padding: 14px 18px;
|
||||
min-width: 160px;
|
||||
text-align: center;
|
||||
}
|
||||
.node .label { font-size: 10px; color: #666; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 6px; }
|
||||
.node .name { font-size: 13px; color: #e0e0e0; font-weight: bold; }
|
||||
.node .sub { font-size: 10px; color: #888; margin-top: 4px; }
|
||||
.node.highlight { border-color: #4a9eff; background: #0f1f33; }
|
||||
.node.highlight .name { color: #4a9eff; }
|
||||
.arrow { color: #444; font-size: 20px; padding: 0 8px; }
|
||||
|
||||
.grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 40px; }
|
||||
.card { background: #1a1a1a; border: 1px solid #2a2a2a; border-radius: 6px; padding: 14px; }
|
||||
.card .k { font-size: 10px; color: #555; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 6px; }
|
||||
.card .v { font-size: 13px; color: #ddd; }
|
||||
.card .v.ok { color: #4caf50; }
|
||||
.card .v.warn { color: #ff9800; }
|
||||
|
||||
.progress { margin-bottom: 40px; }
|
||||
.bar-wrap { background: #1a1a1a; border-radius: 4px; height: 24px; margin-bottom: 8px; overflow: hidden; }
|
||||
.bar { height: 100%; background: #4a9eff; border-radius: 4px; display: flex; align-items: center; padding-left: 10px; font-size: 11px; color: #fff; transition: width 0.3s; }
|
||||
.pct { font-size: 11px; color: #666; }
|
||||
|
||||
.log { background: #0a0a0a; border: 1px solid #222; border-radius: 6px; padding: 16px; font-size: 11px; line-height: 1.8; color: #888; }
|
||||
.log .ok { color: #4caf50; }
|
||||
.log .done { color: #aaa; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Lahr Carpet Cleaning — Image Generation Pipeline</h1>
|
||||
|
||||
<h2>Model Stack</h2>
|
||||
<div class="pipeline">
|
||||
<div class="node">
|
||||
<div class="label">Prompt</div>
|
||||
<div class="name">gen-images-flux.py</div>
|
||||
<div class="sub">28 images (16 hero + 12 svc)</div>
|
||||
</div>
|
||||
<div class="arrow">→</div>
|
||||
<div class="node">
|
||||
<div class="label">API</div>
|
||||
<div class="name">ComfyUI</div>
|
||||
<div class="sub">localhost:8188</div>
|
||||
</div>
|
||||
<div class="arrow">→</div>
|
||||
<div class="node highlight">
|
||||
<div class="label">UNet (model)</div>
|
||||
<div class="name">FLUX.1 Schnell</div>
|
||||
<div class="sub">Q8_0 GGUF · 12GB · 12B params</div>
|
||||
</div>
|
||||
<div class="arrow">→</div>
|
||||
<div class="node">
|
||||
<div class="label">Sampler</div>
|
||||
<div class="name">KSampler</div>
|
||||
<div class="sub">4 steps · euler · cfg=1.0</div>
|
||||
</div>
|
||||
<div class="arrow">→</div>
|
||||
<div class="node">
|
||||
<div class="label">Decode</div>
|
||||
<div class="name">FLUX AE</div>
|
||||
<div class="sub">ae.safetensors · 108MB</div>
|
||||
</div>
|
||||
<div class="arrow">→</div>
|
||||
<div class="node">
|
||||
<div class="label">Output</div>
|
||||
<div class="name">JPEG → WebP</div>
|
||||
<div class="sub">1024×576 · q92 → q80</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Text Encoders</h2>
|
||||
<div class="pipeline" style="margin-bottom:40px">
|
||||
<div class="node">
|
||||
<div class="label">CLIP-L</div>
|
||||
<div class="name">clip_l.safetensors</div>
|
||||
<div class="sub">235MB · short prompts</div>
|
||||
</div>
|
||||
<div class="arrow">+</div>
|
||||
<div class="node highlight">
|
||||
<div class="label">T5-XXL fp8</div>
|
||||
<div class="name">t5xxl_fp8_e4m3fn</div>
|
||||
<div class="sub">4.6GB · long prompt understanding</div>
|
||||
</div>
|
||||
<div class="arrow">→</div>
|
||||
<div class="node">
|
||||
<div class="label">Node</div>
|
||||
<div class="name">DualCLIPLoader</div>
|
||||
<div class="sub">type: flux</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Hardware</h2>
|
||||
<div class="grid">
|
||||
<div class="card"><div class="k">GPU</div><div class="v warn">AMD Radeon (2GB VRAM)</div></div>
|
||||
<div class="card"><div class="k">Execution</div><div class="v warn">CPU only (VRAM too small)</div></div>
|
||||
<div class="card"><div class="k">Speed</div><div class="v">~4 min / image</div></div>
|
||||
<div class="card"><div class="k">Total ETA</div><div class="v">~1h50m for 28 images</div></div>
|
||||
</div>
|
||||
|
||||
<h2>Model Files on Disk</h2>
|
||||
<div class="grid">
|
||||
<div class="card"><div class="k">UNet</div><div class="v ok">flux1-schnell-Q8_0.gguf · 12GB</div></div>
|
||||
<div class="card"><div class="k">T5-XXL</div><div class="v ok">t5xxl_fp8_e4m3fn.safetensors · 4.6GB</div></div>
|
||||
<div class="card"><div class="k">CLIP-L</div><div class="v ok">clip_l.safetensors · 235MB</div></div>
|
||||
<div class="card"><div class="k">VAE</div><div class="v ok">ae.safetensors · 108MB (official BFL)</div></div>
|
||||
</div>
|
||||
|
||||
<h2>Generation Progress</h2>
|
||||
<div class="progress">
|
||||
<div class="bar-wrap"><div class="bar" style="width:14%">4 / 28</div></div>
|
||||
<div class="pct">14% — reload page to update</div>
|
||||
</div>
|
||||
|
||||
<h2>Prompt Strategy</h2>
|
||||
<div class="log">
|
||||
<div class="ok">Low-angle perspective (35mm / 24mm lens specified in prompt)</div>
|
||||
<div class="ok">Carpet/floor texture sharp in foreground — subject recedes into bokeh</div>
|
||||
<div class="ok">Shallow depth of field + vanishing point for depth cues</div>
|
||||
<div class="ok">No people, no machines, no equipment</div>
|
||||
<div class="ok">Finger Lakes / upstate NY context for residential scenes</div>
|
||||
<br>
|
||||
<div class="done">Previous model: RealVisXL V5.0 fp16 (SDXL 3.5B) — rejected: flat angles, poor depth</div>
|
||||
<div class="done">Current model: FLUX.1 Schnell (12B transformer) — better spatial understanding</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,117 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Image Review — All 28</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { background: #0d0d0d; color: #ddd; font-family: monospace; padding: 24px; }
|
||||
.header { margin-bottom: 24px; }
|
||||
.header h1 { font-size: 13px; color: #888; text-transform: uppercase; letter-spacing: 1px; }
|
||||
.header .meta { font-size: 11px; color: #444; margin-top: 6px; }
|
||||
.section-label { font-size: 10px; color: #555; text-transform: uppercase; letter-spacing: 2px; margin: 28px 0 12px; border-bottom: 1px solid #1a1a1a; padding-bottom: 8px; }
|
||||
.grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-bottom: 8px; }
|
||||
.card { background: #141414; border: 1px solid #222; border-radius: 6px; overflow: hidden; cursor: pointer; transition: border-color 0.15s; }
|
||||
.card:hover { border-color: #4a9eff; }
|
||||
.card img { width: 100%; height: 160px; object-fit: cover; display: block; }
|
||||
.card .info { padding: 10px; }
|
||||
.card .name { font-size: 11px; color: #aaa; margin-bottom: 6px; font-weight: bold; }
|
||||
.card .prompt { font-size: 10px; color: #555; line-height: 1.6; }
|
||||
.card .tag { display: inline-block; font-size: 9px; background: #1a2a1a; color: #4a9eff; padding: 2px 6px; border-radius: 3px; margin-bottom: 6px; }
|
||||
|
||||
/* lightbox */
|
||||
.lb { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.93); z-index: 100; padding: 32px; }
|
||||
.lb.open { display: flex; gap: 32px; align-items: flex-start; }
|
||||
.lb img { max-height: 80vh; max-width: 60vw; object-fit: contain; border-radius: 6px; flex-shrink: 0; }
|
||||
.lb .lb-info { flex: 1; }
|
||||
.lb .lb-name { font-size: 13px; color: #eee; margin-bottom: 12px; }
|
||||
.lb .lb-model { font-size: 10px; color: #4a9eff; margin-bottom: 16px; }
|
||||
.lb .lb-prompt { font-size: 12px; color: #aaa; line-height: 1.8; }
|
||||
.lb .lb-close { position: absolute; top: 16px; right: 24px; font-size: 24px; color: #555; cursor: pointer; }
|
||||
.lb .lb-close:hover { color: #eee; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="header">
|
||||
<h1>Lahr Carpet Cleaning — Image Review</h1>
|
||||
<div class="meta">Model: FLUX.1 Schnell Q8_0 GGUF · 4 steps, cfg=1.0, euler/simple · 1024×576 → WebP · 28 images total</div>
|
||||
</div>
|
||||
|
||||
<div class="section-label">Hero Images (16)</div>
|
||||
<div class="grid" id="heroes"></div>
|
||||
|
||||
<div class="section-label">Service Cards (12)</div>
|
||||
<div class="grid" id="services"></div>
|
||||
|
||||
<div class="lb" id="lb" onclick="closeLB()">
|
||||
<img id="lb-img" src="">
|
||||
<div class="lb-info">
|
||||
<div class="lb-name" id="lb-name"></div>
|
||||
<div class="lb-model">FLUX.1 Schnell · Q8_0 GGUF · 4 steps · euler · cfg=1.0</div>
|
||||
<div class="lb-prompt" id="lb-prompt"></div>
|
||||
</div>
|
||||
<div class="lb-close">✕</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const HEROES = [
|
||||
{ file: "hero-carpet-cleaning.webp", prompt: "low-angle 35mm lens perspective looking across thick plush cream carpet in an upstate New York living room, carpet fibers razor sharp in foreground, couch and coffee table receding into shallow bokeh background, warm afternoon window light raking across carpet texture, Finger Lakes farmhouse interior, no people, ultra-realistic architectural photography, 16:9" },
|
||||
{ file: "hero-stairs.webp", prompt: "dramatic low 35mm angle looking up a clean carpeted staircase from floor level, light grey carpet runner sharp and textured in foreground steps, wood banister receding diagonally, bright daylight flooding from above, shallow depth of field, no people, ultra-realistic interior photography, 16:9" },
|
||||
{ file: "hero-upholstery.webp", prompt: "50mm lens low corner angle across a bright residential living room, plush linen fabric sofa arm sharp in near foreground, clean armchair and window receding with bokeh, afternoon countryside light through window, shallow depth of field, no people, ultra-realistic interior photography, 16:9" },
|
||||
{ file: "hero-floors.webp", prompt: "low 24mm angle pressed to gleaming light oak hardwood floor, floor grain razor sharp in extreme foreground receding to hallway vanishing point, white walls, natural light streaming in, shallow depth of field, no people, ultra-realistic interior photography, 16:9" },
|
||||
{ file: "hero-area-rugs.webp", prompt: "low 35mm angle looking across a hand-knotted oriental rug from floor level, rich red and gold rug fibers sharp in foreground, hardwood floor and room receding into bokeh, cozy farmhouse living room, warm natural light, shallow depth of field, no people, ultra-realistic interior photography, 16:9" },
|
||||
{ file: "hero-add-ons.webp", prompt: "low 35mm angle across a clean beige bedroom carpet, carpet pile sharp and detailed in near foreground, wooden bed frame and sheer curtained window receding, crisp morning light, shallow depth of field, no people, no machines, ultra-realistic interior photography, 16:9" },
|
||||
{ file: "hero-commercial.webp", prompt: "low 24mm wide-angle lens across a modern corporate lobby floor, dark charcoal commercial carpet sharp in extreme foreground receding to glass entrance doors, recessed ceiling lights creating depth, strong vanishing point perspective, no people, ultra-realistic architectural photography, 16:9" },
|
||||
{ file: "hero-offices.webp", prompt: "low 24mm angle across clean grey carpet tiles in a modern open-plan office, carpet tile seams sharp in foreground receding to rows of empty desks and glass partitions, professional overhead lighting, strong linear perspective, no people, ultra-realistic architectural photography, 16:9" },
|
||||
{ file: "hero-vacation-rentals.webp", prompt: "low 35mm angle across clean beige carpet in a Finger Lakes cottage living room, carpet fibers sharp in foreground, stone fireplace and lake-view window receding with bokeh, wooden ceiling beams, warm inviting light, shallow depth of field, no people, ultra-realistic interior photography, 16:9" },
|
||||
{ file: "hero-hotels.webp", prompt: "low 24mm lens looking down a long hotel corridor from floor level, patterned burgundy carpet runner sharp in extreme foreground receding to vanishing point, warm wall sconces lining white walls, numbered doors converging in perspective, no people, ultra-realistic hospitality photography, 16:9" },
|
||||
{ file: "hero-retail.webp", prompt: "low 35mm diagonal angle across clean light grey carpet in an upscale retail showroom, carpet surface sharp in foreground, minimalist display fixtures and storefront windows receding with bokeh, bright track lighting overhead, shallow depth of field, no people, ultra-realistic architectural photography, 16:9" },
|
||||
{ file: "hero-property-management.webp", prompt: "low 35mm angle across fresh neutral carpet in an empty move-in ready apartment, carpet texture sharp in foreground, bare white walls and bright windows receding, clean real estate photography perspective, shallow depth of field, no people, ultra-realistic real estate photography, 16:9" },
|
||||
{ file: "hero-about.webp", prompt: "low 35mm angle from lawn level looking up at a classic upstate New York suburban home, green grass blades sharp in extreme foreground, inviting house facade receding upward, mature trees and clear blue sky, warm summer afternoon, no people, ultra-realistic real estate photography, 16:9" },
|
||||
{ file: "hero-service-area.webp", prompt: "low horizon 24mm wide-angle Finger Lakes landscape, green vineyard vines sharp in foreground receding to rolling hills and calm lake, golden hour light casting long shadows, strong depth and distance, no people, ultra-realistic landscape photography, 16:9" },
|
||||
{ file: "hero-living-room.webp", prompt: "low 35mm corner angle across a spacious residential living room, plush light grey carpet sharp and textured in foreground, large sectional sofa and bay windows receding with bokeh, warm afternoon sunlight, shallow depth of field, no people, ultra-realistic interior photography, 16:9" },
|
||||
{ file: "hero-clean-result.webp", prompt: "extreme low 50mm macro angle pressed to immaculate freshly cleaned residential carpet, individual carpet fibers razor sharp in foreground, pile receding into soft bokeh, raking natural light revealing deep clean texture and uniform pile height, no people, ultra-realistic macro carpet photography, 16:9" },
|
||||
];
|
||||
|
||||
const SERVICES = [
|
||||
{ file: "carpet-cleaning.webp", prompt: "low 35mm angle looking across plush clean beige carpet in a residential living room, carpet fibers sharp in foreground, couch and window receding into bokeh, warm afternoon light, shallow depth of field, no people, ultra-realistic interior photography" },
|
||||
{ file: "stairs-cleaning.webp", prompt: "low 35mm angle looking up clean grey carpeted stairs from bottom step, carpet texture sharp on nearest step, stairs receding diagonally upward, wood banister, bright light from above, no people, ultra-realistic interior photography" },
|
||||
{ file: "upholstery-cleaning.webp", prompt: "low 50mm angle across a clean plush linen fabric sofa arm, fabric weave sharp in foreground, living room receding with bokeh, warm light, shallow depth of field, no people, ultra-realistic interior photography" },
|
||||
{ file: "floor-cleaning.webp", prompt: "low 24mm angle pressed to gleaming light oak hardwood floor, wood grain razor sharp in extreme foreground receding down hallway, natural light, no people, ultra-realistic interior photography" },
|
||||
{ file: "area-rug-cleaning.webp", prompt: "low 35mm angle across a vibrant clean oriental rug from floor level, rug fibers and pattern sharp in foreground, hardwood floor and room receding, warm light, shallow depth of field, no people, ultra-realistic interior photography" },
|
||||
{ file: "add-ons.webp", prompt: "low 35mm angle across clean beige bedroom carpet, carpet pile sharp in foreground, bed frame and curtained window receding with bokeh, morning light, no people, ultra-realistic interior photography" },
|
||||
{ file: "commercial-overview.webp", prompt: "low 24mm angle across dark commercial carpet in a corporate lobby, carpet surface sharp in foreground receding to glass entrance, strong vanishing point, no people, ultra-realistic architectural photography" },
|
||||
{ file: "vacation-rentals.webp", prompt: "low 35mm angle across clean carpet in a Finger Lakes cottage living room, carpet sharp in foreground, stone fireplace and window receding with bokeh, rustic warm decor, no people, ultra-realistic interior photography" },
|
||||
{ file: "office-spaces.webp", prompt: "low 24mm angle across grey carpet tiles in a modern open office, tile seams sharp in foreground, empty desks receding with linear perspective, professional lighting, no people, ultra-realistic architectural photography" },
|
||||
{ file: "hotels-inns.webp", prompt: "low 24mm angle down a hotel corridor, patterned carpet runner sharp in foreground, corridor receding to vanishing point, warm wall sconces, no people, ultra-realistic hospitality photography" },
|
||||
{ file: "retail-showrooms.webp", prompt: "low 35mm diagonal angle across light grey carpet in an upscale retail showroom, carpet sharp in foreground, display fixtures and track lighting receding with bokeh, no people, ultra-realistic architectural photography" },
|
||||
{ file: "property-management.webp", prompt: "low 35mm angle across fresh neutral carpet in an empty clean apartment, carpet texture sharp in foreground, white walls and windows receding, no people, ultra-realistic real estate photography" },
|
||||
];
|
||||
|
||||
function openLB(src, name, prompt) {
|
||||
document.getElementById('lb-img').src = src;
|
||||
document.getElementById('lb-name').textContent = name;
|
||||
document.getElementById('lb-prompt').textContent = prompt;
|
||||
document.getElementById('lb').classList.add('open');
|
||||
}
|
||||
function closeLB() { document.getElementById('lb').classList.remove('open'); }
|
||||
|
||||
function buildCards(items, containerId, basePath) {
|
||||
const el = document.getElementById(containerId);
|
||||
items.forEach(item => {
|
||||
const src = basePath + item.file;
|
||||
const name = item.file.replace('.webp','');
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card';
|
||||
card.innerHTML = `<img src="${src}" loading="lazy"><div class="info"><div class="tag">FLUX.1 Schnell</div><div class="name">${name}</div><div class="prompt">${item.prompt.substring(0,120)}…</div></div>`;
|
||||
card.onclick = () => openLB(src, name, item.prompt);
|
||||
el.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
buildCards(HEROES, 'heroes', '../assets/images/hero/');
|
||||
buildCards(SERVICES, 'services', '../assets/images/services/');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,35 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Hero Image Review</title>
|
||||
<style>
|
||||
body { background: #111; color: #eee; font-family: sans-serif; padding: 20px; }
|
||||
h1 { font-size: 16px; margin-bottom: 20px; }
|
||||
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
|
||||
.item { }
|
||||
.item img { width: 100%; height: 200px; object-fit: cover; border-radius: 4px; }
|
||||
.item p { font-size: 12px; margin: 4px 0; color: #aaa; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hero Images — RealVisXL V5.0 (15 of 16)</h1>
|
||||
<div class="grid">
|
||||
<div class="item"><img src="../assets/images/hero/hero-carpet-cleaning.jpg"><p>hero-carpet-cleaning</p></div>
|
||||
<div class="item"><img src="../assets/images/hero/hero-upholstery.jpg"><p>hero-upholstery</p></div>
|
||||
<div class="item"><img src="../assets/images/hero/hero-floors.jpg"><p>hero-floors</p></div>
|
||||
<div class="item"><img src="../assets/images/hero/hero-area-rugs.jpg"><p>hero-area-rugs</p></div>
|
||||
<div class="item"><img src="../assets/images/hero/hero-add-ons.jpg"><p>hero-add-ons</p></div>
|
||||
<div class="item"><img src="../assets/images/hero/hero-commercial.jpg"><p>hero-commercial</p></div>
|
||||
<div class="item"><img src="../assets/images/hero/hero-offices.jpg"><p>hero-offices</p></div>
|
||||
<div class="item"><img src="../assets/images/hero/hero-vacation-rentals.jpg"><p>hero-vacation-rentals</p></div>
|
||||
<div class="item"><img src="../assets/images/hero/hero-hotels.jpg"><p>hero-hotels</p></div>
|
||||
<div class="item"><img src="../assets/images/hero/hero-retail.jpg"><p>hero-retail</p></div>
|
||||
<div class="item"><img src="../assets/images/hero/hero-property-management.jpg"><p>hero-property-management</p></div>
|
||||
<div class="item"><img src="../assets/images/hero/hero-about.jpg"><p>hero-about</p></div>
|
||||
<div class="item"><img src="../assets/images/hero/hero-service-area.jpg"><p>hero-service-area</p></div>
|
||||
<div class="item"><img src="../assets/images/hero/hero-living-room.jpg"><p>hero-living-room</p></div>
|
||||
<div class="item"><img src="../assets/images/hero/hero-clean-result.jpg"><p>hero-clean-result</p></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,125 +0,0 @@
|
||||
"""Single test clip — corrected WanImageToVideo workflow."""
|
||||
import json, time, urllib.request, os, random
|
||||
|
||||
COMFY = "http://localhost:8188"
|
||||
IMAGE_PATH = "assets/images/hero/hero-carpet-cleaning.webp"
|
||||
OUT_DIR = "assets/videos/clips"
|
||||
os.makedirs(OUT_DIR, exist_ok=True)
|
||||
|
||||
|
||||
def upload_image(image_path):
|
||||
fname = os.path.basename(image_path)
|
||||
with open(image_path, "rb") as f:
|
||||
img_data = f.read()
|
||||
boundary = "----FormBoundary123456"
|
||||
body = (
|
||||
f"--{boundary}\r\n"
|
||||
f'Content-Disposition: form-data; name="image"; filename="{fname}"\r\n'
|
||||
f"Content-Type: image/webp\r\n\r\n"
|
||||
).encode() + img_data + f"\r\n--{boundary}--\r\n".encode()
|
||||
req = urllib.request.Request(
|
||||
f"{COMFY}/upload/image", data=body,
|
||||
headers={"Content-Type": f"multipart/form-data; boundary={boundary}"},
|
||||
)
|
||||
with urllib.request.urlopen(req) as resp:
|
||||
result = json.loads(resp.read())
|
||||
print(f" uploaded: {result['name']}")
|
||||
return result["name"]
|
||||
|
||||
|
||||
def build_workflow(image_name, prompt, frames=25):
|
||||
# WanImageToVideo is a conditioning node, NOT a sampler.
|
||||
# outputs: [0]=positive CONDITIONING, [1]=negative CONDITIONING, [2]=latent LATENT
|
||||
# start_image is optional IMAGE — anchors first frame.
|
||||
return {
|
||||
"1": {"class_type": "UnetLoaderGGUF", "inputs": {"unet_name": "Wan2.2-TI2V-5B-Q4_K_M.gguf"}},
|
||||
"2": {"class_type": "CLIPLoader", "inputs": {"clip_name": "umt5_xxl_fp8_e4m3fn_scaled.safetensors", "type": "wan"}},
|
||||
"3": {"class_type": "VAELoader", "inputs": {"vae_name": "wan_2.1_vae.safetensors"}},
|
||||
"4": {"class_type": "LoadImage", "inputs": {"image": image_name}},
|
||||
"5": {"class_type": "CLIPTextEncode", "inputs": {"clip": ["2", 0], "text": prompt}},
|
||||
"6": {"class_type": "CLIPTextEncode", "inputs": {"clip": ["2", 0], "text": "blur, low quality, distortion, text, watermark, people, jitter"}},
|
||||
"7": {
|
||||
"class_type": "WanImageToVideo",
|
||||
"inputs": {
|
||||
"positive": ["5", 0],
|
||||
"negative": ["6", 0],
|
||||
"vae": ["3", 0],
|
||||
"start_image": ["4", 0],
|
||||
"width": 832, "height": 480, "length": frames, "batch_size": 1,
|
||||
},
|
||||
},
|
||||
"8": {
|
||||
"class_type": "KSampler",
|
||||
"inputs": {
|
||||
"model": ["1", 0],
|
||||
"positive": ["7", 0],
|
||||
"negative": ["7", 1],
|
||||
"latent_image": ["7", 2],
|
||||
"seed": 42, "steps": 20, "cfg": 6.0,
|
||||
"sampler_name": "uni_pc", "scheduler": "simple", "denoise": 1.0,
|
||||
},
|
||||
},
|
||||
# VAEDecodeTiled handles video (5D) latents — VAEDecode only handles images (4D)
|
||||
"9": {"class_type": "VAEDecodeTiled", "inputs": {"samples": ["8", 0], "vae": ["3", 0], "tile_size": 512, "overlap": 64, "temporal_size": 64, "temporal_overlap": 8}},
|
||||
"10": {
|
||||
"class_type": "SaveAnimatedWEBP",
|
||||
"inputs": {"images": ["9", 0], "filename_prefix": "wan_test", "fps": 12, "lossless": False, "quality": 85, "method": "default"},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
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=1800):
|
||||
start = time.time()
|
||||
while time.time() - start < timeout:
|
||||
with urllib.request.urlopen(f"{COMFY}/history/{prompt_id}") as resp:
|
||||
hist = json.loads(resp.read())
|
||||
if prompt_id in hist:
|
||||
entry = hist[prompt_id]
|
||||
if entry.get("status", {}).get("status_str") == "error":
|
||||
print(f" ERROR: {entry['status'].get('messages', '')}")
|
||||
return None
|
||||
for node_out in entry.get("outputs", {}).values():
|
||||
if "gifs" in node_out:
|
||||
return node_out["gifs"]
|
||||
if "images" in node_out:
|
||||
return node_out["images"]
|
||||
elapsed = int(time.time() - start)
|
||||
print(f" waiting... {elapsed}s", flush=True)
|
||||
time.sleep(15)
|
||||
return None
|
||||
|
||||
|
||||
def download_output(vid_info, out_path):
|
||||
fname = vid_info["filename"]
|
||||
subfolder = vid_info.get("subfolder", "")
|
||||
img_type = vid_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()
|
||||
with open(out_path, "wb") as f:
|
||||
f.write(data)
|
||||
print(f" saved: {out_path} ({len(data)//1024}KB)")
|
||||
|
||||
|
||||
print("[TEST v2] WanImageToVideo → KSampler → VAEDecode → SaveAnimatedWEBP")
|
||||
image_name = upload_image(IMAGE_PATH)
|
||||
workflow = build_workflow(
|
||||
image_name,
|
||||
"slow dolly forward across clean plush cream carpet, gentle camera push toward the far wall, warm afternoon light, cinematic smooth motion",
|
||||
frames=9,
|
||||
)
|
||||
prompt_id = queue_prompt(workflow)
|
||||
print(f" queued: {prompt_id}")
|
||||
results = wait_for_result(prompt_id)
|
||||
if results:
|
||||
download_output(results[0], f"{OUT_DIR}/test-clip-01.webp")
|
||||
print("SUCCESS")
|
||||
else:
|
||||
print("FAILED")
|
||||