Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
a78764d
Add scrape workflow for live site — runs from GitHub Actions (reachab…
bgillis99-pixel Apr 19, 2026
83830e0
Add NorCal CARB Mobile marketing site skeleton (home page + infra)
bgillis99-pixel Apr 20, 2026
2a44165
Update pricing: OBD $75, OVI $250, RV shows both line items
bgillis99-pixel Apr 20, 2026
465f37e
Rename OVI anchor to #OVI-J1667 and surface SAE J1667 in copy
bgillis99-pixel Apr 21, 2026
44eb2e3
Add CITATIONS.md and BLOG_PLAN.md — sources for every design choice +…
bgillis99-pixel Apr 21, 2026
b841b28
Add MIGRATION_ADDENDUM.md — reconciles with Manus checklist + fills g…
bgillis99-pixel May 6, 2026
1b9ac72
Add about + services pages, update home with Person+hasCredential sch…
bgillis99-pixel May 6, 2026
4c31672
Add service-areas + FAQ + contact + privacy + terms + 404 + extended CSS
bgillis99-pixel May 6, 2026
38de122
Add SEO/config: sitemap, llms.txt, llms-full.txt, _redirects, _header…
bgillis99-pixel May 6, 2026
099eb33
Add blog hub + template + 5 pillar posts (Cluster A: What-is foundation)
bgillis99-pixel May 6, 2026
58306c1
Add 5 more blog posts (Cluster C: deadlines + penalties, plus prep, m…
bgillis99-pixel May 6, 2026
b8faee2
Add CUTOVER.md — dead-simple step-by-step for Bryan
bgillis99-pixel May 6, 2026
4c7ab4e
Switch theme to Manus charcoal + safety lime + Inter (per Bryan)
bgillis99-pixel May 6, 2026
8e526b4
Park dmc-properties in future-projects/ — preserve the idea, stop CI …
bgillis99-pixel May 9, 2026
bf3db5e
Remove workers/dmc-properties/rent-roll.html — parked in future-proje…
bgillis99-pixel May 9, 2026
77c8a05
Drop dmc-properties memo — not needed, can rebuild from scratch if re…
bgillis99-pixel May 15, 2026
eeaa7e5
Resolve PR merge conflicts with main
Copilot Jun 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions workers/norcalcarbmobile/CUTOVER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Cutover — the absolute simplest version

Do these in order. Do **not** skip step 2 (it's the only one that can break email).

---

## Step 1 — Merge this PR (1 click)

Click the green **Merge pull request** button at the top of this PR. Confirm with **Confirm merge**. That's it.

**What happens automatically after you click**: GitHub Actions runs `wrangler deploy` and publishes the site to a preview URL like:

> `https://norcalcarbmobile.<your-account>.workers.dev/`

The URL shows up under the **Actions** tab once the green check appears (usually 30–60 seconds).

**Status check**: nothing is on the real domain yet. Squarespace is still serving `norcalcarbmobile.com`. You can take as long as you want to QA before the next step.

---

## Step 2 — Save your email DNS records (5 minutes, IMPORTANT)

**Why**: You don't want `bryan@norcalcarbmobile.com` to stop receiving mail when DNS flips.

**Easiest way (no terminal)**: open this link in a new tab:

> https://mxtoolbox.com/SuperTool.aspx?action=mx%3anorcalcarbmobile.com

It will show your **MX records**. Take a screenshot. Then run these three more lookups on the same site (change the dropdown to TXT, then DKIM, then DMARC):

- TXT for `norcalcarbmobile.com` (SPF record)
- DKIM for `norcalcarbmobile.com` (try selectors: `default`, `google`, `s1`, `selector1`)
- DMARC for `norcalcarbmobile.com`

Screenshot each one. Save them in a folder. You'll need them in step 4.

---

## Step 3 — QA the preview (5–10 minutes)

Open the preview URL from Step 1. Click around:

- [ ] Home loads
- [ ] Click "Services" — page loads
- [ ] Click "Service Areas" — page loads, all city sections show up
- [ ] Click "FAQ" — questions all expand/show correctly
- [ ] Click "About" — your name + CARB ID IF530523 visible
- [ ] Click "Contact" — phone + email work as click-to-call/mailto
- [ ] Click any blog post — reads cleanly
- [ ] Click "Call 916-890-4427" on mobile — actually dials

If anything looks off, reply in chat and I'll fix it. **Do not move to step 4 until preview is good.**

---

## Step 4 — Add the domain to Cloudflare (10 minutes)

1. Go to https://dash.cloudflare.com/
2. Top-right: **Add a site**
3. Type `norcalcarbmobile.com` → **Continue**
4. Pick the **Free** plan → **Continue**
5. Cloudflare scans your existing DNS records. **Verify the MX/SPF/DKIM/DMARC records from your screenshots are all there**. If anything is missing, click **Add record** and add it. **For MX and TXT records: keep the cloud icon GREY (DNS only) — not orange.**
6. Cloudflare will give you 2 nameservers like `lola.ns.cloudflare.com` and `walt.ns.cloudflare.com`. Copy them.

---

## Step 5 — Point Squarespace at Cloudflare's nameservers (5 minutes)

1. Log in to Squarespace.
2. **Settings** → **Domains** → click `norcalcarbmobile.com`.
3. Find **Nameservers** (sometimes called "Use custom nameservers").
4. Replace Squarespace's nameservers with the two Cloudflare nameservers from step 4.
5. Save.

DNS takes 5–60 minutes to propagate worldwide. While it's propagating, both Squarespace and Cloudflare may serve traffic depending on the visitor.

---

## Step 6 — Tell me you're at this step

Reply in chat: "DNS is on Cloudflare". I will then push a one-line change to `wrangler.toml` that attaches the custom domain. After that, `https://norcalcarbmobile.com/` serves the new site within 1–2 minutes (Cloudflare provisions SSL automatically).

---

## Step 7 — Verify email still works

From your phone or another email account, send an email to `bryan@norcalcarbmobile.com`. Confirm it arrives. If it doesn't, check Cloudflare DNS → confirm MX/SPF/DKIM/DMARC records match the screenshots from step 2.

---

## Step 8 — Post-launch checklist (do over the next 7 days)

- [ ] Resubmit sitemap in [Google Search Console](https://search.google.com/search-console): add property `norcalcarbmobile.com`, submit `https://norcalcarbmobile.com/sitemap.xml`.
- [ ] In [Google Business Profile](https://business.google.com/), open both Sacramento and Oakland listings → Edit → Website → confirm URL is `https://norcalcarbmobile.com/` → Save (forces a refresh).
- [ ] Update website link on **Yelp**, **BBB**, **Bing Places**, **Apple Business Connect** if they're stale.
- [ ] Send a test invoice or any other workflow that depends on the domain to confirm everything works end-to-end.
- [ ] **+7 days from launch**: cancel Squarespace subscription. Save the cancellation confirmation email.

---

## What I'll handle for you

- All ongoing site edits (call, email, Slack, or comment in GitHub)
- Wiring native Google Calendar booking on `/api/schedule`
- Adding more blog posts (~30 more queued in BLOG_PLAN.md)
- Adding city-specific landing pages
- Domain transfer to Cloudflare as registrar (saves ~$15/yr) at +30 days

---

## What you decide — not blockers, can flip later

| Question | Default | Reply with one word to change |
|---|---|---|
| Background color | white | "charcoal" |
| CTA color | red | "green" |
| Font | system | "Inter" |
158 changes: 158 additions & 0 deletions workers/norcalcarbmobile/MIGRATION_ADDENDUM.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# Migration Addendum — Gaps consolidated

Reconciles this plan with the parallel Manus checklist. Captures every item either side caught, plus things neither caught.

---

## A. Gaps Manus caught that we should add

### A1. MX records / email continuity (CRITICAL)

**Risk**: If `norcalcarbmobile.com` has email accounts (`bryan@`, `sales@`, etc.) hosted via Squarespace's Google Workspace integration or any other MX, **changing DNS will break email** unless MX records are preserved.

**Action before DNS flip**:
1. Run `dig norcalcarbmobile.com MX` and screenshot the output. These are the records that must continue working after cutover.
2. In Cloudflare DNS dashboard, recreate every MX record exactly. Do NOT proxy MX records (orange cloud OFF — grey cloud only for MX, TXT, SPF, DKIM, DMARC).
3. Also preserve: SPF (`TXT v=spf1 ...`), DKIM (`TXT default._domainkey...`), DMARC (`TXT _dmarc...`), and any `_acme-challenge` for Let's Encrypt.
4. **Test before flipping**: send a test email to `bryan@norcalcarbmobile.com` *before* changing nameservers. Confirm receipt. Then change nameservers.

**If email is Squarespace-hosted**: leaving Squarespace cancels the email plan. Migrate to Cloudflare Email Routing (free, forwards `@norcalcarbmobile.com` → personal Gmail) or Google Workspace direct (~$6/user/mo) **before** cancelling Squarespace.

### A2. Domain transfer to Cloudflare as registrar (optional, post-launch)

Manus suggests transferring the domain registration itself to Cloudflare. This is **not required for migration** — we just change nameservers — but worth doing later because:
- Cloudflare charges at-cost renewal (typically $9.77/yr for `.com` vs Squarespace's $25/yr).
- One vendor for DNS + registrar is simpler.

Sequence:
1. **Now**: change nameservers only (no transfer). Site goes live.
2. **+30 days post-launch**: initiate transfer (ICANN requires 60 days from any prior change).
3. Get auth code from current registrar (Squarespace), enter in Cloudflare, pay transfer fee (which adds 1 year of registration), confirm via email.

Not blocking the migration. Park as a follow-up.

---

## B. Things this plan has that Manus appears to lack

Don't lose these in the merge:

| Item | Why it matters |
|---|---|
| `Person` + `hasCredential` JSON-LD with CARB Tester IF530523 | E-E-A-T signal Google explicitly requires per Search Quality Rater Guidelines §2.5 |
| `LocalBusiness.location` array with Sacramento + Oakland | Google fuses both offices into one entity |
| `/llms.txt` + `/llms-full.txt` | AI search readiness (ChatGPT/Claude/Perplexity) |
| `robots.txt` allowlist for GPTBot/ClaudeBot/PerplexityBot/Google-Extended/Applebot-Extended/CCBot | Without this, AI engines can't quote the site |
| FAQPage schema with 13 self-contained Q&A | Eligible for Google rich results + AI extraction |
| Service-area-business GBP setting (no street address) | Correct configuration for mobile services per Google's own guidance |
| `CITATIONS.md` with sources for every design choice | Defensible — every decision is backed by a Google/OpenAI/Anthropic/CARB doc |
| 30+ post blog plan with cluster strategy | Programmatic SEO scaling |
| OVI-J1667 terminology throughout (not "smoke test") | Ranks for compliance-literate searchers (fleet managers) |

---

## C. Items NEITHER plan caught — add now

### C1. Cloudflare Turnstile on the contact form
**What**: Free CAPTCHA replacement, invisible by default. Ships with one `<script>` tag + secret in the worker.
**Why**: A `mailto:` link gets harvested. A real form gets spam. Turnstile blocks it without breaking UX.
**When**: Add when we wire up `/api/schedule` (post-launch).

### C2. Final Squarespace backup before DNS flip
**What**: Run `wget --mirror` one more time the morning of cutover. Commit to `workers/norcalcarbmobile/squarespace-snapshot-final/`.
**Why**: If something breaks 2 weeks after cutover, we have an exact byte-for-byte copy of what was live. (Wayback Machine is unreliable for missed pages.)

### C3. Wayback Machine "Save Page Now" for top 20 URLs
**What**: Submit each top-trafficked URL to https://web.archive.org/save/ before flipping DNS.
**Why**: Insurance. If a redirect drops a URL, the old version is preserved at archive.org — you can recover content and the URL keeps a snapshot for entity continuity.

### C4. Google Business Profile post-DNS verification
**What**: After DNS cuts over, log into GBP, click "Refresh" on the website link, confirm GBP can reach the new site at the same domain. Verify both Sacramento and Oakland listings.
**Why**: Sometimes GBP caches a 5xx response from the migration window and de-emphasizes the listing. Forcing a refresh fixes it.

### C5. NAP cross-update on directories
**What**: Update website link (and address mode if changing to service-area) on:
- Yelp
- BBB
- Google Business Profile (Sac + Oak both)
- Yellow Pages / Yellowpages.com
- Manta
- Foursquare
- Apple Business Connect
- Bing Places
**Why**: `sameAs` in our schema only fuses entity if the directory entries actually match. Stale directory entries with a different phone or address pull the entity apart.

### C6. CARB / state directory listings
**What**: If CARB or DMV publishes a list of licensed testers, ensure tester ID IF530523 lists `norcalcarbmobile.com`. Same for any compliance directories used by fleet managers.
**Why**: This is the highest-trust backlink possible — a `.gov` or `.ca.gov` link confirming the credential.

### C7. Plausible or GA4 added day-1
**What**: Pick analytics before launch. Plausible (~$9/mo, privacy-first, simple) or GA4 (free, complex). Add the script tag to all pages.
**Why**: Without it, the 30/60/90-day KPI table in `MIGRATION_PLAN.md` has no numbers. Search Console alone misses non-search traffic.

### C8. UptimeRobot or similar monitor
**What**: Free uptime monitor pinging `https://norcalcarbmobile.com/` every 5 minutes. Alerts to Bryan's phone on 4xx/5xx.
**Why**: Cloudflare is reliable, but a wrangler.toml typo could 502 the site overnight. Alert in 5 minutes, not 5 days.

### C9. Cancellation reminder for Squarespace (calendar item)
**What**: 7 days post-launch → confirm new site is solid → cancel Squarespace subscription.
**Why**: Easy to forget. $20/mo bleed.

---

## D. Stylistic decisions Bryan needs to make (current vs Manus proposal)

Manus is suggesting a redesign brief (charcoal background, safety-green/yellow CTAs, Inter/Roboto fonts). The current build uses Navy `#002244` + Red `#C60C30` + system fonts.

| Decision | Current (this branch) | Manus proposal | Recommendation |
|---|---|---|---|
| Background | White `#ffffff` | Charcoal `#0f1b2e` | Charcoal-on-dark looks tougher / industrial. Navy header on white body (current) loads faster and reads better in sunlight on mobile (truck cabs!). **Stick with current** unless you specifically want a dark-mode look. |
| Hero text color | Navy on white | White on charcoal | Same trade-off. Truck drivers searching on bright phones favor light backgrounds. |
| CTA color | Red `#C60C30` | Safety green/yellow | Red has higher conversion globally per CRO research. Safety green/yellow signals "compliance/industrial" — on-brand for CARB. **Either works**; pick by gut. |
| Font | System fonts | Inter or Roboto | System = faster (no FOUT, no extra request). Inter = more modern look. **Stick with system** for Core Web Vitals. |

If you want the charcoal/safety-green look, one CSS file change flips it — we're not locked in.

---

## E. Updated cutover checklist (combined)

### Phase 1: Pre-flip (this week)
- [x] Branch with home + CSS + wrangler + worker pushed
- [ ] Push remaining pages (about, services, areas, faq, contact, privacy, terms, 404)
- [ ] Push SEO/config (sitemap, llms, _redirects, _headers, robots)
- [ ] Push blog hub placeholder + first 10 pillar posts
- [ ] Run final `wget --mirror` of Squarespace site → commit to `squarespace-snapshot-final/`
- [ ] Submit top 20 URLs to Wayback Machine
- [ ] **CRITICAL**: screenshot current MX/TXT/DKIM/DMARC records via `dig`
- [ ] Decide email plan: Cloudflare Email Routing (free, forward to Gmail) or Google Workspace (paid)
- [ ] Add Plausible or GA4 script

### Phase 2: Cutover (the day)
- [ ] Merge migration branch → main → GitHub Actions deploys to `*.workers.dev`
- [ ] QA the preview URL (every link, every form CTA)
- [ ] In Cloudflare dashboard: add `norcalcarbmobile.com` as a site (if not already)
- [ ] Add nameservers from Cloudflare to Squarespace's domain settings
- [ ] **Recreate all MX/SPF/DKIM/DMARC records in Cloudflare DNS — grey cloud only**
- [ ] Send test email → confirm delivery
- [ ] Uncomment `routes = [...]` in `wrangler.toml` → commit → Cloudflare attaches custom domain
- [ ] Verify SSL (Cloudflare Universal SSL auto-provisions — check `https://norcalcarbmobile.com/`)
- [ ] Spot-check 20 old URLs return 301 → 200

### Phase 3: Post-flip (Day 0–7)
- [ ] Resubmit `sitemap.xml` to Google Search Console
- [ ] Refresh website link in both GBP listings (Sac + Oak)
- [ ] Update website link on Yelp, BBB, Bing Places, Apple Business Connect
- [ ] PageSpeed Insights check — LCP/CLS/INP green
- [ ] Set up UptimeRobot monitor
- [ ] Calendar reminder: cancel Squarespace +7 days

### Phase 4: +30 days
- [ ] Initiate domain transfer Squarespace → Cloudflare (saves ~$15/yr renewal)
- [ ] First KPI check vs. baseline (organic clicks, indexed pages, position)
- [ ] Wire up `/api/schedule` with native Google Calendar integration + Turnstile
- [ ] Publish remaining blog posts (Cluster B, D, F, G)

---

*Source review: this addendum incorporates the Manus migration checklist (Phase 2: Domain Transfer; Phase 3: Post-Migration; Part 2: Redesign Brief) plus items neither plan originally caught.*
39 changes: 39 additions & 0 deletions workers/norcalcarbmobile/public/404.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Page Not Found | NorCal CARB Mobile</title>
<meta name="robots" content="noindex">
<link rel="stylesheet" href="/assets/site.css">
<meta name="theme-color" content="#002244">
</head>
<body>
<a href="#main" class="skip">Skip to content</a>
<header class="site-header">
<div class="container header-inner">
<a href="/" class="brand">NorCal CARB Mobile<small>Licensed &middot; Mobile &middot; NorCal-wide</small></a>
<nav class="nav" aria-label="Primary"><a href="/">Home</a><a href="/services/">Services</a><a href="/service-areas/">Service Areas</a><a href="/faq/">FAQ</a><a href="/about/">About</a><a href="/contact/">Contact</a></nav>
<a href="tel:+19168904427" class="call-btn">Call 916-890-4427</a>
</div>
</header>
<main id="main">
<section class="hero"><div class="container center">
<h1>That page moved &mdash; or never existed.</h1>
<p class="lede" style="text-align:center;margin:0 auto">Try one of these instead, or call us.</p>
<div class="cta-row" style="justify-content:center;margin-top:24px">
<a href="tel:+19168904427" class="btn-primary">Call 916-890-4427</a>
<a href="/" class="btn-secondary">Home</a>
</div>
</div></section>
<section><div class="container">
<div class="grid">
<a href="/services/" class="card" style="text-decoration:none;color:inherit"><h3>Services &amp; Pricing</h3><p>OBD $75, OVI-J1667 $250, motorhome/RV, fleet, agricultural.</p></a>
<a href="/service-areas/" class="card" style="text-decoration:none;color:inherit"><h3>Service Areas</h3><p>Sacramento, Oakland, Stockton, Modesto, San Jose, and all of NorCal.</p></a>
<a href="/faq/" class="card" style="text-decoration:none;color:inherit"><h3>FAQ</h3><p>What CARB tests cost, who needs them, how long they take.</p></a>
<a href="/contact/" class="card" style="text-decoration:none;color:inherit"><h3>Contact &amp; Schedule</h3><p>Call, email, or send a request. Same-hour confirmation.</p></a>
</div>
</div></section>
</main>
<footer><div class="container"><div class="footer-bottom" style="text-align:center">&copy; 2026 NorCal CARB Mobile LLC &middot; CARB Tester ID IF530523 &middot; <a href="/about/">About</a> &middot; <a href="/faq/">FAQ</a> &middot; <a href="/contact/">Contact</a></div></div></footer>
</body></html>
28 changes: 28 additions & 0 deletions workers/norcalcarbmobile/public/_headers
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Default security headers for all pages
/*
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

# Long-lived cache for static assets
/assets/*
Cache-Control: public, max-age=31536000, immutable

# Short cache + correct content types for crawler-facing files
/sitemap.xml
Cache-Control: public, max-age=3600
Content-Type: application/xml; charset=utf-8

/robots.txt
Cache-Control: public, max-age=3600
Content-Type: text/plain; charset=utf-8

/llms.txt
Cache-Control: public, max-age=3600
Content-Type: text/plain; charset=utf-8

/llms-full.txt
Cache-Control: public, max-age=3600
Content-Type: text/plain; charset=utf-8
Loading