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.

src/types.ts · fictional business data / TypeScript
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",
};
Source excerpt · not executed in this design sample · scroll code horizontally on narrow screens

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.

ILLUSTRATIVE MINIATURE
01 / A DATED TREATMENT

Kowalski
Plumbing & Heating

Plumbing and heating help for homes in the local area.

PlumbingHeatingRepairs
Discuss a repair FICTIONAL BUSINESS · SAME CONTENT
ILLUSTRATIVE MINIATURE
02 / A CLEARER HIERARCHY

Kowalski
Plumbing & Heating

Plumbing and heating help for homes in the local area.

PlumbingHeatingRepairs
Discuss a repair FICTIONAL BUSINESS · SAME CONTENT

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.

New illustrative markup, not screenshots. The original project contains two complete fictional-business sites. No performance or conversion result is implied.

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.

src/App.tsx · source routing effect / TypeScript
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);
}, []);
Source excerpt · not executed in this design sample · scroll code horizontally on narrow screens

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 HASH STRING#/old

The source effect reads this value.

THE SELECTED VIEWold

Matches the first condition.

IF oldELSE IF newELSE compare
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.

Local evaluation of the supplied conditions, not a deployed routing or static-host compatibility test.

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.

Open the original published source ↗