[object Object]

← back to Consulting Designerwallcoverings Com

seed growth buckets with live channel state + $0 competitor pixel scan; bind ad-gap finding into brief

f141e5782ac2526c99fc6ff1f49eae85e7924150 · 2026-07-26 08:06:35 -0700 · Steve Abrams

Files touched

Diff

commit f141e5782ac2526c99fc6ff1f49eae85e7924150
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sun Jul 26 08:06:35 2026 -0700

    seed growth buckets with live channel state + $0 competitor pixel scan; bind ad-gap finding into brief
---
 build.mjs                  |   8 +++
 data/ads.json              |  97 +++++++++++++++++++++++++-
 data/best-times.json       |  17 ++++-
 data/competitors.json      | 114 +++++++++++++++++++++++-------
 data/content-calendar.json | 169 ++++++++++++++++++++++++++++++++++++++++++++-
 data/directories.json      |  22 ++++--
 data/media.json            |  15 +++-
 data/socials.json          |  62 ++++++++++++++---
 data/suggestions.json      |  72 +++++++++++--------
 public/portal.html         |   6 +-
 scripts/seed-buckets.mjs   | 143 ++++++++++++++++++++++++++++++++++++++
 11 files changed, 651 insertions(+), 74 deletions(-)

diff --git a/build.mjs b/build.mjs
index 76d6d16..9289923 100644
--- a/build.mjs
+++ b/build.mjs
@@ -15,6 +15,7 @@ mkdirSync(`${OUT}/versions`, { recursive: true });
 const readJ = (p, d) => { try { return JSON.parse(readFileSync(join(HERE, p), 'utf8')); } catch { return d; } };
 const client = readJ('data/client.json', {});
 const SNAP = readJ('data/dw-analysis.json', { catalog: {}, channels: [], fleet: { count: 0, sites: [] }, competitors: [] });
+const ADS = readJ('data/ads.json', []);
 const C = SNAP.catalog || {};
 const FF = C.five_field || {};
 const SO = C.sample_only || { total: 0, quote_only_by_design: 0, backlog: 0 };
@@ -125,6 +126,10 @@ const compRows = (SNAP.competitors || []).map(c =>
 const channelRows = (SNAP.channels || []).map(c =>
   `<tr><td><b>${c.label || c.key}</b></td><td>${c.connected ? '<span class="good">Connected</span>' : '<span class="flag">Not connected</span>'}</td><td>${fmt(c.accounts)}</td><td>${c.note || (c.connected ? 'Live in the marketing command center' : '')}</td></tr>`).join('');
 
+const adRows = ADS.map(a =>
+  `<tr${/\(you\)/.test(a.name) ? ' class="you"' : ''}><td><b>${a.name}</b></td><td>${a.domain}</td><td>${a.ad_pixels && a.ad_pixels.length ? a.ad_pixels.join(' · ') : '—'}</td><td>${a.analytics ? 'Yes' : '—'}</td><td>${a.read || ''}</td></tr>`).join('');
+const rivalsAdvertising = ADS.filter(a => !/\(you\)/.test(a.name) && a.ad_pixels && a.ad_pixels.length).length;
+
 const vendorRows = (C.top_vendors || []).map((v, i) =>
   `<tr><td class="mo">${String(i + 1).padStart(2, '0')}</td><td><b>${v.vendor}</b></td><td>${fmt(v.active)}</td></tr>`).join('');
 
@@ -182,6 +187,7 @@ const SOURCES = [
   ['Social channel status', 'The DW marketing command center API (marketing.designerwallcoverings.com)', 'Authenticated status read at collection time — connected flags + account counts per platform', 'live'],
   ['Microsite fleet', 'The fleet aggregator directory API (all.designerwallcoverings.com)', `${fmt(SNAP.fleet?.count)} sites enumerated at collection time`, 'live'],
   ['Competitor watch-list', 'The connie_competitors table (the DW competitor-scan registry)', 'Rendered verbatim: domain, watch status, products found by prior scans', 'live'],
+  ['Competitor paid-ads table', 'A $0 homepage pixel scan of each tracked domain (Google/Meta/TikTok/Pinterest/Bing tag signatures)', 'Measured at seed time by <code>scripts/seed-buckets.mjs</code>; unreachable homepages say so rather than guessing', 'observed'],
   ['Competitor scoring chart + map pins', 'Analyst assessment over public sites; HQ geo compiled from public records', 'Authored consulting view — NOT scraped metrics; the map is oriented, not surveyed', 'analyst'],
   ['Price-band chart', C.price_bands_basis || 'Priced subset of the mirror', 'Partial-basis chart, labeled as such — full variant pricing lives Shopify-side', 'derived'],
   ['Website & social audits, partners, revenue plays, roadmap', 'Analyst strategy grounded in the operating knowledge of the DW stack', 'Consulting recommendations — models and plays, not measurements', 'analyst'],
@@ -374,6 +380,8 @@ footer{padding:56px clamp(24px,7vw,120px);border-top:1px solid var(--line);color
 
 <section id="advertising"><div class="eyebrow">04 · Channels &amp; Distribution</div><h2 class="disp">Seven channels, one command center.</h2><div class="accbody">
   <p class="lead2">Live from the marketing command center at collection time: <b>${connectedCount} of ${(SNAP.channels || []).length}</b> platforms connected. The Facebook page network alone spans <b>${fmt((SNAP.channels || []).find(c => c.key === 'facebook')?.accounts)}</b> pages; Instagram <b>${fmt((SNAP.channels || []).find(c => c.key === 'instagram')?.accounts)}</b> accounts. The infrastructure exists — the discipline to convert reach into attributed revenue is the build.</p>
+  <div class="flagbox"><div class="i">◆</div><div><h3 class="disp">The paid-ads gap — measured, not guessed</h3><p>A $0 pixel scan of every tracked competitor's homepage (${new Date(SNAP.collected_at || Date.now()).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}): <b>${rivalsAdvertising} of ${ADS.length - 1} rivals are paying to advertise</b> — Google Ads on five of them, Meta on one — while <b>DW's own homepage carries analytics but zero paid-ad pixels</b>. The field is buying the demand DW's catalog should own. With attribution wired first (below), a measured Google Shopping + brand-terms program is the obvious counter.</p></div></div>
+  <table><thead><tr><th>Domain</th><th></th><th>Ad pixels detected</th><th>Analytics</th><th>Read</th></tr></thead><tbody>${adRows}</tbody></table>
   <div class="chartwrap"><div class="ct">Connected accounts per platform (live)</div><canvas id="chanChart"></canvas></div>
   <table><thead><tr><th>Platform</th><th>Status</th><th>Accounts</th><th>Note</th></tr></thead><tbody>${channelRows}</tbody></table>
   <div class="cards2">
diff --git a/data/ads.json b/data/ads.json
index 0637a08..55ba388 100644
--- a/data/ads.json
+++ b/data/ads.json
@@ -1 +1,96 @@
-[]
\ No newline at end of file
+[
+  {
+    "domain": "designerwallcoverings.com",
+    "name": "Designer Wallcoverings (you)",
+    "ad_pixels": [],
+    "analytics": true,
+    "read": "No paid-ad pixels detected on homepage",
+    "scanned_at": "2026-07-26T15:05:31.574Z",
+    "method": "$0 homepage pixel scan"
+  },
+  {
+    "domain": "decoratorsbest.com",
+    "name": "Decorators Best",
+    "ad_pixels": [
+      "google_ads",
+      "microsoft_bing"
+    ],
+    "analytics": true,
+    "read": "Paying to advertise (google_ads, microsoft_bing)",
+    "scanned_at": "2026-07-26T15:05:25.274Z",
+    "method": "$0 homepage pixel scan (advertising-signals pattern)"
+  },
+  {
+    "domain": "wallpaperstogo.com",
+    "name": "Wallpapers To Go",
+    "ad_pixels": [
+      "google_ads",
+      "meta"
+    ],
+    "analytics": false,
+    "read": "Paying to advertise (google_ads, meta)",
+    "scanned_at": "2026-07-26T15:05:25.920Z",
+    "method": "$0 homepage pixel scan (advertising-signals pattern)"
+  },
+  {
+    "domain": "mahoneswallpapershop.com",
+    "name": "Mahones Wallpaper",
+    "ad_pixels": [
+      "google_ads"
+    ],
+    "analytics": true,
+    "read": "Paying to advertise (google_ads)",
+    "scanned_at": "2026-07-26T15:05:26.639Z",
+    "method": "$0 homepage pixel scan (advertising-signals pattern)"
+  },
+  {
+    "domain": "ladesignconcepts.com",
+    "name": "L.A. Design Concepts",
+    "ad_pixels": [
+      "google_ads",
+      "microsoft_bing"
+    ],
+    "analytics": true,
+    "read": "Paying to advertise (google_ads, microsoft_bing)",
+    "scanned_at": "2026-07-26T15:05:27.315Z",
+    "method": "$0 homepage pixel scan (advertising-signals pattern)"
+  },
+  {
+    "domain": "finestwallpaper.com",
+    "name": "Finest Wallpaper",
+    "ad_pixels": [
+      "google_ads"
+    ],
+    "analytics": true,
+    "read": "Paying to advertise (google_ads)",
+    "scanned_at": "2026-07-26T15:05:29.917Z",
+    "method": "$0 homepage pixel scan (advertising-signals pattern)"
+  },
+  {
+    "domain": "burkedecor.com",
+    "name": "Burke Decor",
+    "ad_pixels": [],
+    "analytics": false,
+    "read": "Homepage unreachable to scanner (fetch failed)",
+    "scanned_at": "2026-07-26T15:05:30.106Z",
+    "method": "$0 homepage pixel scan (advertising-signals pattern)"
+  },
+  {
+    "domain": "wallpaperwholesaler.com",
+    "name": "Wallpaper Wholesaler",
+    "ad_pixels": [],
+    "analytics": true,
+    "read": "No paid-ad pixels detected on homepage",
+    "scanned_at": "2026-07-26T15:05:31.016Z",
+    "method": "$0 homepage pixel scan (advertising-signals pattern)"
+  },
+  {
+    "domain": "peelstick.com",
+    "name": "Peelstick",
+    "ad_pixels": [],
+    "analytics": false,
+    "read": "Homepage unreachable to scanner (fetch failed)",
+    "scanned_at": "2026-07-26T15:05:31.405Z",
+    "method": "$0 homepage pixel scan (advertising-signals pattern)"
+  }
+]
\ No newline at end of file
diff --git a/data/best-times.json b/data/best-times.json
index 6a612d6..02ba17d 100644
--- a/data/best-times.json
+++ b/data/best-times.json
@@ -1,4 +1,5 @@
 {
+  "_basis": "Analyst cadence for the interiors/design vertical — refine against GA4 + per-post engagement once attribution lands",
   "instagram": [
     "Tue 11:00",
     "Thu 19:00",
@@ -12,5 +13,19 @@
     "Tue 18:00",
     "Fri 20:00"
   ],
-  "note": "Generic starting cadence — replace with account-specific data via scripts/best-times.js."
+  "pinterest_note": "Pinterest is the missing high-fit channel for wallcoverings — evenings + weekends dominate saves",
+  "youtube": [
+    "Sat 11:00"
+  ],
+  "threads": [
+    "Mon 12:00",
+    "Thu 12:00"
+  ],
+  "bluesky": [
+    "Wed 10:00"
+  ],
+  "linkedin": [
+    "Tue 09:00",
+    "Thu 09:00"
+  ]
 }
\ No newline at end of file
diff --git a/data/competitors.json b/data/competitors.json
index 72c9d4c..5ca31c1 100644
--- a/data/competitors.json
+++ b/data/competitors.json
@@ -1,37 +1,101 @@
 [
   {
-    "id": "comp-1",
-    "name": "decoratorsbest.com",
-    "socials": {},
-    "ads_running": null,
-    "notes": ""
+    "id": "comp-decoratorsbest.com",
+    "name": "Decorators Best",
+    "domain": "decoratorsbest.com",
+    "watch": "active",
+    "products_found": 26607,
+    "notes": "Large catalog, public pricing. Carries Thibaut, Schumacher, Cole & Son, Kravet",
+    "pixels": [
+      "google_ads",
+      "microsoft_bing",
+      "ga4"
+    ],
+    "scanned": true
   },
   {
-    "id": "comp-2",
-    "name": "burkedecor.com",
-    "socials": {},
-    "ads_running": null,
-    "notes": ""
+    "id": "comp-wallpaperstogo.com",
+    "name": "Wallpapers To Go",
+    "domain": "wallpaperstogo.com",
+    "watch": "active",
+    "products_found": 25001,
+    "notes": "Major retailer. York, Thibaut, Brewster, Norwall",
+    "pixels": [
+      "google_ads",
+      "meta"
+    ],
+    "scanned": true
   },
   {
-    "id": "comp-3",
-    "name": "mahoneswallpapershop.com",
-    "socials": {},
-    "ads_running": null,
-    "notes": ""
+    "id": "comp-mahoneswallpapershop.com",
+    "name": "Mahones Wallpaper",
+    "domain": "mahoneswallpapershop.com",
+    "watch": "active",
+    "products_found": 24990,
+    "notes": "Traditional dealer. Most major brands.",
+    "pixels": [
+      "google_ads",
+      "ga4"
+    ],
+    "scanned": true
   },
   {
-    "id": "comp-4",
-    "name": "wallpapersToGo",
-    "socials": {},
-    "ads_running": null,
-    "notes": ""
+    "id": "comp-ladesignconcepts.com",
+    "name": "L.A. Design Concepts",
+    "domain": "ladesignconcepts.com",
+    "watch": "active",
+    "products_found": 13826,
+    "notes": "Discount dealer (40% off). Thibaut, Schumacher, Anna French",
+    "pixels": [
+      "google_ads",
+      "microsoft_bing",
+      "ga4"
+    ],
+    "scanned": true
   },
   {
-    "id": "comp-5",
-    "name": "stevesblindsandwallpaper.com",
-    "socials": {},
-    "ads_running": null,
-    "notes": ""
+    "id": "comp-finestwallpaper.com",
+    "name": "Finest Wallpaper",
+    "domain": "finestwallpaper.com",
+    "watch": "active",
+    "products_found": 1046,
+    "notes": "Ralph Lauren, Thibaut. US/Canada pricing.",
+    "pixels": [
+      "google_ads",
+      "ga4"
+    ],
+    "scanned": true
+  },
+  {
+    "id": "comp-burkedecor.com",
+    "name": "Burke Decor",
+    "domain": "burkedecor.com",
+    "watch": "active",
+    "products_found": 0,
+    "notes": "Home decor + wallpaper. Cole & Son, Brewster, York",
+    "pixels": [],
+    "scanned": false
+  },
+  {
+    "id": "comp-wallpaperwholesaler.com",
+    "name": "Wallpaper Wholesaler",
+    "domain": "wallpaperwholesaler.com",
+    "watch": "active",
+    "products_found": 0,
+    "notes": "Custom site (not Shopify). 32K+ products. JSON-LD structured data. Sitemap: /sitemaps/sitemap-products-{1,2}.xml. Brands: Brewster, MDC, Mind the Gap, Sancar, Eykon, National Wallcovering, Norwall. Quote-only: Astek, Fabricut, Innovations, PJ, S.Harris, Stroheim, Trend, Vervain.",
+    "pixels": [
+      "ga4"
+    ],
+    "scanned": true
+  },
+  {
+    "id": "comp-peelstick.com",
+    "name": "Peelstick",
+    "domain": "peelstick.com",
+    "watch": "paused",
+    "products_found": 0,
+    "notes": "Peel & stick only — different category",
+    "pixels": [],
+    "scanned": false
   }
 ]
\ No newline at end of file
diff --git a/data/content-calendar.json b/data/content-calendar.json
index 0637a08..763feec 100644
--- a/data/content-calendar.json
+++ b/data/content-calendar.json
@@ -1 +1,168 @@
-[]
\ No newline at end of file
+[
+  {
+    "date": "2026-07-26",
+    "title": "New-arrivals story",
+    "brief": "Daily drop from the activation drip — 3 patterns, one mood",
+    "channels": [
+      "instagram",
+      "facebook",
+      "threads"
+    ],
+    "status": "DRAFT",
+    "rail": "Social posting is DRAFT-ONLY until Steve enables it"
+  },
+  {
+    "date": "2026-07-27",
+    "title": "Texture Tuesday",
+    "brief": "One material macro (grasscloth / cork / silk) + care note",
+    "channels": [
+      "instagram",
+      "tiktok",
+      "bluesky"
+    ],
+    "status": "DRAFT",
+    "rail": "Social posting is DRAFT-ONLY until Steve enables it"
+  },
+  {
+    "date": "2026-07-28",
+    "title": "Designer pick",
+    "brief": "One pattern, one room render, one designer quote",
+    "channels": [
+      "instagram",
+      "pinterest",
+      "facebook"
+    ],
+    "status": "DRAFT",
+    "rail": "Social posting is DRAFT-ONLY until Steve enables it"
+  },
+  {
+    "date": "2026-07-29",
+    "title": "Before / after wall",
+    "brief": "Client or render transformation, sample CTA",
+    "channels": [
+      "tiktok",
+      "instagram",
+      "youtube"
+    ],
+    "status": "DRAFT",
+    "rail": "Social posting is DRAFT-ONLY until Steve enables it"
+  },
+  {
+    "date": "2026-07-30",
+    "title": "Trade corner",
+    "brief": "Spec tip for designers (widths, repeats, Type II)",
+    "channels": [
+      "linkedin",
+      "threads"
+    ],
+    "status": "DRAFT",
+    "rail": "Social posting is DRAFT-ONLY until Steve enables it"
+  },
+  {
+    "date": "2026-07-31",
+    "title": "Sample Saturday",
+    "brief": "$4.25 memo program explainer + bundle prompt",
+    "channels": [
+      "instagram",
+      "facebook",
+      "tiktok"
+    ],
+    "status": "DRAFT",
+    "rail": "Social posting is DRAFT-ONLY until Steve enables it"
+  },
+  {
+    "date": "2026-08-01",
+    "title": "Pattern history",
+    "brief": "The story of one motif (damask / toile / chinoiserie)",
+    "channels": [
+      "facebook",
+      "bluesky",
+      "threads"
+    ],
+    "status": "DRAFT",
+    "rail": "Social posting is DRAFT-ONLY until Steve enables it"
+  },
+  {
+    "date": "2026-08-02",
+    "title": "New-arrivals story",
+    "brief": "Daily drop from the activation drip — 3 patterns, one mood",
+    "channels": [
+      "instagram",
+      "facebook",
+      "threads"
+    ],
+    "status": "DRAFT",
+    "rail": "Social posting is DRAFT-ONLY until Steve enables it"
+  },
+  {
+    "date": "2026-08-03",
+    "title": "Texture Tuesday",
+    "brief": "One material macro (grasscloth / cork / silk) + care note",
+    "channels": [
+      "instagram",
+      "tiktok",
+      "bluesky"
+    ],
+    "status": "DRAFT",
+    "rail": "Social posting is DRAFT-ONLY until Steve enables it"
+  },
+  {
+    "date": "2026-08-04",
+    "title": "Designer pick",
+    "brief": "One pattern, one room render, one designer quote",
+    "channels": [
+      "instagram",
+      "pinterest",
+      "facebook"
+    ],
+    "status": "DRAFT",
+    "rail": "Social posting is DRAFT-ONLY until Steve enables it"
+  },
+  {
+    "date": "2026-08-05",
+    "title": "Before / after wall",
+    "brief": "Client or render transformation, sample CTA",
+    "channels": [
+      "tiktok",
+      "instagram",
+      "youtube"
+    ],
+    "status": "DRAFT",
+    "rail": "Social posting is DRAFT-ONLY until Steve enables it"
+  },
+  {
+    "date": "2026-08-06",
+    "title": "Trade corner",
+    "brief": "Spec tip for designers (widths, repeats, Type II)",
+    "channels": [
+      "linkedin",
+      "threads"
+    ],
+    "status": "DRAFT",
+    "rail": "Social posting is DRAFT-ONLY until Steve enables it"
+  },
+  {
+    "date": "2026-08-07",
+    "title": "Sample Saturday",
+    "brief": "$4.25 memo program explainer + bundle prompt",
+    "channels": [
+      "instagram",
+      "facebook",
+      "tiktok"
+    ],
+    "status": "DRAFT",
+    "rail": "Social posting is DRAFT-ONLY until Steve enables it"
+  },
+  {
+    "date": "2026-08-08",
+    "title": "Pattern history",
+    "brief": "The story of one motif (damask / toile / chinoiserie)",
+    "channels": [
+      "facebook",
+      "bluesky",
+      "threads"
+    ],
+    "status": "DRAFT",
+    "rail": "Social posting is DRAFT-ONLY until Steve enables it"
+  }
+]
\ No newline at end of file
diff --git a/data/directories.json b/data/directories.json
index 9545008..5f6b28d 100644
--- a/data/directories.json
+++ b/data/directories.json
@@ -1,18 +1,32 @@
 [
   {
     "name": "Google Business Profile",
-    "status": "todo"
+    "status": "verify",
+    "note": "Confirm ownership + categories + photo freshness for the LA showroom"
   },
   {
     "name": "Yelp",
-    "status": "todo"
+    "status": "verify",
+    "note": "Claim/refresh; interiors buyers cross-check Yelp before showroom visits"
   },
   {
     "name": "Apple Maps",
-    "status": "todo"
+    "status": "todo",
+    "note": "Apple Business Connect listing"
   },
   {
     "name": "Bing Places",
-    "status": "todo"
+    "status": "todo",
+    "note": "Free; feeds Copilot answers"
+  },
+  {
+    "name": "Houzz",
+    "status": "verify",
+    "note": "Category-native directory — pro profile + project photos"
+  },
+  {
+    "name": "Pinterest business account",
+    "status": "todo",
+    "note": "The missing high-fit channel; rich pins off the product feed"
   }
 ]
\ No newline at end of file
diff --git a/data/media.json b/data/media.json
index 0637a08..f00f704 100644
--- a/data/media.json
+++ b/data/media.json
@@ -1 +1,14 @@
-[]
\ No newline at end of file
+[
+  {
+    "kind": "reels-gallery",
+    "label": "DW brand films & reels showcase",
+    "url": "https://marketing.designerwallcoverings.com/showcase",
+    "note": "Existing produced library — the reels feed for the calendar above"
+  },
+  {
+    "kind": "nightly-films",
+    "label": "Nightly recap films",
+    "url": "https://builds.agentabrams.com/nightly",
+    "note": "Internal — proof of the video pipeline capacity"
+  }
+]
\ No newline at end of file
diff --git a/data/socials.json b/data/socials.json
index a4e96da..98539fc 100644
--- a/data/socials.json
+++ b/data/socials.json
@@ -1,12 +1,54 @@
 {
-  "instagram": "https://www.instagram.com/designerwallcoverings",
-  "facebook": "https://www.facebook.com/designerwallcoverings",
-  "tiktok": "https://www.tiktok.com/@designerwallcoverings",
-  "youtube": "connected",
-  "linkedin": "https://www.linkedin.com/company/designerwallcoverings/",
-  "x": null,
-  "pinterest": null,
-  "google_business": null,
-  "connected": false,
-  "notes": "Connect accounts in the command center. Posting stays DRAFT-ONLY until Steve enables."
+  "_status": "Live from the marketing command center, 2026-07-26T15:02:30.181Z",
+  "platforms": [
+    {
+      "key": "facebook",
+      "label": "Facebook Pages",
+      "connected": true,
+      "accounts": 80,
+      "url": "https://www.facebook.com/designerwallcoverings"
+    },
+    {
+      "key": "instagram",
+      "label": "Instagram",
+      "connected": true,
+      "accounts": 34,
+      "url": "https://www.instagram.com/designerwallcoverings"
+    },
+    {
+      "key": "tiktok",
+      "label": "TikTok",
+      "connected": true,
+      "accounts": 1,
+      "url": "https://www.tiktok.com/@designerwallcoverings"
+    },
+    {
+      "key": "youtube",
+      "label": "YouTube",
+      "connected": true,
+      "accounts": 1,
+      "url": "connected"
+    },
+    {
+      "key": "bluesky",
+      "label": "Bluesky",
+      "connected": true,
+      "accounts": 1,
+      "url": ""
+    },
+    {
+      "key": "threads",
+      "label": "Threads",
+      "connected": true,
+      "accounts": 1,
+      "url": ""
+    },
+    {
+      "key": "linkedin",
+      "label": "LinkedIn",
+      "connected": true,
+      "accounts": 1,
+      "url": "https://www.linkedin.com/company/designerwallcoverings/"
+    }
+  ]
 }
\ No newline at end of file
diff --git a/data/suggestions.json b/data/suggestions.json
index 62e2fe7..fe3ca30 100644
--- a/data/suggestions.json
+++ b/data/suggestions.json
@@ -1,52 +1,66 @@
 [
   {
     "id": "sug-1",
-    "title": "Grow retail conversion on the flagship Shopify store",
-    "category": "Growth",
-    "impact": 4,
-    "effort": 2,
-    "rationale": "Seeded from the client intake goals — refine with market research.",
-    "created_at": "2026-07-26T14:36:27.630Z",
-    "source": "intake"
+    "title": "Sellable-variant integrity sprint (22.9k backlog)",
+    "category": "Revenue",
+    "impact": 5,
+    "effort": 3,
+    "rationale": "A third of live catalog is browseable-not-buyable; top 8 vendor lines cover ~17k. Data work, not spend."
   },
   {
     "id": "sug-2",
-    "title": "Win AI-answer citations (AEO) for wallcovering queries",
-    "category": "Growth",
+    "title": "Per-channel UTM + coupon attribution",
+    "category": "Measurement",
     "impact": 4,
-    "effort": 2,
-    "rationale": "Seeded from the client intake goals — refine with market research.",
-    "created_at": "2026-07-26T14:36:27.630Z",
-    "source": "intake"
+    "effort": 1,
+    "rationale": "Reach exists (80 FB pages, 34 IG); nothing proves revenue per channel. Prerequisite to scaling any spend."
   },
   {
     "id": "sug-3",
-    "title": "Turn the 15.8k staged-draft activation drip into measurable revenue",
-    "category": "Growth",
+    "title": "Sampled-but-didn’t-buy email sequence",
+    "category": "Conversion",
     "impact": 4,
     "effort": 2,
-    "rationale": "Seeded from the client intake goals — refine with market research.",
-    "created_at": "2026-07-26T14:36:27.630Z",
-    "source": "intake"
+    "rationale": "The $4.25 memo program produces daily purchase-intent signals; follow-up converts existing intent at near-zero cost."
   },
   {
     "id": "sug-4",
-    "title": "Social-to-revenue attribution across the 6 connected channels",
-    "category": "Growth",
+    "title": "Sync social cadence to the activation drip",
+    "category": "Content",
+    "impact": 3,
+    "effort": 1,
+    "rationale": "~500 new products/day = native daily story; calendar above is pre-drafted for it."
+  },
+  {
+    "id": "sug-5",
+    "title": "Pinterest launch (business account + rich pins)",
+    "category": "Channel",
     "impact": 4,
     "effort": 2,
-    "rationale": "Seeded from the client intake goals — refine with market research.",
-    "created_at": "2026-07-26T14:36:27.630Z",
-    "source": "intake"
+    "rationale": "Highest-fit missing channel for wallcoverings discovery; product feed already exists."
   },
   {
-    "id": "sug-5",
-    "title": "Leverage the ~50 sister-site microsite fleet as a discovery network",
-    "category": "Growth",
+    "id": "sug-6",
+    "title": "Designer-net trade portal",
+    "category": "Trade",
     "impact": 4,
+    "effort": 3,
+    "rationale": "Highest-LTV segment currently buys through retail flow; formalize trade pricing behind a login."
+  },
+  {
+    "id": "sug-7",
+    "title": "AEO blocks fleet-wide",
+    "category": "Search",
+    "impact": 3,
     "effort": 2,
-    "rationale": "Seeded from the client intake goals — refine with market research.",
-    "created_at": "2026-07-26T14:36:27.630Z",
-    "source": "intake"
+    "rationale": "Flagship AEO block exists; extend to the 287-site fleet for AI-answer citations."
+  },
+  {
+    "id": "sug-8",
+    "title": "Hospitality / contract quote desk",
+    "category": "Projects",
+    "impact": 3,
+    "effort": 3,
+    "rationale": "Type II lines in catalog; one hotel corridor = 400 rolls. Spec-sheet + quote flow."
   }
 ]
\ No newline at end of file
diff --git a/public/portal.html b/public/portal.html
index 637fe63..c73b603 100644
--- a/public/portal.html
+++ b/public/portal.html
@@ -170,6 +170,8 @@ footer{padding:56px clamp(24px,7vw,120px);border-top:1px solid var(--line);color
 
 <section id="advertising"><div class="eyebrow">04 · Channels &amp; Distribution</div><h2 class="disp">Seven channels, one command center.</h2><div class="accbody">
   <p class="lead2">Live from the marketing command center at collection time: <b>7 of 7</b> platforms connected. The Facebook page network alone spans <b>80</b> pages; Instagram <b>34</b> accounts. The infrastructure exists — the discipline to convert reach into attributed revenue is the build.</p>
+  <div class="flagbox"><div class="i">◆</div><div><h3 class="disp">The paid-ads gap — measured, not guessed</h3><p>A $0 pixel scan of every tracked competitor's homepage (Jul 26): <b>5 of 8 rivals are paying to advertise</b> — Google Ads on five of them, Meta on one — while <b>DW's own homepage carries analytics but zero paid-ad pixels</b>. The field is buying the demand DW's catalog should own. With attribution wired first (below), a measured Google Shopping + brand-terms program is the obvious counter.</p></div></div>
+  <table><thead><tr><th>Domain</th><th></th><th>Ad pixels detected</th><th>Analytics</th><th>Read</th></tr></thead><tbody><tr class="you"><td><b>Designer Wallcoverings (you)</b></td><td>designerwallcoverings.com</td><td>—</td><td>Yes</td><td>No paid-ad pixels detected on homepage</td></tr><tr><td><b>Decorators Best</b></td><td>decoratorsbest.com</td><td>google_ads · microsoft_bing</td><td>Yes</td><td>Paying to advertise (google_ads, microsoft_bing)</td></tr><tr><td><b>Wallpapers To Go</b></td><td>wallpaperstogo.com</td><td>google_ads · meta</td><td>—</td><td>Paying to advertise (google_ads, meta)</td></tr><tr><td><b>Mahones Wallpaper</b></td><td>mahoneswallpapershop.com</td><td>google_ads</td><td>Yes</td><td>Paying to advertise (google_ads)</td></tr><tr><td><b>L.A. Design Concepts</b></td><td>ladesignconcepts.com</td><td>google_ads · microsoft_bing</td><td>Yes</td><td>Paying to advertise (google_ads, microsoft_bing)</td></tr><tr><td><b>Finest Wallpaper</b></td><td>finestwallpaper.com</td><td>google_ads</td><td>Yes</td><td>Paying to advertise (google_ads)</td></tr><tr><td><b>Burke Decor</b></td><td>burkedecor.com</td><td>—</td><td>—</td><td>Homepage unreachable to scanner (fetch failed)</td></tr><tr><td><b>Wallpaper Wholesaler</b></td><td>wallpaperwholesaler.com</td><td>—</td><td>Yes</td><td>No paid-ad pixels detected on homepage</td></tr><tr><td><b>Peelstick</b></td><td>peelstick.com</td><td>—</td><td>—</td><td>Homepage unreachable to scanner (fetch failed)</td></tr></tbody></table>
   <div class="chartwrap"><div class="ct">Connected accounts per platform (live)</div><canvas id="chanChart"></canvas></div>
   <table><thead><tr><th>Platform</th><th>Status</th><th>Accounts</th><th>Note</th></tr></thead><tbody><tr><td><b>Facebook Pages</b></td><td><span class="good">Connected</span></td><td>80</td><td>Live in the marketing command center</td></tr><tr><td><b>Instagram</b></td><td><span class="good">Connected</span></td><td>34</td><td>Live in the marketing command center</td></tr><tr><td><b>TikTok</b></td><td><span class="good">Connected</span></td><td>1</td><td>Live in the marketing command center</td></tr><tr><td><b>YouTube</b></td><td><span class="good">Connected</span></td><td>1</td><td>Live in the marketing command center</td></tr><tr><td><b>Bluesky</b></td><td><span class="good">Connected</span></td><td>1</td><td>Live in the marketing command center</td></tr><tr><td><b>Threads</b></td><td><span class="good">Connected</span></td><td>1</td><td>Live in the marketing command center</td></tr><tr><td><b>LinkedIn</b></td><td><span class="good">Connected</span></td><td>1</td><td>Live in the marketing command center</td></tr></tbody></table>
   <div class="cards2">
@@ -282,8 +284,8 @@ footer{padding:56px clamp(24px,7vw,120px);border-top:1px solid var(--line);color
 
 <section id="sources"><div class="eyebrow">13 · Sources &amp; Methodology</div><h2 class="disp">Where every number comes from.</h2><div class="accbody">
   <p class="lead2">Snapshot collected <b>Jul 26, 2026, 8:02 AM</b> by a read-only collector. Live rows are measured; analyst rows are authored judgment; anything illustrative says so.</p>
-  <div class="srclegend"><span class="srcb r">Live snapshot</span><span class="srcb a">Analyst view</span><span class="srcb d">Computed</span><span class="srcb e">Illustrative</span></div>
-  <table><thead><tr><th>Section</th><th>Where it comes from</th><th>How it's produced</th><th>Provenance</th></tr></thead><tbody><tr><td><b>Catalog KPIs — statuses, vendors, types, integrity, cadence</b></td><td>The dw_unified local mirror (READ-ONLY psql over the /tmp socket)</td><td>Collected by <code>scripts/collect-dw.mjs</code> at <b>Jul 26, 2026, 8:02 AM</b>; every number on this page renders from that snapshot</td><td class="srccell"><span class="srcb r">Live snapshot</span></td></tr><tr><td><b>5-field / sample-only split</b></td><td>Mirror rollup columns (has_sample_variant, has_product_variant, has_description, image_url) + the quotes tag</td><td>Direct SQL counts; quote-only lines (by-design sample-only) separated from true backlog</td><td class="srccell"><span class="srcb r">Live snapshot</span></td></tr><tr><td><b>Social channel status</b></td><td>The DW marketing command center API (marketing.designerwallcoverings.com)</td><td>Authenticated status read at collection time — connected flags + account counts per platform</td><td class="srccell"><span class="srcb r">Live snapshot</span></td></tr><tr><td><b>Microsite fleet</b></td><td>The fleet aggregator directory API (all.designerwallcoverings.com)</td><td>287 sites enumerated at collection time</td><td class="srccell"><span class="srcb r">Live snapshot</span></td></tr><tr><td><b>Competitor watch-list</b></td><td>The connie_competitors table (the DW competitor-scan registry)</td><td>Rendered verbatim: domain, watch status, products found by prior scans</td><td class="srccell"><span class="srcb r">Live snapshot</span></td></tr><tr><td><b>Competitor scoring chart + map pins</b></td><td>Analyst assessment over public sites; HQ geo compiled from public records</td><td>Authored consulting view — NOT scraped metrics; the map is oriented, not surveyed</td><td class="srccell"><span class="srcb a">Analyst view</span></td></tr><tr><td><b>Price-band chart</b></td><td>products whose minimum variant exceeds the $4.25 memo sample — the priced subset of the mirror (~3.8k); most actives resolve min-variant to the sample, and full variant-level pricing lives Shopify-side</td><td>Partial-basis chart, labeled as such — full variant pricing lives Shopify-side</td><td class="srccell"><span class="srcb d">Computed</span></td></tr><tr><td><b>Website & social audits, partners, revenue plays, roadmap</b></td><td>Analyst strategy grounded in the operating knowledge of the DW stack</td><td>Consulting recommendations — models and plays, not measurements</td><td class="srccell"><span class="srcb a">Analyst view</span></td></tr><tr><td><b>Hero & section imagery</b></td><td>Unsplash editorial stock (luxury materials, per the luxe-only imagery rule)</td><td>Hotlinked, decorative only — carries no data meaning</td><td class="srccell"><span class="srcb e">Illustrative</span></td></tr></tbody></table>
+  <div class="srclegend"><span class="srcb r">Live snapshot</span><span class="srcb o">Observed live</span><span class="srcb a">Analyst view</span><span class="srcb d">Computed</span><span class="srcb e">Illustrative</span></div>
+  <table><thead><tr><th>Section</th><th>Where it comes from</th><th>How it's produced</th><th>Provenance</th></tr></thead><tbody><tr><td><b>Catalog KPIs — statuses, vendors, types, integrity, cadence</b></td><td>The dw_unified local mirror (READ-ONLY psql over the /tmp socket)</td><td>Collected by <code>scripts/collect-dw.mjs</code> at <b>Jul 26, 2026, 8:02 AM</b>; every number on this page renders from that snapshot</td><td class="srccell"><span class="srcb r">Live snapshot</span></td></tr><tr><td><b>5-field / sample-only split</b></td><td>Mirror rollup columns (has_sample_variant, has_product_variant, has_description, image_url) + the quotes tag</td><td>Direct SQL counts; quote-only lines (by-design sample-only) separated from true backlog</td><td class="srccell"><span class="srcb r">Live snapshot</span></td></tr><tr><td><b>Social channel status</b></td><td>The DW marketing command center API (marketing.designerwallcoverings.com)</td><td>Authenticated status read at collection time — connected flags + account counts per platform</td><td class="srccell"><span class="srcb r">Live snapshot</span></td></tr><tr><td><b>Microsite fleet</b></td><td>The fleet aggregator directory API (all.designerwallcoverings.com)</td><td>287 sites enumerated at collection time</td><td class="srccell"><span class="srcb r">Live snapshot</span></td></tr><tr><td><b>Competitor watch-list</b></td><td>The connie_competitors table (the DW competitor-scan registry)</td><td>Rendered verbatim: domain, watch status, products found by prior scans</td><td class="srccell"><span class="srcb r">Live snapshot</span></td></tr><tr><td><b>Competitor paid-ads table</b></td><td>A $0 homepage pixel scan of each tracked domain (Google/Meta/TikTok/Pinterest/Bing tag signatures)</td><td>Measured at seed time by <code>scripts/seed-buckets.mjs</code>; unreachable homepages say so rather than guessing</td><td class="srccell"><span class="srcb o">Observed live</span></td></tr><tr><td><b>Competitor scoring chart + map pins</b></td><td>Analyst assessment over public sites; HQ geo compiled from public records</td><td>Authored consulting view — NOT scraped metrics; the map is oriented, not surveyed</td><td class="srccell"><span class="srcb a">Analyst view</span></td></tr><tr><td><b>Price-band chart</b></td><td>products whose minimum variant exceeds the $4.25 memo sample — the priced subset of the mirror (~3.8k); most actives resolve min-variant to the sample, and full variant-level pricing lives Shopify-side</td><td>Partial-basis chart, labeled as such — full variant pricing lives Shopify-side</td><td class="srccell"><span class="srcb d">Computed</span></td></tr><tr><td><b>Website & social audits, partners, revenue plays, roadmap</b></td><td>Analyst strategy grounded in the operating knowledge of the DW stack</td><td>Consulting recommendations — models and plays, not measurements</td><td class="srccell"><span class="srcb a">Analyst view</span></td></tr><tr><td><b>Hero & section imagery</b></td><td>Unsplash editorial stock (luxury materials, per the luxe-only imagery rule)</td><td>Hotlinked, decorative only — carries no data meaning</td><td class="srccell"><span class="srcb e">Illustrative</span></td></tr></tbody></table>
   <div class="srcnote"><div class="i">&#9881;</div><div><h4>How this document is built</h4><p>Generated, not hand-typed. A read-only collector (<code>scripts/collect-dw.mjs</code>) snapshots the DW systems into <code>data/dw-analysis.json</code>; the build (<code>build.mjs</code>) renders that snapshot into this page. The ↻ Refresh button re-runs collector + build on demand. Nothing writes to any DW production system.</p></div></div>
 </div></section>
 
diff --git a/scripts/seed-buckets.mjs b/scripts/seed-buckets.mjs
new file mode 100644
index 0000000..2238344
--- /dev/null
+++ b/scripts/seed-buckets.mjs
@@ -0,0 +1,143 @@
+#!/usr/bin/env node
+// Seed the growth command-center buckets with REAL DW state.
+// - socials: live channel status from the snapshot
+// - competitors + ads: connie registry + a $0 pixel scan of each competitor's
+//   homepage (advertising-signals pattern, inline light version)
+// - content-calendar: DRAFT-ONLY posts aligned to the activation drip
+// - suggestions: impact/effort moves from the analysis brief
+// Repeatable; overwrites bucket files (they remain editable in /admin).
+import { readFileSync, writeFileSync } from 'node:fs';
+import { dirname, join } from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const HERE = dirname(fileURLToPath(import.meta.url));
+const DATA = join(HERE, '..', 'data');
+const readJ = (f, d) => { try { return JSON.parse(readFileSync(join(DATA, f), 'utf8')); } catch { return d; } };
+const writeJ = (f, v) => writeFileSync(join(DATA, f), JSON.stringify(v, null, 2));
+const SNAP = readJ('dw-analysis.json', { channels: [], competitors: [] });
+
+/* ---- socials: live connection state --------------------------------------- */
+const socialUrls = readJ('socials.json', {});
+const socials = {
+  _status: 'Live from the marketing command center, ' + (SNAP.collected_at || ''),
+  platforms: (SNAP.channels || []).map(c => ({
+    key: c.key, label: c.label || c.key, connected: !!c.connected,
+    accounts: c.accounts || 0,
+    url: socialUrls[c.key] || socialUrls[c.key === 'facebook' ? 'facebook' : c.key] || '',
+  })),
+};
+
+/* ---- best times: interiors-category cadence (analyst) ---------------------- */
+const bestTimes = {
+  _basis: 'Analyst cadence for the interiors/design vertical — refine against GA4 + per-post engagement once attribution lands',
+  instagram: ['Tue 11:00', 'Thu 19:00', 'Sat 10:00'],
+  facebook: ['Wed 13:00', 'Sun 12:00'],
+  tiktok: ['Tue 18:00', 'Fri 20:00'],
+  pinterest_note: 'Pinterest is the missing high-fit channel for wallcoverings — evenings + weekends dominate saves',
+  youtube: ['Sat 11:00'],
+  threads: ['Mon 12:00', 'Thu 12:00'],
+  bluesky: ['Wed 10:00'],
+  linkedin: ['Tue 09:00', 'Thu 09:00'],
+};
+
+/* ---- competitors + $0 pixel scan ------------------------------------------- */
+const PIXELS = [
+  ['google_ads', /googleadservices|AW-\d{9}|gtag\('config',\s*'AW-/i],
+  ['meta', /connect\.facebook\.net|fbq\(/i],
+  ['tiktok', /analytics\.tiktok\.com|ttq\./i],
+  ['pinterest', /pintrk\(|ct\.pinterest\.com/i],
+  ['microsoft_bing', /bat\.bing\.com/i],
+  ['ga4', /gtag\('config',\s*'G-|googletagmanager\.com\/gtag/i],
+];
+async function scanDomain(domain) {
+  try {
+    const r = await fetch('https://' + domain, {
+      headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36' },
+      signal: AbortSignal.timeout(12000), redirect: 'follow',
+    });
+    const html = (await r.text()).slice(0, 400000);
+    const hits = PIXELS.filter(([, re]) => re.test(html)).map(([k]) => k);
+    return { ok: true, pixels: hits };
+  } catch (e) { return { ok: false, pixels: [], error: String(e.message || e).slice(0, 80) }; }
+}
+
+const comps = [];
+const ads = [];
+for (const c of (SNAP.competitors || [])) {
+  const scan = await scanDomain(c.domain);
+  comps.push({
+    id: 'comp-' + c.domain, name: c.name, domain: c.domain,
+    watch: c.enabled ? 'active' : 'paused', products_found: c.products_found,
+    notes: c.notes || '', pixels: scan.pixels, scanned: scan.ok,
+  });
+  ads.push({
+    domain: c.domain, name: c.name,
+    ad_pixels: scan.pixels.filter(p => p !== 'ga4'),
+    analytics: scan.pixels.includes('ga4'),
+    read: scan.ok
+      ? (scan.pixels.filter(p => p !== 'ga4').length ? 'Paying to advertise (' + scan.pixels.filter(p => p !== 'ga4').join(', ') + ')' : 'No paid-ad pixels detected on homepage')
+      : 'Homepage unreachable to scanner (' + (scan.error || '') + ')',
+    scanned_at: new Date().toISOString(),
+    method: '$0 homepage pixel scan (advertising-signals pattern)',
+  });
+  console.log(c.domain, '→', scan.ok ? (scan.pixels.join(',') || 'no pixels') : 'unreachable');
+}
+// DW itself, same lens
+const self = await scanDomain('www.designerwallcoverings.com');
+ads.unshift({ domain: 'designerwallcoverings.com', name: 'Designer Wallcoverings (you)', ad_pixels: self.pixels.filter(p => p !== 'ga4'), analytics: self.pixels.includes('ga4'), read: self.pixels.filter(p => p !== 'ga4').length ? 'Running: ' + self.pixels.filter(p => p !== 'ga4').join(', ') : 'No paid-ad pixels detected on homepage', scanned_at: new Date().toISOString(), method: '$0 homepage pixel scan' });
+
+/* ---- content calendar: DRAFT-ONLY, drip-aligned ----------------------------- */
+const cal = [];
+const themes = [
+  ['New-arrivals story', 'Daily drop from the activation drip — 3 patterns, one mood', ['instagram', 'facebook', 'threads']],
+  ['Texture Tuesday', 'One material macro (grasscloth / cork / silk) + care note', ['instagram', 'tiktok', 'bluesky']],
+  ['Designer pick', 'One pattern, one room render, one designer quote', ['instagram', 'pinterest', 'facebook']],
+  ['Before / after wall', 'Client or render transformation, sample CTA', ['tiktok', 'instagram', 'youtube']],
+  ['Trade corner', 'Spec tip for designers (widths, repeats, Type II)', ['linkedin', 'threads']],
+  ['Sample Saturday', '$4.25 memo program explainer + bundle prompt', ['instagram', 'facebook', 'tiktok']],
+  ['Pattern history', 'The story of one motif (damask / toile / chinoiserie)', ['facebook', 'bluesky', 'threads']],
+];
+const start = new Date();
+for (let d = 0; d < 14; d++) {
+  const day = new Date(start.getTime() + d * 86400000);
+  const t = themes[d % themes.length];
+  cal.push({
+    date: day.toISOString().slice(0, 10),
+    title: t[0], brief: t[1], channels: t[2],
+    status: 'DRAFT', rail: 'Social posting is DRAFT-ONLY until Steve enables it',
+  });
+}
+
+/* ---- media + directories + suggestions -------------------------------------- */
+const media = [
+  { kind: 'reels-gallery', label: 'DW brand films & reels showcase', url: 'https://marketing.designerwallcoverings.com/showcase', note: 'Existing produced library — the reels feed for the calendar above' },
+  { kind: 'nightly-films', label: 'Nightly recap films', url: 'https://builds.agentabrams.com/nightly', note: 'Internal — proof of the video pipeline capacity' },
+];
+const directories = [
+  { name: 'Google Business Profile', status: 'verify', note: 'Confirm ownership + categories + photo freshness for the LA showroom' },
+  { name: 'Yelp', status: 'verify', note: 'Claim/refresh; interiors buyers cross-check Yelp before showroom visits' },
+  { name: 'Apple Maps', status: 'todo', note: 'Apple Business Connect listing' },
+  { name: 'Bing Places', status: 'todo', note: 'Free; feeds Copilot answers' },
+  { name: 'Houzz', status: 'verify', note: 'Category-native directory — pro profile + project photos' },
+  { name: 'Pinterest business account', status: 'todo', note: 'The missing high-fit channel; rich pins off the product feed' },
+];
+const suggestions = [
+  { id: 'sug-1', title: 'Sellable-variant integrity sprint (22.9k backlog)', category: 'Revenue', impact: 5, effort: 3, rationale: 'A third of live catalog is browseable-not-buyable; top 8 vendor lines cover ~17k. Data work, not spend.' },
+  { id: 'sug-2', title: 'Per-channel UTM + coupon attribution', category: 'Measurement', impact: 4, effort: 1, rationale: 'Reach exists (80 FB pages, 34 IG); nothing proves revenue per channel. Prerequisite to scaling any spend.' },
+  { id: 'sug-3', title: 'Sampled-but-didn’t-buy email sequence', category: 'Conversion', impact: 4, effort: 2, rationale: 'The $4.25 memo program produces daily purchase-intent signals; follow-up converts existing intent at near-zero cost.' },
+  { id: 'sug-4', title: 'Sync social cadence to the activation drip', category: 'Content', impact: 3, effort: 1, rationale: '~500 new products/day = native daily story; calendar above is pre-drafted for it.' },
+  { id: 'sug-5', title: 'Pinterest launch (business account + rich pins)', category: 'Channel', impact: 4, effort: 2, rationale: 'Highest-fit missing channel for wallcoverings discovery; product feed already exists.' },
+  { id: 'sug-6', title: 'Designer-net trade portal', category: 'Trade', impact: 4, effort: 3, rationale: 'Highest-LTV segment currently buys through retail flow; formalize trade pricing behind a login.' },
+  { id: 'sug-7', title: 'AEO blocks fleet-wide', category: 'Search', impact: 3, effort: 2, rationale: 'Flagship AEO block exists; extend to the 287-site fleet for AI-answer citations.' },
+  { id: 'sug-8', title: 'Hospitality / contract quote desk', category: 'Projects', impact: 3, effort: 3, rationale: 'Type II lines in catalog; one hotel corridor = 400 rolls. Spec-sheet + quote flow.' },
+];
+
+writeJ('socials.json', socials);
+writeJ('best-times.json', bestTimes);
+writeJ('competitors.json', comps);
+writeJ('ads.json', ads);
+writeJ('content-calendar.json', cal);
+writeJ('media.json', media);
+writeJ('directories.json', directories);
+writeJ('suggestions.json', suggestions);
+console.log('Buckets seeded:', { competitors: comps.length, ads: ads.length, calendar: cal.length, suggestions: suggestions.length });

← 153dc6e flagship 14-section DW analysis app + 6 luxury-leader concep  ·  back to Consulting Designerwallcoverings Com  ·  auto-save: 2026-07-26T08:11:40 (1 files) — scripts/collect-d 4c656f4 →