← back to Prestige Car Wash
Growth admin: expandable SWOT · Costs · Payback panel on idea cards (dtd:A, contrarian-gated)
ed6822215e8fc2b0424a4a3260524313108e2905 · 2026-07-26 07:34:57 -0700 · steve@designerwallcoverings.com
Files touched
M public/admin/index.html
Diff
commit ed6822215e8fc2b0424a4a3260524313108e2905
Author: steve@designerwallcoverings.com <steve@designerwallcoverings.com>
Date: Sun Jul 26 07:34:57 2026 -0700
Growth admin: expandable SWOT · Costs · Payback panel on idea cards (dtd:A, contrarian-gated)
---
public/admin/index.html | 60 ++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 59 insertions(+), 1 deletion(-)
diff --git a/public/admin/index.html b/public/admin/index.html
index 33e5fc2..a1ecddd 100644
--- a/public/admin/index.html
+++ b/public/admin/index.html
@@ -53,6 +53,26 @@
.k-brand-promo{background:rgba(10,132,255,.16);color:var(--brand)}
.k-hyperframes-motion{background:rgba(255,180,84,.16);color:var(--warn)}
.k-internal-pitch{background:var(--panel2);color:var(--mut)}
+ /* --- idea analysis panel (SWOT · costs · payback) --- */
+ details.analysis{margin-top:10px;border:1px solid var(--line);border-radius:10px;background:var(--panel2);overflow:hidden}
+ details.analysis>summary{cursor:pointer;list-style:none;padding:9px 12px;font-size:12.5px;font-weight:700;color:var(--brand);user-select:none}
+ details.analysis>summary::-webkit-details-marker{display:none}
+ details.analysis>summary::before{content:'▸ ';font-weight:800}
+ details.analysis[open]>summary::before{content:'▾ '}
+ .analysis-body{padding:4px 12px 12px}
+ .swot-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin:6px 0 10px}
+ .swot-col{border-radius:8px;padding:8px 10px;font-size:12.5px}
+ .swot-col h5{margin:0 0 5px;font-size:11px;text-transform:uppercase;letter-spacing:.4px}
+ .swot-col ul{margin:0;padding-left:15px} .swot-col li{margin:3px 0;line-height:1.35}
+ .s-str{background:rgba(48,209,88,.10)} .s-str h5{color:var(--good,#30d158)}
+ .s-wk{background:rgba(255,180,84,.10)} .s-wk h5{color:var(--warn,#ffb454)}
+ .s-op{background:rgba(10,132,255,.10)} .s-op h5{color:var(--brand)}
+ .s-th{background:rgba(255,69,58,.10)} .s-th h5{color:#ff453a}
+ .cost-card{border:1px solid var(--line);border-radius:8px;padding:8px 10px;margin:8px 0}
+ .cost-card h5{margin:0 0 4px;font-size:12px;text-transform:capitalize}
+ .cost-card.model{border-color:var(--brand);border-left-width:3px}
+ .cost-card.model h5{text-transform:none}
+ @media(max-width:560px){.swot-grid{grid-template-columns:1fr}}
</style>
</head>
<body>
@@ -171,7 +191,7 @@ const TABS=[
{id:'suggestions',label:'💡 Grow the Business',api:'/api/admin/suggestions',search:['title','rationale','category'],
card:s=>cardWrap(`<div class="row"><div class="cat">${enc(s.category)}</div>
<span class="badge ${s.impact>=5?'b-good':s.impact>=3?'b-warn':'b-mut'}">impact ${s.impact} · effort ${s.effort}</span></div>
- <div class="ttl">${enc(s.title)}</div><div class="blurb">${enc(s.rationale)}</div>${when(s.created_at)}`)},
+ <div class="ttl">${enc(s.title)}</div><div class="blurb">${enc(s.rationale)}</div>${analysisPanel(s)}${when(s.created_at)}`)},
{id:'ads',label:'📣 Online Ads',api:'/api/admin/ads',search:['platform','type','headline'],
card:a=>cardWrap(`<div class="row"><div class="cat">${enc(a.platform)} · ${enc(a.type)}</div><span class="badge b-warn">${enc(a.status)}</span></div>
<div class="ttl">${enc(a.headline)}</div><div class="blurb">${enc(a.body)}</div>
@@ -201,6 +221,44 @@ window.go=id=>{CUR=id;document.querySelectorAll('#tabs button').forEach(b=>b.cla
$('#q').value='';render();};
function cardWrap(inner){return `<div class="card"><div class="body">${inner}</div></div>`;}
+// Expandable SWOT · costs · payback panel for an idea card. Renders only the
+// sub-objects that exist, so ideas without analysis are unaffected (additive).
+function swotCol(title,arr,cls){
+ if(!Array.isArray(arr)||!arr.length) return '';
+ return `<div class="swot-col ${cls}"><h5>${title}</h5><ul>${arr.map(x=>`<li>${enc(x)}</li>`).join('')}</ul></div>`;
+}
+function costRows(obj){
+ return Object.entries(obj).filter(([k,v])=>k!=='recommended'&&k!=='note'&&typeof v!=='object')
+ .map(([k,v])=>`<div class="kv"><span>${enc(k.replace(/_/g,' '))}</span><span>${enc(v)}</span></div>`).join('');
+}
+function analysisPanel(s){
+ const w=s.swot, c=s.costs; if(!w && !c) return '';
+ const hasPricing = c && Object.entries(c).some(([k,v])=>k!=='payback_model'&&v&&typeof v==='object'&&'recommended' in v);
+ const hasPayback = c && c.payback_model && typeof c.payback_model==='object';
+ let html='';
+ if(w){
+ html+=`<div class="swot-grid">${swotCol('💪 Strengths',w.strengths,'s-str')}${swotCol('⚠️ Weaknesses',w.weaknesses,'s-wk')}${swotCol('🚀 Opportunities',w.opportunities,'s-op')}${swotCol('🛑 Threats',w.threats,'s-th')}</div>`;
+ }
+ if(c){
+ if(c.note) html+=`<div class="blurb" style="font-style:italic;margin:4px 0">${enc(c.note)}</div>`;
+ for(const [key,val] of Object.entries(c)){
+ if(key==='note'||!val||typeof val!=='object') continue;
+ // payback_model is a MODELLED OUTCOME, not a purchasing option — label it so
+ // a client never misreads it as a third price. (contrarian gate, 2026-07-26)
+ if(key==='payback_model'){
+ html+=`<div class="cost-card model"><h5>📈 Payback model <span class="badge b-mut">modelled · not a price</span></h5>${costRows(val)}</div>`;
+ continue;
+ }
+ const rec = val.recommended===true?' <span class="badge b-good">recommended</span>'
+ : val.recommended===false?' <span class="badge b-mut">fallback</span>':'';
+ html+=`<div class="cost-card"><h5>${enc(key.replace(/_/g,' '))}${rec}</h5>${costRows(val)}</div>`;
+ }
+ }
+ // Honest summary — only name sub-sections that are actually populated.
+ const parts=[]; if(w) parts.push('SWOT'); if(hasPricing) parts.push('Costs'); if(hasPayback) parts.push('Payback');
+ const summary = parts.join(' · ') || 'Analysis';
+ return `<details class="analysis"><summary>${summary}</summary><div class="analysis-body">${html}</div></details>`;
+}
const gridWrap=cards=>`<div class="grid">${cards}</div>`;
function render(){
← 78ccc70 lhf: contrarian fixes — panel note replaces per-card filler,
·
back to Prestige Car Wash
·
auto-save: 2026-07-26T07:41:30 (2 files) — public/admin/inde 6edbd5d →