# Repo Rules

The conventions every page in this repo follows. Read this before adding or
editing a funnel page. The short version: **pages are CheckoutChamp exports,
the repo is the source of truth, and every page must be QA'd, routed, and
listed on the hub before it ships.**

---

## 1. CheckoutChamp page structure (the two-pane contract)

Every funnel page that gets pasted into CheckoutChamp (CKC) is exactly two
files, plus one generated file:

| File | What it is | Where it goes |
| --- | --- | --- |
| `index.html` | The **HTML pane** — a full document (`<html><head>…<body>…`) | Pasted into CKC's page HTML field |
| `styles.css` | The **CSS pane** | Pasted into CKC's page CSS field |
| `preview.html` | `index.html` with `styles.css` inlined into `<head>` — **generated, never edited by hand** | QA only (local server / Cloudflare) |

Rules that follow from how CKC works:

- **Never link `styles.css` from `index.html`.** CKC injects the CSS pane
  itself; the preview build script does the inlining for QA.
- **CKC strips `<script src>` tags from `<head>`.** External libraries
  (Trustpilot bootstrap, Loox) must be injected from a body script that
  creates the `<script>` element at runtime. Font `<link>` tags are fine.
- **All page JavaScript lives in `<script script-type="user-script">` blocks
  at the end of `<body>`.** Keep data/wiring and third-party loaders in
  separate blocks so diffs stay readable.
- **CKC platform functions** (`callMethod`, `route`, `linkMethod`) only exist
  on the live funnel domain. Every page defines preview-safe no-ops guarded
  by the hostname check (`localhost|127.0.0.1|.pages.dev`) so QA clicks never
  throw — the guard must never override the real functions in production.
- **Add-to-Cart buttons** follow the CKC button contract: class
  `fk-addToCartBtn`, inline `onclick="callMethod(event)"`, plus the attribute
  pile (`data-id="fkt-…"`, `type="button"`, `action="submit"`,
  `variantvalue=""`, `replaceproductid=""`, `billnow="false"`, …). The JS
  click handler refreshes `productInfo` first, then defers to
  `route()`/`callMethod()`.
- **Lazy-loading:** CKC has its own lazy machinery — mark icons, arrows, and
  above-the-fold media with `fk-disable-lazy`. Content images may carry
  native `loading="lazy"`.
- `cc-id-*` ids are CKC-builder artifacts on exported pages; hand-authored
  pages use stable, readable ids instead. Never rely on `cc-id-*` from JS.

## 2. Funnel wiring contracts (PuppyPad)

These keys are read by the checkout and upsell pages — break them and the
funnel silently breaks:

- `localStorage.productInfo` — `"?products=<variantId>:1;<gift>:1;…"`, built
  from `window.productVariants` on every selection change and on ATC click.
- `localStorage.selectedSize` / `selectedColor` / `puppypadSelections`, and
  `sessionStorage.puppypadUIState` — persisted on selection and on ATC so the
  upsells offer matching pads.
- **The product catalogue is canonical in `puppypad/product/index.html`**
  (`window.productVariants`: `629.x` single / `630.x` B3G3 / `631.x` B2G1,
  `:china` / `:outofstock` flags, gifts `625/626/627`). Any page that carries
  its own copy (currently `puppypad/hybrid-lp`) **must be updated in the same
  commit** whenever the catalogue changes.
- **Geo switching:** the `worldwideId|usId` variant format +
  `applyUSProducts()` / `cleanWorldwideProducts()` + the ipapi.co lookup
  (cached in `sessionStorage.sl` / `.cc`, city swapped into `#loc`). Copied
  verbatim from the PDP — keep it byte-identical across pages.
- New-funnel-step reminder: a page that goes **straight to checkout** still
  needs its ATC step pointed at the checkout step inside CKC's funnel config.

## 3. Design systems

- **PuppyPad** and **CoolingBed** are different design systems. Never mix.
- PuppyPad rules live in `puppypad/DESIGN-SYSTEM.md` (living reference at
  `/pp/design-system`). The ones that are easy to get wrong: no drop shadows
  on content cards; one green (`#028C2B`) reserved for buy actions; selected
  state = peach fill + navy border; ice/cream alternating section tints with
  the butter marquee as the only interrupt; AwesomeSerif / Schibsted Grotesk /
  Bebas Neue; 5px card radius, 50px pills.
- CoolingBed's system (navy ink, ice blues, hard offset shadows) is described
  in `README.md`.
- Giant animated/marquee rows must **not** carry `will-change: transform` —
  thousands-of-px layers can exceed GPU texture limits and paint blank at
  browser zoom. Reserve `will-change` for small, viewport-sized layers.

## 4. A/B testing convention

- The **control keeps the base page name** (`listicle/`, `product/`); each
  variant gets a `-descriptor` suffix (`listicle-b/`, `product-buybox-first/`)
  or a purpose name (`hybrid-lp/`).
- Variants must keep **commerce parity** with the control (same product IDs,
  same storage contracts, same checkout hand-off) unless the test says
  otherwise — the design layout is the variable, not the wiring.
- Hub tags tell the truth: `A · control` / `B · variant` plus a status chip —
  **Live** (running in CKC) or **Staged** (built and QA'd, not yet pasted).

## 5. New page checklist (do all of these — no exceptions)

1. Create the folder: `«brand»/«page»/index.html` + `styles.css` following
   §1. Reuse the design-system tokens, don't fork them.
2. Add `scripts/build_«page».py` (copy an existing one) and run it to
   generate `preview.html`. Re-run after every edit.
3. Add a pretty URL to `_redirects`.
4. **Add a row to the hub** (root `index.html`) with the right role, a
   one-line description, A/B tag if applicable, and an honest Live/Staged
   status chip. *A page that isn't on the hub doesn't exist.*
5. Run `python3 scripts/build_hub_meta.py` so the hub shows the page's
   modified date and version log.
6. QA in the local server (`node scripts/serve.js`, port 8642) at phone /
   tablet / desktop widths: console clean, selectors wired, `productInfo`
   correct, modals, sticky bar, swipe.
7. Commit. After the page is pasted into CKC and the funnel step is wired,
   flip its hub chip from **Staged** to **Live**.

## 6. Hub metadata & version logs

- The hub reads `hub-meta.json` (repo root), generated by
  `scripts/build_hub_meta.py` from **git history per page folder**: each
  commit touching a page becomes a version entry (v1…vN) with date, time and
  the commit subject as the summary; uncommitted edits appear as a
  **Working copy** entry using file mtimes.
- Every row shows its last-modified date/time; the version chip on the right
  opens the log popup.
- **Run the generator after every commit that touches a page** (and before
  pushing) so the deployed hub stays accurate:

  ```
  python3 scripts/build_hub_meta.py
  ```

## 7. Git hygiene

- Commit messages: `«page»: what changed and why` (they become the version
  log summaries shown on the hub — write them for that audience).
- Generated files (`preview.html`, `hub-meta.json`) are committed so
  Cloudflare serves them; regenerate rather than hand-edit.
- One page per commit where practical; catalogue changes touch every page
  that embeds the catalogue in the same commit (§2).
