← 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
M build.mjsM data/ads.jsonM data/best-times.jsonM data/competitors.jsonM data/content-calendar.jsonM data/directories.jsonM data/media.jsonM data/socials.jsonM data/suggestions.jsonM public/portal.htmlA scripts/seed-buckets.mjs
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 & 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 & 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 & 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">⚙</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 →