[object Object]

← back to Prestige Car Wash

Guides: evergreen car-care articles (/guides + 3 posts) — BUILT, DEPLOY HELD

2cec35342880408f89a97d09ba6b0cf60335a9ed · 2026-07-26 21:02:04 -0700 · Steve Abrams

DTD A (3/2). 3 honest server-rendered guides w/ BlogPosting+BreadcrumbList schema,
internal links to services/booking, sitemap + homepage-footer link. Head-injection
refactored into shared decorate(). Locally verified (200s, schema, 404, sitemap, no
fabricated stats). DEPLOY HELD: contrarian NO-GO — site-wide canonical points at
prestigehandcarwash.com which serves a SEPARATE GoDaddy site, so shipping SEO pages
hands authority to GoDaddy. Domain-identity/DNS decision surfaced to Steve (gated).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 2cec35342880408f89a97d09ba6b0cf60335a9ed
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sun Jul 26 21:02:04 2026 -0700

    Guides: evergreen car-care articles (/guides + 3 posts) — BUILT, DEPLOY HELD
    
    DTD A (3/2). 3 honest server-rendered guides w/ BlogPosting+BreadcrumbList schema,
    internal links to services/booking, sitemap + homepage-footer link. Head-injection
    refactored into shared decorate(). Locally verified (200s, schema, 404, sitemap, no
    fabricated stats). DEPLOY HELD: contrarian NO-GO — site-wide canonical points at
    prestigehandcarwash.com which serves a SEPARATE GoDaddy site, so shipping SEO pages
    hands authority to GoDaddy. Domain-identity/DNS decision surfaced to Steve (gated).
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 public/index.html |   2 +-
 server.js         | 159 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 157 insertions(+), 4 deletions(-)

diff --git a/public/index.html b/public/index.html
index e997d86..6c034bd 100644
--- a/public/index.html
+++ b/public/index.html
@@ -119,7 +119,7 @@ fetch('/api/wait').then(r=>r.json()).then(w=>{const el=document.getElementById('
 </div></section>
 
 <footer class="foot"><div class="wrap">
-  © 2026 Prestige Car Wash · San Fernando Valley, CA · <a href="/services">Services</a> · <a href="/contact">Book</a>
+  © 2026 Prestige Car Wash · San Fernando Valley, CA · <a href="/services">Services</a> · <a href="/guides">Guides</a> · <a href="/contact">Book</a>
   <div style="margin-top:6px;opacity:.7">Instagram @prestigecwash</div>
 </div></footer>
 
diff --git a/server.js b/server.js
index f0311c6..068b93c 100644
--- a/server.js
+++ b/server.js
@@ -546,6 +546,123 @@ function faqHtml() {
       <div style="padding:0 18px 16px;color:var(--mut);font-size:15px;line-height:1.55">${esc(f.a)}</div>
     </details>`).join('');
 }
+// ── Evergreen car-care guides (DTD iteration 5) ────────────────────────────────────────
+// Genuinely useful, HONEST top-of-funnel content: established detailing best-practice
+// guidance phrased as advice (no fabricated statistics, no invented studies). Rendered
+// server-side so it's fully crawlable, with BlogPosting + BreadcrumbList JSON-LD and
+// internal links into services/booking. GUIDE_DATE is the real publish date.
+const GUIDE_DATE = '2026-07-26';
+function guides() {
+  return [
+    {
+      slug: 'how-often-to-wash-a-ceramic-coated-car',
+      title: 'How Often Should You Wash a Ceramic-Coated Car?',
+      description: 'A ceramic coating changes the maintenance math. Here is a sensible wash rhythm that protects the coating without over-washing.',
+      dek: 'A coating is slick and sacrificial — but it still needs the right care to keep beading and looking its best.',
+      sections: [
+        { h: 'The short answer', p: 'For a daily-driven coated car, every two weeks is a sensible default. Stretch it if the car lives in a garage; tighten it after rain, road grime, or bird droppings — droppings are acidic and can etch even a coated surface if left to bake in the sun.' },
+        { h: 'Why gentle washing matters more, not less', p: 'A coating makes dirt easier to remove, but automatic tunnel brushes still drag grit across the surface and dull the gloss over time. A gentle two-bucket hand wash with a pH-neutral soap preserves the coating’s hydrophobic behavior far longer than a tunnel ever will.' },
+        { h: 'What to avoid', p: 'Skip strong degreasers and high-alkaline wheel chemicals on coated paint — they strip the coating’s top layer. And never let water spot-dry in direct sun; the dissolved minerals bond to the coating and are a pain to remove.' },
+        { h: 'When to top it up', p: 'If water stops beading and starts sheeting flat, the coating is asking for help. A spray-on SiO2 booster applied after a wash restores hydrophobicity between professional maintenance visits.' }
+      ]
+    },
+    {
+      slug: 'hand-wash-vs-automatic-tunnel',
+      title: 'Hand Wash vs. Automatic Tunnel: What Is Actually Safe for Your Paint',
+      description: 'Automatic tunnels are fast and cheap — but here is what they do to your clear coat, and why hand washing is gentler.',
+      dek: 'The difference comes down to one thing: grit, and whether it gets dragged across your paint.',
+      sections: [
+        { h: 'Where tunnel washes go wrong', p: 'Tunnels recirculate water and run stiff brushes or cloth strips that have already touched every car before yours. The grit they carry is exactly what puts fine swirl marks and hairline scratches into a clear coat.' },
+        { h: 'Why two-bucket hand washing is gentler', p: 'Two buckets — one of clean soapy water, one to rinse the mitt — let grit drop out of circulation instead of going back onto the paint. A foam cannon lifts and floats dirt off the surface before the mitt ever touches it.' },
+        { h: 'The finishes that cannot take a tunnel', p: 'Ceramic coatings, matte and satin paint, and paint protection film (PPF) can all be dulled, hazed, or lifted at the edges by automated equipment. For any of these, hand washing is the safe default.' },
+        { h: 'The honest trade-off', p: 'Tunnels win on speed and price per wash. If your car is a daily beater, that may be all you need. If you care about the finish — or you have invested in a coating, a wrap, or a fresh repaint — hand washing pays for itself in preserved gloss and resale value.' }
+      ]
+    },
+    {
+      slug: 'protecting-matte-and-ppf-finishes',
+      title: 'Protecting Matte and PPF Finishes: A Care Guide',
+      description: 'Matte paint and paint protection film need different care than glossy clear coat. Here is how to keep them looking right.',
+      dek: 'The wrong product on a matte finish is a mistake you cannot polish out — so it pays to know the rules.',
+      sections: [
+        { h: 'Matte is a different animal', p: 'Matte and satin clear coats get their look from a micro-textured surface that scatters light. Anything that fills or polishes that texture — wax, sealant, polish, or a buffer — creates permanent shiny spots that can only be corrected with a refinish.' },
+        { h: 'How to wash matte safely', p: 'Hand wash only, with a matte-specific (wax-free) pH-neutral shampoo, a soft mitt, and straight-line motions rather than circles. Dry with a clean microfiber or a filtered-air blower instead of dragging a towel across the texture.' },
+        { h: 'Caring for PPF', p: 'Paint protection film is tough, but its edges can trap dirt and lift if scrubbed hard. Keep the edges clean, avoid blasting high-pressure water straight at the seams, and skip cutting compounds. Many modern films are self-healing — light swirls vanish in the sun or after a warm rinse.' },
+        { h: 'What we do', p: 'Our hand wash is matte- and PPF-safe by default: no automatic brushes, never any wax on matte, and a fragrance-free option on request. Tell us what your car is wearing and we will treat it accordingly.' }
+      ]
+    }
+  ];
+}
+function _e(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); }
+function guideLayout(title, description, bodyHtml) {
+  return `<!doctype html><html lang="en"><head>
+<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>${_e(title)}</title>
+<meta name="description" content="${_e(description)}">
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800;900&display=swap" rel="stylesheet">
+<link rel="stylesheet" href="/assets/pcw.css">
+</head><body>
+<nav class="nav"><div class="wrap row">
+  <div class="brand"><span class="mk">🚗</span><div>Prestige<br><small>Car Wash · SFV</small></div></div>
+  <button class="hamb" onclick="document.getElementById('lnk').classList.toggle('open')">☰</button>
+  <div class="links" id="lnk"><a href="/">Home</a><a href="/services">Services</a><a href="/guides" class="on">Guides</a><a class="btn" href="/contact">Book a Wash</a></div>
+</div></nav>
+${bodyHtml}
+<footer class="foot"><div class="wrap">© 2026 Prestige Car Wash · San Fernando Valley, CA · <a href="/services">Services</a> · <a href="/guides">Guides</a> · <a href="/contact">Book</a></div></footer>
+</body></html>`;
+}
+function guideCtaHtml() {
+  return `<div class="card" style="max-width:760px;margin:26px auto 0"><div class="body" style="text-align:center">
+    <div class="ttl" style="font-size:20px">Want it done right, by hand?</div>
+    <div class="blurb">We hand wash every car — coating-, matte-, and PPF-safe — with no upsell pressure.</div>
+    <div style="margin-top:12px;display:flex;gap:10px;justify-content:center;flex-wrap:wrap">
+      <a class="btn" href="/contact">Book a Wash</a><a class="btn ghost" href="/services">See Services &amp; Pricing</a>
+    </div>
+  </div></div>`;
+}
+function guideArticleHtml(g) {
+  const secs = g.sections.map(s => `<h2>${_e(s.h)}</h2>\n<p>${_e(s.p)}</p>`).join('\n');
+  return `<article class="section"><div class="wrap" style="max-width:760px">
+    <div style="margin-bottom:10px"><a href="/guides" style="color:var(--mut);font-size:13px">← Car-care guides</a></div>
+    <span class="pill">Car-care guide</span>
+    <h1>${_e(g.title)}</h1>
+    <p class="lead">${_e(g.dek)}</p>
+    ${secs}
+    ${guideCtaHtml()}
+  </div></article>`;
+}
+function guideIndexHtml() {
+  const cards = guides().map(g => `<a class="card" href="/guides/${_e(g.slug)}" style="text-decoration:none;color:inherit"><div class="body">
+      <div class="cat">Car-care guide</div>
+      <div class="ttl">${_e(g.title)}</div>
+      <div class="blurb">${_e(g.description)}</div>
+      <div style="margin-top:8px;color:var(--brand);font-weight:700;font-size:14px">Read →</div>
+    </div></a>`).join('');
+  return `<section class="section"><div class="wrap">
+    <span class="pill">Guides</span>
+    <h1>Car-care guides</h1>
+    <p class="sub">Straight, no-nonsense advice on keeping your car’s finish looking its best — from the crew that hand washes them.</p>
+    <div class="grid" style="--cols:3">${cards}</div>
+  </div></section>`;
+}
+function buildArticleLd(g, canonicalUrl) {
+  const name = (places() || {}).name || 'Prestige Car Wash';
+  const ld = {
+    '@context': 'https://schema.org', '@type': 'BlogPosting',
+    headline: g.title, description: g.description,
+    datePublished: GUIDE_DATE, dateModified: GUIDE_DATE,
+    author: { '@type': 'Organization', name }, publisher: { '@type': 'Organization', name },
+    articleSection: 'Car care'
+  };
+  if (canonicalUrl) ld.mainEntityOfPage = canonicalUrl;
+  return ld;
+}
+function buildBreadcrumbLd(items) {
+  return {
+    '@context': 'https://schema.org', '@type': 'BreadcrumbList',
+    itemListElement: items.map((it, i) => ({ '@type': 'ListItem', position: i + 1, name: it.name, item: it.url }))
+  };
+}
 // Open Graph / Twitter tags for the homepage — reuses the page's own <title> and meta
 // description so social previews stay in sync with on-page SEO. og:image is a real asset.
 function buildSocialTags(html, pagePath) {
@@ -586,8 +703,11 @@ function gaSnippet() {
   return `\n<script async src="https://www.googletagmanager.com/gtag/js?id=${id}"></script>` +
          `\n<script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag('js',new Date());gtag('config','${id}');</script>`;
 }
-function pageHtml(file, pagePath, ld) {
-  let html = fs.readFileSync(path.join(__dirname, 'public', file), 'utf8');
+// Shared page decorator: takes a full HTML string and injects the FAQ/trust placeholders,
+// the mobile bar, OG/canonical tags, JSON-LD, and the GA snippet. Used by BOTH file-backed
+// pages (pageHtml) and server-generated pages (the /guides articles), so head/injection logic
+// lives in exactly one place.
+function decorate(html, pagePath, ld) {
   // Fill the FAQ accordion server-side so the visible content is in the crawler-received
   // HTML and matches the FAQPage JSON-LD. Only touches a page that carries the placeholder.
   if (html.includes('id="faqList"')) {
@@ -612,6 +732,9 @@ function pageHtml(file, pagePath, ld) {
   inject += gaSnippet();
   return html.replace('</head>', inject + '\n</head>');
 }
+function pageHtml(file, pagePath, ld) {
+  return decorate(fs.readFileSync(path.join(__dirname, 'public', file), 'utf8'), pagePath, ld);
+}
 const sendPage = (res, file, pagePath, ld) => {
   // If tag INJECTION throws, still serve the raw page (graceful degradation). If the file
   // itself is unreadable, sendFile's callback guarantees a clean 500 — never a hung response.
@@ -625,12 +748,42 @@ app.get('/api/faqs', (req, res) => res.json(faqs()));
 app.get('/', (req, res) => sendPage(res, 'index.html', '', [buildJsonLd(), buildFaqLd()]));
 app.get('/services', (req, res) => sendPage(res, 'services.html', '/services', null));
 app.get('/contact', (req, res) => sendPage(res, 'contact.html', '/contact', null));
+// Car-care guides — server-generated (shares the decorate() pipeline: OG/canonical/GA/mobile bar).
+app.get('/guides', (req, res) => {
+  try {
+    const html = guideLayout('Car-Care Guides — Prestige Car Wash',
+      'Honest, practical car-care guides from a San Fernando Valley hand-wash & detail shop: ceramic-coating care, hand wash vs. tunnel, and matte & PPF protection.',
+      guideIndexHtml());
+    res.type('html').send(decorate(html, '/guides', null));
+  } catch { res.status(500).end(); }
+});
+app.get('/guides/:slug', (req, res) => {
+  const g = guides().find(x => x.slug === req.params.slug);
+  const base = (((places() || {}).website) || '').replace(/\/$/, '');
+  if (!g) {
+    const nf = guideLayout('Guide Not Found — Prestige Car Wash', 'That guide could not be found.',
+      `<section class="section"><div class="wrap"><h1>Guide not found</h1><p class="sub">That guide may have moved. <a href="/guides">Browse all car-care guides →</a></p></div></section>`);
+    return res.status(404).type('html').send(decorate(nf, '/guides', null));
+  }
+  const canonical = base ? base + '/guides/' + g.slug : '';
+  const ld = [
+    buildArticleLd(g, canonical),
+    buildBreadcrumbLd([
+      { name: 'Home', url: base || '/' },
+      { name: 'Guides', url: (base || '') + '/guides' },
+      { name: g.title, url: canonical || ('/guides/' + g.slug) }
+    ])
+  ];
+  try {
+    res.type('html').send(decorate(guideLayout(g.title + ' — Prestige Car Wash', g.description, guideArticleHtml(g)), '/guides/' + g.slug, ld));
+  } catch { res.status(500).end(); }
+});
 
 // XML sitemap generated from the public URL — kept in one place, no static file to drift.
 app.get('/sitemap.xml', (req, res) => {
   const base = ((places() || {}).website || '').replace(/\/$/, '');
   if (!base) return res.status(404).end();
-  const urls = ['/', '/services', '/contact'];
+  const urls = ['/', '/services', '/contact', '/guides', ...guides().map(g => '/guides/' + g.slug)];
   const xml = '<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n' +
     urls.map(u => `  <url><loc>${base}${u}</loc></url>`).join('\n') + '\n</urlset>';
   res.type('application/xml').send(xml);

← d13f501 Discovery: honest 'find us & review' trust block (real off-s  ·  back to Prestige Car Wash  ·  Path B: self-canonicalize to prestige.agentabrams.com (stop 61afb84 →