From 7 orchestra passes auditing 48 pages with GPT/Gemini/Grok:
All 14 sections present in canonical order
800+ word first-person logbook with emotional pivots + reflection
11+ unique WebP images with photo credits
Complete meta tags (ai-summary, OG, Twitter Cards, canonical)
3 JSON-LD schemas (BreadcrumbList, WebPage with Place mainEntity, FAQPage)
Port-specific content (no generic templates, no copy-paste)
Correct disclaimer level per port-disclaimer-registry.json
Zero audit issues from all 21 detection functions
Required HTML Structure
Head Section
<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width,initial-scale=1"><!-- ICP-2 v2.1 --><metaname="ai-summary"content="[PORT] features [SIGNATURE]. [KEY FACT]. [WHAT CRUISERS DO]. Max 250 chars, first 155 standalone."><metaname="last-reviewed"content="YYYY-MM-DD"><metaname="content-protocol"content="ICP-2 v2.1"><metaname="author"content="In the Wake"><metaname="description"content="First-person logbook guide to [PORT] — [KEY DETAILS]"><!-- OpenGraph --><metaproperty="og:type"content="article"><metaproperty="og:site_name"content="In the Wake"><metaproperty="og:title"content="[PORT] Port Guide — [SUBTITLE]"><metaproperty="og:description"content="[SAME AS description]"><metaproperty="og:url"content="https://cruisinginthewake.com/ports/[SLUG].html"><metaproperty="og:image"content="https://cruisinginthewake.com/assets/social/port-hero.jpg"><!-- Twitter Cards --><metaname="twitter:card"content="summary_large_image"><metaname="twitter:title"content="[SAME AS og:title]"><metaname="twitter:description"content="[SAME AS og:description]"><metaname="twitter:image"content="https://cruisinginthewake.com/assets/social/port-hero.jpg"><title>[PORT] Cruise Port Guide — In the Wake</title><linkrel="canonical"href="https://cruisinginthewake.com/ports/[SLUG].html"></head>
Structurally mandated by scripts/port-weather-validator-core.js for every port — tropical, temperate, polar, inland. The skeleton is the same for all ports; content inside each slot must be honest to the port. Reference template: ports/cozumel.html (passing). For non-Caribbean comparison: ports/bergen.html already uses this skeleton with North Atlantic content — note "Beach: N/A" is an acceptable honest activity-row value.
<divid="port-weather-widget"data-port-id="[SLUG]"data-port-name="[PORT]"data-lat="[LAT]"data-lon="[LON]"data-region="[REGION]"><noscript><divclass="seasonal-guide seasonal-guide-static"><!-- 1. At a Glance — all 5 metrics required (Temperature/Humidity/Rain/Wind/Daylight) --><divclass="seasonal-glance-grid"><divclass="seasonal-glance-item"><spanclass="glance-label">Temperature</span><spanclass="glance-value">[honest range]</span></div><!-- + Humidity, Rain, Wind, Daylight --></div><!-- 2. Best Time — 3 cruise-seasons + 5 activity-rows + months-to-avoid --><divclass="cruise-seasons-grid"><divclass="cruise-season cruise-season-high"><spanclass="season-label">Peak Season</span><spanclass="season-months">[months]</span></div><divclass="cruise-season cruise-season-transitional"><spanclass="season-label">Transitional Season</span><spanclass="season-months">[months]</span></div><divclass="cruise-season cruise-season-low"><spanclass="season-label">Low Season</span><spanclass="season-months">[months]</span></div></div><divclass="best-months-activities"><!-- All 5 activity-rows are required by label. For ports where an activity does not apply, the months value may be "N/A" or a brief honest note ("nearest swimmable beach is 90 min by car"). Never invent. --><divclass="activity-row"><spanclass="activity-label">Beach</span><spanclass="activity-months">[honest]</span></div><divclass="activity-row"><spanclass="activity-label">Snorkeling</span><spanclass="activity-months">[honest]</span></div><divclass="activity-row"><spanclass="activity-label">Hiking</span><spanclass="activity-months">[honest]</span></div><divclass="activity-row"><spanclass="activity-label">City Walking</span><spanclass="activity-months">[honest]</span></div><divclass="activity-row"><spanclass="activity-label">Low Crowds</span><spanclass="activity-months">[honest]</span></div></div><divclass="months-to-avoid"><spanclass="avoid-label">Consider avoiding:</span><spanclass="avoid-months">[months OR "None"]</span><spanclass="avoid-reason">([honest regional reason])</span></div><!-- 3. What Catches Visitors Off Guard — 3-7 items --><ulclass="catches-list"><li>…</li></ul><!-- 4. Packing Tips — 3-7 items --><ulclass="packing-list"><li>…</li></ul><!-- 5. Weather Hazards — always present; for low-hazard regions, state that honestly --><divclass="hazard-warning"><spanclass="hazard-icon">⚠️</span><divclass="hazard-content"><strong>[Honest regional hazard name]</strong><p>[Season / risk window]</p></div></div></div></noscript></div>
Weather FAQ topics (BLOCKING — 4 required, must appear in BOTH on-page FAQ list AND FAQPage JSON-LD schema). From scripts/port-weather-validator-core.js lines 127–132, the validator matches any of these regex patterns:
Topic
Validator regex (alternatives)
Honest phrasing per port
Best time to visit
best time…(visit|go|cruise) OR when…(visit|go|cruise)
"When is the best time to visit [PORT]?" — answer with THAT port's actual peak season
Hurricane zone → "When is hurricane season?". Non-hurricane → "When is the storm season?" or "Are there severe weather periods?" — Bergen: "North Atlantic storm season Oct–Feb"; Mediterranean: "Meltemi winds Jul–Aug"
Packing for weather
pack…(weather|clothes|clothing|jacket|layer) OR what…(pack|bring|wear) OR how…(dress|pack)
"What should I pack for [PORT]'s weather?" or "How should I dress?"
Rain concerns
rain…(ruin|cancel|affect|stop) OR will…rain OR weather…ruin
"Will rain affect my visit?" or "Does rain ruin excursions?"
The validator's FAQ_COUNT rule compares on-page FAQ count to FAQPage schema Question count. Keep them equal — add a schema Question entry for every on-page FAQ. Supported on-page formats (any one): <details class="faq-item"><summary>question, <summary>Q: question, or <p><strong>Q: question</strong>...</p>.
Regex collision avoidance. The validator counts a single FAQ against every topic regex it matches. Two FAQs matching the same topic regex trigger FAQ_DUP; one FAQ matching two topic regexes can starve a topic of its own match. Watched cases observed in production ports:
what…wear collides with Packing even when the question is about dress code, not weather. Pattern caught on abu-dhabi.html ("What should I wear to the Sheikh Zayed Grand Mosque?") and bali.html ("What should I wear to Balinese temples?"). Mitigation: for non-weather dress-code questions, reword as "What is the dress code at [VENUE]?" — no wear / pack / bring, no match.
when…cruise collides with Best time because the regex substring matches cruises inside any question that contains both when and the word cruise[s]. Pattern caught on baltimore.html (draft Q "When is hurricane and storm season for Baltimore cruises?" double-matched Storm + Best-time). Mitigation: for non-best-time "When…" questions, do not use the word cruise or cruises anywhere in the question. "When is hurricane and storm season in Baltimore?" is safe.
Default-safe phrasings (won't collide):
Best time: "When is the best time to visit [PORT]?" (uses best time + visit)
Storm season: "When is [PORT]'s storm season?" or "When is hurricane season in [PORT]?" (uses storm season or hurricane; avoid the word cruise)
Packing: "What should I pack for [PORT]'s weather?" (uses pack + weather; avoid wear and bring in other FAQs on the same page)
Rain: "Will rain ruin my visit to [PORT]?" (uses rain + ruin)
Forbidden seasonal terms. The validator enforces the cruise-seasons-grid terminology via TERM_001 / DEDUP. Do not write the phrase Shoulder Season (or lowercase shoulder season) anywhere on the page — the validator treats it as a forbidden term that conflicts with the canonical Peak / Transitional / Low labels. Caught on bodrum.html mid-pilot. Acceptable substitutes when describing the in-between months in prose: transitional months, transitional season (matches the grid label), the May/October shoulders only if you genuinely need the word "shoulders" (the validator allows the noun in a non-Season context, but the safer move is to avoid it).
Sourcing rule. For an existing port being repaired, draw weather content from what is already verifiable on its page: lat/lon, region, existing climate references, existing Author's Note, existing notices. Do not fabricate temperatures, hurricane seasons, hazards, or activities that aren't supported by the page.
Sidebar (<aside>)
<asideclass="card"style="grid-column: 2;"><!-- Author card --><!-- At a Glance (country, currency, language, etc.) --><!-- Key Facts --><!-- Nearby Ports --><!-- Recent Stories --><!-- Whimsical Units (optional) --></aside>
Content Rules (from careful-not-clever audit)
MUST DO
Use LOCAL CURRENCY for all prices (not USD unless the port uses USD)
Write port-SPECIFIC depth soundings (not generic)
Include "soundings in another's wake" disclaimer for unvisited ports
Every <img> in a <figure> must have <figcaption> with <span class="photo-credit">
All internal port links must end with .html
Use <div class="navbar"> for outer wrapper, <nav class="site-nav"> for inner
Populate all 5 weather-guide activity-rows (Beach/Snorkeling/Hiking/City Walking/Low Crowds) and all 4 weather FAQ topics for every port. For activities or hazards that do not apply to a port, write the honest answer ("Beach: N/A — nearest swimmable beach is 90 min by car"); never fabricate to satisfy the slot, but never omit the slot
MUST NOT
Use identical text from other port pages (template filler)
Use generic weather data ("Varies by season — check forecast"). Substitute real ranges grounded in the port's existing climate references; if data is missing, say so honestly ("Cool maritime; expect 50–65°F summer, 30–45°F winter — verify against current forecast")
Use "Tap water safety varies by destination" (specify actual water safety)
Use forbidden words: nightlife, once-in-a-lifetime, must-do, life-changing, YOLO
Repeat same emotional phrases across pages ("breath caught" is on 46 pages already)
Have contradictory Author's Notes (sidebar says unvisited, body says visited)
EMOTIONAL PIVOT MARKERS (need 2+ unique per page)
Choose DIFFERENT ones for each port — avoid "breath caught" and "whispered a quiet prayer" (overused):
node scripts/validate-port-weather.js ports/[SLUG].html → PASS or WARN (BLOCKING sub-validator that v2 spawns; WARN with only SPEC_REG is acceptable when the port is not yet in scripts/files-7/port-registry.json)
node admin/port-page-audit.cjs ports/[SLUG].html → 0 CRITICAL, 0 HIGH
node admin/validate-port-page-v2.js ports/[SLUG].html --gold-standard → 0 gold standard gaps