Dr J's Binding Protocol — Document 16

Build Compliance

Practical standards, templates, and patterns derived from building Patriot Chassis and The High Road Manufacturing. Follow this document to score A+ on the first pass.

← Back to Protocol Index

Contents

  1. HTML Page Template
  2. 43-Point Scoresheet Reference
  3. Component Architecture
  4. Image Pipeline
  5. Page Classification & Sitemap Rules
  6. Form Accessibility Pattern
  7. Email & Contact Info Handling
  8. Auth System Pattern
  9. Client Portal Pattern
  10. Product Data & JS Generation
  11. Required CSS Utilities
  12. Pre-Build Checklist

1. HTML Page Template

Every page on every site must start from this template. No exceptions. The scoresheet checks for every element listed here — missing any one of them costs you a point.

<!-- COPY THIS. Every page starts here. --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Page Title — Site Name</title> <meta name="description" content="Unique 150-160 char description for this page."> <link rel="canonical" href="https://yourdomain.com/this-page.html"> <!-- Open Graph (all 4 required) --> <meta property="og:type" content="website"> <meta property="og:site_name" content="Site Name"> <meta property="og:title" content="Page Title — Site Name"> <meta property="og:description" content="Same as meta description."> <meta property="og:url" content="https://yourdomain.com/this-page.html"> <meta property="og:image" content="https://yourdomain.com/images/logo/logo.png"> <!-- Favicon --> <link rel="icon" type="image/png" href="images/logo/logo.png"> <!-- Fonts --> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=...&display=swap" rel="stylesheet"> <!-- Stylesheet --> <link rel="stylesheet" href="css/styles.css?v=YYYYMMDD"> </head> <body> <!-- Skip link (accessibility) --> <a href="#main-content" class="skip-link">Skip to main content</a> <!-- Header injected by components.js --> <div id="site-header"></div> <!-- Page content wrapped in <main> --> <main id="main-content"> <h1>One H1 Per Page</h1> <!-- Page content here --> </main> <!-- Footer injected by components.js --> <div id="site-footer"></div> <!-- Scripts: always defer, never blocking --> <script src="js/components.js?v=YYYYMMDD" defer></script> <!-- Structured data (at least on key pages) --> <script type="application/ld+json">{"@context":"https://schema.org",...}</script> </body> </html>
Rule: Every element in this template is checked by the scoresheet. Missing any one — charset, viewport, title, description, canonical, OG tags, favicon, skip-link, semantic HTML, DOCTYPE — costs you a point. Copy this template for every new page.

Head Element Checklist

ElementScoresheet CheckNotes
<!DOCTYPE html>Best Practices: DOCTYPEMust be first line
<html lang="en">Accessibility: Lang attributeRequired on every page
<meta charset="UTF-8">Best Practices: CharsetMust contain the word "charset"
<meta name="viewport">Accessibility: Viewport metaRequired on every page
<title>SEO: Title tagUnique per page, under 60 chars
<meta name="description">SEO: Meta descriptionUnique per page, 150-160 chars
<link rel="canonical">Content: Canonical URLFull absolute URL to this page
<meta property="og:*">SEO: Open Graph tagsAt least one og: tag per page
<link rel="icon">Best Practices: FaviconAt least one page (components.js injects it)
<link rel="preconnect">Content: Resource hintsAt least one preconnect or preload site-wide

Body Element Checklist

ElementScoresheet CheckNotes
Skip-to-content linkAccessibility: Skip linkText must contain "skip" + "main" or "content"
Semantic wrapper (<main>, <section>, etc.)Accessibility: Semantic HTMLEvery page needs at least one: header, main, nav, footer, section, article
Exactly one <h1>Content: H1 + No multiple H1sTwo checks: must exist AND must not be more than one
No heading level skipsAccessibility: Heading skipsh1 → h2 → h3 (never h1 → h3)
All scripts deferredPage Weight: No blocking JSEvery <script src> must have async or defer
No inline handlersBest Practices: No inline handlersZero onclick=, onchange=, onload=, onerror=, onmouseover=
ARIA landmark or roleAccessibility: ARIAAt least one page needs role= or aria-label= (components.js covers this)

2. 43-Point Scoresheet Reference

The scoresheet (build-process/tools/site-scoresheet.sh) runs 43 checks across 8 categories. Target is A+ (95%+, which means 41/43 or better). Here is every check with the exact pass/fail criteria.

Page Weight & Assets (5 checks)

CheckPass CriteriaHow to Comply
Total site weight under 50 MBHTML + CSS + JS + images < 52,428,800 bytesCompress images aggressively. Use WebP.
No oversized images (>500 KB)Zero images over 500 KB in /imagesResize and compress before adding to repo. Target 50-200 KB.
CSS total under 100 KBAll .css files combined < 102,400 bytesOne stylesheet per site. No frameworks.
JS total under 200 KBAll .js files (excluding *-data.js) < 204,800 bytesData files are excluded from count. Keep logic JS lean.
No render-blocking JavaScriptZero <script src> tags without async or deferAlways add defer to script tags.

Performance (5 checks)

CheckPass CriteriaHow to Comply
Estimated FCP under 1.8sCalculated from critical resource sizesKeep CSS small. Defer all JS.
All images have width & heightEvery <img> must have both width= AND height= HTML attributesUse actual pixel dimensions. CSS sizing is not enough — the scoresheet checks HTML attributes.
Lazy loading on below-fold imagesAt least 1 image has loading="lazy"Add to all images below the fold. Never lazy-load the hero.
Font preload enabledAt least one <link rel="preload"> with "fonts"Preload the primary heading font file.
Font display swapdisplay=swap in Google Fonts URL or @font-faceGoogle Fonts URLs include this by default: &display=swap
Gotcha — Image Dimensions: The scoresheet uses regex <img [^>]*width=[^>]*height= to check. The width attribute must appear BEFORE height in the tag. Both must be HTML attributes, not CSS. Get the actual dimensions from the image file and set them explicitly.

SEO (7 checks)

CheckPass CriteriaHow to Comply
robots.txt presentFile exists at site rootCreate during site setup.
sitemap.xml presentFile exists at site rootCreate during site setup.
All pages in sitemapEvery HTML file found in sitemap (by filename grep)See Section 5 for page classification. Private pages need <meta name="robots" content="noindex"> to be excluded from this check.
Title tag on all pagesEvery HTML file has <title>Use the page template.
Meta description on all pagesEvery HTML file has name="description"Unique per page.
Open Graph on all pagesEvery HTML file has property="og:"Use the page template.
Structured data (JSON-LD)At least 1 page has application/ld+jsonAdd to homepage, product pages, FAQ, contact at minimum.
Gotcha — Sitemap Filename: The scoresheet checks if the HTML filename (e.g., index.html) appears in sitemap.xml. If your homepage URL is https://domain.com/ without index.html, it fails the grep. Use https://domain.com/index.html in the sitemap.

Accessibility (8 checks)

CheckPass CriteriaHow to Comply
Alt text on all imagesEvery <img> has alt= attributeDecorative images use alt="" (empty, not missing).
Lang attribute on all pagesEvery HTML file has <html lang=>Use the page template.
Viewport meta on all pagesEvery HTML file has name="viewport"Use the page template.
Semantic HTML on all pagesEvery HTML file contains at least one of: <header>, <main>, <nav>, <footer>, <section>, <article>Wrap page content in <main>. This is the safest bet — it works even on pages where components.js injects header/footer after parse.
No heading level skipsHeading levels increase by 1 (h1→h2→h3)Never jump from h1 to h3. Plan your heading tree.
Skip-to-content linkAt least 1 page has text containing "skip" + "main"/"content"Use the page template skip link. Also checked in JS files.
ARIA landmarks / rolesAt least 1 page has role= or aria-label=components.js typically provides this via nav markup.
Form labels match inputsTotal <label> elements ≥ total visible inputsSee Section 6. The scoresheet counts <label> tags — NOT aria-label attributes.
Critical Gotcha — Labels: The scoresheet counts <label> elements in static HTML (script blocks are stripped). It counts <input> (excluding hidden/submit/button/image and tabindex="-1"), <select>, and <textarea>. Labels must be ≥ inputs across ALL pages combined. The aria-label attribute does NOT count. You must have actual <label> elements.

Content Quality (5 checks)

CheckPass CriteriaHow to Comply
Every page has an H1Every HTML file has at least one <h1>One per page, no exceptions.
No multiple H1s per pageNo HTML file has more than one <h1>Conditional content (blocked states, loading states) must use <h2> or lower.
Canonical URL on all pagesEvery HTML file has rel="canonical"Full absolute URL.
No generic link textZero instances of "click here", "here", "read more", "learn more" as link textUse descriptive link text: "View our chassis catalog" not "Click here".
Resource hintsAt least 1 preconnect or preloadGoogle Fonts preconnect in the template covers this.

Best Practices (5 checks)

CheckPass CriteriaHow to Comply
DOCTYPE on all pagesEvery HTML file starts with <!DOCTYPE html>Use the page template.
Charset on all pagesEvery HTML file contains "charset"Use the page template.
Favicon linkedAt least 1 page has rel="icon"In template or injected by components.js.
Custom 404 page404.html exists in site rootCreate during site setup.
No inline event handlersZero onclick=, onmouseover=, onload=, onerror=, onchange= in any HTML fileUse addEventListener in external JS files instead. No exceptions.

Security (4 checks)

CheckPass CriteriaHow to Comply
All links use HTTPSZero bare http:// URLs (excludes schema.org, localhost, w3.org)Always use https://.
No exposed email addressesZero email-pattern strings on lines without mailto:, "email", placeholder=, or application/ld+jsonSee Section 7. Always wrap emails in mailto: links. Keep JSON-LD on one line with its script tag.
No document.write()Zero instances in HTMLNever use document.write.
No console.log in production JSZero console.log in .js filesRemove all debug logging before finalizing.

Optimization (4 checks)

CheckPass CriteriaHow to Comply
WebP alternatives for all JPGsEvery .jpg in /images has a matching .webpSee Section 4 — Image Pipeline.
CSS design tokens (>5)More than 5 CSS custom properties (--variable)Use the design token system from doc 3.
Responsive breakpoints (≥3)At least 3 @media queries in styles.cssStandard set: 480px, 768px, 1024px minimum.
Self-hosted or SRI on external scriptsNo external scripts without integrity=Self-host everything. No CDN scripts without SRI hashes.

3. Component Architecture

Every site uses a shared pattern: static HTML pages with JavaScript-injected shared components (header, nav, footer). This is the proven architecture from Patriot Chassis and The High Road.

File Structure

sitename/ ├── index.html # Homepage ├── about.html # About page ├── shop.html # Product listing ├── product.php # Dynamic product detail (PHP) ├── contact.html # Contact form ├── faq.html # FAQ with structured data ├── gallery.html # Photo gallery ├── cart.html # Shopping cart (noindex) ├── login.html # Client login (noindex) ├── register.html # Client registration (noindex) ├── account.html # Account dashboard (noindex) ├── portal.html # Client portal (noindex) ├── checkout-success.html # Post-checkout (noindex, excluded from sitemap) ├── checkout-cancel.html # Post-checkout (noindex, excluded from sitemap) ├── privacy.html # Privacy policy ├── terms.html # Terms of service ├── policies.html # Return/warranty policies ├── 404.html # Custom 404 (excluded from sitemap) ├── robots.txt # Search engine directives ├── sitemap.xml # All public pages by filename ├── css/ │ └── styles.css # Single stylesheet with design tokens ├── js/ │ ├── components.js # Header, nav, footer injection │ ├── cart.js # Cart logic + Stripe checkout │ ├── auth.js # Authentication state management │ ├── portal.js # Client portal logic │ ├── products-data.js # Generated product catalog (excluded from JS size count) │ └── videos-data.js # Generated video catalog (excluded from JS size count) ├── api/ │ ├── config.php # DB connection, Stripe keys, site config │ ├── cart.php # Stripe checkout session creation │ ├── webhook.php # Stripe webhook handler │ ├── auth.php # Login/register endpoints │ ├── auth-session.php # Session validation │ └── generate-products-js.php # Regenerate products-data.js from DB ├── images/ │ ├── logo/ # Site logos (PNG + WebP) │ ├── hero/ # Hero images │ ├── gallery/ # Gallery images │ ├── products/ # Product images (WebP required) │ └── about/ # About page images ├── feeds/ │ ├── google-merchant.xml # Google Shopping feed │ └── facebook-catalog.csv # Meta Commerce feed └── tools/ └── .snapshots/ # Performance audit history

How components.js Works

The components.js file runs on every page (loaded with defer). It finds #site-header and #site-footer divs and injects the full header/nav and footer HTML. This ensures:

Rule: Every page must have <div id="site-header"></div> and <div id="site-footer"></div> as injection points. The page's own content must be wrapped in a semantic element (<main>, <section>, etc.) that exists in the static HTML — do not rely on components.js to provide semantic structure for scoresheet compliance.

4. Image Pipeline

Every image added to a site must pass through this pipeline. No shortcuts.

The Pipeline

Acquire — get the source image (photo, screenshot, logo). Prefer the highest resolution original available.
Optimize — resize to the maximum display size needed (typically 1200px wide for full-width, 600px for product thumbnails). Compress to under 500 KB. Target 50-200 KB for most images.
Convert to WebP — every .jpg file must have a matching .webp with the same filename (e.g., hero.jpghero.webp). The scoresheet checks for this. PNG files used for logos/icons are exempt but should have WebP too when possible.
Get dimensions — record the actual pixel width and height of the file. You will need these for the HTML attributes. Use php -r 'print_r(getimagesize("path"));' or any image info tool.
Write the HTML — every <img> tag must include: alt, width, height, and (if below-fold) loading="lazy".

Image Tag Template

<!-- Above the fold (hero, first visible image) --> <img src="images/hero/main.webp" alt="Descriptive alt text per doc 9 guidelines" width="1200" height="800"> <!-- Below the fold (everything else) --> <img src="images/products/item.webp" alt="Product name — specific descriptive context" width="600" height="400" loading="lazy"> <!-- WRONG: Missing width/height --> <img src="images/logo.png" alt="Logo" class="logo-img"> ↑ CSS sizing is NOT enough. Scoresheet checks HTML attributes. <!-- WRONG: width after height --> <img src="images/photo.webp" alt="Photo" height="400" width="600"> ↑ Scoresheet regex requires width= BEFORE height= in the tag.

Size Limits

Asset TypeBudgetScoresheet Check
Any single image< 500 KBPage Weight: No oversized images
All images combinedPart of 50 MB totalPage Weight: Total site weight
All CSS combined< 100 KBPage Weight: CSS total
All JS (non-data) combined< 200 KBPage Weight: JS total

5. Page Classification & Sitemap Rules

Every HTML page falls into one of three categories. The category determines whether it goes in the sitemap and whether it needs a noindex tag.

Category Definitions

CategoryIn Sitemap?Noindex?Examples
Public contentYes — listed by filenameNoindex.html, about.html, shop.html, gallery.html, faq.html, contact.html, capabilities.html, privacy.html, terms.html, policies.html
Private / sessionNo — excluded by noindexYes — <meta name="robots" content="noindex, nofollow">login.html, register.html, account.html, cart.html, portal.html
System / excludedNo — excluded by script logicOptional404.html, checkout-success.html, checkout-cancel.html, product.html (template)
Rule: The scoresheet auto-excludes these filenames from the sitemap check: 404.html, checkout-*.html, quote.html, portal.html, product.html. It also excludes any page with <meta name="robots" content="noindex">. Every other HTML file must appear in sitemap.xml by filename.

Sitemap Format

<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <!-- Use index.html explicitly — scoresheet greps by filename --> <url><loc>https://domain.com/index.html</loc><changefreq>weekly</changefreq><priority>1.0</priority></url> <url><loc>https://domain.com/about.html</loc><changefreq>monthly</changefreq><priority>0.9</priority></url> <url><loc>https://domain.com/shop.html</loc><changefreq>weekly</changefreq><priority>0.9</priority></url> <!-- List ALL public content pages --> </urlset>

Noindex Template for Private Pages

<!-- Add INSIDE <head>, after meta description --> <meta name="robots" content="noindex, nofollow">

6. Form Accessibility Pattern

The scoresheet counts <label> elements and compares to visible form inputs. Labels must be ≥ inputs across all pages combined. This is the most common failure on new builds.

Standard Form Inputs (with visible labels)

<!-- Contact forms, login forms, registration forms --> <div class="form-group"> <label class="form-label" for="email">Email Address</label> <input type="email" class="form-input" id="email" name="email" required> </div>

Toolbar Controls (visually hidden labels)

Search boxes, sort dropdowns, and filter selects in toolbars don't have visible labels. They still need <label> elements — use the .sr-only class to hide them visually while keeping them accessible and scoresheet-compliant.

<!-- Search input in toolbar --> <label for="shop-search" class="sr-only">Search products</label> <input type="text" id="shop-search" placeholder="Search..."> <!-- Sort dropdown in toolbar --> <label for="sort-filter" class="sr-only">Sort products</label> <select id="sort-filter"> <option value="featured">Featured</option> ... </select> <!-- WRONG: aria-label without <label> element --> <input type="text" id="search" aria-label="Search products"> ↑ aria-label is great for screen readers but the scoresheet counts <label> ELEMENTS, not aria-label attributes. Add BOTH for maximum accessibility + scoresheet compliance.

Inputs That Don't Need Labels

The scoresheet excludes these input types from the count:

Best Practice: Add both a <label> element AND an aria-label attribute. The label satisfies the scoresheet; the aria-label provides a better screen reader experience for toolbar controls.

7. Email & Contact Info Handling

The scoresheet flags any email address that appears in HTML or JS source on a line that doesn't contain mailto:, "email", placeholder=, or application/ld+json. It also checks JS files.

Safe Patterns

<!-- SAFE: Email in a mailto: link (entire line excluded) --> <a href="mailto:info@domain.com">info@domain.com</a> <!-- SAFE: Email in a placeholder attribute --> <input type="email" placeholder="you@company.com"> <!-- SAFE: JSON-LD on same line as script tag --> <script type="application/ld+json">{"@type":"LocalBusiness","email":"info@domain.com"}</script>

Unsafe Patterns

<!-- UNSAFE: Plain text email without mailto: --> <p>Contact us at info@domain.com.</p> Fix: wrap in <a href="mailto:info@domain.com"> <!-- UNSAFE: JSON-LD content on separate line from script tag --> <script type="application/ld+json"> {"email":"info@domain.com"} </script> Fix: put opening tag and JSON on the same line The filter works LINE BY LINE. "application/ld+json" must be on the SAME LINE as the email for it to be excluded. <!-- UNSAFE: Email in JS file without "email" key --> const contact = 'info@domain.com'; Fix: use "email" in the key name: const email = 'info@domain.com';
Rule: Every email address in HTML must be wrapped in a mailto: link. Every email in JSON-LD must be on the same line as the application/ld+json script tag. Every email in JS must be on a line containing the string "email" (with quotes).

8. Auth System Pattern

Both Patriot Chassis and The High Road use the same standalone auth pattern. This is the standard for all client-facing sites.

Architecture

FileRole
api/auth.phpLogin (POST), register (POST), logout (POST). Returns session cookie.
api/auth-session.phpSession validation (GET). Returns user data if authenticated, 401 if not.
js/auth.jsClient-side auth state. Checks session on page load, manages login/logout UI, redirects.
login.htmlLogin form. Noindex. Redirects to account.html on success.
register.htmlRegistration form. Noindex. Redirects to login.html on success.
account.htmlAccount dashboard. Noindex. Shows login form if not authenticated, account info if authenticated.

Session Flow

1. User submits login form → POST /api/auth.php {action:"login", email, password} 2. Server verifies credentials → bcrypt password_verify() 3. Server creates PHP session → session_start(), $_SESSION['user_id'] = ... 4. Server returns {ok:true, user:{...}} + Set-Cookie: PHPSESSID=... 5. Client stores nothing → session cookie is httpOnly, handled by browser 6. On page load → auth.js calls GET /api/auth-session.php 7. Server checks $_SESSION → returns user data or 401 8. auth.js updates UI based on response (show/hide login, portal, account links)

Security Requirements

9. Client Portal Pattern

The client portal is a restricted area for authenticated clients to view their products, orders, and agreements. Both PC and THR use the same dual-state pattern.

Dual-State Page Structure

<!-- Blocked state: shown to non-authenticated users --> <div id="blocked-state" style="display:none"> <h2>Sorry, Not Authorized</h2> <!-- h2, NOT h1 (page already has h1 in main content) --> <p>This area is reserved for authorized clients...</p> <a href="shop.html">Browse Available Products</a> <a href="register.html">Create an Account</a> </div> <!-- Portal content: shown to authenticated clients --> <main id="main-content" style="display:none"> <h1>Client Portal</h1> <!-- The one and only h1 --> <!-- Tabs, product list, orders, agreements --> </main> <!-- auth.js toggles visibility based on session state -->
Gotcha — Multiple H1s: The blocked state heading must be <h2>, not <h1>. The <h1> belongs to the main portal content. The scoresheet counts ALL <h1> tags in the file regardless of display state. Two H1 tags = failure.

Portal Sections

SectionContentData Source
ProductsClient's product catalog with search, sort, filterapi/client-products.php → site DB
ManufacturingProduction status, materials, revision trackingapi/client-products.php with mfg fields
OrdersOrder history with status trackingapi/orders.php → site DB
AgreementsPurchase agreements and termsapi/agreements.php → site DB
AccountProfile, company info, password changeapi/profile.php → site DB

10. Product Data & JS Generation

Product data follows the three-tier architecture (see doc 10, section 4). The key implementation detail is how products flow from the database to the frontend.

The Pipeline

Portal (portal_products) ↓ Sync THR Hub (thr_products) ↓ Deploy Site DB (pc_products, ths_products, ao_products) ↓ generate-products-js.php products-data.js (static JS file served to browser)

generate-products-js.php

This script reads from the site's own DB table (never the Hub) and writes a static JavaScript file that the shop page consumes. Key behaviors:

Regeneration

After any product sync or deploy, regenerate the JS file:

php sitename/api/generate-products-js.php
Rule (from doc 10): NO website ever reads product data from the Hub. Not the public shop, not the client portal, not orders. ALL product reads come from the site's own table. The Hub is for admin management only.

11. Required CSS Utilities

Every site's styles.css must include these utility classes. They are referenced by the page template, form patterns, and component architecture.

/* Screen-reader only — visually hidden but accessible */ .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; } /* Skip-to-content link — hidden until focused */ .skip-link { position: absolute; top: -100%; left: 16px; background: var(--accent-primary); color: #fff; padding: 8px 16px; border-radius: 0 0 8px 8px; z-index: 10000; font-size: 14px; font-weight: 600; text-decoration: none; transition: top 0.2s; } .skip-link:focus { top: 0; } /* Honeypot field — hidden from real users */ .hidden-field { position: absolute; left: -9999px; opacity: 0; height: 0; overflow: hidden; }

12. Pre-Build Checklist

Before writing any HTML for a new site, complete these setup tasks. They take 10 minutes and prevent hours of rework.

Site Setup (Do First)

Create the folder structure — site root, css/, js/, api/, images/ (with logo/, hero/, products/, gallery/ subdirs), feeds/, tools/
Create css/styles.css with design tokens from discovery phase, plus the three required utility classes (.sr-only, .skip-link, .hidden-field) and at least 3 @media breakpoints
Create js/components.js with header, nav, and footer injection. Include ARIA roles, skip-nav target, and semantic HTML in the injected markup
Create robots.txtUser-agent: * with Sitemap: directive
Create sitemap.xml — start with index.html and add pages as you build them. Use filenames, not bare /
Create 404.html — uses the page template, has a friendly message and link back to homepage
Process ALL images before building pages — optimize, generate WebP companions, record dimensions. Don't add images ad-hoc during page building

Per-Page Checklist

For every new HTML page you create:

Copy the page template from Section 1. Fill in title, description, canonical URL, OG tags.
Classify the page (Section 5). Public pages go in sitemap immediately. Private pages get noindex meta tag.
Wrap content in <main> (or <section> / <article>). Every page must have static semantic HTML.
One <h1> only. Conditional states (blocked, loading, error) use <h2> or lower.
Every <img> gets alt, width, height. Below-fold images get loading="lazy".
Every form input gets a <label>. Toolbar controls get <label class="sr-only">.
Zero inline handlers. No onclick=, onchange=, etc. Use external JS with addEventListener.
Emails in mailto: links. JSON-LD on one line with its script tag.
No console.log in production JS. No document.write. No bare http:// URLs.
Run the scoresheet after completing the page. Fix failures immediately, not at the end of the build.
The Goal: Every page scores A+ on the first scoresheet run. No rework, no retrofitting, no "I'll fix it later." The template and checklists in this document exist because we learned the hard way on PC and THR. Follow them and you skip that learning curve entirely.