← back to Marketing Command Center
make quick post choices clearer and safer
def5ea96a97a5718744f92a0a23cbfb98bfc0afa · 2026-08-28 09:52:30 -0700 · Steve Abrams
Files touched
M public/panels/quickpost.htmlM public/panels/quickpost.js
Diff
commit def5ea96a97a5718744f92a0a23cbfb98bfc0afa
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Aug 28 09:52:30 2026 -0700
make quick post choices clearer and safer
---
public/panels/quickpost.html | 12 ++++++------
public/panels/quickpost.js | 16 ++++++++++------
2 files changed, 16 insertions(+), 12 deletions(-)
diff --git a/public/panels/quickpost.html b/public/panels/quickpost.html
index d7edb98..24d1e3a 100644
--- a/public/panels/quickpost.html
+++ b/public/panels/quickpost.html
@@ -14,7 +14,7 @@
<details class="kp-more"><summary>Paste a picture link instead</summary><input id="kp-media" type="url" placeholder="https://…/picture.jpg" autocomplete="off"></details>
</section>
<section class="kp-pane" data-step="2" hidden>
- <div class="kp-number">2</div><div class="kp-copy"><h3>Add music?</h3><p>Music is optional. Pick one safe source, or choose “No music.”</p></div><div id="kp-music" class="kp-choices"><div class="kp-wait">Loading safe music…</div></div>
+ <div class="kp-number">2</div><div class="kp-copy"><h3>Add music?</h3><p>Music is optional. Choose a song, or choose “No music.”</p></div><div id="kp-music" class="kp-choices"><div class="kp-wait">Loading music choices…</div></div>
</section>
<section class="kp-pane" data-step="3" hidden>
<div class="kp-number">3</div><div class="kp-copy"><h3>Choose the words</h3><p>Tell us the idea. We will suggest three captions you can use or change.</p></div>
@@ -22,13 +22,13 @@
<div id="kp-suggestions" class="kp-suggestions" aria-live="polite"></div><label class="kp-label" for="kp-caption">Your final words</label><textarea id="kp-caption" placeholder="Pick an idea above, or write your own words here."></textarea><div id="kp-count" class="kp-count">0 / 2,200</div>
</section>
<section class="kp-pane" data-step="4" hidden>
- <div class="kp-number">4</div><div class="kp-copy"><h3>Where should it go?</h3><p>Pick one, several, or tap “Choose all.” Gray choices are not connected yet.</p></div>
- <div class="kp-allrow"><button id="kp-all" type="button">✓ Choose all that work</button><button id="kp-none" type="button">Clear</button></div><div id="kp-channels" class="kp-channels" aria-live="polite"><div class="kp-wait">Checking your social accounts…</div></div>
+ <div class="kp-number">4</div><div class="kp-copy"><h3>Where should it go?</h3><p>Pick one or more places. Or choose every connected account.</p></div>
+ <div class="kp-allrow"><button id="kp-all" type="button">✓ Post to all connected accounts</button><button id="kp-none" type="button">Clear choices</button></div><div id="kp-channels" class="kp-channels" aria-live="polite"><div class="kp-wait">Checking your social accounts…</div></div>
<label class="kp-label" for="kp-account">Instagram account</label><select id="kp-account"><option value="">Loading accounts…</option></select>
</section>
<section class="kp-pane" data-step="5" hidden>
- <div class="kp-number">5</div><div class="kp-copy"><h3>Check everything</h3><p>Nothing posts until you check the box and press the green button.</p></div><div id="kp-review" class="kp-review"></div>
- <label class="kp-confirm"><input id="kp-confirm" type="checkbox"><span>I checked the picture, words, music, and accounts. Post it now.</span></label><button id="kp-publish" class="kp-publish" type="button" disabled>Post now</button><button id="kp-draft" class="kp-draft" type="button">Save as drafts instead</button><div id="kp-result" class="kp-result" role="status" aria-live="polite"></div>
+ <div class="kp-number">5</div><div class="kp-copy"><h3>Check everything</h3><p>Look it over. Nothing posts until you say yes.</p></div><div id="kp-review" class="kp-review"></div>
+ <label class="kp-confirm"><input id="kp-confirm" type="checkbox"><span>I checked the picture, words, music, and accounts. I’m ready to post.</span></label><button id="kp-publish" class="kp-publish" type="button" disabled>Review and post</button><button id="kp-draft" class="kp-draft" type="button">Save a draft</button><div id="kp-result" class="kp-result" role="status" aria-live="polite"></div>
</section>
<footer class="kp-nav"><button id="kp-back" type="button" disabled>← Back</button><button id="kp-next" type="button">Next: Music →</button></footer>
</main>
@@ -36,7 +36,7 @@
<style>
.kidpost{--navy:#172235;--paper:#fffdf7;--sun:#f6c84c;--green:#287a55;--coral:#df684f;max-width:1120px;margin:0 auto;color:var(--navy);font-family:"Avenir Next",Avenir,"Trebuchet MS",sans-serif}.kp-hero{display:flex;justify-content:space-between;align-items:flex-end;gap:24px;padding:24px 28px;background:var(--navy);color:#fff;border-radius:24px 24px 0 0;box-shadow:0 18px 45px rgba(23,34,53,.14)}.kp-kicker{font-size:12px;font-weight:800;letter-spacing:.16em;color:var(--sun)}.kp-hero h2{font:700 clamp(28px,4vw,48px)/1.02 "Cormorant Garamond",Georgia,serif;margin:7px 0}.kp-hero p{margin:0;color:#dce4ef;font-size:16px}.kp-score{background:var(--sun);color:var(--navy);border-radius:18px;padding:12px 17px;text-align:center;min-width:94px}.kp-score b{display:block;font-size:20px}.kp-score span{font-size:11px;font-weight:700}
.kp-steps{display:grid;grid-template-columns:repeat(5,1fr);background:#edf0f3;padding:12px;gap:8px}.kp-step{border:0;background:transparent;padding:9px 4px;border-radius:14px;color:#6c7480;cursor:pointer}.kp-step span{display:grid;place-items:center;width:34px;height:34px;margin:0 auto 5px;border:2px solid #aab1ba;border-radius:50%;font-weight:800}.kp-step b{font-size:12px}.kp-step.on{background:#fff;color:var(--navy);box-shadow:0 4px 14px rgba(23,34,53,.09)}.kp-step.on span,.kp-step.done span{border-color:var(--green);background:var(--green);color:#fff}.kp-card{background:var(--paper);border:1px solid #e0ddd2;border-top:0;border-radius:0 0 24px 24px;box-shadow:0 18px 45px rgba(23,34,53,.14);overflow:hidden}.kp-pane{padding:30px;min-height:390px}.kp-number{float:left;display:grid;place-items:center;width:48px;height:48px;border-radius:16px;background:var(--sun);font-size:24px;font-weight:900;margin-right:14px}.kp-copy{min-height:58px}.kp-copy h3{font:700 28px/1.1 "Cormorant Garamond",Georgia,serif;margin:0 0 5px}.kp-copy p{margin:0;color:#667080;font-size:15px}
-.kp-assets{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:12px;margin-top:22px;max-height:360px;overflow:auto;padding:4px}.kp-asset{position:relative;aspect-ratio:1;border:3px solid transparent;border-radius:16px;overflow:hidden;background:#ece8de;cursor:pointer;padding:0}.kp-asset img{width:100%;height:100%;object-fit:cover}.kp-asset.on{border-color:var(--green);box-shadow:0 0 0 3px #c8ead9}.kp-asset.on:after{content:'✓';position:absolute;right:7px;top:7px;display:grid;place-items:center;width:28px;height:28px;border-radius:50%;background:var(--green);color:#fff;font-weight:900}.kp-more{margin-top:18px;color:#5c6570}.kp-more summary{cursor:pointer;font-weight:700}.kp-more input,.kp-prompt input,#kp-caption,#kp-account{width:100%;box-sizing:border-box;margin-top:9px;border:2px solid #d7d9dc;border-radius:13px;padding:12px 14px;font:inherit;background:#fff}.kp-more input:focus,.kp-prompt input:focus,#kp-caption:focus,#kp-account:focus{outline:3px solid #f8dfa0;border-color:#c99927}
+.kp-assets{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:12px;margin-top:22px;max-height:360px;overflow:auto;padding:4px}.kp-asset{position:relative;aspect-ratio:1;border:3px solid transparent;border-radius:16px;overflow:hidden;background:#ece8de;cursor:pointer;padding:0}.kp-asset img{width:100%;height:100%;object-fit:cover}.kp-asset-name{position:absolute;inset:auto 0 0;padding:18px 7px 6px;background:linear-gradient(transparent,rgba(23,34,53,.88));color:#fff;font-size:11px;font-weight:700;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.kp-asset.on{border-color:var(--green);box-shadow:0 0 0 3px #c8ead9}.kp-asset.on:after{content:'✓';position:absolute;right:7px;top:7px;display:grid;place-items:center;width:28px;height:28px;border-radius:50%;background:var(--green);color:#fff;font-weight:900}.kp-more{margin-top:18px;color:#5c6570}.kp-more summary{cursor:pointer;font-weight:700}.kp-more input,.kp-prompt input,#kp-caption,#kp-account{width:100%;box-sizing:border-box;margin-top:9px;border:2px solid #d7d9dc;border-radius:13px;padding:12px 14px;font:inherit;background:#fff}.kp-more input:focus,.kp-prompt input:focus,#kp-caption:focus,#kp-account:focus{outline:3px solid #f8dfa0;border-color:#c99927}
.kp-asset-tools{display:flex;gap:10px;align-items:center;margin-top:20px;flex-wrap:wrap}.kp-filter{display:flex;gap:6px;flex-wrap:wrap}.kp-filter button{border:1px solid #dadddf;background:#fff;border-radius:999px;padding:8px 11px;font:700 12px inherit;cursor:pointer}.kp-filter button.on{background:var(--navy);border-color:var(--navy);color:#fff}.kp-asset-tools input{flex:1;min-width:180px;border:2px solid #d7d9dc;border-radius:999px;padding:9px 13px;font:inherit}
.kp-choices,.kp-channels{display:grid;grid-template-columns:repeat(auto-fill,minmax(205px,1fr));gap:12px;margin-top:22px}.kp-choice,.kp-channel{min-height:86px;text-align:left;border:2px solid #dadddf;background:#fff;border-radius:16px;padding:14px;cursor:pointer;color:var(--navy);font:inherit}.kp-choice b,.kp-channel b{display:block;font-size:15px;margin-bottom:4px}.kp-choice small,.kp-channel small{display:block;color:#6b7480;line-height:1.35}.kp-choice.on,.kp-channel.on{border-color:var(--green);background:#ecf8f1;box-shadow:0 0 0 2px #c8ead9}.kp-channel.off{opacity:.48;cursor:not-allowed}.kp-label{display:block;margin-top:20px;font-weight:800;font-size:14px}.kp-prompt{display:flex;gap:10px}.kp-prompt input{margin:9px 0 0}.kp-prompt button,#kp-all,#kp-none{border:0;border-radius:12px;padding:12px 16px;font-weight:800;cursor:pointer;white-space:nowrap}.kp-prompt button{margin-top:9px;background:var(--sun);color:var(--navy)}
.kp-suggestions{display:grid;gap:9px;margin-top:14px}.kp-suggestion{border:2px solid #dadddf;background:#fff;border-radius:14px;padding:12px 14px;text-align:left;cursor:pointer;font:inherit;line-height:1.45}.kp-suggestion:hover{border-color:#c99927;background:#fffaec}.kp-suggestion:before{content:'Use this';display:block;color:var(--green);font-size:11px;font-weight:900;text-transform:uppercase;letter-spacing:.08em;margin-bottom:4px}#kp-caption{min-height:130px;resize:vertical}.kp-count{text-align:right;color:#707987;font-size:12px;margin-top:4px}.kp-allrow{display:flex;gap:9px;margin-top:20px}#kp-all{background:var(--navy);color:#fff}#kp-none{background:#e9ecef;color:var(--navy)}
diff --git a/public/panels/quickpost.js b/public/panels/quickpost.js
index e9c43c7..f88800c 100644
--- a/public/panels/quickpost.js
+++ b/public/panels/quickpost.js
@@ -29,7 +29,11 @@ window.MCC_PANELS.quickpost = {
if (state.step === 5) renderReview();
root.scrollIntoView({behavior:'smooth', block:'start'});
}
- $$('.kp-step').forEach(b => b.onclick = () => showStep(+b.dataset.go));
+ $$('.kp-step').forEach(b => b.onclick = () => {
+ const target = +b.dataset.go;
+ if (target > state.step) return note(state.step, 'Finish this step first, then press Next.');
+ showStep(target);
+ });
$('#kp-back').onclick = () => showStep(state.step - 1);
$('#kp-next').onclick = () => {
if (state.step === 1 && !state.mediaUrl) return note(1, 'Pick a picture or paste a picture link first.');
@@ -58,8 +62,8 @@ window.MCC_PANELS.quickpost = {
const filter = root.querySelector('.kp-filter button.on')?.dataset.filter || 'all';
const q = ($('#kp-asset-search')?.value || '').trim().toLowerCase();
const assets = allAssets.filter(a => (filter === 'all' || String(a.kind || '').toLowerCase() === filter) && (!q || `${a.name || ''} ${(a.tags || []).join(' ')} ${JSON.stringify(a.aiTags || {})}`.toLowerCase().includes(q))).slice(0, 120);
- $('#kp-assets').innerHTML = assets.length ? assets.map((a,i) => `<button class="kp-asset" type="button" data-url="${esc(new URL(a.src, O).href)}" aria-label="Choose ${esc(a.name || 'picture '+(i+1))}"><img loading="lazy" src="${esc(a.src)}" alt="${esc(a.name || 'Wallpaper picture')}"></button>`).join('') : '<div class="kp-wait">No matching pictures yet. Try another word or paste a link below.</div>';
- $$('.kp-asset').forEach(b => b.onclick = () => { $$('.kp-asset').forEach(x => x.classList.remove('on')); b.classList.add('on'); state.mediaUrl = b.dataset.url; $('#kp-media').value = state.mediaUrl; });
+ $('#kp-assets').innerHTML = assets.length ? assets.map((a,i) => `<button class="kp-asset" type="button" data-url="${esc(new URL(a.src, O).href)}" aria-label="Choose ${esc(a.name || 'picture '+(i+1))}" aria-pressed="false"><img loading="lazy" src="${esc(a.src)}" alt="${esc(a.name || 'Wallpaper picture')}"><span class="kp-asset-name">${esc(a.name || 'DW picture')}</span></button>`).join('') : '<div class="kp-wait">No matching pictures yet. Try another word or paste a link below.</div>';
+ $$('.kp-asset').forEach(b => b.onclick = () => { $$('.kp-asset').forEach(x => { x.classList.remove('on'); x.setAttribute('aria-pressed','false'); }); b.classList.add('on'); b.setAttribute('aria-pressed','true'); state.mediaUrl = b.dataset.url; $('#kp-media').value = state.mediaUrl; });
}
$$('.kp-filter button').forEach(b => b.onclick = () => { $$('.kp-filter button').forEach(x => x.classList.remove('on')); b.classList.add('on'); renderAssets(); });
$('#kp-asset-search').addEventListener('input', renderAssets);
@@ -70,7 +74,7 @@ window.MCC_PANELS.quickpost = {
const d = await fetch(O + '/api/sounds/catalog', {credentials:'same-origin'}).then(r => r.json());
const items = (d.catalog || []).flatMap(s => s.items || []).filter(x => x.badge === 'brandsafe').slice(0, 8);
$('#kp-music').innerHTML = `<button class="kp-choice on" type="button" data-music=""><b>🔇 No music</b><small>That is completely okay.</small></button>` + items.map(x => `<button class="kp-choice" type="button" data-music="${esc(x.url)}" data-name="${esc(x.name)}"><b>🎵 ${esc(x.name)}</b><small>${esc((x.note || 'Brand-safe music').slice(0,100))}</small></button>`).join('');
- $$('.kp-choice').forEach(b => b.onclick = () => { $$('.kp-choice').forEach(x => x.classList.remove('on')); b.classList.add('on'); state.song = b.dataset.music ? {name:b.dataset.name,url:b.dataset.music} : null; });
+ $$('.kp-choice').forEach(b => { b.setAttribute('aria-pressed', b.classList.contains('on')); b.onclick = () => { $$('.kp-choice').forEach(x => { x.classList.remove('on'); x.setAttribute('aria-pressed','false'); }); b.classList.add('on'); b.setAttribute('aria-pressed','true'); state.song = b.dataset.music ? {name:b.dataset.name,url:b.dataset.music} : null; }; });
} catch (_) { $('#kp-music').innerHTML = '<button class="kp-choice on" type="button"><b>🔇 No music</b><small>Music choices could not load.</small></button>'; }
}
@@ -93,8 +97,8 @@ window.MCC_PANELS.quickpost = {
try {
const [t,s] = await Promise.all([fetch(O + '/api/composer/targets',{credentials:'same-origin'}).then(r=>r.json()), fetch(O + '/api/channels/status',{credentials:'same-origin'}).then(r=>r.json())]);
state.targets = t.targets || []; state.status = s.platforms || s.status || s || {};
- $('#kp-channels').innerHTML = state.targets.map(x => { const st=state.status[x.id]||{}; const works=!!st.connected && st.postable!==false; const why=works?'Ready to post':(st.postableNote||'Not connected yet'); return `<button class="kp-channel${works?'':' off'}" type="button" data-channel="${esc(x.id)}" ${works?'':'disabled'}><b>${icons[x.id]||x.icon||'●'} ${esc(x.label)}</b><small>${esc(why)}</small></button>`; }).join('');
- $$('.kp-channel:not(.off)').forEach(b => b.onclick = () => { b.classList.toggle('on'); state.channels = $$('.kp-channel.on').map(x=>x.dataset.channel); });
+ $('#kp-channels').innerHTML = state.targets.map(x => { const st=state.status[x.id]||{}; const works=!!st.connected && st.postable!==false; const why=works?'Ready to post':(st.postableNote||'Not connected yet'); return `<button class="kp-channel${works?'':' off'}" type="button" data-channel="${esc(x.id)}" aria-pressed="false" ${works?'':'disabled'}><b>${icons[x.id]||x.icon||'●'} ${esc(x.label)}</b><small>${esc(why)}</small></button>`; }).join('');
+ $$('.kp-channel:not(.off)').forEach(b => b.onclick = () => { b.classList.toggle('on'); b.setAttribute('aria-pressed', String(b.classList.contains('on'))); state.channels = $$('.kp-channel.on').map(x=>x.dataset.channel); });
} catch (_) { $('#kp-channels').innerHTML = '<div class="kp-error">Social accounts could not be checked.</div>'; }
}
$('#kp-all').onclick = () => { $$('.kp-channel:not(.off)').forEach(b=>b.classList.add('on')); state.channels = $$('.kp-channel.on').map(x=>x.dataset.channel); };
← 00e262c add honest cross browser CTA harness
·
back to Marketing Command Center
·
finish cross browser quick post proof 5b4d463 →