← back to Marketing Command Center
public/panels/composer.html
129 lines
<div class="cmp-head">
<div class="muted" style="font-size:12.5px">Combine an image or banner + copy, pick your socials, and publish (or stage) to all of them at once.</div>
<button class="btn ghost" id="cmp-refresh" style="font-size:12.5px;padding:6px 12px">↻ Refresh</button>
</div>
<div class="cmp-grid">
<!-- LEFT: source rail — assets + banners -->
<section class="cmp-col cmp-left">
<div class="cmp-tabs">
<button class="cmp-tab on" data-src="assets">🖼️ Assets</button>
<button class="cmp-tab" data-src="reels">🎬 Reels</button>
<button class="cmp-tab" data-src="banners">🎏 Banners</button>
</div>
<input id="cmp-search" class="cmp-search" placeholder="Filter…">
<div id="cmp-srcgrid" class="cmp-srcgrid"><div class="loading">Loading…</div></div>
</section>
<!-- MIDDLE: the post being assembled — THIS is where it all combines -->
<section class="cmp-col cmp-mid">
<div class="cmp-preview" id="cmp-preview">
<div class="cmp-ph">Pick an asset, reel, or banner on the left ←</div>
<img id="cmp-img" style="display:none" alt="selected">
<video id="cmp-video" style="display:none" controls muted playsinline preload="metadata"></video>
</div>
<div id="cmp-bannernote" class="muted" style="font-size:11.5px;color:#8a6a2a;min-height:14px"></div>
<div class="cmp-copywrap">
<div class="cmp-copybar">
<label class="muted" style="font-size:11.5px">Tone
<select id="cmp-tone" style="width:auto;font-size:12px">
<option value="luxury">Luxury</option>
<option value="playful">Playful</option>
<option value="editorial">Editorial</option>
<option value="trade">Trade / B2B</option>
</select>
</label>
<button class="btn ghost" id="cmp-gen" style="font-size:12px;padding:5px 10px">✨ Generate copy</button>
<span id="cmp-count" class="muted" style="font-size:11px;margin-left:auto">0</span>
</div>
<textarea id="cmp-caption" class="cmp-caption" placeholder="Write your caption — or hit ✨ Generate copy. #designerwallcoverings"></textarea>
</div>
</section>
<!-- RIGHT: publish targets + fire -->
<section class="cmp-col cmp-right">
<div class="cmp-rhead">
<span style="font-weight:700;font-size:13px">Post to</span>
<button class="btn ghost" id="cmp-all" style="font-size:11px;padding:3px 9px">Select connected</button>
</div>
<div id="cmp-targets" class="cmp-targets"><div class="loading">Loading…</div></div>
<!-- Facebook Page picker — revealed only when Facebook is a selected target -->
<div id="cmp-fbpages" class="cmp-fbpages" style="display:none">
<div class="cmp-fbpages-head">
<span>📘 Post to Page(s)</span>
<span class="cmp-fbpages-tools">
<button id="cmp-fball" class="cmp-mini">all</button>
<button id="cmp-fbrefresh" class="cmp-mini" title="Refresh Pages from Meta">↻</button>
</span>
</div>
<input id="cmp-fbsearch" class="cmp-search" style="margin-bottom:6px" placeholder="Filter pages…">
<div id="cmp-fbpages-list" class="cmp-fbpages-list"><div class="loading">Loading…</div></div>
</div>
<label class="cmp-dry"><input type="checkbox" id="cmp-dry" checked> Dry-run (stage, don't post live)</label>
<button class="btn gold" id="cmp-publish" style="width:100%;font-size:14px;padding:11px">Publish</button>
<div id="cmp-result" class="cmp-result"></div>
</section>
</div>
<style>
.cmp-head{display:flex;justify-content:space-between;align-items:center;gap:14px;margin-bottom:14px;flex-wrap:wrap}
.cmp-grid{display:grid;grid-template-columns:280px 1fr 260px;gap:16px;align-items:start}
@media(max-width:1100px){.cmp-grid{grid-template-columns:1fr}}
.cmp-col{background:var(--card,#fff);border:1px solid var(--line,#e7e1d6);border-radius:14px;padding:12px;
box-shadow:0 1px 3px rgba(0,0,0,.04);max-height:calc(100vh - 190px);display:flex;flex-direction:column}
.cmp-tabs{display:flex;gap:6px;margin-bottom:9px}
.cmp-tab{flex:1;font-size:12px;padding:6px 8px;border:1px solid var(--line,#e7e1d6);background:#fbf9f5;
border-radius:9px;cursor:pointer;color:var(--mut,#8a8170);font-weight:600}
.cmp-tab.on{background:var(--ink,#2a2620);color:#fff;border-color:var(--ink,#2a2620)}
.cmp-search{width:100%;font-size:12px;padding:7px 9px;border:1px solid var(--line,#e7e1d6);border-radius:9px;margin-bottom:9px}
.cmp-srcgrid{overflow-y:auto;display:grid;grid-template-columns:1fr 1fr;gap:8px;flex:1}
.cmp-thumb{border:1px solid var(--line,#ece6da);border-radius:9px;overflow:hidden;cursor:pointer;background:#f0ece3;
aspect-ratio:1/1;position:relative}
.cmp-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.cmp-thumb.sel{outline:3px solid var(--gold,#b8945a);outline-offset:-1px}
.cmp-thumb .lbl{position:absolute;left:0;right:0;bottom:0;font-size:9.5px;background:rgba(0,0,0,.55);color:#fff;
padding:2px 5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cmp-mid{gap:12px}
.cmp-preview{border:1px solid var(--line,#ece6da);border-radius:12px;background:#f7f4ee;min-height:260px;
display:flex;align-items:center;justify-content:center;overflow:hidden}
.cmp-preview img{width:100%;max-height:420px;object-fit:contain;display:block}
.cmp-preview video{width:100%;max-height:460px;object-fit:contain;display:block;background:#000}
.cmp-ph{color:var(--mut,#8a8170);font-size:13px;font-style:italic;padding:30px}
/* reel thumbnails — portrait video tiles with a play badge */
.cmp-srcgrid.reels{grid-template-columns:1fr 1fr}
.cmp-reel{border:1px solid var(--line,#ece6da);border-radius:9px;overflow:hidden;cursor:pointer;background:#000;
position:relative;aspect-ratio:9/16}
.cmp-reel video{width:100%;height:100%;object-fit:cover;display:block;pointer-events:none}
.cmp-reel.sel{outline:3px solid var(--gold,#b8945a);outline-offset:-1px}
.cmp-reel .play{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
font-size:26px;color:#fff;text-shadow:0 2px 8px rgba(0,0,0,.6);pointer-events:none}
.cmp-reel .lbl{position:absolute;left:0;right:0;bottom:0;font-size:9.5px;background:rgba(0,0,0,.6);color:#fff;
padding:3px 5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* facebook page picker */
.cmp-fbpages{border:1px solid var(--line,#ece6da);border-radius:10px;padding:9px;margin-bottom:10px;background:#fbfaf6}
.cmp-fbpages-head{display:flex;justify-content:space-between;align-items:center;font-size:12px;font-weight:700;margin-bottom:7px}
.cmp-fbpages-tools{display:flex;gap:5px}
.cmp-mini{font-size:10.5px;padding:2px 7px;border:1px solid var(--line,#e7e1d6);background:#fff;border-radius:7px;cursor:pointer;color:var(--mut,#8a8170)}
.cmp-fbpages-list{max-height:150px;overflow-y:auto;display:flex;flex-direction:column;gap:3px}
.cmp-fbrow{display:flex;align-items:center;gap:7px;font-size:12px;padding:4px 6px;border-radius:7px;cursor:pointer}
.cmp-fbrow:hover{background:#f2ede3}
.cmp-fbrow .nm{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cmp-fbrow .ig{font-size:9.5px;color:#8a8170}
.cmp-copybar{display:flex;align-items:center;gap:10px;margin-bottom:7px}
.cmp-caption{width:100%;min-height:120px;font-size:13.5px;line-height:1.5;padding:10px;resize:vertical;
border:1px solid var(--line,#e7e1d6);border-radius:10px;font-family:inherit}
.cmp-rhead{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}
.cmp-targets{display:flex;flex-direction:column;gap:7px;overflow-y:auto;flex:1;margin-bottom:10px}
.cmp-tgt{display:flex;align-items:center;gap:9px;font-size:13px;padding:8px 10px;border:1px solid var(--line,#ece6da);
border-radius:10px;cursor:pointer;background:#fdfcf9}
.cmp-tgt.off{opacity:.5;cursor:not-allowed}
.cmp-tgt .ico{font-size:16px}
.cmp-tgt .nm{flex:1;font-weight:600}
.cmp-tgt .st{font-size:9.5px;font-weight:700;padding:1px 7px;border-radius:999px}
.cmp-tgt .st.on{background:#e3efe0;color:#3a6b3a}
.cmp-tgt .st.off{background:#f3e9e9;color:#9a5a5a}
.cmp-dry{display:flex;align-items:center;gap:7px;font-size:12px;color:var(--mut,#8a8170);margin-bottom:10px}
.cmp-result{margin-top:11px;font-size:12px;line-height:1.5}
.cmp-result .ok{color:#3a6b3a}.cmp-result .warn{color:#8a6a2a}.cmp-result .err{color:#9a3a3a}
</style>