Kowalski Plumbing & Heating exists so I could build its website twice. It is a fictional Ann Arbor business with a complete dated site, a complete modern refresh, and a comparison view between them.
I wanted a refresh pitch to have something more useful than “your website looks old.” Showing the same business both ways makes the difference inspectable. It also creates a constraint: the refreshed site should not win the comparison just because I gave it different services or better business information.
01 / FIELD NOTESThe “before” is a working page
The dated treatment uses details from an older style of business website: tiny text, dense copy, a table-like layout, and no responsive behavior. It was built for this demonstration, not recovered from an actual business.
Both presentations read the same business data. In the published excerpt, that includes the name, service area and hours. Every business detail in the following example, including the license-like identifier, belongs to the fictional demo.
export const BUSINESS_DATA: BusinessInfo = {
name: "Kowalski Plumbing & Heating",
tagline: "Honest plumbing and heating services in Ann Arbor and Washtenaw County since 1989.",
established: 1989,
phone: "(734) 555-0189",
// … email, address, city, zip
serviceArea: ["Ann Arbor", "Ypsilanti", "Saline", "Dexter", "Chelsea", "Milan"],
hours: "Monday - Friday: 7:00 AM - 6:00 PM | 24/7 Emergency Service",
licenseNumber: "MI-PLUMB-1989-42819",
};Keeping those values shared makes the presentation the variable. It does not turn the demo into a controlled conversion experiment, but it avoids changing the business information at the same time as the layout.
02 / FIELD NOTESOne record, two treatments
A visitor can inspect each complete view, not just a screenshot. That matters here because the refresh includes responsive behavior and navigation, which a still image cannot fully demonstrate.
The local figure below is a smaller illustration of the same idea. Change the service summary once and see that both presentations receive the exact same text. The miniature is new explanatory markup; it is not the original Kowalski interface.
CONTROL THE COMPARISON
Same words. Different hierarchy.
Change the service summary once. Both miniatures receive the same literal text.
Kowalski
Plumbing & Heating
Plumbing and heating help for homes in the local area.
Kowalski
Plumbing & Heating
Plumbing and heating help for homes in the local area.
Both treatments use one shared sample summary.
Read the explanation without interacting
The two original presentations share a business-data module. The dated treatment changes density and hierarchy, not the services or business identity. These small new layouts demonstrate that principle without reproducing the complete original screens. They intentionally remain readable on mobile.
The project account uses React 19, TypeScript, Vite and Tailwind CSS v4, with static deployment on a Cloudflare Worker. The two presentations can have different layout code without each owning a separate copy of the business details.
03 / FIELD NOTESLet a link choose the view
The original router reads the hash and selects old, new or compare. It also listens for changes, so switching the hash updates the selected view. The cleanup removes that listener when the effect is disposed.
useEffect(() => {
const handleHashChange = () => {
const hash = window.location.hash;
if (hash === "#/old" || hash === "#old") {
setCurrentRoute("old");
} else if (hash === "#/new" || hash === "#new") {
setCurrentRoute("new");
} else {
setCurrentRoute("compare");
}
};
handleHashChange();
window.addEventListener("hashchange", handleHashChange);
return () => window.removeEventListener("hashchange", handleHashChange);
}, []);The condition is intentionally small. #/old and #old select the old view. #/new and #new select the new view. Everything else falls through to the comparison.
FOLLOW THE CONDITIONAL
Where does this hash go?
Inspect the exact branches in the published effect. The inspector does not navigate the page.
The source effect reads this value.
Matches the first condition.
Read the explanation without interacting
The exact values #/old and #old select old. #/new and #new select new. All other values select compare. The source registers a hashchange listener and removes it in the effect cleanup.
This lets a message point straight to a version. The inspector above reproduces the conditions locally without navigating away from this article. It is not a test of the deployed host or its configuration.
04 / FIELD NOTESWhat I would add next
The original write-up calls out a scroll-synchronized side-by-side view. That would make it easier to compare corresponding sections without repeatedly changing tabs.
It also calls for a Lighthouse before/after panel. At present, the story is about the visible design and implementation choices; it does not contain performance measurements to support a stronger claim.
For a real client refresh, the “before” should be the actual starting point. Here, building it on purpose made a navigable demonstration of the pattern. That distinction belongs beside the comparison, not buried after it.
Source, dates, and what this page verifies
This rewrite describes the supplied fictional-business project and its article published July 17, 2026. The original publication date and canonical source address are preserved. The original application and current deployment have not been audited.
New miniatures are explicitly illustrative, not screenshots or full reproductions. All teaching inputs stay in memory. No booking tool, enquiry service, message provider or outside page is loaded by an interaction. Original code excerpts remain unchanged and are displayed rather than executed as application code.
The named source limits and exact explanation of each new model are recorded in docs/batch-4/SOURCE-REVIEW.md. No business, medical, conversion or performance outcome was measured for these demos.