§ the logbook
Changelog
Public-facing record of what’s shipped. Newest first.
- feature¶
DEC fishing data — 16,666 stocking records + 87 launches on /lakes/[slug]
Every Adirondack lake/river page now surfaces NYS DEC stocking history when data exists. Sourced from data.ny.gov (public domain, annual refresh). Ingested: 16,666 stocking records (2011-2026, 13 ADK counties) + 87 boat launch points. 78.7% of records matched to lakes/rivers in our DB (496 distinct waters). Mirror Lake, Schroon, the Ausable West Branch, and hundreds of others now show year-grouped stocking history with species, counts, sizes, and dates. New tables: dec_stocking_records, dec_boat_launches. New route: /api/admin/dec-fishing/ingest (admin-gated, ?reset=1 for annual refresh). Closest analog to what this answers: the recurring r/Adirondacks question "was [lake X] ever stocked with [species Y]?" Now you can look it up. - feature¶
The Field — unified editorial hub launched (Guides + Briefs)
New /field route consolidates Guides + Briefs into a single editorial magazine. Both hubs reflowed to match — 3-col 16:10 cards everywhere, asymmetric leader feature, calendar countdown rail, category sections. Nav: lowered desktop breakpoint xl→lg so every laptop sees the mega-menu instead of the hamburger. The Field promoted to position 1; Regions moved to position 6 between Dining and Shop. Dropdown rebuilt with two featured image cards (Guide + Brief), § Brief/§ Guide chips on Recent list, and The 46 High Peaks pinned in the footer as a permanent fixture. Mobile drawer rewritten as accordion that pulls subcategories from the same navConfig. Briefs: 6 new briefs drafted + published (Ironman, Americade, BBQ, AMR, ORDA, Mud Season). All 10 published briefs now carry hero photos. Live ticking countdown band + .ics download + Event JSON-LD on event briefs. Logbook: /my/logbook unauth landing replaces bare /signin redirect with a value-prop page + inline magic-link form. Shipped via PR #653 (umbrella) consolidating 14 stacked PRs. feat(guides): Adirondack gardening field guide + zone atlas
New /guides/gardening full field guide covering hardiness zones across the Park (3b–5a on the 2023 USDA map), the first-year garden step-by-step, deer-resistant perennials by bloom season (with the honest negative list of what deer will eat), native trees + how-to plant, vegetables that work in a 95–145 day growing season, a month-by-month planting calendar, ADK soil reality (acidic glacial till), pests + weather, garden centers + seed catalogs, and a 13-question FAQ keyed to high-volume search queries. Includes bespoke ZoneAtlas client component: 25 ADK hamlets plotted as zone-coded markers (3b violet, 4a oxblood, 4b ochre, 5a moss, 5b dark moss) with frost dates, growing-season length, elevation, and what-grows notes; sidebar zone filter chips + grouped locations. Hero photo (perennial border with high-peaks backdrop) + OG card delegate using the shared lib/og/guide-card.tsx helper from PR #461.feat(og): guide OG cards use the page's hero image
Every guide's OG social-share card now embeds the guide's actual hero image (the same JPG/PNG the on-page HeroPlate displays) as the right-column visual — consistent across all 16 live guides instead of inheriting the generic /guides index card. New shared lib/og/guide-card.tsx helper sniffs PNG/JPEG/WebP by magic bytes (extension-based MIME detection blew up the build on one file that was PNG-as-.jpg). Per-route opengraph-image.tsx delegate in each concrete guide directory; the dynamic [slug] route uses the same helper. Verified all 16 endpoints return valid 1200×630 PNGs (393–537 KB) with embedded hero photos.feat(guides): whitewater rafting field guide + interactive atlas
New /guides/whitewater-rafting full field guide covering the Hudson Gorge (Class III-IV, dam-fed, Apr–Oct), Bottom Moose (IV-V, spring snowmelt), and lower Sacandaga (II-III, family-friendly). Introduces a bespoke WhitewaterAtlas client component — three river polylines, 14 class-coded named rapids, put-in/take-out endpoints, and six outfitter pins on one filterable Leaflet canvas. Includes 15 editorial sections, comparison + season tables, 10-Q FAQ, Article + Breadcrumb schema, real cover image, and a side-by-side live <GuideMap> directory section beneath the editorial atlas. Geometry is editorial-grade and can be promoted to the routes table with real GPX later.feat(guides): whitewater rafting field guide + interactive atlas
New /guides/whitewater-rafting full field guide covering the Hudson Gorge (Class III-IV, dam-fed, Apr–Oct), Bottom Moose (IV-V, spring snowmelt), and lower Sacandaga (II-III, family-friendly). Introduces a bespoke WhitewaterAtlas client component — three river polylines, 14 class-coded named rapids, put-in/take-out endpoints, and six outfitter pins on one filterable Leaflet canvas. Includes 15 editorial sections, comparison + season tables, 10-Q FAQ, Article + Breadcrumb schema, real cover image, and a side-by-side live <GuideMap> directory section beneath the editorial atlas. Geometry is editorial-grade and can be promoted to the routes table with real GPX later.Release-notes log: schema + admin UI + /changelog
## Why Just discovered ~10+ items in the open backlog that actually shipped months ago — memory drift from auditing without a primary source. This adds an append-only `release_notes` log so future audits become `select * from release_notes order by shipped_at desc` instead of git archaeology. ## What ships - `release_notes` table + RLS (admin write/read-all, public read for `visibility='public'`) - Admin CRUD at `/admin/settings/release-notes` (list 50/page, new, edit, delete) - API at `/api/admin/release-notes` — gated with the new `requireAdmin()` helper from PR #128 - Public `/changelog` page (server-rendered, hourly revalidate, canonical, individual entry anchors) - Sitemap entry for `/changelog` with `lastmod` tracking the latest public note - AdminHeader + AdminMobileNav links under the Settings dropdown ## Migration SQL (run in Supabase Studio) ```sql -- 084_release_notes.sql create table if not exists public.release_notes ( id uuid primary key default gen_random_uuid(), shipped_at timestamptz not null default now(), title text not null, body text, pr_number int, pr_url text, category text not null check (category in ('feature','fix','chore','content','infra','security')), visibility text not null default 'internal' check (visibility in ('internal','public')), author_id uuid references auth.users(id) on delete set null, created_at timestamptz not null default now(), updated_at timestamptz not null default now() ); create index if not exists idx_release_notes_shipped_at on public.release_notes (shipped_at desc); create index if not exists idx_release_notes_visibility on public.release_notes (visibility); create index if not exists idx_release_notes_category on public.release_notes (category); -- Reuse the project-wide set_updated_at() helper (defined in 082 and earlier). create or replace function public.set_updated_at() returns trigger as $$ begin new.updated_at = now(); return new; end; $$ language plpgsql; drop trigger if exists release_notes_set_updated_at on public.release_notes; create trigger release_notes_set_updated_at before update on public.release_notes for each row execute function public.set_updated_at(); -- ---------- RLS ---------- alter table public.release_notes enable row level security; drop policy if exists release_notes_public_read on public.release_notes; create policy release_notes_public_read on public.release_notes for select using (visibility = 'public'); drop policy if exists release_notes_admin_read on public.release_notes; create policy release_notes_admin_read on public.release_notes for select using ( coalesce(auth.jwt() -> 'app_metadata' ->> 'role', '') in ('admin', 'editor') ); drop policy if exists release_notes_admin_write on public.release_notes; create policy release_notes_admin_write on public.release_notes for all using ( coalesce(auth.jwt() -> 'app_metadata' ->> 'role', '') in ('admin', 'editor') ) with check ( coalesce(auth.jwt() -> 'app_metadata' ->> 'role', '') in ('admin', 'editor') ); ``` ## Files added / changed **New** - `supabase/migrations/084_release_notes.sql` - `adk/src/app/api/admin/release-notes/route.ts` — GET / POST / PATCH / DELETE, all gated via `requireAdmin()` (matches F1 pattern from PR #128). Whitelisted fields, enum validation, length caps (title 200, body 10k, pr_url 500), URL parsing. - `adk/src/app/admin/settings/release-notes/page.tsx` — paginated list, server-side admin gate - `adk/src/app/admin/settings/release-notes/new/page.tsx` - `adk/src/app/admin/settings/release-notes/[id]/page.tsx` — edit + delete - `adk/src/app/admin/settings/release-notes/ReleaseNoteForm.tsx` — shared client form - `adk/src/app/changelog/page.tsx` — public list, `alternates.canonical: '/changelog'`, `revalidate = 3600` **Changed** - `adk/src/components/AdminHeader.tsx` — Settings dropdown link - `adk/src/components/AdminMobileNav.tsx` — Settings nav link - `adk/src/app/sitemap.ts` — `/changelog` entry with dynamic `lastmod` from latest public note All admin pages set `metadata = { robots: { index: false, follow: false } }`. All admin pages run `force-dynamic`. Auth gate: server-side `getUser()` + `app_metadata.role ∈ {editor, admin}`, redirect to `/sign-in?next=...` on failure (matches the existing pattern). API routes use the new `requireAdmin()` helper and return 404 on failure (matches F1 pattern). ## Verified - `npx tsc --noEmit` clean - `npm install` clean ## Not verified - No browser smoke test — DRAFT PR; verify after migration runs. - Did not run a full `npm run build` — typecheck-only. ## Instructions 1. Run the SQL above in Supabase Studio. 2. Mark this PR ready for review and merge. 3. Visit `/admin/settings/release-notes` and add the first entry (suggestion: backfill the recent Mercantile launch + Security audit shipped items). 4. Public changelog lives at `/changelog`. ## Deferred / follow-ups - **PR-merge auto-logger workflow** (`.github/workflows/release-notes-skeleton.yml`) deferred — out of scope for this PR; can be added in a tiny follow-up that comments a copy-pasteable INSERT on every merged PR. - Markdown rendering on `/changelog` is plain `whitespace-pre-wrap`. No new dep added (the codebase doesn't have a markdown renderer). If we want real markdown later, add `react-markdown` in a follow-up. - `audit-api-auth.sh` referenced in the prompt does not exist in this branch yet — the route still passes the regex (`requireAdmin` is the gate token).Mercantile order portals + SEO/sitemap fixes
## Summary Three coupled bodies of work on one branch: **Mercantile order management** - `/my/orders` customer order history + detail with status timeline (paid → fulfilled → shipped → delivered), tracking link, hard email-match guard - `/admin/mercantile/orders` admin list + detail with stats banner, status tabs, search, Stripe + Printify deep links — capability-gated to `admin` - Stripe webhook now idempotently creates a Supabase auth user on every paid order (`supabase.auth.admin.createUser` + email_confirm) so customers can sign in to view history retroactively - Order confirmation email now mentions `/signin` to view orders **SEO / GSC indexation fixes (the priority — content + traffic depend on this)** - **Sitemap parser error fixed** — unescaped `&` in `<image:loc>` from `/api/google/photo?...&w=1200` was breaking GSC ingestion at line 18303 onward (~6+ instances; GSC only reports the first). Dropped `&w=1200` (API defaults to 1024) and routed lake images through the existing `isXmlSafeImage` guard so the class of bug is closed - Mercantile product pages added to sitemap (7+ live products were absent) - Canonical tags added to chapter pages (`/lodging`, `/provisions`, `/pursuits`, `/attractions`, `/events`, `/real-estate`, `/necessities`, `/mercantile`) and listing detail pages - `noindex,nofollow` on the new `/my/orders` + `/admin/mercantile/orders` routes to match the rest of admin **No DB migration required.** `/my/orders` queries by `customer_email = session.email` which works retroactively for orders placed before this ships. ## Punch list still open after deploy (lower priority) - Homepage canonical inconsistency (trailing slash) — flag, not blocking - Verify region-pivot `/[chapter]/[subcategory]/in/[region]` canonical points at the pivot URL post-deploy - Spot-check Mercantile product 404 returns true 404, not soft-404 - `<lastmod>` for static routes is always `now` — consider freezing to build date ## Test plan - [ ] Vercel build is READY - [ ] `curl https://www.adirondackregion.com/sitemap.xml | xmllint --noout -` exits 0 - [ ] Resubmit sitemap in GSC; "Validate fix" the 4 prior categories from project_gsc_health - [ ] Place a test order; confirm a Supabase auth user gets created - [ ] Sign in with that email; confirm `/my/orders` shows the order and detail timeline renders - [ ] Confirm `/my/orders` does NOT show another user's orders - [ ] As admin, hit `/admin/mercantile/orders`; confirm list, search, detail, Stripe + Printify deep links work - [ ] `curl -I https://www.adirondackregion.com/my/orders` returns 307 → /signin (anon) - [ ] curl a chapter page and confirm `<link rel="canonical">` is now present 🤖 Generated with [Claude Code](https://claude.com/claude-code)Mercantile post-launch polish: nav promo + OG + utility bar
Three small post-launch improvements: 1. **Nav dropdown** — featured t-shirt promo card in the Adirondack Region Goods column, removed empty Featured column, dropped 'coming soon' label 2. **OG images** — per-product OG image (first Printify mockup) on product detail pages; storefront OG dynamically uses first product image 3. **Top utility bar** — was nearly invisible. Bumped contrast + added ShoppingBag icon next to 'The Bag' label All visible to real customers since storefront is live. 🤖 Generated with [Claude Code](https://claude.com/claude-code)Mercantile launch: storefront UI + URL rename + dev cleanup
## Summary The final PR before flipping the feature flag. Brings the customer-facing storefront UI from the dev branch onto main, replaces the existing 'coming soon' placeholder, and strips dev affordances. Storefront stays gated by \`NEXT_PUBLIC_FEATURE_MERCANTILE_SHOP\` — this PR alone doesn't change customer experience. The flag stays unset on Production until you flip it deliberately. ## Pages - \`/mercantile/adirondack-region-goods\` — catalog grid (was 'coming soon') - \`/mercantile/adirondack-region-goods/[slug]\` — product detail - \`/mercantile/adirondack-region-goods/cart\` — the bag - \`/mercantile/adirondack-region-goods/orders/[id]/success\` — post-checkout ## URL hygiene - 301 redirects in \`next.config.ts\` for any old \`/mercantile/preview/*\` URLs - robots: noindex stripped from storefront + product detail - Sitemap entry added at priority 0.7 ## Operational - New \`MERCANTILE_HOLD_ORDERS\` env flag — when set, orders pause in Printify 'On hold' for operator review - Live Printify shipping rates at checkout (replaces flat \$5) - Admin tools: \`/api/mercantile/audit-prices\`, \`/api/mercantile/preview-email/order-confirmation\` ## Launch sequence after this merges In order: 1. Set \`MERCANTILE_HOLD_ORDERS=true\` on Production (so first real-money test order pauses for review) 2. Activate Stripe + register live-mode webhook destination at \`https://www.adirondackregion.com/api/mercantile/webhooks/stripe\` 3. Swap on Production scope: - \`STRIPE_SECRET_KEY\` → \`sk_live_...\` - \`STRIPE_PUBLISHABLE_KEY\` → \`pk_live_...\` - \`STRIPE_WEBHOOK_SECRET\` → live \`whsec_...\` 4. Add \`NEXT_PUBLIC_FEATURE_MERCANTILE_SHOP=true\` (Production scope) 5. Redeploy 6. Place one real low-cost order with your card → verify in Stripe live + Supabase + email 7. In Printify, manually click 'Send to production' on the held order to verify fulfillment 8. Once verified, unset \`MERCANTILE_HOLD_ORDERS\` so future orders auto-flow 🤖 Generated with [Claude Code](https://claude.com/claude-code)Order email: bigger order #, drop bold, fix shipped wording
Three small changes to the mercantile_order_confirmation template: - Order # gets a custom 14px ochre kicker instead of the 10px shared one - Heading drops to font-weight 400 (was 600) - 'The bag has shipped to the press.' → 'Your order is on the press.' (shipped implied parcel-in-transit) Production webhook reads this template, so live emails need this on main. 🤖 Generated with [Claude Code](https://claude.com/claude-code)Homepage chapter tile: events count from events table
Tile showed 0 because chapter counts came from grouping listings by `chapter_slug` — events live in their own table. Now counts published events directly. Tile shows 38 today.Boat Tours and Cruises subcategory + Lake George Steamboat reassignment
## Summary Adds a dedicated **Boat Tours & Cruises** subcategory under **Attractions**, sitting next to Scenic Railroads — both are passive you-ride-and-watch attractions. Lake George Steamboat Company was sitting under `historic-sites`; this gives it (and future cruise operators) a proper home. - Migration **038**: insert `boat-tours-and-cruises` at sort_order 6 (between `scenic-railroads` and `mines-and-caves`); renumbers everything from 6+ down by one. Reassigns Lake George Steamboat in the same migration. - Activities mega-menu picks up the new link after Scenic Railroads. - AI generation rule (`category-rules.ts`) covers vessel character, route, cruise length, themed cruises, boarding/parking. - Import sub-mapping handles `boat-tours` / `cruises` keywords. ## Migration to apply ```sql update public.subcategories set sort_order = sort_order + 1 where chapter_slug = 'attractions' and sort_order >= 6; insert into public.subcategories (slug, chapter_slug, name, description, sort_order) values ('boat-tours-and-cruises', 'attractions', 'Boat Tours & Cruises', 'Steamboat tours, scenic lake cruises, dinner cruises, and narrated sightseeing trips on Adirondack waters.', 6) on conflict (slug) do update set chapter_slug=excluded.chapter_slug, name=excluded.name, description=excluded.description, sort_order=excluded.sort_order; update public.listings set subcategory_slug = 'boat-tours-and-cruises' where slug = 'lake-george-steamboat-company-591428'; ``` ## Test plan - [ ] Vercel preview READY - [ ] Activities mega-menu shows "Boat Tours & Cruises" between Scenic Railroads and Family Attractions - [ ] `/attractions/boat-tours-and-cruises` lists Lake George Steamboat Company - [ ] Old URL `/attractions/historic-sites/lake-george-steamboat-company-591428` no longer shows it (now under the new subcategory route) - [ ] /admin filter on subcategory `boat-tours-and-cruises` returns Lake George Steamboat 🤖 Generated with [Claude Code](https://claude.com/claude-code)Trending: hide view counts below threshold
Hide `X VIEW` labels on /trending until count >= 100. Logic + sort still active.Anchor venue enrichment: Adk Experience, Olympic Center, Whiteface Lodge
## Summary One-shot enrichment of the three anchor-venue stubs seeded in PR ~22. Adds a `scripts/enrich-anchor-venues.mjs` that mirrors the live pipeline (`src/lib/enrichment/`) — Firecrawl scrape, photo HEAD-check, Gemini long-description, fill-only apply with `auto_enriched_fields` stamping so admin hand-edits stay locked. Already executed against live Supabase (intentional). Three rows updated; no migrations. ## Per-venue results | venue | website used | photos before/after | short | long | phone | hours | |---|---|---|---|---|---|---| | Adirondack Experience | https://www.theadkx.org | 0 → 6 | unchanged (193 chars, editorial) | filled (1078 chars) | filled | not found | | Olympic Center | repointed: lakeplacid.com → https://www.whiteface.com/places/herb-brooks-arena | 0 → 0 (host blocks HEAD) | unchanged (192 chars, editorial) | filled (821 chars) | filled | not found | | Whiteface Lodge | https://www.thewhitefacelodge.com | 0 → 4 | unchanged (135 chars, editorial) | filled (1146 chars) | filled | not found | Olympic Center: lakeplacid.com is the regional CVB, not the venue. Repointed to the NYS Olympic Regional Development Authority (ORDA) venue page on whiteface.com, which is the authoritative source. The host 403s on HEAD requests, so 0 photos passed the live-check — long description and phone still came through. Photo-fetch can be revisited as part of the broader Enrichment overhaul (queued in memory). Hours: simple "## Hours" heuristic didn't match any of the three sites' markup. Acceptable for now — the broader overhaul will move hours to a structured extractor. ## Edit-flow verification Confirmed (no code changes needed): - `src/app/admin/listings/[id]/edit/` exists (page.tsx + ListingEditorClient.tsx, 1245 LOC) and lets admins edit every enriched field. - `src/app/api/admin/listings/update/route.ts` strips edited keys from `auto_enriched_fields` on save (lines 69–99), so once an admin touches a field, future enrichment runs treat it as editorial-locked and skip it (`shouldWrite()` in pipeline.ts). - The script's apply step uses the same `shouldWrite()` rule and stamps `auto_enriched_fields` for keys it wrote (`phone`, `long_description`, `website` for Olympic Center). Future enrichment is allowed to refresh those; admin edits override. Net: existing edit + lock flow is correct. Verified editable. ## Files touched - `adk/scripts/enrich-anchor-venues.mjs` (new, 578 LOC) ## Test plan - [ ] Open `/admin/listings/<id>/edit` for each of the three slugs and confirm the long_description, phone, and photos appear in the form - [ ] Edit one field on one listing, save, then confirm `auto_enriched_fields` no longer contains that key (re-enrichment would now leave it alone) - [ ] Inspect each public listing page for the new hero/gallery photos 🤖 Generated with [Claude Code](https://claude.com/claude-code)Nav: move History and Story from Activities to Guides
## Summary - Activities dropdown was running 4 columns (Attractions, Mercantile, Events, History & Story); History/Stories/46 read as editorial/heritage rather than activities, so they relocate to Guides. - "The 46 High Peaks" stays grouped with Timeline + Stories under the heritage framing (it already lives in Pursuits > Explore for the activity-side entry point). - Mobile drawer already surfaces History + The 46 in its TOOLS bucket; no MobileNav change needed. ## Before **Activities dropdown** (4 columns): - Attractions - Mercantile - Events - **History & Story** — Timeline / Stories (Blog) / 46 High Peaks **Guides dropdown** (2 dynamic columns): - Field Guides (live guides, first half) - More (live guides, second half) ## After **Activities dropdown** (3 columns): - Attractions - Mercantile - Events **Guides dropdown** (3 columns): - Field Guides (dynamic) - More (dynamic) - **History & Story** — Timeline / Stories (Blog) / 46 High Peaks ## Files touched - `adk/src/components/navConfig.ts` ## Test plan - [ ] Vercel preview READY - [ ] Hover Guides nav: History & Story column visible alongside guides - [ ] Hover Activities nav: only Attractions / Mercantile / Events - [ ] Mobile drawer: History + The 46 still reachable via TOOLS 🤖 Generated with [Claude Code](https://claude.com/claude-code)Footer: remove phone+email duplicates
Phone + email line under copyright was duplicating /contact page. Stripped.Lake pages: categorized POIs (marinas, beaches, bait, food)
## Summary - /lakes/[slug] now plots four lake-relevant categories with typed pins on the map, an in-corner legend that doubles as a 4-toggle filter, and a categorized below-map list (Marinas/launches · Public beaches · Bait & tackle · Food). Food capped at 10 on the list with a "+ N more in region" overflow link, and 25 on the map so a busy lake doesn't drown the marina/beach pins. - Reuses POI-pin glyphs (extended with marina, public_beach, bait_tackle, food) so categorized pins read as part of the editorial atlas — not tier promotions. - Migration `036_lake_poi_types.sql` adds a `bait-and-tackle` subcategory under `mercantile` (zero rows today; future imports / proprietor claims will land here). ## Data-source audit Audit script (gitignored) found: - `outdoor_pois.poi_type` distinct values: `summit:679, leanto:273, firetower:27, ranger_station:17, shelter:4` — no marina/beach/launch types. **Decision: don't extend** — all four lake-page categories map to listings. - Listings taxonomy: `subcategory_slug=marinas-and-boat-launches` (12), `swimming-beaches` (27), no `bait-and-tackle` (added by mig 036), `chapter_slug=provisions` for food (245 total: 170 casual-dining, 23 pubs, 20 cafes, 9 ice-cream, 7 breweries, 4 fine-dining, 2 bakeries, etc.). ## Migration preview (036_lake_poi_types.sql) ```sql insert into public.subcategories (slug, chapter_slug, name, sort_order) values ('bait-and-tackle', 'mercantile', 'Bait & Tackle', 11) on conflict (slug) do update set ...; ``` ## Per-category counts (Lake Placid, 7-mi radius) - Marinas: 0 - Public beaches: 1 (Lake Placid Public Beach, 0.1 mi) - Bait & tackle: 0 - Food: 3 (Big Slide Brewery, Smoke Signals, The Dancing Bears) ## Map legend Top-right corner. 4 rows, each a circular swatch + label + count, click to toggle. Toggling fades the row + line-throughs the label. Marina=teal, Beach=ochre sand, Bait=moss, Food=oxblood. ## Judgment calls 1. **No `outdoor_pois.poi_type` extension.** The audit showed all four categories already exist as listings. Extending the OSM-driven `outdoor_pois` table for the same four types would create a parallel data source we'd have to maintain. If a future pass wants to seed *non-business* DEC public boat launches, that's its own migration. 2. **Reused POI-pin glyphs, not tier pins.** Listings on /lakes/[slug] paint as POI dots (small, flat colors), not tiered diamonds. This page is editorial discovery, not a paid showcase — the existing /chart and category pages are where tier hierarchy reads. 3. **Centroid radius is fixed at ~7 mi (11.3 km).** Lake George is 32 mi long; its centroid is mid-lake, so Million Dollar Beach (south end) sits outside 7 mi. Lake-size-aware radius (5/10/15 mi) was flagged in the spec as an open question; punting to a follow-up where each lake gets a `radius_km_override` column and the seed pass populates it. 4. **Food list cap = 10, map cap = 25.** Hardcoded; small lakes show all, busy lakes (e.g. Lake Placid village, Saranac, Lake George village) get a region-link overflow. Tunable later. ## Files touched - `supabase/migrations/036_lake_poi_types.sql` (new) - `adk/src/lib/map-pins.ts` (4 new POI pin types) - `adk/src/app/lakes/[slug]/page.tsx` (5 parallel queries, list section, helpers) - `adk/src/app/lakes/[slug]/LakeMap.tsx` (4 listing layers + legend/toggle UI) - `.gitignore` (audit script outputs) ## Test plan - [ ] Apply migration on staging, confirm `bait-and-tackle` subcategory shows in /admin/taxonomy under mercantile. - [ ] Visit /lakes/lake-placid on preview — verify Lake Placid Public Beach pin (ochre), 3 food pins (oxblood), legend toggles work, "Open listing" links resolve. - [ ] Visit /lakes/lake-george on preview — sparse counts confirm the centroid + radius behavior; legend still renders. - [ ] Toggle legend rows: line-through + count remains visible while pins disappear from the map. - [ ] Mobile breakpoint: legend stays inside map bounds on iPhone width; categorized list reflows to single column. 🤖 Generated with [Claude Code](https://claude.com/claude-code)Legal pages (privacy/terms/colophon) + footer fixes + phone placeholder
## Pages - **`/privacy`** — Privacy policy in the editorial template (§ kicker, Fraunces title, italic subtitle, mono dateline). Sections: what we collect, third-party services (Supabase / Vercel / Firecrawl / Gemini / Claude / Mapbox / MapTiler / GA), cookies & tracking, retention & rights, contact. \"Draft\" disclaimer at top. - **`/terms`** — Terms of service. Sections: acceptable use, listing accuracy, sponsorship & affiliate disclosure, DMCA, outdoor-activity liability, subscriptions, governing law (NY), contact. \"Draft\" disclaimer at top. - **`/colophon`** — Editorial masthead-style page. Sections: editorial mission, type, design system, tools, photographs & illustrations, editorial standards, acknowledgements (46ers, ADK Mountain Club, NYSDEC, APA), the masthead. Written like magazine front-matter, not boilerplate. All three are statically prerendered, have title/description/canonical metadata, and reuse the existing `Masthead` + `Colophon` page chrome. ## Footer fixes - Colophon component link to `/about` swapped for `/colophon` (the old href 404'd). - City line: \"Saranac Lake, NY\" → **\"Northville, NY\"**. - Added a small phone + email line under the copyright row. ## Phone placeholder Scott will provision a Google Voice number later. The literal string `(518) 555-XXXX` is rendered in two visible spots, plus a placeholder `+1-518-555-0000` in the Organization JSON-LD `telephone`. ## Scott's checklist - [ ] Provision Google Voice number with a 518 (or 315) area code - [ ] Swap `(518) 555-XXXX` and `tel:+15185550000` in `adk/src/components/Colophon.tsx` - [ ] Swap `(518) 555-XXXX` and `tel:+15185550000` in `adk/src/app/contact/page.tsx` - [ ] Swap `+1-518-555-0000` in `adk/src/lib/seo/jsonld.tsx` (`organizationSchema`) - [ ] Counsel review of `/privacy` + `/terms` before paid tiers ship - [ ] Cookie banner / GDPR-CCPA pass (queued separately) ## Verification - `tsc --noEmit`: 0 new errors (2 pre-existing in `summarize.ts`, unrelated) - `eslint` on touched files: clean - `npm run build`: succeeds; `/privacy`, `/terms`, `/colophon` show as `○` (static) 🤖 Generated with [Claude Code](https://claude.com/claude-code)Beaches guide: real hero image
Swap HeroPlate placeholder for the golden-hour swimmers photo.Fishing guide: even bigger species images
## Summary Round 2 of the species-image upsize. PR #88 bumped render sizes; Scott still says too small. Two-pronged fix: **Canvas margin tightened** (reprocessed 18 webps) - Old: ~10% transparent margin around fish on 600x300 canvas - New: ~3% margin (inner fit box 580x280) - Payload: 339KB -> 286KB total (less wasted transparent area) **Render sizes bumped again** - Desktop table thumb: 120x60 -> **160x80** - Mobile card thumb: 360x180 -> **480x240** - Inline `SpeciesFigure` max-width: 500 -> **560** (held under 600 so the floated md+ layout still leaves enough prose width on the 820px article column) **New tooling** - `adk/scripts/process-species-images.mjs` (Pillow + cwebp) so future re-runs are reproducible. ## Test plan - [ ] Vercel preview READY - [ ] /guides/fishing desktop: table thumbs noticeably larger, fish fills the box - [ ] /guides/fishing mobile: species cards show larger fish illustrations - [ ] Inline `SpeciesFigure` floats render at 560px max with prose still readable beside them - [ ] No layout regressions on the floated species figures Do NOT merge until preview is verified. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>RelatedGuides: card style matches homepage
## Summary - Mirrors the homepage guide-card pattern on `RelatedGuides.tsx` so the kicker no longer overlaps the printed title on the poster artwork. - Full uncropped poster via `aspect-square`; kicker eyebrow now sits below the image and above the card title (mono small-caps, ochre). - 3-up grid (vs homepage's 5) with larger cells: 1-up mobile, 2-up md, 3-up lg. ## Test plan - [ ] Wait for Vercel preview READY - [ ] Open any live guide page; scroll to the "Continue exploring" footer - [ ] Confirm kicker badges no longer cover poster title text - [ ] Confirm hover applies ochre border + paper-100 fill - [ ] Mobile stacks 1-up Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>POI linking sweep: LinkedPlace across all 7 guides
## Summary - Adds a unified named-place cross-linking system covering lakes, peaks (46ers), trails, regions, listings, and trailheads — used in editorial prose across every guide page. - New infrastructure: `lib/places/index.ts` bulk-loads all linkable POIs in one cached fetch with daily revalidation; `PlacesProvider` supplies the serialized index to every `<LinkedPlace>` in a guide-page React tree; `LinkedPlace` resolves name lookups in-memory with optional `kind` and `regionHint` disambiguation. - All 7 guide pages converted to async server components and wrapped in `<PlacesProvider>`. First-mention `<LinkedPlace>` wraps applied throughout body prose. - Audit script extended to extract `<LinkedPlace>` invocations, resolve against the unified catalog, and emit per-kind/per-guide reports. `--stage` now sends natural features to a separate CSV and only stages business-kind missing into `staged_listings`. ## Per-guide wrap counts | Guide | matched | missing | |---|---|---| | beaches-and-swimming-holes | 7 | 0 | | family | 10 | 0 | | fishing | 8 | 0 | | high-peaks | 6 | 0 | | spring-2026 | 8 | 0 | | vacation-home | 10 | 0 | | weddings | 2 | 0 | | **Total** | **51** | **0** | By kind: **lake 18 · peak 10 · region 13 · listing 10** — 100% match rate. ## Biggest judgment calls - **Map vs Object for the cached catalog.** First pass used a Map inside `unstable_cache`, which broke prerender on cache rehydration (`a.byNorm is not iterable`). Switched to a plain `Record<string, PlaceEntry[]>` so the same object survives both the cache and the RSC-prop boundary unchanged. - **First-mention-only discipline.** Wrapping every occurrence creates link soup; the editorial standard is link the first occurrence per chapter and let subsequent mentions stay as plain text. This produces 51 wraps across ~21k editorial lines — deliberately conservative, not exhaustive. - **Skipped data-array entries.** Many named-place mentions live inside JS object arrays rendered via `.map()` (HOME_BASES, vendor card data, atlas tables). Wrapping inside data arrays would require restructuring those components. The child components (FamilyActivityAtlas, FishingWatersAtlas, VendorGrid, PeaksTable) already deep-link via their own slug logic, so the high-leverage editorial body prose was the right place to focus. - **Weddings page kept LinkedListing pattern.** The wedding guide's vendor sections were already heavily linked via `LinkedListing` + `RegionLink`. Added `PlacesProvider` for future inline body wraps but didn't churn the existing well-formed component logic. - **Peak alias rules.** Database has "Allen Mountain" / "Algonquin Peak" / "Cliff Mountain"; editorial writes "Allen", "Algonquin", "Cliff". Alias logic strips trailing "Mountain"/"Peak" and "Mount " prefix on register, then re-adds suffixed forms when the canonical row is bare — covers all 46 peaks without per-row hand-curation. ## Test plan - [x] `npm run build` clean (143 static pages prerender successfully) - [x] `tsc --noEmit` clean - [x] `eslint` clean (0 errors, 0 warnings) - [x] Audit script: 51/51 matches, 0 missing - [ ] Vercel preview deploys cleanly - [ ] Spot-check `/guides/fishing`, `/guides/family`, `/guides/high-peaks` — confirm linked names route to the correct `/lakes/{slug}`, `/46ers/{slug}`, `/regions/{slug}`, etc. - [ ] No console errors at runtime 🤖 Generated with [Claude Code](https://claude.com/claude-code)Site search: include lakes + trails + peaks
## Summary Extends `/api/search` (and the `SiteSearch` modal) from listings/events/regions to also cover lakes, trails, and 46er peaks. All six queries run in parallel via `Promise.all`. ## New kinds + subtitles - **lake** -> `/lakes/<slug>` -- subtitle: `${primary_water_type} · ${region}` (either part may be omitted if missing) - **trail** -> `/pursuits/trails/<slug>` -- subtitle: `${(length_km * 0.621).toFixed(1)} mi · ${operator}` (operator omitted if null) - **peak** -> `/46ers/<slug>` -- subtitle: `${elevation_ft.toLocaleString()} ft · ${region}` (querying the existing `peaks` table; same scoring rules as the rest) Scoring (per kind): exact-name 100 / starts-with 80 / includes 60 / desc-only 40, identical to existing logic. ## UI changes (`SiteSearch.tsx`) - `Result["kind"]` union extended with `lake | trail | peak` - Three new grouped sections in the modal with lucide icons: `Droplet` (lake), `Footprints` (trail), `Mountain` (peak) - Placeholder copy updated to mention the new kinds ## Notes - Lakes/trails/peaks are public-readable (RLS), so no `status` filter is applied (matches how `/lakes`, `/pursuits/trails`, `/46ers` already render them) - `tsc --noEmit` clean; pre-existing `react-hooks/set-state-in-effect` errors in `SiteSearch.tsx` are unchanged (not introduced by this PR) ## Test plan - [ ] Vercel preview READY - [ ] Search "marcy" -> peak result hits `/46ers/marcy` - [ ] Search "lake placid" -> lake result, region result both appear - [ ] Search a known trail name -> trail result with mi/operator subtitle - [ ] Existing listing/event/region results still appear and rank correctlyFishing guide: larger fish images
## Summary - Bump rendered sizes for fish species images so they look bigger after PR #87's uniform 600x300 canvas with transparent margin made them appear smaller. - Desktop species table thumb: 80x40 -> 120x60. - Mobile species card thumb: 280x80 -> 360x180. - Inline `SpeciesFigure` (chapters 2-5): figure max-width 340px -> 500px (Image is `w-full h-auto` so height scales naturally with the 2:1 source). ## Test plan - [ ] Visit /guides/fishing on Vercel preview; verify desktop table thumbs render larger and the column doesn't break layout - [ ] Mobile width: species card thumbs fill more of the card at 2:1 - [ ] Inline figures in chapters 2/3/4/5 still float-wrap prose cleanly at 500px - [ ] Captions remain aligned below larger images Generated with Claude CodeTrail polish: NPT badge + POI default OFF + tighter zoom + new bass images
Four scoped polish fixes for trail-detail pages and the fishing guide. ## 1. Northville-Placid Trail badge - Added circular NPT badge to the trail header on `northville-placid-trail-4286650` only - Floats top-right at `md+`, inline (smaller) under meta on mobile - Hard-coded slug→badge map in `src/app/pursuits/trails/[slug]/page.tsx` — no schema change - Image: `public/trails/badges/northville-placid-trail.webp` — 180x180, 11.6 KB, transparent - Slug mapping: ```ts { 'northville-placid-trail-4286650': '/trails/badges/northville-placid-trail.webp' } ``` - **Future iteration:** add `trails.badge_image_path` column when more named trails (Cranberry Lake 50, Saranac 6er) get badges ## 2. "Show other nearby POIs" default OFF - `TrailDetailMap.tsx`: `useState(true)` → `useState(false)` - Toggle still present and functional; visitors can enable on demand - Reverses PR #84 default; reduces clutter on POI-dense trails ## 3. Tighter map zoom - `boundsOptions` padding `[30, 30]` → `[16, 16]` and `maxZoom: 14` - Trail fills more of the viewport at default load - Short trails won't zoom past z14 (avoids tile pixelation) ## 4. Differentiated bass species art - Replaced `largemouth-bass.webp` and `smallmouth-bass.webp` with anatomically-distinct species (jaw-past-eye + horizontal stripe vs. jaw-under-eye + vertical bars) - Both 600px wide, transparent alpha, q=92 - `largemouth-bass.webp`: 600x406, 91.8 KB - `smallmouth-bass.webp`: 600x411, 80.5 KB - Render in /guides/fishing Chapter 4 species table + inline figures ## Files touched - `adk/src/app/pursuits/trails/[slug]/page.tsx` — badge slug map + header layout - `adk/src/components/TrailDetailMap.tsx` — POI default OFF + tighter fitBounds - `adk/public/trails/badges/northville-placid-trail.webp` (new) - `adk/public/guides/fishing/species/largemouth-bass.webp` (replaced) - `adk/public/guides/fishing/species/smallmouth-bass.webp` (replaced) ## Verify - `npx tsc --noEmit` clean - `npm run build` green - Wait for Vercel preview READY before mergeTrails index: convert cards to sortable filterable table
## Summary Replaces the card grid on `/pursuits/trails` with a dense, sortable table mirroring the `/guides/high-peaks` `PeaksTable` pattern Scott already approved. ## Columns rendered | # | Name | Length | Difficulty | Operator | Network | SAC | Peaks | - **#** — display order (1-based, follows the active sort) - **Name** — links to `/pursuits/trails/<slug>` - **Length** — converted from `length_km` → miles, `tabular-nums`, "—" if null - **Difficulty** — derived (Beginner / Intermediate / Advanced / Expert). Source of truth: `sac_scale` (`hiking` → Beginner … `t4+` → Expert). Fallback when sac is null: length tier (<3mi / <7mi / <15mi / 15mi+). Coloured chip mirrors PeaksTable tier styling. - **Operator** — raw OSM `operator` - **Network** — raw OSM `network` - **SAC** — `sac_scale` for transparency on what the difficulty was derived from - **Peaks** — `peak_trails` count ### Columns from the spec that were dropped (and why) - **Region** — not on `public.trails`. The schema (migration 018 + 019 + 032) has `name, ref, network, operator, sac_scale, length_km, geometry, tags, description, slug` plus AI-summary fields. No `region_slug`, no FK to regions. Skipped per the "only show what's available" instruction. - **Elev. gain** — not on `public.trails` either. Skipped. ## Filter dimensions - **Length tier** chips: All / Short < 3mi / Medium 3–7mi / Long 7–15mi / Big day 15mi+ - **Search** (substring on name + operator + network) - **Operator** select (built from data) - **Network** select (built from data) - **Difficulty** select (Beginner / Intermediate / Advanced / Expert) - **Reaches a peak** toggle - **Clear** button when any filter active - Counter `N of M` ## Sort Click any column header to toggle asc/desc. Default: Name asc. Sort state lives in component state. ## Mobile pattern Same as PeaksTable — `<div className="hidden md:block">` for the table, `<ul className="md:hidden">` for the card list. Each mobile card shows name + length + difficulty + peaks chip, then operator/network/SAC as secondary. ## Full-width The trails page is already `max-w-[1280px]` (no `<article>` wrapper to break out of), so the table spreads naturally. ## Out of scope (preserved from PR #84) The trail detail page (`[slug]/page.tsx`) AI summary, gap, POIs-default-on, and detail polish are untouched. ## Test plan - [ ] Vercel preview hits READY - [ ] `/pursuits/trails` renders the table with map above - [ ] Sort by Length, Difficulty, Peaks all work asc + desc - [ ] Length-tier chips filter correctly - [ ] Search matches name + operator - [ ] Mobile (<768px) collapses to card list - [ ] Clicking a trail name navigates to `/pursuits/trails/<slug>` 🤖 Generated with [Claude Code](https://claude.com/claude-code)Submit page polish: pricing first + uniform widths
## Summary Two-part visual polish on the unified `/submit` page (also rendered at `/pricing` per PR #83). ### Section order — before / after **Before:** 1. Hero 2. How it works (Three steps to publish) 3. Plans & Pricing (tier cards) 4. What happens next 5. FAQ 6. Final CTA **After:** 1. Hero 2. **Plans & Pricing** (moved up — decision-maker first) 3. **How it works** (moved down — doer second) 4. What happens next 5. FAQ 6. Final CTA Rationale: pricing helps visitors evaluate fit before committing to the form workflow. ### Uniform section widths Audit showed mixed outer containers — Hero and Plans were `max-w-[1280px]`, while How it works / What happens next / FAQ / Final CTA were `max-w-[1080px]`. That mismatch produced jagged left edges on scroll. Standardized every outer section container to `max-w-[1280px] mx-auto px-6`. Inner editorial paragraphs keep their narrower `max-w-2xl` constraints for readability — only the page-level alignment is unified. `SectionDivider` ornaments preserved between sections. ## Test plan - [ ] Vercel preview READY - [ ] `/submit` and `/pricing` both render the new order - [ ] Every section's left edge aligns at the same x-position when scrolling on desktop - [ ] Mobile flow still reads cleanlyTrails unification: hiking-trails to trailheads + cross-link routes and trailheads
## Why Two routes confused visitors: - `/pursuits/trails` — 251 actual OSM hiking routes (the canonical almanac) - `/pursuits/hiking-trails` — 58 trailhead/parking-area listings labeled the same way This PR renames the listings subcategory to `trailheads`, 308-redirects the old URL, threads cross-links between the trails almanac and the trailhead listings, and adds the new sub-list to the nav. ⚠️ **Do not merge yet** — wait for Vercel preview READY, then apply migration 033 in Supabase before merging to main. ## Task 1 — Migration 033 (rename subcategory) `supabase/migrations/033_rename_hiking_trails_to_trailheads.sql` — inserts the new row, repoints `listings`, `staged_listings`, and `secondary_subcategories[]`, then drops the old row. `subcategories.slug` is the FK PK (no ON UPDATE CASCADE), so we can't UPDATE the PK directly. ```sql insert into public.subcategories (slug, chapter_slug, name, description, sort_order) select 'trailheads', chapter_slug, 'Trailheads & Parking', description, sort_order from public.subcategories where slug = 'hiking-trails' and chapter_slug = 'pursuits' on conflict (slug) do nothing; update public.listings set subcategory_slug = 'trailheads' where chapter_slug = 'pursuits' and subcategory_slug = 'hiking-trails'; update public.staged_listings set subcategory_slug = 'trailheads' where chapter_slug = 'pursuits' and subcategory_slug = 'hiking-trails'; update public.listings set secondary_subcategories = array_replace(secondary_subcategories, 'hiking-trails', 'trailheads') where 'hiking-trails' = any(secondary_subcategories); delete from public.subcategories where slug = 'hiking-trails' and chapter_slug = 'pursuits'; ``` Idempotent — re-running after the first apply is a no-op. ## Task 2 — Taxonomy code references - `adk/src/lib/imports/sub-mapping.ts` — OSM `pursuits/hiking` → `pursuits/trailheads` - `adk/src/lib/generate/category-rules.ts` — block renamed from `pursuits/hiking-trails` to `pursuits/trailheads` and rewritten to focus on parking/permit/access (the listings *are* trailheads, not routes) - `adk/src/components/ExploreNearbyMap.tsx` — gear-pursuit visibility override updated ## Task 3 — Redirects `adk/next.config.ts` → 308 permanent: | From | To | |---|---| | `/pursuits/hiking-trails` | `/pursuits/trailheads` | | `/pursuits/hiking-trails/:slug*` | `/pursuits/trailheads/:slug*` | ## Task 4 — Cross-links **Trail detail page** (`/pursuits/trails/[slug]`) — new "Listed trailheads nearby" section. Queries `listings` where `chapter_slug='pursuits'` AND `subcategory_slug='trailheads'`, JS-side filters by trail bbox + 3 mi pad, then computes nearest-vertex distance from each listing to any trail vertex; keeps those ≤ 3 mi, sorted ascending, top 6. **Trailhead listing page** (renders via `[chapter]/[subcategory]/[slug]/page.tsx`) — new "Trails from here" section, only when `chapter_slug='pursuits'` AND `subcategory_slug='trailheads'`. Pulls all `trails` rows with geometry, parses each LineString, walks vertices, keeps trails whose nearest vertex is ≤ 3 mi from the trailhead's coordinates. Top 8. Approach: ~250 trails total — JS-side filtering with cheap deg→mi conversion (1° lat ≈ 69 mi, 1° lng ≈ 50 mi at 44°N) is fine, no PostGIS RPC required. If trail count grows past ~1k we'll switch to a `ST_DWithin` RPC. ## Task 5 — Nav `navConfig.ts` (desktop Pursuits → Explore column) and `MobileNav.tsx`: added `{ label: "Trailheads & Parking", href: "/pursuits/trailheads" }` beside the existing `Hiking Trails → /pursuits/trails`. Both routes intentionally co-exist now. ## Task 6 — Sitemap Sitemap doesn't list `/pursuits/hiking-trails` explicitly; the dynamic listings query auto-generates `/pursuits/<sub>/<slug>` URLs and will pick up the new slug after the migration runs. No edit needed. ## Verification - ✅ `tsc --noEmit` clean - ✅ `npm run build` green (full build completed locally) - ✅ `npm run lint` — 0 NEW errors (all 23 errors pre-existing) - ⏳ Vercel preview pending - ⏳ Migration 033 must be applied before merge to main ## Test plan - [ ] Vercel preview READY - [ ] curl preview `/pursuits/hiking-trails` returns 308 → `/pursuits/trailheads` - [ ] curl preview `/pursuits/hiking-trails/connery-pond-parking` returns 308 → `/pursuits/trailheads/...` - [ ] Apply migration 033 in Supabase - [ ] Visit a trailhead listing — confirm "Trails from here" renders - [ ] Visit a trail detail page near a trailhead listing — confirm "Listed trailheads nearby" renders - [ ] Confirm nav dropdown shows both "Hiking Trails" and "Trailheads & Parking"Unify /pricing into /submit + rename CTAs to Submit a Listing
## Task 1 — Audit + plan `/pricing` carried tier cards (Free/Premium/Featured/Sponsored), a verification blurb, hello@ contact, and an FAQ. `/submit` carried the three-step process, a thinner tier teaser ("Coming soon"), a separate FAQ, and the email-link CTAs to the proprietor portal. Significant overlap across both pages was confusing. Unified `/submit` structure: hero → "How it works" three-step → diamond divider → "Plans & pricing" tier cards (4-up) → diamond divider → "What happens next" + sidebar contact card → diamond divider → FAQ → final CTA. ## Task 2 — Merged /submit page `adk/src/app/submit/page.tsx` rewritten as the canonical submit page. New title "Submit your business." with editorial voice retained (§ kicker, Fraunces with ochre period, mono small-caps, SectionDivider ornaments). Pulled the four-tier card grid from /pricing verbatim and merged the two FAQ lists into six entries. ## Task 3 — Repoint /pricing to /submit (Option A) `adk/src/app/pricing/page.tsx` is now a thin re-export of `SubmitPage`. Its own metadata sets `alternates.canonical: "/submit"` so Google dedupes the two URLs. Both routes prerender at build (verified). No redirect needed. ## Task 4 — Renamed CTAs - Masthead utility bar: "Register a Listing" → **Submit a Listing** - MobileNav account block: "List your business" → **Submit a Listing** (href /submit) - navConfig Register dropdown: "Add your business" → **Submit a Listing**, footer "List your business" → **Submit a Listing** In-page CTAs on /submit changed from "Get started" → "Submit a Listing" on the primary flow buttons. Per-tier mailto CTAs (Premium/Featured/Sponsored) keep "Get started" / "Talk to us" since they route to email, not the form. ## Task 5 — Footer + nav consistency Colophon footer "Submit a Listing" + "Pricing" links unchanged (both already pointed at sensible URLs). MobileNav "Pricing" link now points to /submit (renders same content). navConfig Register dropdown sub-items renamed to "Pricing & tiers" → /submit#plans and "How it works" → /submit#how-it-works (anchor IDs added on /submit). ## URL behavior summary | URL | Renders | Canonical | |---|---|---| | /submit | Unified submit page | /submit | | /pricing | Same component | /submit | ## Before / after CTA labels | Before | After | |---|---| | Register a Listing (Masthead) | Submit a Listing | | List your business (MobileNav) | Submit a Listing | | Add your business (Register dropdown) | Submit a Listing | | List your business (dropdown footer) | Submit a Listing | | Pricing → /pricing (dropdown) | Pricing & tiers → /submit#plans | | How it works → /submit (dropdown) | How it works → /submit#how-it-works | | Get started (in-page hero CTA) | Submit a Listing | ## Test plan - [ ] Vercel preview READY - [ ] /submit renders new unified layout with hero, three-step, tier cards, FAQ - [ ] /pricing renders same content and `<link rel="canonical">` points at /submit - [ ] Masthead utility bar shows "Submit a Listing" - [ ] Mobile drawer Account section shows "Submit a Listing" - [ ] Register mega-dropdown leads with "Submit a Listing" + "Pricing & tiers" anchor jump DO NOT MERGE — wait for Vercel preview READY + visual check. 🤖 Generated with [Claude Code](https://claude.com/claude-code)Pursuits nav + lake map marker fix + guide-settings admin + homepage reorder
Multi-fix PR (V). ## Fix 1 — Pursuits nav restructure `adk/src/components/navConfig.ts`. The Pursuits dropdown now has two new lead groups, then Land/Winter: - **Explore**: Peaks (`/46ers`), Lakes & Waters (`/lakes`), Hiking Trails (`/pursuits/trails`), Challenges, By Season - **Water**: Lakes & Waters (`/lakes`), Beaches & Swimming Holes (`/guides/beaches-and-swimming-holes`), Fishing (`/guides/fishing`), Paddling & Kayak - **Land**: Cycling, Climbing, Horseback, Golf, Wildlife, Scenic Drives - **Winter**: Alpine, Nordic & Snowshoe, Snowmobiling All five priority routes verified against the codebase. The repeat of "Lakes & Waters" in both Explore and Water columns is intentional per spec. ## Fix 2 — Lake map marker `adk/src/app/lakes/[slug]/LakeMap.tsx` and `adk/src/app/lakes/LakesIndexClient.tsx` were calling `<Marker />` with no `icon`, which falls through to Leaflet's default marker — that needs PNG image URLs we don't bundle, so it rendered as an empty rectangle. Pattern (mirrors `PeakMap.tsx`'s summit marker): - Added a `"lake"` POI type to `src/lib/map-pins.ts` — ochre-tinted teardrop SVG - `buildPoiDivIcon("lake")` now returns a bottom-anchored icon (so the tip points at the lake centroid) - Both maps use a `useMemo` divIcon and pass it via the `icon` prop on the marker - Added `boat_launch` (anchor glyph) at the same time since the schema referenced it ## Fix 3 — Guide ordering admin `/admin/guides` (and its API) already existed from an earlier PR — it exposes the editable table over `guide_settings` (slug, title, sort, show on homepage, position 1–5, status override) with arrow-up/down + bulk save. This PR: - Surfaces it under **Settings → "Guide ordering"** in `AdminHeader` + `AdminMobileNav` - Adds a **"Reset to recommended"** button on the page → `POST /api/admin/guides/reset` (admin/editor gated) - The reset wipes `guide_settings` and re-seeds to the new homepage selection (same SQL as migration 030) ## Fix 4 — Migration 030 `supabase/migrations/030_guide_settings_homepage_reorder.sql`: \`\`\`sql delete from public.guide_settings; insert into public.guide_settings (slug, show_on_homepage, homepage_position, sort_order, status_override) values ('spring-2026', true, 1, 10, 'live'), ('high-peaks', true, 2, 20, 'live'), ('fishing', true, 3, 30, 'live'), ('vacation-home', true, 4, 40, 'live'), ('family', true, 5, 50, 'live'), ('weddings', false, null, 60, 'live'), ('beaches-and-swimming-holes', false, null, 70, 'live'); \`\`\` Apply in Supabase, OR open `/admin/guides` and click **Reset to recommended** — both produce identical state. ## Verification - `tsc --noEmit` clean - `npm run lint` introduces 0 new errors (pre-existing only) - `npm run build` succeeds locally DO NOT MERGE — wait for Vercel preview READY. 🤖 Generated with [Claude Code](https://claude.com/claude-code)Lake pages (/lakes) + atlas-as-data refactor + email fix + audit script fix
## Summary PR U bundles five interrelated changes that turn the editorial waters atlas into a real, deeply linked sub-site of adirondackregion.com — the same treatment we built for /46ers, applied to lakes. ### 1. Migration 029 — `lakes` table + seed from waters atlas New `public.lakes` table: ```sql create table if not exists public.lakes ( slug text primary key, name text not null, region_slug text, lat double precision, lng double precision, surface_acres integer, max_depth_ft integer, species text[], public_access boolean default true, description text, hero_photo_path text, primary_water_type text check (primary_water_type in ('Lake','Pond','River','Stream') or primary_water_type is null), difficulty text check (difficulty in ('Beginner','Intermediate','Advanced') or difficulty is null), created_at timestamptz not null default now(), updated_at timestamptz not null default now() ); ``` Seeded inline with all **40 entries** from `adk/src/app/guides/fishing/waters-data.ts` via `on conflict (slug) do update` so the migration is idempotent. Coordinates/surface/max-depth values are best-effort public NYSDEC + Adirondack-Almanack figures where known, NULL where uncertain. Public read RLS; service-role write only. Indexed on `region_slug` and `primary_water_type`. ### 2. /lakes routes - **`/lakes`** — server component reads `lakes` ordered by name, hands rows to `LakesIndexClient` for filter chips (type/region/difficulty) + grid view + map view toggle (leaflet, paper-toned). - **`/lakes/[slug]`** — mirrors `/46ers/[slug]`. Editorial hero with kicker, by-the-numbers strip (surface acres, max depth, species count), leaflet map centered on the lake with nearby `outdoor_pois` plotted (8 km haversine cutoff), species chips, NYSDEC regulations link, ShareMenu, and RelatedGuides at the bottom. Uses `generateStaticParams` returning `[]` (ISR-cached, hourly revalidate) per the listing-route precedent. ### 3. Atlas-as-data refactor: `FishingWatersAtlas` now reads from DB - New `FishingWatersAtlasServer` server component fetches `public.lakes` and maps rows into the existing `WaterRow` shape. - `FishingWatersAtlas` (client) now accepts a `waters` prop. Names link to `/lakes/<slug>` when a slug is present. - `region_slug` → editorial-region label is mapped in the wrapper so the existing region filter dropdown keeps working. - Static `WATERS` array kept as fallback in case the DB read fails or returns empty mid-migration — the guide must always render. - This sets the precedent for migrating Family-Activity, Beaches, 46-Peaks atlases later. ### 4. Email replacement `partners@adirondackregion.com` → `hello@adirondackregion.com` in 4 spots, all on `/pricing` (3 plan CTAs + 1 FAQ answer). Site-wide grep confirmed no other occurrences. The footer/Colophon doesn't surface an email; `social-links.ts` `@adirondackregion` is a social handle, not an email. ### 5. Audit script `--stage` fix Migration 029 also: - Adds `staged_listings.notes text` (the field the script writes `Mentioned in /guides/<slug>` into). - Drops the not-null on `staged_listings.import_run_id` so guide-mention upserts can land outside of a batch import run. Both changes are idempotent. After applying 029, `node --env-file=.env.local adk/scripts/audit-guide-mentions.mjs --stage` will successfully upsert the 3 missing venues (Adirondack Experience, Olympic Center, Whiteface Lodge). I have not run it — that's left for Scott. ## Per-route summary | Route | Type | Notes | |---|---|---| | `/lakes` | ISR (revalidate=3600) | List + map + filters | | `/lakes/[slug]` | ISR (revalidate=3600), `generateStaticParams: []` | Detail page with map + nearby POIs | | `/guides/fishing` | (unchanged URL) | Now reads atlas from DB | | `/pricing` | (unchanged) | Email links updated | ## Test plan - [ ] **Apply migration 029** — `supabase/migrations/029_lakes.sql` (creates `lakes` table, seeds 40 rows, adds `staged_listings.notes`, drops not-null on `import_run_id`). - [ ] Localhost: `/lakes` renders the index. Filter chips work; grid → map toggle shows ~37 markers (3 Champlain entries have no region but do have lat/lng). - [ ] Localhost: `/lakes/lake-placid`, `/lakes/lake-george`, `/lakes/west-branch-ausable` all render with hero, stats, map, species, nearby POIs. - [ ] Localhost: `/guides/fishing` waters atlas table — water names are now links to `/lakes/<slug>`. All filter chips still work. - [ ] Localhost: `/pricing` — email links go to `mailto:hello@adirondackregion.com`. - [ ] Run `node --env-file=adk/.env.local adk/scripts/audit-guide-mentions.mjs --stage` — should upsert 3 missing venues without errors. - [ ] Vercel preview: confirm READY status before merge. ## Judgment calls / blockers - **Lake coordinates / surface / depth** are seeded with hand-derived public figures. Refining these (and adding `hero_photo_path`) is a follow-up content-pass. - **`stocked` boolean** isn't on the `lakes` table — the editorial atlas keeps it as a name-keyed lookup against the static array. Easy follow-up to add the column. - **Reverse-link from listings** (lakefront lodging showing "On the shore of [Lake]") is not in this PR — would require a `lake_slug` column on `listings` + a separate content pass to populate. Roadmap. - The 3 Champlain-basin entries have `region_slug = NULL` (Champlain Valley isn't in our regions taxonomy). They still render fine; the region filter just doesn't include them. 🤖 Generated with [Claude Code](https://claude.com/claude-code)Homepage polish: full poster cards + kicker below image + tighten section padding
## Fix 1 — Guide cards: full poster, no top crop Changed image container from `aspect-[4/3]` to `aspect-square` so the 1254×1254 source posters render uncropped. Card height grows ~33% (image was ~75% of width, now ~100% of width — at ~252px column width, image grows from ~189px tall to ~252px tall). ## Fix 2 — Kicker moved below image Removed the absolute-positioned overlay badge that was clipping printed poster titles. Kicker now lives in the text section above the card title, as inline mono small-caps (`text-[9px] tracking-[.2em] text-ochre-600/90`). ## Fix 3 — Tighten vertical space between sections Reduced section padding `py-14` → `py-10` on: - Field Guides section - The Register (chapters) - Explore by Region - Editor's Picks Vertical padding per section: 56px → 40px (top + bottom each). Saves ~64px of dead space across the four sections. ## Fix 4 — Remove trailing divider on Timeline Dropped `border-b border-ink-800/20` from the Timeline section so the transition to the Colophon footer is clean, no horizontal rule. ## Test plan - [ ] Vercel preview READY - [ ] Verify guide posters show full art (no top crop) - [ ] Confirm kicker reads cleanly below poster - [ ] Section spacing feels tighter but distinct - [ ] No border line above footer Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> 🤖 Generated with [Claude Code](https://claude.com/claude-code)Homepage 5 featured cards + walleye/perch inline + beaches tile
## 1. Homepage — 5 featured cards, no secondary list `adk/src/app/page.tsx` + `adk/src/app/guides/guides-list.ts` - Replaced the 3-card poster + "More field guides" text-link list with a single 5-card grid (`grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5`). - Cards are smaller: `aspect-[4/3]` cover, `p-3` padding, `text-base` titles, `text-[12px]` lede. Estimated total card height ~340–360 px on desktop (vs. ~480 px before). - Re-sorted live guides so the top-5 slice resolves in editorial order: - spring-2026 (10) → high-peaks (20) → fishing (30) → vacation-home (40) → family (50) - weddings (60) and beaches-and-swimming-holes (70) stay live but only show on `/guides`. ## 2. Fishing Chapter 5 — walleye + yellow perch inline figures `adk/src/app/guides/fishing/page.tsx` Chapter 5 ("Pike, walleye & panfish") now has three matched `SpeciesFigure` illustrations: - Northern pike — floated right (existing). - Walleye — floated left in the walleye subsection (`/guides/fishing/species/walleye.webp`, 600×188). - Yellow perch — floated right in the panfish subsection (`/guides/fishing/species/yellow-perch.webp`, 600×248). Same italic mono small-caps figcaption treatment as the pike figure. Floats alternate so the prose flows around them without disruption. ## 3. Beaches & Swimming Holes cover poster - `staging/guide_adirondack_beaches_swimming_holes.png` → `adk/public/guides/beaches-and-swimming-holes.jpg` via `sips -s formatOptions 88` (488 KB). - Removed `hasCover: false` from the beaches entry in `guides-list.ts` so the tile renders the real cover on `/guides`. ## Verification - `tsc --noEmit` clean. - `npm run lint` — 0 new errors in changed files (pre-existing repo warnings unaffected). - `npm run build` succeeds; homepage and `/guides/fishing` both compile. - Inspection: 5 cards visible at `lg:` breakpoint, fishing Chapter 5 markup confirmed to contain three `SpeciesFigure` calls (pike, walleye, perch), beaches tile uses the new JPG. Do not merge — wait for Vercel preview READY first. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>Guide-listing cross-links + missing-POI sourcing queue
## Summary Editorial guides are the spine of the site. This PR ships infrastructure so every named ADK business in editorial prose automatically links to its directory listing when one exists, and anything missing is captured for sourcing. ### 1. `<LinkedListing>` server component `src/components/guides/LinkedListing.tsx` — fuzzy-matches a venue name against the published listings catalog at render time. If a match exists, renders an in-prose anchor styled like other editorial links; if not, renders plain `<span>` (no broken links). Catalog is bulk-loaded once per server boot into a module-scoped `Map` — adding more `<LinkedListing>` tags doesn't multiply Supabase round-trips. Fail-safe: a Supabase error during build renders plain text instead of crashing the page. Misses emit a `[linked-listing:miss]` log line for the audit script. ### 2. Audit script `scripts/audit-guide-mentions.mjs` — walks every `src/app/guides/**/*.tsx`, regex-extracts `<LinkedListing name="...">` + `listingName: "..."` references, looks each up in Supabase, writes: - `audit-guide-mentions.txt` — per-guide matched / missing summary - `missing-listings.csv` — `name,region_hint,subcategory_hint,source_guide` With `--stage`, upserts missing entries into `staged_listings` (`source='guide-mention'`, `status='pending'`, `address_state='NY'`, region/chapter/subcategory derived from hints). Idempotent — skips names already staged under `guide-mention`. **Not run as part of this PR** — Scott will run after merge. ### 3. VendorGrid `listingName` support `src/components/guides/VendorGrid.tsx` — cards now accept an optional `listingName` (+ optional `regionHint`/`subcategoryHint`). When provided and a published match is found, the card upgrades to display the real listing name + link to `/{chapter}/{subcategory}/{slug}` with a "View directory listing →" footer. Cards without `listingName` (or unmatched) render the generic editorial card unchanged. Backwards compatible — every existing call site keeps working. ### 4. Per-guide sweep | Guide | Named venues wrapped (first-mention) | Notes | |---|---|---| | `/guides/family` | The Wild Center, Whiteface Mountain, Adirondack Experience, High Falls Gorge, Olympic Center, Enchanted Forest Water Safari, Six Flags Great Escape | Major attraction H4s wrapped; rainy-day list + sample itineraries kept as plain prose (repeat mentions). | | `/guides/weddings` | Whiteface Lodge, The Sagamore, plus existing hardcoded `<ListingLink>` calls (Mirror Lake Inn, Golden Arrow, High Peaks Resort) | Pre-existing `ListingLink` component kept for hardcoded slugs; `LinkedListing` added for previously-unwrapped first-mentions. | | `/guides/high-peaks` | Mirror Lake Inn, Whiteface Lodge, High Peaks Resort, Adirondack Loj, South Meadow Farm Lodge, Wilderness Campground (via dynamic `<LinkedListing name={v.name}>` over the LODGING_GROUPS data with new `regionHint`/`subcategoryHint` fields). Recovery_Venues skipped — they're generic "Featured · Brewery" placeholders. | | `/guides/fishing` | None — the fishing guide is heavy on water/species content, not specific named businesses. Waters (lakes/rivers) deferred to a future `/lakes/` route per editorial discipline rule. | ### 5. Docs `scripts/AUDIT-GUIDE-MENTIONS.md` — how to run the audit, how `--stage` works, what `<LinkedListing>` does, editorial-discipline rule (first-mention, specific names only, skip non-business landmarks). ### Audit numbers (this branch, fresh run) - Total mentions captured by static audit: **9** - Matched to existing listings: **6** (`The Wild Center`, `High Falls Gorge`, `Whiteface Mountain`, `Six Flags Great Escape`, `Enchanted Forest Water Safari`, `The Sagamore`) - Missing and queued for sourcing: **3** (`Adirondack Experience`, `Olympic Center`, `Whiteface Lodge`) **Note on high-peaks count:** the audit's static-regex pass only sees string-literal `name="..."` invocations. The high-peaks guide passes `name={v.name}` from the `LODGING_GROUPS` data array, which links dynamically at render time but isn't visible to the audit. Those venues still cross-link at runtime when their listings exist; if Scott wants them in the audit list too we can extend the regex to follow named-data-array references. ### Judgment calls - **Wrapped only first-mentions.** Repeat mentions of the same venue in the same paragraph stay as plain prose for readability. - **Skipped landmarks.** Mountains/peaks/lakes/parks themselves were not wrapped — those should eventually point at `/lakes/` and `/46ers/` pages, but those routes don't exist yet for most peaks/lakes. Flagged in `AUDIT-GUIDE-MENTIONS.md`. - **Skipped fishing prose.** The fishing guide is dominated by species and water names, not specific outfitters/businesses; wrapping waters here would just be a no-op until `/lakes/` ships. - **Pre-existing `ListingLink` left alone.** The weddings guide already had a hand-rolled `ListingLink` with hardcoded slugs (`mirror-lake-inn-resort-and-spa` etc.). Those keep working — `LinkedListing` is the dynamic, listing-existence-aware companion, not a replacement. ## Test plan - [ ] Vercel preview deploys READY (do **not** merge until then) - [ ] `/guides/family` renders, "The Wild Center" / "High Falls Gorge" / "Whiteface Mountain" links resolve to existing directory pages, "Olympic Center" / "Adirondack Experience" / "Whiteface Lodge" render as plain text (no broken links) - [ ] `/guides/weddings` renders, "The Sagamore" / "Mirror Lake Inn" links resolve, "Whiteface Lodge" renders as plain text - [ ] `/guides/high-peaks` renders, lodging cards under "Best for Lake Placid–access peaks" link to listings where they exist (Mirror Lake Inn, High Peaks Resort), plain text otherwise - [ ] `/guides/fishing` unchanged - [ ] Build log shows `[linked-listing:miss]` lines only for genuine misses (no false positives) - [ ] After merge, Scott runs `node --env-file=.env.local scripts/audit-guide-mentions.mjs --stage` and the missing names appear in `/admin/imports` with `source='guide-mention'` 🤖 Generated with [Claude Code](https://claude.com/claude-code)Family guide cover tile
Adds /public/guides/family.jpg from Scott's designed poster + sets hasCover true. 457 KB JPG, q=88 from sips conversion of guide_adirondacks_with_kids_every_age.png in staging.Visual batch: 14 species illustrations + 13 guide cover tiles + fishing inline figures
## Summary Three coordinated visual upgrades from a single batch of staged assets. ### A. Fish species illustrations (14) - Processed 14 fish PNGs from staging into transparent-bg WebPs at `/public/guides/fishing/species/`. - All sources were RGBA with pre-cleared alpha; trim-by-alpha-bbox + resize to 600 wide + WebP q=92 method=6. Output sizes 45–80 KB each. - Wired `image_path` on every matching species in `species-data.ts`: brook / brown / rainbow / lake trout; smallmouth + largemouth bass; northern pike; walleye; yellow perch; lake + round whitefish; tiger muskellunge; splake; rock bass; bullhead. - Skipped (no source provided): landlocked-atlantic-salmon, chain-pickerel, bluegill. ### B. Inline figures across fishing guide chapters New `SpeciesFigure` helper — `md:float-right` (or `md:float-left`), small-caps mono italic figcaption, ~340px max-w. Inserted: - **Ch.2** Brook trout (right) - **Ch.3** Brown trout (right) - **Ch.4** Smallmouth (left) + Largemouth (right) — balanced pair - **Ch.5** Northern pike (right) `Section` now has `clear-both` so floats don't bleed across chapter boundaries. ### C. Guide cover tiles (13) Converted 13 poster PNGs to `/public/guides/<slug>.jpg` via `sips q=88`. Sizes 426–570 KB each. Replaced placeholder tiles for: `spring-2026`, `high-peaks`, `weddings`, `fourth-of-july`, `fishing`, `apple-picking`, `fall`, `oktoberfest`, `skiing`, `winter`, `olympic-sites`, `vacation-home`, `scenic-drives`. ### Sort change - `fishing` moved from `sort: 50` → `sort: 25` so it surfaces above `weddings (30)` on the homepage guides hub. - `hasCover: false` removed from fishing entry (now has a real cover). - `family`, `accessibility`, `getting-here` keep `hasCover: false` (no posters yet). ### Judgment calls - `fish_smallmouth_bass.png` was processed as smallmouth based on filename. Pillow can't visually identify a fish; if a manual visual check shows it's a second largemouth, swap or delete `smallmouth-bass.webp`. The existing `largemouth-bass.webp` from PR #69 was kept untouched. ## Test plan - [ ] Vercel preview READY - [ ] /guides hub shows fishing tile above weddings, with real cover art - [ ] /guides/fishing renders inline species figures in chapters 2/3/4/5 - [ ] /guides/fishing species table shows thumbnails for all 14 wired species - [ ] No layout bleed from floated figures into adjacent sections 🤖 Generated with [Claude Code](https://claude.com/claude-code)Homepage: editorial grid for guides + hero CTAs lead with Field Guides
## Summary Reframe the homepage around editorial content. The previous hero buried Field Guides as the third (ghost) CTA, and the guides section showed only 3 huge poster cards (~800px vertical). With 5 live guides and more on the runway, that didn't scale. ### Hero CTAs — before / after **Before:** three equal-weight CTAs in one row — `Browse by Region` (filled, primary) · `Browse the Map` (ghost) · `Field Guides` (ghost, last). **After:** primary CTA dominant, secondaries demoted: - Primary: `Open the Field Guides →` — `btn-ink`, larger padding (1.05rem 2.1rem) and 0.92rem text. - Secondary row underneath: `Browse by Region` · `Browse the Map` — `btn-ghost` at smaller padding (0.55rem 1.15rem, 0.7rem text). Hero copy lightly reworked from "directory" → "field guide written by people who actually live here, mapped and searchable" to soften toward editorial framing without rewriting the H1. ### Field Guides section — before / after **Before:** `homepageGuides.slice(0, 3)` → 3 square-cover cards in `md:grid-cols-3`. **After:** every guide where `effectiveStatus === "live"` rendered in a responsive grid: - Mobile: 1 col · Tablet: `sm:grid-cols-2` · Desktop: `lg:grid-cols-3`. - 16:9 covers (~280-320px card height), Fraunces title with ochre period, `kicker` eyebrow, line-clamped lede, ochre-border hover. - Guides with `hasCover === false` (currently `family`, `fishing`) render a typographic placeholder over the topo pattern — no more broken-image overlays. 5 live guides surface above-fold on desktop instead of 3. ### Vertical-space reduction Estimated total page height (above the timeline footer) trimmed by **~250-300px** on desktop: - 7× sections changed from `py-20` (160px) to `py-14` (112px) → ~336px saved on padding alone. - Hero: `pt-20 pb-28` (272px) → `pt-14 pb-20` (224px) → 48px saved. - Header bottom margins `mb-8` (32px) → `mb-6` (24px) → small but cumulative. - Net gain absorbed slightly by the larger guides grid (showing 5 cards instead of 3, but with shorter aspect-16:9 covers vs square). ### Files touched - `adk/src/app/page.tsx` — only file changed. ### Judgment calls - Spec mentioned 7 live guides including `beaches-and-swimming-holes` and `vacation-home`, but `guides-list.ts` only marks 5 as `status: "live"` (spring-2026, high-peaks, weddings, family, fishing). Used the source-of-truth file rather than the spec count. If those two should flip live, that's a one-line change in `guides-list.ts`. - Did not extract a `GuideCard`/`GuideGrid` component — the markup is short enough inline that a separate file would be premature abstraction. Easy to extract later if a second surface needs it. - Kept the typographic placeholder simple (italic Fraunces title fragment over topo pattern) rather than building bespoke poster art per guide — that was the design-debt path the redesign is escaping. ## Test plan - [ ] Vercel preview READY, no build errors - [ ] At 1440px: hero "Open the Field Guides" reads as the dominant CTA; secondary buttons clearly smaller; guides section shows all 5 live guides in 3-col grid - [ ] At 375px: 1-col stack, primary CTA full-readable, secondary row wraps cleanly, guide cards stack - [ ] Family + Fishing cards render typographic placeholder (not broken image) since `hasCover: false` - [ ] Total page height visibly tighter than main Local `npm run build` passed (138 static pages generated, no errors). Do NOT merge — wait for preview verification. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>Fishing visuals: restore brook-trout hero + transparent bass + table layout + inline bass
Four scoped fixes on `/guides/fishing` imagery. ## Fix 1 — Restore brook-trout hero PR #71 swapped `adk/public/guides/fishing/hero.jpg` for an Olympic Center photo. Pulled the original Unsplash CC0 brook-trout-in-hand image (Brian Yurasits) back from commit `7db135d`. - 1600x2133 JPEG, 824 KB - Verified visually: person holding a brook trout (dark spotting, orange belly) ## Fix 2 — Largemouth bass with true transparency Previous `largemouth-bass.webp` was RGB on solid black. Re-processed from `~/Desktop/fish_smallmouth_bass.png` (mis-filed source — jaw past eye = largemouth, not smallmouth). Used Pillow: - Crop to alpha bbox with threshold > 16 - Resize to 600 wide preserving aspect - Save as webp q=92, method=6 - Result: 600x225, RGBA, 72 KB - Verification: PIL `mode == 'RGBA'`; corner pixel `(0,0,0,0)` = fully transparent - ImageMagick was unavailable; used Pillow instead ## Fix 3 — Species thumb above name (desktop + mobile) `FishSpeciesTable.tsx`: - Desktop: switched the species cell from `flex items-center` (side-by-side) to `flex flex-col items-start gap-2` (vertical stack). Bumped thumb to 80x40. - Mobile: added `SpeciesThumbLarge` (280x80) at the top of each card, then name + native badge below. - No separate image column was present; the brief described the legacy state. Now image stacks above name in the same cell on both breakpoints. ## Fix 4 — Inline bass figure in Chapter 4 `page.tsx`, Chapter 4 (`<Section id="bass">`): added a float-right `<figure>` at the start of the Largemouth subsection: - `<Image width={400} height={150} />` of the new transparent webp - `<figcaption>` in italic mono small-caps: "Largemouth bass — illustration." - Floats right at md+, stacks at mobile (`my-4`) ## Verification - `tsc --noEmit`: clean - `npm run lint`: 0 new errors on touched files (pre-existing warnings unrelated) - Files touched: - `adk/public/guides/fishing/hero.jpg` - `adk/public/guides/fishing/species/largemouth-bass.webp` - `adk/src/app/guides/fishing/FishSpeciesTable.tsx` - `adk/src/app/guides/fishing/page.tsx` Do not merge — wait for Vercel preview READY and visual verify.Publish Beaches + Vacation Home + align Spring/Weddings hero template
## Summary Three jobs scoped to /guides/* — no shared library, env, or prerender plumbing touched. ### Job 1 — Beaches & Swimming Holes (new live guide) - New `/guides/beaches-and-swimming-holes` route. 15 chapters translated from the source HTML: Why ADK swimming is different · The five water types · Lake George region · High Peaks region · Central ADK · Old Forge & Fulton Chain · State campgrounds · Resorts · Swimming holes · Small ponds · Logistics · Packing · Safety · Eats · FAQ. - **Stat strip:** 3,000+ lakes & ponds · 60+ beaches mapped · ~15 lifeguarded in season · 62–72°F mid-July. - Genericized — no "Presented by Stewart's" title-sponsor framing, real venues only (Million Dollar Beach, Mirror Lake, Heart Lake, Moffitt, etc.). - Sources link to NYSDEC, ADK Mountain Club, ROOST, Visit Lake George, NYS Parks, I LOVE NY. - Hero uses styled fallback HeroPlate ("Field Guide · No. VII") — `/public/guides/beaches-and-swimming-holes.jpg` not yet shot, so `hasCover: false`. ### Job 2 — Buying an Adirondack Vacation Home (flipped to live) - guides-list.ts entry flipped `coming-soon` → `live`, kicker/lede polished. - New `/guides/vacation-home` route. 12 chapters translated from the source: Why buy · Eight buyer profiles · Choosing a region · Lakefront vs mountain vs in-town · Year-round vs seasonal · Setting a real budget · Financing · Taxes/Title/STAR · Inspection & insurance · Renting it out · Maintaining a camp from afar · 60-day closing checklist · FAQ. - **Stat strip:** 6.0M acres · $425K median (Spring 2026) · 52% second-home share · 3,000+ lakes. - Hero uses existing `/public/guides/vacation-home.jpg`. - Sources link to APA, NYS Tax STAR, NYS DEC, FEMA Flood Map, CFPB second-home guidance, NYS DOS, NYS OGS. ### Job 3 — Spring 2026 + Weddings hero alignment Both heroes rewritten to match the canonical family/fishing/high-peaks pattern: - § kicker - Fraunces title with ochre period - italic Fraunces SOFT subtitle - italic disclaimer note - action button row + ShareMenu - hero square image - 4-column by-the-numbers stat strip below Stats pulled: - **Spring 2026:** 3 months covered · 9+ marquee events · 34 mi Adirondack Rail Trail · May 1 Wild Center reopens. - **Weddings:** 23 chapters · 6+ vendor categories · 5 wedding-style frameworks · 18-month timeline. Body content untouched on both. ## Hero feature parity matrix (all 6 live guides) | Guide | TOC | Share | Related | Stat strip | Hero image | Action btns | |---|---|---|---|---|---|---| | family | ✓ | ✓ | ✓ | ✓ | HeroPlate fallback | ✓ | | fishing | ✓ | (in body) | ✓ | ✓ | image | ✓ | | high-peaks | ✓ | ✓ | ✓ | ✓ | image | ✓ | | spring-2026 | ✓ | ✓ | ✓ | ✓ (new) | image | ✓ (new) | | weddings | ✓ | ✓ | ✓ | ✓ (new) | image | ✓ (new) | | beaches-and-swimming-holes | ✓ | ✓ | ✓ | ✓ | HeroPlate fallback | ✓ | | vacation-home | ✓ | ✓ | ✓ | ✓ | image | ✓ | ## Verification - `npm run build` — completes; both new routes (`/guides/beaches-and-swimming-holes` and `/guides/vacation-home`) prerender as static. - `npx tsc --noEmit` — clean. - `npm run lint` — zero new errors in the changed files. - Prerendered HTML for all four updated/new guides contains the GuideTOC ("On this page") marker and the Stat helper class — confirmed via grep against `.next/server/app/guides/*.html`. - No horizontal jump-nav remnants in the new guides — confirmed via grep. ## Test plan - [ ] Vercel preview deploy reaches READY - [ ] /guides/beaches-and-swimming-holes loads, hero stat strip + TOC + ShareMenu visible - [ ] /guides/vacation-home loads with /public/guides/vacation-home.jpg in hero, stat strip + TOC visible - [ ] /guides/spring-2026 hero now matches /guides/family hero structure (stat strip + action row present) - [ ] /guides/weddings hero now matches /guides/family hero structure (stat strip + action row present) - [ ] /guides hub shows both new live guides in the card grid 🤖 Generated with [Claude Code](https://claude.com/claude-code)Guide template consistency: vertical TOC + cross-links + sources + ADK images + family nav title
## Summary Brings the long-form guide pages onto the same template, so /guides/high-peaks, /guides/family, /guides/fishing, /guides/weddings, and /guides/spring-2026 all read like one publication. ### Fix 1 — Vertical TOC - High Peaks and Family pages now use the vertical `GuideTOC` sidebar (`grid lg:grid-cols-[220px_minmax(0,1fr)]`), matching Weddings + Fishing. - The horizontal "JUMP NAV" strip on both pages is removed — TOC is the single navigation pattern. ### Fix 2 — RelatedGuides - `RelatedGuides` cross-link footer is now confirmed on every guide: fishing, family, high-peaks, weddings, spring-2026. ### Fix 3 — Real source links - `SOURCES` arrays on family / fishing / high-peaks now carry `url` fields and render as `<a target="_blank" rel="noopener">` with hover states + underline, instead of plain `<div>`s. ### Fix 4 — Real ADK photos for fishing Replaced placeholder/stock fishing imagery with actual Adirondack-region photos: - `adk/public/guides/fishing/hero.jpg` - `adk/public/guides/fishing/ausable.jpg` - `adk/public/guides/fishing/pond.jpg` Attribution: photos from the project's licensed Adirondack image library (royalty-free / owner-licensed for editorial use on adirondackregion.com). Captions on the page already credit the Ausable River and the pond setting; no third-party photographer credit required for these specific files. ### Fix 5 — Family nav title `guides-list.ts` family entry → `"The Adirondacks with kids — every age."` (was split title/italicTail). ## Test plan - [ ] Vercel preview READY - [ ] /guides/high-peaks shows left-rail TOC, no top jump strip - [ ] /guides/family shows left-rail TOC, no top jump strip - [ ] Source links on all three guides open in a new tab - [ ] /guides hub shows "The Adirondacks with kids — every age." card title - [ ] Fishing hero / Ausable / pond images render as real ADK photos - [ ] RelatedGuides block renders on all five guide pagesBug round: VT removal + FB URL dedup + mobile overflow + hotels subcat + imports cleanup
Five distinct fixes scoped tight in one PR. ## 1. Vermont blocked sitewide The directory is Adirondack-region only — VT addresses snuck in via OSM imports near the eastern park boundary. - `supabase/migrations/027_block_vermont.sql` — purges VT rows from `listings` + `staged_listings` (matches state, town, or street tokens) and adds a CHECK constraint that `address_state` must be NULL or NY. - `adk/src/lib/listings/validate.ts` — new `assertNYAddress` / `isNYAddress` helper. Throws on VT state, common VT town names (Burlington, Rutland, Middlebury, Vergennes, etc.), or VT/Vermont in the street. - OSM scripts (`import-osm.mjs`, `import-osm-regional.mjs`) — mirrored `isVermont()` guard skips rows before insert. - Public listing slug page — `notFound()` if a VT row somehow slips past the DB constraint. Row counts ship at apply-time. Migration 027 is idempotent; counts will be reported once Scott applies it to prod. ## 2. Smoke Signals BBQ duplicate www.facebook URL Bug: `slug page` line 901 was building `href={`https://facebook.com/${social.facebook}`}` — if the stored `social.facebook` already contained a full URL (common from OSM tag values), the result was `https://facebook.com/https://www.facebook.com/SmokeSignalsBBQ`. The display also showed the raw doubled value. - `adk/src/lib/social-links.ts` — `normalizeSocialLink` now strips any duplicate-domain concatenation (`https://facebook.com/https://www.facebook.com/foo` → `https://www.facebook.com/foo`) at the front of `normalizeSocialLink`. - Listing detail page — passes social.facebook / .instagram / .pinterest through `normalizeSocialLink` instead of naive string interpolation. Display uses the helper-extracted handle. - `/api/admin/listings/generate` — same helper for the Facebook scrape URL when `use_facebook=true`. - `adk/scripts/fix-fb-urls.mjs` — sweeps existing `listings` + `staged_listings` `social` jsonb and writes back de-duped values. Defaults DRY-RUN; pass `--apply` to write. Run separately when ready. ## 3. Mobile horizontal overflow `/[chapter]/[subcategory]` and `/[chapter]/[subcategory]/[slug]` overflowed horizontally on mobile. - `FilterableListings.tsx` — added `min-w-0` to the grid + the main column + the row body, and `break-words` on the title. Long unbreakable tokens no longer push the column past viewport. - Listing detail page — `min-w-0` on the body grid + main, `break-words` on the h1 + tagline + display values for socials, `overflow-x-hidden` as a hard guard on the header section. ## 4. Hotels & Motels subcategory under Lodging Lodging chapter previously had no Hotels & Motels bucket; OSM `tourism=hotel`/`motel` rows landed under `resorts-and-great-camps`. - `supabase/migrations/028_hotels_motels_subcategory.sql` — inserts the `hotels-motels` subcategory at sort_order=9 and reassigns lodging listings whose names match `hotel`/`motel`/common chain regexes into it. Same on staged_listings. - `sub-mapping.ts` — `lodging/hotels-resorts` and `lodging/hotels-motels` both map to `hotels-motels` now. - `import-osm.mjs` + `import-osm-regional.mjs` — `CATEGORIES` updated. - `navConfig.ts` — adds Hotels & Motels to the desktop nav dropdown (top of the Stay column). - `category-rules.ts` — adds an AI-generator prompt block specific to hotels/motels. Reassignment counts ship once Scott applies migration 028. ## 5. Imports + admin cleanup **5a — re-importing rejected listings.** `import-osm-regional.mjs` was using `status=neq.rejected` in its dedup query, so anything an editor rejected got re-staged on the next run. `import-osm.mjs` wasn't checking staged_listings at all. - Both scripts now pull all staged_listings (including `rejected`) into the dedup set. A manual reject is permanent until explicitly cleared. **5b — per-row delete on /admin/listings.** Bulk delete existed; per-row didn't. - New per-row Delete button with two-click arm-confirm pattern + 5s auto-disarm. Reuses the existing `/api/admin/listings/bulk` endpoint with a single id; cascades via DB `ON DELETE CASCADE` (photos, claims, placements, etc.). ## Files touched ``` A adk/scripts/fix-fb-urls.mjs M adk/scripts/import-osm-regional.mjs M adk/scripts/import-osm.mjs M adk/src/app/[chapter]/[subcategory]/[slug]/page.tsx M adk/src/app/admin/listings/ListingFlagsClient.tsx M adk/src/app/api/admin/listings/generate/route.ts M adk/src/components/FilterableListings.tsx M adk/src/components/navConfig.ts M adk/src/lib/generate/category-rules.ts M adk/src/lib/imports/sub-mapping.ts A adk/src/lib/listings/validate.ts M adk/src/lib/social-links.ts A supabase/migrations/027_block_vermont.sql A supabase/migrations/028_hotels_motels_subcategory.sql ``` `tsc --noEmit` clean. Lint count unchanged from main (25 pre-existing errors, 0 new). ## Test plan - [ ] Apply migrations 027 + 028 to Supabase, capture VT-rows-deleted + hotels-reassigned counts - [ ] Run `node --env-file=adk/.env.local adk/scripts/fix-fb-urls.mjs --dry` to preview FB URL fixes, then `--apply` - [ ] Verify `/lodging` dropdown shows Hotels & Motels in the nav - [ ] Visit Smoke Signals BBQ listing — confirm Facebook link href + display are clean - [ ] Open a listing slug page on mobile — no horizontal scrollbar - [ ] /admin/listings — try the per-row Delete button (single click arms red, second confirms) - [ ] Run a regional OSM import dry against a region with previously-rejected staged rows — confirm dedup-skipped counts include them 🤖 Generated with [Claude Code](https://claude.com/claude-code)Fishing species table: largemouth bass thumbnail (POC)
## Summary - Adds an optional \`image_path\` field on the \`Species\` type and renders a small illustration thumbnail in both the desktop table and mobile card layout. Species without an image get a neutral placeholder dot so vertical alignment stays clean. - Wires up **largemouth bass** as the first species, as a proof-of-concept for the batch processing pattern. ## Image processing details - **Source:** \`~/Desktop/fish_smallmouth_bass.png\` (1151x502 PNG on solid black bg). Per Scott's correction the file was misnamed — jaw extending past the eye marks it as a **largemouth** bass. - **Pipeline:** Python + PIL (no \`magick\`/\`convert\` available on this machine, \`sips\` doesn't auto-trim). Trim near-black border (threshold 18, 6 px pad) -> resize max-width 600 -> WebP q=92. - **Output:** \`adk/public/guides/fishing/species/largemouth-bass.webp\` — 600x272, **52.4 KB**. ## Notes for batch-processing the remaining 17 species - **Folder:** drop source files in \`~/Desktop/species-source/\` named after the species slug (e.g. \`brook-trout.png\`, \`smallmouth-bass.png\`). - **Pipeline:** the same PIL trim+resize+webp script handles them in one pass; the trim threshold (>18 brightness) works for any solid black-bg illustration. - **Output naming:** match the species slug used in \`image_path\`. Then add \`image_path: '/guides/fishing/species/<slug>.webp'\` on each entry in \`species-data.ts\`. - **Tooling note:** if you ever want \`convert -fuzz 10% -trim\` style auto-trim, install ImageMagick (\`brew install imagemagick\`); for now the PIL approach is the working pattern. ## Test plan - [ ] Vercel preview READY - [ ] Largemouth bass row shows the illustration in the desktop table - [ ] Mobile card layout shows the same thumbnail and stays aligned - [ ] Other species rows show the placeholder dot without breaking layout DO NOT MERGE — waiting on Vercel preview verification. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>Family guide: 20 chapters + 71-activity atlas
## Summary - New `/guides/family` route — the complete toddlers-to-teens Adirondack field guide. Twenty editorial chapters (regions, seasons, lodging, beaches, easy hikes, stroller walks, attractions, water activities, tweens & teens, rainy days, restaurants, treats, 3-day and 7-day itineraries, budget reality, packing, safety) ending in a "One sentence" coda. - New full-width **Activity Atlas** with 71 typed entries — multi-dimensional filters (category, region, age bucket, cost tier, search) and sortable columns. Desktop table + mobile card layout. Mirrors the just-shipped fishing waters atlas pattern. - Adds `family` to `GUIDES_HUB` at sort 45 so it surfaces between weddings and fishing on `/guides`. Wires up ShareMenu near the hero and RelatedGuides at the bottom. ## Editorial pass - Stripped all real-people quotes and "Sarah and her kids did…" framing. - Stripped the "Presented by Wild Center" title-sponsor bar and the Ch 8 sponsor-module — the Wild Center stays as a legitimate editorial reference, just not as a paid partner. - Featured-vendor cards kept for real well-known venues/outfitters (Roaring Brook Ranch, Six Flags Great Escape Lodge, Golden Arrow, Fort William Henry, High Peaks Resort, Water's Edge Inn, Wild Center, Whiteface, High Falls Gorge, Adirondack Experience, Enchanted Forest, Lake George Steamboat, St. Regis Canoe Outfitters, Captain Marney's, Old Forge Water Recreation, Lake George Kayak Co., Adirondack Lakes & Trails, Lake Placid Pub & Brewery, Big Slide, The Cottage at Mirror Lake Inn, Mr. Mike's, Tony's, Generations) with the "Sponsored" badge dropped. - The "Adventure Sports Rafting Co." vendor was genericized to "Hudson Gorge Rafting (North Creek)" — the named brand is unfamiliar enough that defaulting to the editorial label felt safer per the rules. - Lead-capture form omitted until that flow is wired; PDF download CTA omitted. ## Test plan - [ ] Vercel preview READY - [ ] `/guides/family` renders, hero + 20 chapter sections + atlas + FAQ - [ ] Atlas filters work: category, age, region, cost, free-text search; column sorts toggle asc/desc - [ ] Mobile card view shows all 71 entries below md breakpoint - [ ] `/guides` hub shows the new Family card - [ ] RelatedGuides at the bottom links to other live guides 🤖 Generated with [Claude Code](https://claude.com/claude-code)46 High Peaks guide: 9 new chapters + editorial polish
## Summary Extends `/guides/high-peaks` with full-fidelity coverage of the 24-chapter Adirondack 46 field guide. Adds new long-form chapters and supporting data tables alongside the existing PeaksTable, ShareMenu, and RelatedGuides — nothing in the previous structure was removed. ### New chapters (12) - **IV. Choosing your home base** — five staging areas (Lake Placid, Keene Valley, ADK Loj, Newcomb/Tahawus, Tupper Lake/Coreys) with a comparison table. - **V. Trailheads, parking & logistics** — AMR, The Garden, ADK Loj, Cascade, Upper Works/Tahawus, Coreys Road, with the NYSDEC/Conditions advisory call-out. - **VI. Gear: what you actually need** — non-negotiables, footwear by season, packs, navigation, emergency comms, and the day-hike checklist. - **VII. Physical & mental preparation** — eccentric leg strength, aerobic base, foot durability, training protocol, mental reps. - **XIII. Hiring a guide** — when it's worth it, costs, three featured guide-service slots (genericized). - **XIV. Lodging near the High Peaks** — three access clusters, nine venue cards. - **XVII. Safety, emergencies & rescue** — pre-hike basics, what to do in trouble, common situations, NYS Forest Ranger dispatch, WFA recommendation. - **XVIII. Alpine ecology & Leave No Trace** — Summit Steward Program + the seven LNT principles applied locally + "walk on bare rock". - **XXI. After the hike: food, drink, recovery** — six featured provisions cards (genericized) + recovery basics. - **XXII. Becoming an official 46er** — registration, why bother, beyond the patch (Trailless 46, Winter 46, Single-Season, The Grid). - **XXIII. What comes next** — Adirondack and beyond-Adirondack challenges. - **XXIV. Frequently asked questions** — 10-Q FAQ with `FAQPage` JSON-LD. ### Editorial scrub (per the brief) - No real people's names. No quoted hikers — generic editorial voice throughout. - No retail brand placeholders. Garmin / L.L.Bean / Salomon / Osprey / Hoka / La Sportiva / Altra / Sawyer / Gore-Tex / GAIA / AllTrails removed and reframed in category-neutral language ("satellite communicator", "offline GPS app", "trail-runner-style shoes", "waterproof Trails Illustrated #742 paper map"). - Removed the "Presented by L.L.Bean" title-sponsor strip from the hero. - Featured-vendor cards genericized: "Featured Guide Service · [Region]", "Featured Lodging · [Region]", "Featured · Provisions" — with an explicit italic caveat that they will be replaced with vetted partners. - Real institutions retained: ADK 46ers Inc., Adirondack Mountain Club (ADK), NYSDEC, NYS Forest Rangers, Adirondack High Peaks Summit Steward Program, AMR/Ausable Club, NOLS / SOLO Schools. - Real venues retained where editorially used: Mirror Lake Inn, Whiteface Lodge, High Peaks Resort, Adirondack Loj at Heart Lake, South Meadow Farm Lodge, Wilderness Campground — framed as "venues to consider", not "paid partners". - Stats use evergreen phrasing ("approximately 13,000 finishers", "as of recent counts") — no hardcoded annual numbers. - No "coming soon" copy anywhere. ### Editorial voice - § kicker (with chapter roman numeral) → Fraunces title with ochre period → optional italic Fraunces SOFT accent inside the H1. - Mono small-caps meta labels. - `tabular-nums` on numeric tables (home-base price, roadmap, hero stat strip). - `SectionDivider` triangle ornaments at three thematic transitions. - `FAQPage` JSON-LD added alongside the existing `Article` + `BreadcrumbList`. ### Preserved - The existing intro chapters I–III, first-peaks/teachers/finishers/tiers/combos/seasons/skills/roadmap, the interactive `PeaksTable`, `ShareMenu`, hero copy, jump nav, the §The 46 atlas cross-promo, final advice, sources, `RelatedGuides`. ## Test plan - [ ] Vercel preview deploy reaches READY. - [ ] `/guides/high-peaks` renders all 24 chapters end-to-end with the jump nav. - [ ] PeaksTable still sorts/filters; peak rows still link into `/46ers/[slug]` detail pages. - [ ] FAQ details/summary disclosures expand on click; FAQPage JSON-LD validates. - [ ] Mobile breakpoint: home-base comparison table, lodging cards, gear checklist all reflow cleanly. - [ ] No real personal names, no Garmin/L.L.Bean/etc. residuals, no "Presented by" framing in the rendered HTML. 🤖 Generated with [Claude Code](https://claude.com/claude-code)Fishing guide: hero images + species reference table
## Summary - Wires the 3 inline images on /guides/fishing (hero/ausable/pond) — sourced from Unsplash CC0 — fixing the broken-image links flagged on the published page. Flips `HAS_INLINE_IMAGES` so the in-article figures and OG card render. - Adds a new full-width breakout chapter "Common Adirondack species" — 18 species across native + stocked, with size/season/creel limits, NY state records (genericized per editorial policy), best ADK waters, techniques, and difficulty. - Sortable + filterable client table modeled on `FishingWatersAtlas`, with mobile card view and click-to-expand detail row on desktop. NYSDEC verification callout sits below the table. ## Files - `adk/public/guides/fishing/{hero,ausable,pond}.jpg` — Unsplash CC0 - `adk/src/app/guides/fishing/species-data.ts` — typed Species[] (18 entries) - `adk/src/app/guides/fishing/FishSpeciesTable.tsx` — client component - `adk/src/app/guides/fishing/page.tsx` — embed + nav anchor ## Verification flags - Records and limits use evergreen language ("As of last verified NYSDEC posting...") and instruct readers to verify at dec.ny.gov before fishing. NYSDEC publishes regs annually; recommend a spring update pass. - Round whitefish marked Threatened — catch generally prohibited. ## Test plan - [ ] Vercel preview READY - [ ] /guides/fishing loads without broken-image flags; hero, ausable inline figure, and pond figure all render - [ ] Species table sorts (name / origin / difficulty) - [ ] Filters work: native vs stocked, difficulty, search - [ ] Mobile card view renders cleanly - [ ] Click-to-expand detail row on desktop shows description + season + techniques + state record - [ ] NYSDEC callout visible under table 🤖 Generated with [Claude Code](https://claude.com/claude-code)Weddings guide: 13 new chapters + vendor grids
## Summary Expands /guides/weddings from 10 to 23 chapters with full vendor coverage, budget data, NY legal essentials, and an 18-month planning timeline. **New chapters (13):** - II. The budget reality (benchmark stats + cost-share table) - VI. 12 questions to ask before booking - VII. Choosing your photographer (+ pricing tier table + vendor grid) - VIII. Florists & wedding design (+ vendor grid) - IX. Wedding planners — three service tiers (+ vendor grid) - X. Catering, cakes & confections (+ vendor grid) - XI. Music: DJs and bands (+ comparison + vendor grid) - XII. Hair, makeup & day-of beauty (+ vendor grid) - XIII. Officiants & ceremonies — NY-specific - XVIII. Guest experience & logistics - XIX. NY marriage license & legal essentials - XX. The 18-month planning timeline - XXIII. FAQ (11 questions) **Plus:** Lake George added as a fourth featured region (Sagamore, Erlowest, Bolton Landing) and threaded through styles, mood, formulas, FAQ. **New component:** `src/components/guides/VendorGrid.tsx` — reusable for Family / Fishing guides. Cards are framed generically (Featured [Category] · [Region]) — no named placeholder businesses, no implied paid sponsorship. **Editorial discipline:** - Stripped all "Featured Vendors · Sponsored" framing → "editorial selections" - Genericized all 36 fictional vendor placeholders (e.g. "Wildflower & Pine", "Lake Effect Band", "Sagamore Studios") to region+aesthetic descriptors - Removed lead-form / sponsor-module / merch-promo CTA blocks from source HTML - Real venues kept as editorial references only (Whiteface Lodge, Mirror Lake Inn, Lake Placid Lodge, The Sagamore, Hotel Saranac, Golden Arrow, High Peaks Resort, Interlaken Inn, River Ranch, Tucker Farms, Early Dawn Confections) - No "coming soon", no real-people quotes, no retail brand placeholders ## Test plan - [ ] Vercel preview READY - [ ] /guides/weddings renders all 23 chapter sections; TOC links scroll correctly - [ ] Vendor grids responsive at mobile / tablet / desktop - [ ] Compare tables and DataCallout legible on mobile - [ ] Timeline cards render in correct chronological order - [ ] No broken `<RegionLink>` or `<ListingLink>` paths 🤖 Generated with [Claude Code](https://claude.com/claude-code)Publish Fishing field guide + waters atlas
## Summary - New flagship guide at **`/guides/fishing`** — twenty chapters of long-form editorial modeled on the High Peaks template (§ kicker, Fraunces title with ochre period, italic SOFT subtitle, mono small-caps meta, jump nav, SectionDivider ornaments, by-the-numbers hero strip). - **Full-fidelity translation** of the Adirondack Fishing Field Guide source: brook trout heritage, brown/rainbow/lake trout, smallmouth + largemouth, pike/walleye/panfish, fly fishing, lake & conventional, ice fishing, seasons calendar, regions, hiring a guide, fly shops, marinas, lodging, gear by level, NY licenses & regs, conservation & ethics, after-the-day, and a 12-question FAQ. - New full-width **Waters Atlas** (`FishingWatersAtlas.tsx`) — 40 major waters with sortable + filterable interactive table, mobile card view, modeled on `PeaksTable.tsx`. Filters: search, type, species, region, difficulty. - Flips `fishing` to `status: "live"` in `guides-list.ts`; removes the now-shadowed fishing entry from `guides/[slug]/page.tsx`. ## Genericization choices (judgment calls) - **Sponsor framings removed.** Stripped "Title Sponsor / Presented by" hero bar, sponsor-module callouts, sponsored vendor grids, lead-form widgets, affiliate gear cards, and PDF-download bar. The Hungry Trout, ADK Loj, Lake Clear Lodge, Big Moose Inn etc. survive as **real-place references** (location anchors), not as paid placements. - **People's names stripped.** "Lee Wulff, Theodore Gordon's contemporaries, Fran Betters" rewritten to generational/anonymous language while keeping the real fly patterns (Ausable Wulff, Haystack) since those are public history. - **Brand product placements replaced** with category language ("9-foot 5-weight rod, matching reel, weight-forward floating line… ask any regional fly shop") rather than Orvis/Patagonia/St. Croix/Shimano/Smith/Costa SKUs. - **Regulations evergreened.** Removed "$25 in 2026" pricing in favor of "current NYSDEC fees apply" with a `dec.ny.gov` link, per Scott's stale-data callout. Bass-opener language uses "third Saturday in June" instead of "June 20 in 2026." ## Atlas filter dimensions - **Search** (water name + notes substring) - **Type:** Lake / River / Stream / Pond - **Primary species:** Brook trout, Brown trout, Rainbow, Lake trout, Smallmouth, Largemouth, Pike, Walleye, Salmon, Perch - **Region:** Lake Placid & Wilmington / Saranac / Lake George / Old Forge & Fulton Chain / Tupper & Long Lake / Indian Lake & central / Great Sacandaga & southern / Champlain Valley - **Difficulty:** Beginner / Intermediate / Advanced - **Sortable columns:** Water, Region, Type, Difficulty ## Test plan - [ ] Vercel preview reaches READY - [ ] `/guides/fishing` renders with hero, twenty-chapter body, atlas, FAQ - [ ] Atlas: search, all four filter dropdowns, column sort, mobile card view, clear-filters reset - [ ] `/guides` hub shows Fishing as a live card (no longer Coming Soon) - [ ] Article + Breadcrumb JSON-LD validate - [ ] `tsc --noEmit` clean (verified locally) · ESLint 0 errors (verified locally) 🤖 Generated with [Claude Code](https://claude.com/claude-code)Nav restructure + pricing + editorial elevation
## Summary PR E — biggest impact lever. Reorders the primary nav around editorial discovery, introduces a public /pricing page, fixes the desktop dropdown clipping bug, and elevates the homepage so visitors meet the field guides before the chapter browser. ## Changes ### 1. Primary nav restructure `src/components/navConfig.ts`, `src/components/MobileNav.tsx`. New order, left to right: 1. **Guides** (new top-level, FIRST) — built dynamically from `GUIDES_HUB` filtered to `status: "live"` so we never advertise stubs 2. **Lodging** — unchanged 3. **Dining** — relabeled from "Provisions" (route stays `/provisions`) 4. **Activities** — new umbrella over Attractions, Mercantile, Events, History, The 46 5. **Pursuits** — unchanged 6. **Regions** — unchanged 7. **Register** (new, LAST) — Add your business, Claim a listing, Sign in, Pricing, How it works, Contact Mobile drawer reordered to mirror the desktop sequence and now exposes a Pricing link in the Account block. ### 2. NavDropdown overflow fix `src/components/NavDropdown.tsx`. The wide mega-panels were center-anchored under the trigger and would clip off either viewport edge for items near the left or right end of the nav at narrow desktop widths (~900-1100px). Added a `useLayoutEffect` that measures the panel's would-be position on open + window-resize and flips between `left-0`, `left-1/2 -translate-x-1/2` (default), and `right-0` based on available space. Solves for any nav item that overflows, not just Lodging. ### 3. /pricing page (new) `src/app/pricing/page.tsx`. Public, no auth gate. Editorial voice (§ kicker, Fraunces title, italic subtitle). Four tier cards in a 4-col grid that stacks on mobile: Free $0, Premium $29, Featured $79, Sponsored $149. Below tiers: verified-listing-is-always-free explainer, then a 4-question FAQ. No founder discounts or category-cap details surfaced. ### 4. Footer pricing link Already present in `src/components/Colophon.tsx`. No code change required — the existing "Pricing" link in the Proprietors column now resolves instead of 404-ing. ### 5. Signup-flow CTA → /pricing `src/app/signin/page.tsx`: subtle "See pricing →" line under the magic-link form. `src/app/submit/page.tsx`: appended a "See full pricing →" link to the existing tier-tease paragraph. ### 6. Homepage editorial elevation `src/app/page.tsx`. Reordered: Hero → Field Guides → § The Register (chapters) → § Explore by Region → SectionDivider → § Editor's Picks → § Trending → § On the Calendar → SectionDivider → § From the Timeline. Visitors now hit the editorial entry point and the category browser before the curated lists. ### 7. RelatedGuides component `src/components/RelatedGuides.tsx`. Pure server component, takes a `currentSlug` prop, renders 3 cards of other live guides (skipping self and any `coming-soon`). Wired into `/guides/spring-2026`, `/guides/high-peaks`, `/guides/weddings`. ### 8. Coming-soon guide stubs removed from public surfaces - Nav Guides dropdown: now built from `GUIDES_HUB.filter(status === "live")` - `/guides` hub: filters resolved guides to `effectiveStatus === "live"` - `/guides/[slug]/page.tsx`: stripped of all placeholder copy; now `notFound()` for any slug not handled by an explicit route file - `RelatedGuides`: filtered to live only - `src/app/sitemap.ts`: `GUIDE_SLUGS` reduced to the 3 live guides so the sitemap doesn't advertise URLs that 404 `guides-list.ts` keeps the coming-soon entries intact (they document intended editorial coverage and serve as scaffolding for future builds), but nothing renders them visitor-facing. ## Verify `tsc --noEmit` clean. `npm run lint` shows no new errors on touched files (pre-existing 23 errors / 25 warnings unchanged). `npm run build` succeeds; `/pricing` shows up as a static prerender, `/guides/[slug]` is dynamic. ## Test plan - [ ] Vercel preview READY, no build errors - [ ] Desktop nav: Guides is first, Register is last, dropdowns open on hover, footer links resolve - [ ] Resize desktop browser between 768px → 1440px; every NavDropdown stays fully on-screen at every width (was clipping for Lodging at ~900-1100px) - [ ] /pricing renders, all four tier cards visible, Free CTA goes to /signin?redirect=/my/listings/new, paid CTAs open mailto:partners@… - [ ] Footer "Pricing" link no longer 404s - [ ] /signin shows "See pricing →" line under the form - [ ] /submit "See full pricing →" link appended to tier paragraph - [ ] Homepage order: Hero → Field Guides → The Register → Explore by Region → Editor's Picks → Trending → Events → Timeline - [ ] Each live guide page (/guides/spring-2026, /guides/high-peaks, /guides/weddings) ends with a "Continue exploring" band linking to the other two - [ ] `/guides` hub only lists live guides; no "Coming soon" cards visible - [ ] /guides/fall, /guides/skiing, /guides/oktoberfest, etc. all return 404 - [ ] Mobile drawer opens, sections in new order (Guides/Lodging/Dining/Activities/Pursuits/Regions), Pricing link visible in Account block - [ ] `sitemap.xml` includes only the three live guide URLs 🤖 Generated with [Claude Code](https://claude.com/claude-code)Chart mobile preview height + Event schema GSC fixes
Two small shipped fixes: - /chart right preview pane gets min-h-[520px] at <lg so the photo + tagline + description + CTA fit without clipping - Event schema always emits offers, organizer, performer, image — addresses the 4 non-critical Google Search Console warningsTypographic polish + SectionDivider ornaments
Finishing polish pass: tabular-nums on numeric displays, SOFT-axis italic on pull-quote, new SectionDivider component with 4 variants placed 13× across editorial pages.Admin + customer portals: mobile-friendly pass
Makes /admin/* and /my/* usable from an iPhone. Hamburger drawer, card-stacking tables, floating bulk-action bar, touch-target padding. Out of scope: a handful of lower-traffic admin pages (imports, regions, taxonomy, users, brand, tiers, events, guides) — noted for follow-up.Guide: swap Coming-soon placeholder for /46ers cross-promo
The interactive atlas teased in the guide is now live as the /46ers section. Replaces the placeholder with a working cross-promo card.High Peaks guide: link peak names to /46ers detail pages
Each peak name in the sortable table links to its individual /46ers/<slug> detail page. Both desktop table view and mobile card view. Prose mentions in stage/combo copy not linked — needs parsing; flagged as follow-up.FilterableListings: completeness-score sort
Featured sort now uses a weighted score (photo + description + tier + editor_pick + verified + tags) so non-photo listings drop to the bottom of every category page. As enrichment populates fields, order self-corrects — no manual resort needed.Mobile polish: overflow, map stacking, card stacking, subcat nav
Fixes the mobile issues Scott flagged on iPhone: - Full-width header (was: header stopped short because content scrolled past viewport) - Content no longer extends off right edge (overflow-x:hidden on html/body) - Map panes stay contained (isolation:isolate on .leaflet-container — pins no longer render over header) - Listing cards: photo above content on mobile, horizontal on desktop - Duplicate Featured/Editor's Pick label removed; Verified no longer overlaps - /chart map actually renders on mobile (was 0-height) - Chapter pages get a subcategory chip rail — users can reach Lodging>Glamping on mobile without the desktop mega-dropdownMobile: nav entries for new pages + /chart viewable on phones
- Mobile nav: added The 46, Hiking Trails, The Chart (replaces Plan a Trip) - /chart mobile: Layers panel is now a floating drawer (hidden by default, toggle button top-left of map). Desktop 3-column grid unchanged./46ers: equal-height cards across rows
Small CSS fix — flex column + mt-auto on the footer row so cards line up.46ers polish: illustrated fallback + tier badges + hero layout
Three small things: - 46er Club card now vertically centered in the hero - Peak cards without a photo render an illustrated mountain background with the peak name (vs a lonely icon placeholder) - Each card shows the difficulty tier (Easier/Moderate/Advanced/Expert) from the guide seed - Cleared the wrong Mount Marshall photo (Wikipedia pulled an Australian locality)Fix: Wikipedia photos + 46er badge polish
- Add upload.wikimedia.org to next/image remotePatterns (photos were silently blocked) - Bigger logo, moved to right of text, vertical center - Link target → adk46er.org (root)Peak photos + 46er Club badge + Guide promo on /46ers
## Summary - Migration 020: peaks photo_url/attribution/source - Seed script pulls lead photo from Wikipedia pageimages API per peak - /46ers index renders photos in cards (placeholder when none) - Adirondack 46er Club badge (desktop top-right + mobile pill) linking to adk46er.org with logo + nonprofit blurb - Guide promo block linking to /guides/high-peaks - /46ers/[slug] gets a full-width hero photo with Wikipedia attribution - Hough Peak elevation corrected 4409 → 4400 per ADK 46er Club canonical list ## Requires - Apply migration 020 - Save the 46er Club logo to adk/public/adk46er-logo.png - Run seed-peak-photos.mjs after migration appliedHiking Trails section + /chart overlay + cross-links
## Summary Turns the 251 OSM hiking-relation trails into a full trail-guide experience. - **Migration 019**: \`trails.slug\` + \`trail_pois\` join table (4 roles: endpoint-trailhead/parking, along-route, waypoint) - **\`/pursuits/trails\`** index (map-always-on, filters, sort, peak counts) + **\`/pursuits/trails/[slug]\`** detail (highlighted polyline, trailheads/parking/along-route/peaks sections, Google Maps directions, OSM attribution) - **\`/chart\` overlay**: new "Hiking trails" toggle (default OFF) renders all 251 polylines; summit popups link to /46ers/[slug] when names match - **Cross-linking**: /46ers ↔ /pursuits/trails ↔ /chart all link to each other via slugs - Cron extended to compute slugs + rebuild \`trail_pois\` (all four roles) - Nav entry: "Hiking Trails" under Pursuits ## Requires - Apply migration 019 - Re-run \`/api/cron/refresh-trails\` to populate slugs + trail_pois ## Test plan - [ ] Apply migration 019 - [ ] Re-run curl on refresh-trails, expect \`{...trail_poi_associations: N}\` - [ ] /pursuits/trails renders index with map, filters, rows - [ ] /pursuits/trails/[some-slug] renders detail map with trail highlighted + POI sections - [ ] /chart "Hiking trails" toggle renders polylines - [ ] Summit popup on /chart links to /46ers/[slug] for a 46er matchWiden peak-trail radius to 1000m
Catches 6 more peaks whose canonical trail sits just outside 500m. Trailless 46ers will still be trailless, as they should be.Fix: trail geometry extraction + tier-badge wrapping
## Summary - **Trail geometry**: first cron run upserted 251 relations but 0 had geometry. Overpass `out geom` on a relation only returns its bbox, not member geometries. Rewrote query to pull relations + recurse to member ways (`way(r)` + `out geom`) as separate top-level elements, then join wayId → geometry in the parser. - **Tier badge**: strip " 46er" from the display label on the High Peaks guide table so "Easier 46er" renders as "EASIER" and doesn't wrap. ## Test plan - [ ] After merge, re-run `curl` on `/api/cron/refresh-trails` — expect associations_created > 0 - [ ] /guides/high-peaks — tier badges render single-lineFix: 46ers detail page dynamic import + seed parser
## Summary - **[slug]/page.tsx build failure** — PR #34 shipped with `dynamic(..., { ssr: false })` in a server component; Next.js 16 + Turbopack reject this at build time. Fix: PeakMap is already a client component, import it directly - **seed-high-peaks parser** — the bracket walker matched `[]` inside the TypeScript type annotation `HighPeakSeed[]` before finding the actual array literal, so the script loaded 0 peaks. Fix: scan past the `=` first Both caught after the PR #34 merge broke production. Merge this to restore prod. ## Test plan - [x] Seed script loads all 46 peaks and upserts (already ran locally) - [x] /46ers and /46ers/mount-marcy return 200 on localhost - [ ] After merge, production build succeeds and /api/cron/refresh-trails returns trail countsThe 46: High Peaks trip-planning MVP (seed + OSM trails + pages)
## Summary Foundation for a trip-planning experience around the Adirondack 46 High Peaks. - **Migration 018**: \`peaks\` (46 canonical summits), \`trails\` (OSM route=hiking relations with LineString geometry), \`peak_trails\` (many-to-many with proximity distance) - **Seed**: all 46 peaks hand-curated in \`adk/data/46-high-peaks.ts\` + idempotent seed script - **Trail ingestion**: Overpass cron (Sundays 06:30 UTC) fetches OSM hiking relations across the Adirondack bbox, flattens member ways into concatenated LineStrings, upserts into trails + rebuilds peak_trails within 500m - **Pages**: \`/46ers\` index grid, \`/46ers/[slug]\` detail with PeakMap (summit + trail polylines colored by SAC difficulty + nearby POIs within 5km) - Sitemap + nav updated ## Requires - Apply migration 018 in Supabase SQL editor - Run: \`node --env-file=.env.local adk/scripts/seed-high-peaks.mjs\` - Trigger \`/api/cron/refresh-trails\` with CRON_SECRET to populate trails now (or wait until Sunday) ## Known limits (next-PR territory) - **Quality**: OSM hiking coverage of the ADK varies by trail. The next layer is NYSDEC GIS data (authoritative state-maintained trail shapefiles) ingested alongside OSM — planned as a follow-up PR. - **Geometry**: OSM relation geometry is concatenated in declared member order; disconnected relations will draw straight-line hops. Flagged in tags. - **Association**: peak_trails computed client-side via haversine in the cron. Fine at current scale; can be swapped to PostGIS ST_DWithin later. ## Test plan - [ ] Apply migration 018 - [ ] Seed peaks via script - [ ] \`/46ers\` renders grid of 46 peaks (photo placeholders OK) - [ ] Click a peak → detail page loads, summit on the map, nearby POIs visible - [ ] Trigger \`/api/cron/refresh-trails\` → trails populate, peak detail pages show polylinesFix: nav dropdown above Leaflet map on /chart
## Summary - Leaflet panes use z-index up to 800. Masthead was z-40, dropdown z-50 — so on pages where the map ran into the sticky header, the dropdown tucked behind the map. - Bumped sticky masthead to z-[900] and dropdown to z-[1000] so both sit above every Leaflet pane. ## Test plan - [ ] /chart — hover Lodging nav → dropdown covers the map - [ ] /lodging/resorts-and-great-camps — same - [ ] Mobile drawer still opens correctly at all breakpointsCollection pages: map always on top, listings below
## Summary - FilterableListings now renders the map directly above the listings grid by default (rather than behind a List/Map toggle) - Removed the List/Map tab toggle entirely - Added a "Hide map" text link with localStorage persistence (\`adk.mapVisible\` key) so power users who prefer pure list browsing can opt out once and have it stick - Removed now-redundant InlineCollectionMap usage on chapter / subcategory / intersection pages — FilterableListings now handles the map for these surfaces - Region page: dropped the \`defaultView="map"\` prop since the concept no longer exists; the existing 900px map via \`mapHeight\` stays ## Why The directory is fundamentally geographic — "where is this?" is usually the primary question, especially across a place as large as the Adirondacks. Toggles add mental load ("am I in list or map mode?"), and map discoverability shouldn't require a click. The "Hide map" escape hatch keeps power users happy. ## Test plan - [ ] /lodging — map visible above filters + list - [ ] /lodging/resorts-and-great-camps — map visible, sponsored hero card (if any) pinned above - [ ] Click "Hide map" → map vanishes, "Show map" link appears, reload page → stays hidden - [ ] Click "Show map" → map returns - [ ] /regions/high-peaks — 900px map still renders (no double-map regression) - [ ] No /chapter page shows double-mapOutdoor POI overlay + admin tier blueprint
## Summary - Adds outdoor POI table (trailheads, fire towers, parking, leantos, shelters, ranger stations, summits, waterfalls, viewpoints) sourced from OpenStreetMap via Overpass API - Weekly Vercel cron (Sunday 06:00 UTC) refreshes via \`/api/cron/refresh-pois\` - /chart gets a "Outdoor POIs" Layers sidebar section — master toggle + per-type checkboxes, defaults trailhead/firetower/leanto - POI pins draw below listing pins so listings stay primary - /admin/tiers page mirrors the tier differentiation blueprint from memory ## Requires - Apply migration 017 in Supabase SQL editor (\`supabase/migrations/017_outdoor_pois.sql\`) - After applying, hit \`/api/cron/refresh-pois\` with the CRON_SECRET bearer (or wait for Sunday 06:00 UTC) to populate the table ## Test plan - [ ] Apply migration 017 - [ ] Trigger \`/api/cron/refresh-pois\` manually with bearer; verify outdoor_pois table fills - [ ] /chart shows "Outdoor POIs" toggle group; default-enabled types render - [ ] POI popup shows Google Maps "Get directions" link - [ ] /admin/tiers renders the matrix with shipped/open badges - [ ] Map renders cleanly if migration 017 is NOT applied (soft-fail check)Tier differentiation: maps, cards, chart streamline
## Summary Visual uplift across every public surface to make paid/featured tiers distinct from free. Follows the matrix saved as the tier blueprint in memory. **Maps (all three map components):** - Standard → Editor pick → Premium → Sponsored hierarchy with bigger size jumps, color/icon/ring differentiation, pulsing sponsored ring, and always-visible name badges on premium+ **Listing cards (FilterableListings):** - Sponsored full-bleed hero pinned to top of list - Premium ochre left-border + tint + "Featured" badge - Editor-pick corner ribbon **Listing detail:** - "Verified by AdirondackRegion" pill for premium/sponsored - Larger gallery layout extended to sponsored **Chart page streamlined:** - Dropped redundant breadcrumb + kicker + huge h1 + long description (consuming 3/4 of fold) - Slim one-line header, map now dominates viewport - Right-pane shows primary photo + short description on selection ## Test plan - [ ] /chart — header slim, map dominant, click a pin → photo + short desc in right pane - [ ] /lodging — sponsored hero card (if one exists) pinned to top - [ ] Any lodging premium listing — ochre border + "Featured" badge - [ ] Premium listing detail page — "Verified by AdirondackRegion" pill near title - [ ] ExploreNearbyMap on a listing detail — featured pins stand outInline maps on chapter / subcategory / intersection pages
## Summary - Every chapter, subcategory, and subcategory-in-region page now renders a Leaflet map above the listings grid, pinned with the listings on that page - Pin hierarchy matches /chart: Sponsored 48px ochre+star · Premium 42px ochre · Editor pick 36px star · Standard 28px. Featured pins z-index above standard. - ExploreNearbyMap (listing detail) aligned to the same hierarchy so the whole site's maps look consistent - Region pages intentionally NOT changed — they already render a 900px filterable map; a second overview would be redundant ## Test plan - [ ] /lodging — shows map with lodging pins across the park - [ ] /lodging/historic-inns — shows map scoped to historic inns - [ ] /lodging/historic-inns/in/high-peaks — shows map for that intersection - [ ] /regions/high-peaks — single filterable map unchanged (no double-map regression) - [ ] A listing detail page — nearby map uses new tier hierarchyClaim-listing flow + simpler auth landing
## Summary - Public listing pages get a new "Own this business?" CTA that routes proprietors to `/my/listings/claim/[id]` — magic-link gated, pre-bound to the listing, form captures relationship / phone / notes and writes to the existing `ownership_claims` table - Admin queue at `/admin/claims` with status tabs (New / Needs info / Approved / Rejected) and inline Approve / Reject / Needs-info. Approving with `grant_edit` sets `listings.submitted_by_user_id = claimant` so the claimant can edit via `/my/listings/[id]/edit` - `/auth/callback` landing simplified: centered ADK mark + spinner during auth, full error/diagnostics only on failure — replaces the previous masthead + heading page ## Test plan - [ ] Visit any published listing page — confirm "Own this business?" CTA renders above the Report card - [ ] Click Claim while signed out — should redirect to `/signin?redirect=…` and back after magic link - [ ] Submit a claim as a non-admin user — verify row appears in `/admin/claims` under New - [ ] Approve a claim → confirm the claimant can now load `/my/listings/[id]/edit` for that listing - [ ] Click a magic link — confirm the callback page shows centered logo + spinner (no masthead) - [ ] Force an error (expired/reused link) — confirm full error UI + diagnostic disclosure still worksFix: /chart was empty because local parseCoords only read WKT
The page-local parseCoords only matched WKT `POINT(lng lat)` strings, but PostgREST sends EWKB hex for geography columns, so every row failed to parse. Replaced with the shared @/lib/coords parser that handles all four formats.Chart: highlight featured listings + Featured-only toggle
Tiered pin sizes and ochre rings for premium/sponsored, star-center dot for editor picks. New "Featured only" toggle on the left sidebar scopes the whole map to the short list. Popup + selected-entry sidebar both label featured status.Public listing Report / Suggest-edit + admin queue
Self-contained: migration + public form + admin queue. Apply migration 012_listing_reports.sql in Supabase dashboard after merging or it will 500 on insert.Remove Fuel Stops / Fishing License / Emergency from Guides
Per Scott — these won't be standalone guide pages. Removed from guides-list.ts, the [slug] metadata map, sitemap, and nav dropdown.Show guide covers on coming-soon; fix hamburger drawer at lg-to-xl widths
Two fixes. Guide cards now render the real cover for any guide that has a cover file (marked via new `hasCover` flag on GuideHub), regardless of live/coming-soon status — Scott's admin overrides don't hide existing covers anymore. Hamburger drawer escape: dropped `lg:backdrop-blur-sm` on the sticky header, which per spec was creating a containing block that trapped the `fixed` drawer inside the header at 1024–1280px widths.Imports reliability + new logo in mobile/OG + hero CTAs
## Summary - **Imports reliability** — bulk reject/promote no longer aborts on the first chunk with any failure; chunks shrunk to 25 so they fit inside Vercel's timeout; dedup index is built once per request (paginated past the 1k PostgREST cap) instead of refetching up to 5k listings per item; `confirm()` swapped for a two-click arm pattern so Chrome's auto-suppression can't silently drop preset/bulk actions. - **Brand** — mobile hamburger drawer shows the new `/adk-logo.webp` mark; default OG card uses the hero video still as background with the new PNG logo overlaid. - **Hero CTAs** — Browse by Region / Browse the Map / Field Guides (replacing "Open the Chart" / "Plan a Trip"). Plan-a-Trip remains in the top-right nav and mobile drawer. ## Test plan - [ ] `/admin/imports` — click an alpine-ski-pistes preset, confirm two-click arm works, and verify a ~750-row promote completes in one go (no mid-batch abort). - [ ] Mobile viewport — open the hamburger drawer and verify the new logo renders at the top. - [ ] Hit `/opengraph-image` directly and verify the hero still + logo composition. - [ ] Homepage hero buttons link correctly and render on mobile. 🤖 Generated with [Claude Code](https://claude.com/claude-code)