← back to Dw Launches
Design refinement: DW luxury editorial theme for social composer
b3d1a74bdf16b466094fa08ffd86ab829783225a · 2026-06-19 10:26:17 -0700 · Steve
- Wordmark: serif 22px with restrained tracking, gold dot, eyebrow label above
- Palette: eliminated neon green accent; all interactive states now use muted gold/gold-dim
- Channel chips: replaced full candy-fill avatar circles with 8px brand-color dots + subtle pill styling
- Section headers: replaced ALL-CAPS bureaucratic h3 gold labels with serif .sl-title + numeric step badge
- Topbar nav: lighter weight, bottom-border active indicator vs old box border
- Buttons: differentiated hierarchy — ghost (Save), gold-border (Submit), italic (Steve sign-off), solid gold (Publish), suppressed-panel (Publish/disabled)
- Preview panel: device-chrome wrapper with shadow; preview card uses clean white with DW-warm bg tone
- Spacing: consistent scale (--sp-* tokens), tighter card gap 14px, wrap padding 28px
- Preview column: position:sticky so it follows scroll
- Media items: remove button appears on hover only (cleaner resting state)
- Inputs/selects: gold focus ring, custom select arrow, uniform border-radius
- Badges: bordered style with status-tinted backgrounds replacing plain filled pills
- Toast: entrance animation, box-shadow elevation
- All JS IDs/classes, fetch calls, and behavioral logic unchanged
Files touched
Diff
commit b3d1a74bdf16b466094fa08ffd86ab829783225a
Author: Steve <steve@designerwallcoverings.com>
Date: Fri Jun 19 10:26:17 2026 -0700
Design refinement: DW luxury editorial theme for social composer
- Wordmark: serif 22px with restrained tracking, gold dot, eyebrow label above
- Palette: eliminated neon green accent; all interactive states now use muted gold/gold-dim
- Channel chips: replaced full candy-fill avatar circles with 8px brand-color dots + subtle pill styling
- Section headers: replaced ALL-CAPS bureaucratic h3 gold labels with serif .sl-title + numeric step badge
- Topbar nav: lighter weight, bottom-border active indicator vs old box border
- Buttons: differentiated hierarchy — ghost (Save), gold-border (Submit), italic (Steve sign-off), solid gold (Publish), suppressed-panel (Publish/disabled)
- Preview panel: device-chrome wrapper with shadow; preview card uses clean white with DW-warm bg tone
- Spacing: consistent scale (--sp-* tokens), tighter card gap 14px, wrap padding 28px
- Preview column: position:sticky so it follows scroll
- Media items: remove button appears on hover only (cleaner resting state)
- Inputs/selects: gold focus ring, custom select arrow, uniform border-radius
- Badges: bordered style with status-tinted backgrounds replacing plain filled pills
- Toast: entrance animation, box-shadow elevation
- All JS IDs/classes, fetch calls, and behavioral logic unchanged
---
public/index.html | 803 ++++++++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 687 insertions(+), 116 deletions(-)
diff --git a/public/index.html b/public/index.html
index a232f61..48d15b6 100644
--- a/public/index.html
+++ b/public/index.html
@@ -5,95 +5,640 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>DW Launches — Social Composer</title>
<style>
+ /* ─── Design tokens ─────────────────────────────────────────── */
:root{
- --bg:#0e0f12; --panel:#16181d; --panel2:#1d2027; --line:#2a2e37;
- --ink:#e9e7e1; --muted:#9aa0ab; --gold:#c8a96a; --accent:#34f480;
- --danger:#e0606e; --warn:#e0b35a; --radius:12px;
+ /* surfaces */
+ --bg:#0a0b0d;
+ --panel:#111318;
+ --panel2:#181b22;
+ --panel3:#1e222b;
+ --line:#252932;
+ --line-soft:#1e2129;
+
+ /* ink */
+ --ink:#e8e4dc;
+ --ink-2:#b0aaa0;
+ --muted:#6e7480;
+
+ /* brand palette */
+ --gold:#c2a064;
+ --gold-dim:#8a6e3e;
+ --gold-faint:rgba(194,160,100,.08);
+ --gold-faint2:rgba(194,160,100,.14);
+
+ /* status */
+ --danger:#c0505e;
+ --danger-faint:rgba(192,80,94,.12);
+ --warn:#b8923a;
+ --warn-faint:rgba(184,146,58,.12);
+ --ok:#3a8a60;
+ --ok-faint:rgba(58,138,96,.12);
+ --info:#3a6aa0;
+ --info-faint:rgba(58,106,160,.12);
+
+ /* interaction */
+ --focus:rgba(194,160,100,.35);
+
+ --radius:10px;
+ --radius-sm:6px;
+ --radius-lg:14px;
+
--font:"Helvetica Neue",Helvetica,Arial,sans-serif;
--serif:"Georgia","Times New Roman",serif;
+
+ /* spacing scale */
+ --sp-1:4px; --sp-2:8px; --sp-3:12px; --sp-4:16px; --sp-5:20px;
+ --sp-6:24px; --sp-7:28px; --sp-8:32px;
+ }
+
+ /* ─── Reset ─────────────────────────────────────────────────── */
+ *{box-sizing:border-box;margin:0;padding:0}
+ body{
+ background:var(--bg);
+ color:var(--ink);
+ font-family:var(--font);
+ font-size:13.5px;
+ line-height:1.5;
+ -webkit-font-smoothing:antialiased;
+ }
+
+ /* ─── Topbar ─────────────────────────────────────────────────── */
+ header.topbar{
+ display:flex;
+ align-items:center;
+ justify-content:space-between;
+ padding:0 28px;
+ height:56px;
+ border-bottom:1px solid var(--line);
+ background:var(--panel);
+ position:sticky;
+ top:0;
+ z-index:20;
+ }
+
+ /* Wordmark */
+ .brand{
+ display:flex;
+ flex-direction:column;
+ gap:0;
+ line-height:1;
+ }
+ .brand-eyebrow{
+ font-family:var(--font);
+ font-size:9.5px;
+ letter-spacing:3px;
+ text-transform:uppercase;
+ color:var(--muted);
+ margin-bottom:3px;
+ }
+ .brand-name{
+ font-family:var(--serif);
+ font-size:22px;
+ font-weight:400;
+ letter-spacing:.8px;
+ color:var(--ink);
+ line-height:1;
+ }
+ .brand-name .dot{color:var(--gold)}
+
+ /* Topbar nav */
+ nav.tabs{display:flex;gap:2px}
+ nav.tabs button{
+ background:transparent;
+ border:none;
+ color:var(--muted);
+ padding:8px 16px;
+ border-radius:var(--radius-sm);
+ cursor:pointer;
+ font-size:12.5px;
+ font-family:var(--font);
+ letter-spacing:.3px;
+ transition:color .15s, background .15s;
+ }
+ nav.tabs button:hover{color:var(--ink-2);background:var(--panel2)}
+ nav.tabs button.active{
+ color:var(--ink);
+ background:var(--panel2);
+ box-shadow:inset 0 -2px 0 var(--gold);
+ }
+
+ /* ─── Layout ─────────────────────────────────────────────────── */
+ .wrap{
+ display:flex;
+ gap:20px;
+ padding:20px 28px;
+ max-width:1520px;
+ margin:0 auto;
}
- *{box-sizing:border-box}
- body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--font);font-size:14px}
- header.topbar{display:flex;align-items:center;justify-content:space-between;padding:14px 22px;border-bottom:1px solid var(--line);background:var(--panel)}
- .brand{font-family:var(--serif);font-size:20px;letter-spacing:.5px}
- .brand .dot{color:var(--gold)}
- .brand small{display:block;font-family:var(--font);font-size:11px;color:var(--muted);letter-spacing:2px;text-transform:uppercase}
- nav.tabs{display:flex;gap:4px}
- nav.tabs button{background:transparent;border:1px solid var(--line);color:var(--muted);padding:7px 16px;border-radius:8px;cursor:pointer;font-size:13px}
- nav.tabs button.active{background:var(--panel2);color:var(--ink);border-color:var(--gold)}
- .wrap{display:flex;gap:18px;padding:18px 22px;max-width:1500px;margin:0 auto}
.col{flex:1;min-width:0}
- .card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:16px;margin-bottom:16px}
- .card h3{margin:0 0 12px;font-size:12px;letter-spacing:1.5px;text-transform:uppercase;color:var(--gold)}
- /* 1. channel rail */
- .chanrail{display:flex;gap:10px;flex-wrap:wrap}
- .chip{display:flex;align-items:center;gap:8px;padding:8px 12px;border:1px solid var(--line);border-radius:999px;cursor:pointer;user-select:none;background:var(--panel2)}
- .chip .av{width:26px;height:26px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:12px;color:#0e0f12}
- .chip.sel{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent) inset}
- .chip.disabled{opacity:.42;cursor:not-allowed}
- .chip .connect{font-size:10px;color:var(--warn);border:1px dashed var(--warn);border-radius:6px;padding:1px 5px}
- textarea,input[type=text],input[type=datetime-local],select{width:100%;background:var(--panel2);border:1px solid var(--line);color:var(--ink);border-radius:8px;padding:10px;font-family:inherit;font-size:13px}
- textarea{min-height:130px;resize:vertical;line-height:1.5}
- label{display:block;font-size:11px;color:var(--muted);margin:10px 0 4px;letter-spacing:.5px}
- .row{display:flex;gap:10px}.row>*{flex:1}
- button.btn{background:var(--gold);color:#1a1408;border:none;border-radius:8px;padding:10px 16px;font-weight:600;cursor:pointer;font-size:13px}
- button.ghost{background:transparent;border:1px solid var(--line);color:var(--ink)}
- button.danger{background:var(--danger);color:#fff}
- button:disabled{opacity:.4;cursor:not-allowed}
- /* per-channel tabs */
- .ctabs{display:flex;gap:4px;flex-wrap:wrap;margin-bottom:12px}
- .ctabs button{padding:6px 12px;font-size:12px;border-radius:7px;border:1px solid var(--line);background:var(--panel2);color:var(--muted);cursor:pointer}
- .ctabs button.active{color:var(--ink);border-color:var(--accent)}
- /* media */
- .media-drop{border:1.5px dashed var(--line);border-radius:10px;padding:16px;text-align:center;color:var(--muted);cursor:pointer}
- .media-drop.over{border-color:var(--accent);color:var(--ink)}
+
+ /* ─── Cards ─────────────────────────────────────────────────── */
+ .card{
+ background:var(--panel);
+ border:1px solid var(--line);
+ border-radius:var(--radius-lg);
+ padding:20px;
+ margin-bottom:14px;
+ }
+
+ /* Section label — editorial, not bureaucratic */
+ .section-label{
+ display:flex;
+ align-items:baseline;
+ gap:10px;
+ margin-bottom:16px;
+ padding-bottom:12px;
+ border-bottom:1px solid var(--line-soft);
+ }
+ .section-label .sl-step{
+ font-family:var(--font);
+ font-size:10px;
+ font-weight:600;
+ letter-spacing:1.5px;
+ text-transform:uppercase;
+ color:var(--gold-dim);
+ min-width:16px;
+ }
+ .section-label .sl-title{
+ font-family:var(--serif);
+ font-size:14px;
+ font-weight:400;
+ letter-spacing:.3px;
+ color:var(--ink);
+ }
+ /* Legacy h3 inside .card — keep for any missed cards */
+ .card h3{
+ font-family:var(--font);
+ font-size:10px;
+ letter-spacing:1.5px;
+ text-transform:uppercase;
+ color:var(--gold-dim);
+ margin:0 0 14px;
+ font-weight:600;
+ }
+
+ /* ─── Channel rail ───────────────────────────────────────────── */
+ .chanrail{display:flex;gap:8px;flex-wrap:wrap}
+ .chip{
+ display:flex;
+ align-items:center;
+ gap:8px;
+ padding:7px 13px 7px 9px;
+ border:1px solid var(--line);
+ border-radius:999px;
+ cursor:pointer;
+ user-select:none;
+ background:var(--panel2);
+ color:var(--ink-2);
+ font-size:12.5px;
+ transition:border-color .15s, color .15s, background .15s;
+ }
+ .chip:hover:not(.disabled){
+ border-color:var(--gold-dim);
+ color:var(--ink);
+ background:var(--panel3);
+ }
+ /* Brand-color dot replaces full candy avatar */
+ .chip .av{
+ width:8px;
+ height:8px;
+ border-radius:50%;
+ flex-shrink:0;
+ }
+ .chip.sel{
+ border-color:var(--gold);
+ color:var(--ink);
+ background:var(--gold-faint2);
+ }
+ .chip.sel .av{
+ /* Slightly brightened dot on selected */
+ box-shadow:0 0 0 2px rgba(0,0,0,.4);
+ }
+ .chip.disabled{opacity:.35;cursor:not-allowed}
+ .chip .connect{
+ font-size:9.5px;
+ color:var(--warn);
+ border:1px solid var(--warn-faint);
+ border-radius:4px;
+ padding:1px 5px;
+ background:var(--warn-faint);
+ }
+
+ /* ─── Form elements ──────────────────────────────────────────── */
+ textarea,
+ input[type=text],
+ input[type=datetime-local],
+ select{
+ width:100%;
+ background:var(--panel2);
+ border:1px solid var(--line);
+ color:var(--ink);
+ border-radius:var(--radius-sm);
+ padding:9px 11px;
+ font-family:inherit;
+ font-size:13px;
+ line-height:1.5;
+ transition:border-color .15s, box-shadow .15s;
+ -webkit-appearance:none;
+ appearance:none;
+ }
+ textarea:focus,
+ input[type=text]:focus,
+ input[type=datetime-local]:focus,
+ select:focus{
+ outline:none;
+ border-color:var(--gold-dim);
+ box-shadow:0 0 0 3px var(--focus);
+ }
+ textarea{min-height:128px;resize:vertical;line-height:1.6}
+ select{cursor:pointer;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%236e7480'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 10px center;padding-right:28px}
+
+ label{
+ display:block;
+ font-size:11px;
+ color:var(--muted);
+ margin:12px 0 4px;
+ letter-spacing:.4px;
+ text-transform:uppercase;
+ }
+ label:first-child{margin-top:0}
+
+ .row{display:flex;gap:10px}
+ .row>*{flex:1}
+
+ /* ─── Buttons ────────────────────────────────────────────────── */
+ button.btn{
+ border:none;
+ border-radius:var(--radius-sm);
+ padding:9px 18px;
+ font-weight:500;
+ cursor:pointer;
+ font-size:13px;
+ font-family:inherit;
+ letter-spacing:.2px;
+ transition:opacity .15s, background .15s, box-shadow .15s;
+ }
+ /* Primary — gold CTA (Save draft) */
+ button.btn:not(.ghost):not(.danger):not(.btn-submit):not(.btn-approve):not(.btn-publish){
+ background:var(--gold-faint2);
+ color:var(--gold);
+ border:1px solid var(--gold-dim);
+ }
+ button.btn:not(.ghost):not(.danger):not(.btn-submit):not(.btn-approve):not(.btn-publish):hover{
+ background:rgba(194,160,100,.22);
+ }
+ /* Ghost */
+ button.ghost{
+ background:transparent;
+ border:1px solid var(--line);
+ color:var(--ink-2);
+ }
+ button.ghost:hover{border-color:var(--ink-2);color:var(--ink)}
+
+ /* Submit for approval */
+ button.btn-submit{
+ background:var(--panel2);
+ border:1px solid var(--gold-dim);
+ color:var(--gold);
+ }
+ button.btn-submit:hover{background:var(--gold-faint2)}
+
+ /* Steve sign-off */
+ button.btn-approve{
+ background:var(--panel2);
+ border:1px solid var(--line);
+ color:var(--ink-2);
+ font-style:italic;
+ }
+ button.btn-approve:hover{border-color:var(--gold-dim);color:var(--ink)}
+
+ /* Publish — solid gold when enabled, visually suppressed when gated */
+ button.btn-publish{
+ background:var(--gold);
+ color:#0d0b06;
+ font-weight:600;
+ letter-spacing:.3px;
+ }
+ button.btn-publish:hover:not(:disabled){
+ background:#d4b070;
+ box-shadow:0 2px 12px rgba(194,160,100,.25);
+ }
+ button.btn-publish:disabled{
+ background:var(--panel3);
+ border:1px solid var(--line);
+ color:var(--muted);
+ cursor:not-allowed;
+ font-weight:400;
+ letter-spacing:0;
+ }
+
+ button.danger{background:var(--danger);color:#fff;border:none}
+ button.danger:hover{opacity:.88}
+ button:disabled{cursor:not-allowed}
+
+ /* ─── Per-channel tabs ───────────────────────────────────────── */
+ .ctabs{
+ display:flex;
+ gap:3px;
+ flex-wrap:wrap;
+ margin-bottom:14px;
+ padding-bottom:12px;
+ border-bottom:1px solid var(--line-soft);
+ }
+ .ctabs button{
+ padding:5px 12px;
+ font-size:12px;
+ border-radius:var(--radius-sm);
+ border:1px solid transparent;
+ background:transparent;
+ color:var(--muted);
+ cursor:pointer;
+ font-family:inherit;
+ transition:color .15s, background .15s, border-color .15s;
+ }
+ .ctabs button:hover{color:var(--ink-2);background:var(--panel2)}
+ .ctabs button.active{
+ color:var(--ink);
+ background:var(--panel2);
+ border-color:var(--line);
+ box-shadow:inset 0 -2px 0 var(--gold);
+ }
+
+ /* ─── Media ──────────────────────────────────────────────────── */
+ .media-drop{
+ border:1px dashed var(--line);
+ border-radius:var(--radius);
+ padding:18px 14px;
+ text-align:center;
+ color:var(--muted);
+ font-size:12px;
+ cursor:pointer;
+ transition:border-color .15s, color .15s, background .15s;
+ margin-top:4px;
+ }
+ .media-drop:hover{border-color:var(--gold-dim);color:var(--ink-2);background:var(--gold-faint)}
+ .media-drop.over{border-color:var(--gold);color:var(--ink);background:var(--gold-faint2)}
.media-list{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}
- .media-item{position:relative;width:74px;height:74px;border-radius:8px;background:var(--panel2);border:1px solid var(--line);display:flex;align-items:center;justify-content:center;font-size:10px;color:var(--muted);overflow:hidden;text-align:center;padding:4px}
- .media-item img{width:100%;height:100%;object-fit:cover}
- .media-item .x{position:absolute;top:2px;right:3px;cursor:pointer;color:var(--danger);font-weight:700}
- .media-item .tag{position:absolute;bottom:2px;left:3px;font-size:8px;background:#000a;padding:0 3px;border-radius:3px}
- .counter{font-size:11px;color:var(--muted);text-align:right}
- .counter.over{color:var(--danger);font-weight:700}
- /* preview */
- .pv{background:#fff;color:#111;border-radius:12px;overflow:hidden;border:1px solid var(--line)}
- .pv .pvhead{display:flex;align-items:center;gap:8px;padding:10px 12px}
- .pv .pvav{width:32px;height:32px;border-radius:50%;background:var(--gold)}
- .pv .pvname{font-weight:700;font-size:13px}
- .pv .pvsub{font-size:11px;color:#777}
- .pv .pvbody{padding:0 12px 10px;font-size:13px;line-height:1.45;white-space:pre-wrap}
- .pv .pvimg{width:100%;background:#e6e2da;display:flex;align-items:center;justify-content:center;color:#999;font-size:11px}
+ .media-item{
+ position:relative;
+ width:76px;
+ height:76px;
+ border-radius:var(--radius-sm);
+ background:var(--panel2);
+ border:1px solid var(--line);
+ display:flex;
+ align-items:center;
+ justify-content:center;
+ font-size:9.5px;
+ color:var(--muted);
+ overflow:hidden;
+ text-align:center;
+ padding:4px;
+ transition:border-color .15s;
+ }
+ .media-item:hover{border-color:var(--gold-dim)}
+ .media-item img{width:100%;height:100%;object-fit:cover;border-radius:3px}
+ .media-item .x{
+ position:absolute;
+ top:3px;right:4px;
+ cursor:pointer;
+ color:var(--danger);
+ font-weight:700;
+ font-size:13px;
+ line-height:1;
+ opacity:0;
+ transition:opacity .12s;
+ }
+ .media-item:hover .x{opacity:1}
+ .media-item .tag{
+ position:absolute;
+ bottom:3px;left:3px;
+ font-size:8px;
+ background:rgba(0,0,0,.65);
+ padding:1px 4px;
+ border-radius:3px;
+ letter-spacing:.3px;
+ text-transform:uppercase;
+ }
+
+ /* ─── Counters ───────────────────────────────────────────────── */
+ .counter{font-size:11px;color:var(--muted);text-align:right;margin-top:3px}
+ .counter.over{color:var(--danger);font-weight:600}
+
+ /* ─── Preview panel ──────────────────────────────────────────── */
+ /* Outer wrapper — device chrome feel */
+ .preview-device{
+ background:var(--panel2);
+ border:1px solid var(--line);
+ border-radius:var(--radius-lg);
+ padding:14px;
+ margin-top:0;
+ }
+ .pv{
+ background:#fff;
+ color:#111;
+ border-radius:10px;
+ overflow:hidden;
+ box-shadow:0 1px 4px rgba(0,0,0,.18);
+ }
+ .pv .pvhead{display:flex;align-items:center;gap:9px;padding:10px 13px}
+ .pv .pvav{width:34px;height:34px;border-radius:50%;flex-shrink:0}
+ .pv .pvname{font-weight:700;font-size:13px;color:#111;line-height:1.2}
+ .pv .pvsub{font-size:11px;color:#888;margin-top:1px}
+ .pv .pvbody{
+ padding:2px 13px 11px;
+ font-size:13px;
+ line-height:1.5;
+ white-space:pre-wrap;
+ color:#1a1a1a;
+ }
+ .pv .pvimg{
+ width:100%;
+ background:#edeae4;
+ display:flex;
+ align-items:center;
+ justify-content:center;
+ color:#bbb;
+ font-size:11px;
+ letter-spacing:.5px;
+ text-transform:uppercase;
+ }
.pv.ig .pvimg{aspect-ratio:1/1}
.pv.pinterest .pvimg{aspect-ratio:2/3}
.pv.youtube .pvimg{aspect-ratio:16/9}
- .pv .pvactions{display:flex;gap:18px;padding:8px 12px;color:#555;font-size:12px;border-top:1px solid #eee}
- .pv .pvtitle{font-weight:700;font-size:14px;padding:8px 12px 2px}
- .pv .thread .tw{border-bottom:1px solid #eee;padding:8px 0}
- .badge{display:inline-block;font-size:10px;padding:2px 7px;border-radius:999px;letter-spacing:.5px;text-transform:uppercase}
- .b-draft{background:#3a3a3a;color:#ccc}.b-pending{background:#5a4a1a;color:var(--warn)}
- .b-approved{background:#1a4a2a;color:var(--accent)}.b-published{background:#1a3a5a;color:#7ec8ff}
- .gatebar{background:var(--panel2);border:1px solid var(--line);border-radius:10px;padding:12px;margin-top:8px}
- .gatebar.warn{border-color:var(--warn)}
+ .pv.facebook .pvimg{aspect-ratio:1.91/1}
+ .pv.linkedin .pvimg{aspect-ratio:1.91/1}
+ .pv .pvactions{
+ display:flex;
+ gap:18px;
+ padding:9px 13px;
+ color:#777;
+ font-size:12px;
+ border-top:1px solid #f0ede8;
+ }
+ .pv .pvtitle{font-weight:700;font-size:14px;padding:9px 13px 3px;color:#111}
+ .pv .thread .tw{border-bottom:1px solid #f0ede8;padding:8px 0}
+
+ /* ─── Badges ─────────────────────────────────────────────────── */
+ .badge{
+ display:inline-block;
+ font-size:9.5px;
+ padding:2px 8px;
+ border-radius:999px;
+ letter-spacing:.6px;
+ text-transform:uppercase;
+ font-weight:500;
+ }
+ .b-draft{background:#252830;color:#7a8090;border:1px solid #2e3240}
+ .b-pending{background:var(--warn-faint);color:var(--warn);border:1px solid rgba(184,146,58,.25)}
+ .b-approved{background:var(--ok-faint);color:#5ab880;border:1px solid rgba(58,138,96,.25)}
+ .b-published{background:var(--info-faint);color:#6aacda;border:1px solid rgba(58,106,160,.25)}
+
+ /* ─── Gate bar ───────────────────────────────────────────────── */
+ .gatebar{
+ background:var(--panel2);
+ border:1px solid var(--line);
+ border-radius:var(--radius);
+ padding:12px 14px;
+ margin-top:10px;
+ font-size:12.5px;
+ }
+ .gatebar.warn{border-color:rgba(184,146,58,.4);background:var(--warn-faint)}
+ .gatebar strong{font-size:12.5px;color:var(--ink-2)}
+ .gatebar .muted{margin-top:4px}
+
+ /* Approval row — stack buttons cleanly */
+ .approval-actions{display:flex;flex-direction:column;gap:8px;margin-top:14px}
+ .approval-actions .row{gap:8px}
+ .approval-actions .row>*{flex:1}
+
+ /* ─── Misc helpers ───────────────────────────────────────────── */
.muted{color:var(--muted);font-size:12px}
- /* calendar */
- .cal{display:grid;grid-template-columns:repeat(7,1fr);gap:6px}
- .cal .dh{font-size:11px;color:var(--muted);text-align:center;padding:4px}
- .cal .cell{min-height:78px;background:var(--panel2);border:1px solid var(--line);border-radius:8px;padding:5px;font-size:11px}
- .cal .cell .dn{color:var(--muted);font-size:10px}
- .cal .ev{font-size:10px;margin-top:3px;padding:2px 4px;border-radius:5px;background:#222633;border-left:2px solid var(--gold);overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
- .launchcard{background:var(--panel2);border:1px solid var(--line);border-radius:10px;padding:12px;margin-bottom:10px;cursor:pointer}
- .launchcard:hover{border-color:var(--gold)}
- .launchcard .when{font-size:11px;color:var(--muted)}
- .toast{position:fixed;bottom:18px;left:50%;transform:translateX(-50%);background:var(--panel2);border:1px solid var(--gold);padding:12px 18px;border-radius:10px;z-index:50;max-width:480px}
- .toast.err{border-color:var(--danger)}
- .hide{display:none}
- .queueslots{display:flex;gap:6px;flex-wrap:wrap}
- .queueslots button{font-size:11px;padding:5px 9px}
- .field-block{border:1px solid var(--line);border-radius:8px;padding:10px;margin-top:8px;background:var(--panel2)}
+ .hide{display:none!important}
+ .divider{height:1px;background:var(--line-soft);margin:14px 0}
+
+ /* ─── Queue slots ────────────────────────────────────────────── */
+ .queueslots{display:flex;gap:6px;flex-wrap:wrap;margin-top:6px}
+ .queueslots button{
+ font-size:11px;
+ padding:4px 10px;
+ border-radius:var(--radius-sm);
+ background:var(--panel2);
+ border:1px solid var(--line);
+ color:var(--ink-2);
+ cursor:pointer;
+ font-family:inherit;
+ transition:border-color .12s, color .12s;
+ }
+ .queueslots button:hover{border-color:var(--gold-dim);color:var(--ink)}
+
+ /* ─── Field block (platform fields) ─────────────────────────── */
+ .field-block{
+ border:1px solid var(--line-soft);
+ border-radius:var(--radius-sm);
+ padding:12px;
+ margin-top:10px;
+ background:var(--panel2);
+ }
+
+ /* ─── Calendar ───────────────────────────────────────────────── */
+ .cal{display:grid;grid-template-columns:repeat(7,1fr);gap:5px}
+ .cal .dh{
+ font-size:10.5px;
+ color:var(--muted);
+ text-align:center;
+ padding:5px 2px;
+ letter-spacing:.5px;
+ text-transform:uppercase;
+ }
+ .cal .cell{
+ min-height:76px;
+ background:var(--panel2);
+ border:1px solid var(--line);
+ border-radius:var(--radius-sm);
+ padding:6px;
+ font-size:11px;
+ }
+ .cal .cell .dn{color:var(--muted);font-size:10px;margin-bottom:2px}
+ .cal .ev{
+ font-size:10px;
+ margin-top:3px;
+ padding:2px 5px;
+ border-radius:4px;
+ background:var(--panel3);
+ border-left:2px solid var(--gold);
+ overflow:hidden;
+ white-space:nowrap;
+ text-overflow:ellipsis;
+ color:var(--ink-2);
+ }
+ #cal-label{
+ text-align:center;
+ font-family:var(--serif);
+ font-size:17px;
+ color:var(--ink);
+ letter-spacing:.3px;
+ }
+
+ /* ─── Launch cards (Drafts view) ─────────────────────────────── */
+ .launchcard{
+ background:var(--panel2);
+ border:1px solid var(--line);
+ border-radius:var(--radius);
+ padding:13px 15px;
+ margin-bottom:8px;
+ cursor:pointer;
+ transition:border-color .15s, background .15s;
+ }
+ .launchcard:hover{border-color:var(--gold-dim);background:var(--panel3)}
+ .launchcard .when{
+ font-size:11px;
+ color:var(--muted);
+ margin-top:5px;
+ display:flex;
+ align-items:center;
+ gap:4px;
+ }
+
+ /* ─── Toast ──────────────────────────────────────────────────── */
+ .toast{
+ position:fixed;
+ bottom:20px;
+ left:50%;
+ transform:translateX(-50%);
+ background:var(--panel2);
+ border:1px solid var(--gold-dim);
+ padding:11px 18px;
+ border-radius:var(--radius);
+ z-index:50;
+ max-width:500px;
+ font-size:13px;
+ color:var(--ink-2);
+ box-shadow:0 4px 24px rgba(0,0,0,.4);
+ animation:toastin .18s ease;
+ }
+ .toast.err{border-color:var(--danger);color:var(--danger)}
+ @keyframes toastin{from{opacity:0;transform:translateX(-50%) translateY(8px)}to{opacity:1;transform:translateX(-50%) translateY(0)}}
+
+ /* ─── State line ─────────────────────────────────────────────── */
+ #state-line{margin-bottom:6px;display:flex;align-items:center;gap:8px}
+
+ /* ─── Preview sticky wrapper ─────────────────────────────────── */
+ .preview-col{
+ flex:1;
+ min-width:0;
+ position:sticky;
+ top:72px;
+ align-self:flex-start;
+ }
</style>
</head>
<body>
<header class="topbar">
- <div class="brand">Designer Wallcoverings<span class="dot">.</span><small>Launches · Social Composer</small></div>
+ <div class="brand">
+ <div class="brand-eyebrow">Launches · Social Composer</div>
+ <div class="brand-name">Designer Wallcoverings<span class="dot">.</span></div>
+ </div>
<nav class="tabs">
<button id="nav-compose" class="active" onclick="showView('compose')">Compose</button>
<button id="nav-calendar" onclick="showView('calendar')">Calendar</button>
@@ -105,20 +650,27 @@
<div id="view-compose">
<div class="wrap">
<!-- LEFT: editor -->
- <div class="col" style="flex:1.2">
+ <div class="col" style="flex:1.25">
+
<!-- 1. Channel selector rail -->
<div class="card">
- <h3>1 · Channels</h3>
+ <div class="section-label">
+ <span class="sl-step">01</span>
+ <span class="sl-title">Channels</span>
+ </div>
<div id="chanrail" class="chanrail"></div>
</div>
<!-- 2. Global editor -->
<div class="card">
- <h3>2 · Global post (master)</h3>
- <label>Launch title (admin only)</label>
+ <div class="section-label">
+ <span class="sl-step">02</span>
+ <span class="sl-title">Global Post</span>
+ </div>
+ <label>Launch title <span class="muted" style="display:inline;text-transform:none;letter-spacing:0">(admin only)</span></label>
<input type="text" id="launch-title" placeholder="e.g. Trending Wallcoverings Collection 2026 — Weekly Arrivals">
- <label>Master body — selected channels inherit this by default</label>
- <textarea id="global-body" placeholder="Write the master post… (the word "Wallpaper" is banned — use "Wallcoverings")"></textarea>
+ <label>Master body <span class="muted" style="display:inline;text-transform:none;letter-spacing:0">— selected channels inherit this by default</span></label>
+ <textarea id="global-body" placeholder='Write the master post… (the word "Wallpaper" is banned — use "Wallcoverings")'></textarea>
<div class="counter" id="global-counter"></div>
<!-- 4. Media attach -->
@@ -130,14 +682,20 @@
<!-- 3. Per-channel tabs + 6. platform fields -->
<div class="card" id="perchan-card">
- <h3>3 · Per-channel overrides</h3>
+ <div class="section-label">
+ <span class="sl-step">03</span>
+ <span class="sl-title">Per-channel Overrides</span>
+ </div>
<div class="ctabs" id="ctabs"></div>
<div id="perchan-editor" class="muted">Select 2+ channels to unlock per-channel overrides.</div>
</div>
<!-- 7. Scheduling -->
<div class="card">
- <h3>7 · Scheduling</h3>
+ <div class="section-label">
+ <span class="sl-step">07</span>
+ <span class="sl-title">Scheduling</span>
+ </div>
<div class="row">
<div>
<label>Mode</label>
@@ -149,7 +707,7 @@
</select>
</div>
<div id="sched-dt-wrap">
- <label>Date & time (applies to selected channels w/o their own)</label>
+ <label>Date & time</label>
<input type="datetime-local" id="sched-dt">
</div>
</div>
@@ -157,34 +715,44 @@
<label>Preset queue slots (PT)</label>
<div class="queueslots" id="queue-slots"></div>
</div>
- <p class="muted" style="margin-top:8px">Per-channel cadence (overrides above) is set inside each channel tab.</p>
+ <p class="muted" style="margin-top:10px">Per-channel cadence overrides global schedule — set inside each channel tab.</p>
</div>
- <!-- 9. Draft / approval -->
+ <!-- 9. State & approval -->
<div class="card">
- <h3>9 · State & approval</h3>
+ <div class="section-label">
+ <span class="sl-step">09</span>
+ <span class="sl-title">State & Approval</span>
+ </div>
<div id="state-line"><span class="badge b-draft">draft</span></div>
<div class="gatebar warn" id="gatebar">
<strong>Publish is gated.</strong>
- <div class="muted">Nothing goes live without explicit Steve sign-off. The publish button stays disabled behind the approval flag. This tool never calls a live social API.</div>
+ <div class="muted">Nothing goes live without explicit Steve sign-off. The publish button stays disabled until approval. This tool never calls a live social API.</div>
</div>
- <div class="row" style="margin-top:12px">
- <button class="btn ghost" onclick="saveLaunch('draft')">Save draft</button>
- <button class="btn ghost" onclick="submitApproval()">Submit for approval</button>
- </div>
- <div class="row" style="margin-top:8px">
- <button class="btn ghost" id="btn-approve" onclick="approve()">Steve sign-off ✓</button>
- <button class="btn" id="btn-publish" onclick="publish()" disabled>Publish (gated stub)</button>
+ <div class="approval-actions">
+ <div class="row">
+ <button class="btn ghost" onclick="saveLaunch('draft')">Save draft</button>
+ <button class="btn btn-submit" onclick="submitApproval()">Submit for approval</button>
+ </div>
+ <div class="row">
+ <button class="btn btn-approve" id="btn-approve" onclick="approve()">Steve sign-off ✓</button>
+ <button class="btn btn-publish" id="btn-publish" onclick="publish()" disabled>Publish (gated)</button>
+ </div>
</div>
</div>
</div>
- <!-- RIGHT: preview -->
- <div class="col">
+ <!-- RIGHT: preview (sticky) -->
+ <div class="col preview-col">
<div class="card">
- <h3>5 · Live preview</h3>
+ <div class="section-label">
+ <span class="sl-step">05</span>
+ <span class="sl-title">Live Preview</span>
+ </div>
<div class="ctabs" id="pv-tabs"></div>
- <div id="preview"></div>
+ <div class="preview-device">
+ <div id="preview"></div>
+ </div>
</div>
</div>
</div>
@@ -194,11 +762,11 @@
<div id="view-calendar" class="hide">
<div class="wrap"><div class="col">
<div class="card">
- <h3>8 · Calendar</h3>
- <div class="row" style="align-items:center;margin-bottom:12px">
- <button class="btn ghost" onclick="calShift(-1)">‹ Prev</button>
- <div id="cal-label" style="text-align:center;font-family:var(--serif);font-size:16px"></div>
- <button class="btn ghost" onclick="calShift(1)">Next ›</button>
+ <h3>Calendar</h3>
+ <div class="row" style="align-items:center;margin-bottom:16px">
+ <button class="btn ghost" onclick="calShift(-1)" style="flex:0 0 auto;padding:7px 14px">‹ Prev</button>
+ <div id="cal-label"></div>
+ <button class="btn ghost" onclick="calShift(1)" style="flex:0 0 auto;padding:7px 14px">Next ›</button>
</div>
<div class="cal" id="cal-grid"></div>
</div>
@@ -209,7 +777,7 @@
<div id="view-drafts" class="hide">
<div class="wrap"><div class="col">
<div class="card">
- <h3>Drafts & queue</h3>
+ <h3>Drafts & Queue</h3>
<div id="drafts-list"></div>
</div>
</div></div>
@@ -270,7 +838,8 @@ function renderChanRail(){
const sel = state.channelsSelected.includes(c.id);
const d=document.createElement('div');
d.className='chip'+(sel?' sel':'')+(c.connected?'':' disabled');
- d.innerHTML=`<span class="av" style="background:${COLORS[c.id]}">${c.name[0]}</span>${c.name}`
+ // refined: colored dot, not full candy avatar
+ d.innerHTML=`<span class="av" style="background:${COLORS[c.id]}"></span>${c.name}`
+ (c.connected?'':' <span class="connect">connect</span>');
if(c.connected) d.onclick=()=>toggleChan(c.id);
el.appendChild(d);
@@ -359,11 +928,11 @@ function renderPerChanEditor(){
const limit=CHAR_LIMITS[id];
const effBody = c.body || state.global.body || '';
let html = `
- <label>${chanName(id)} copy (overrides master; blank = inherit master)</label>
+ <label>${chanName(id)} copy <span class="muted" style="display:inline;text-transform:none;letter-spacing:0">(overrides master; blank = inherit master)</span></label>
<textarea id="pc-body" placeholder="Inherit master, or override here…">${esc(c.body||'')}</textarea>
<div class="counter" id="pc-counter"></div>
- <label>${chanName(id)} media (blank = inherit master media)</label>
- <div class="media-drop" id="pc-drop">Add ${chanName(id)} media (image / carousel / video)</div>
+ <label>${chanName(id)} media <span class="muted" style="display:inline;text-transform:none;letter-spacing:0">(blank = inherit master media)</span></label>
+ <div class="media-drop" id="pc-drop">Add ${chanName(id)} media</div>
<input type="file" id="pc-file" class="hide" multiple accept="image/*,video/*">
<div class="media-list" id="pc-media"></div>`;
html += platformFields(id, c);
@@ -403,7 +972,7 @@ function platformFields(id,c){
if(id==='instagram') return `<div class="field-block"><label>IG first comment</label><input type="text" id="f-firstComment" value="${fb(f.firstComment)}" placeholder="First comment (hashtags / link)"></div>`;
if(id==='youtube') return `<div class="field-block"><label>YouTube title</label><input type="text" id="f-ytTitle" value="${fb(f.ytTitle)}"><label>Thumbnail ref</label><input type="text" id="f-ytThumbnail" value="${fb(f.ytThumbnail)}"></div>`;
if(id==='pinterest') return `<div class="field-block"><label>Pin title</label><input type="text" id="f-pinTitle" value="${fb(f.pinTitle)}"><label>Board</label><input type="text" id="f-board" value="${fb(f.board)}"><label>Destination link</label><input type="text" id="f-destinationLink" value="${fb(f.destinationLink)}"></div>`;
- if(id==='twitter') return `<div class="field-block"><label>Thread split (one tweet per line)</label><textarea id="f-thread" style="min-height:90px">${fb((f.thread||[]).join('\n'))}</textarea></div>`;
+ if(id==='twitter') return `<div class="field-block"><label>Thread split <span class="muted" style="display:inline;text-transform:none;letter-spacing:0">(one tweet per line)</span></label><textarea id="f-thread" style="min-height:90px">${fb((f.thread||[]).join('\n'))}</textarea></div>`;
return '';
}
function wirePlatformFields(id,c){
@@ -422,7 +991,7 @@ function renderSchedule(){
document.getElementById('queue-wrap').classList.toggle('hide', mode!=='queue');
if(mode==='queue'){
const q=document.getElementById('queue-slots'); q.innerHTML='';
- QUEUE_SLOTS.forEach(t=>{const b=document.createElement('button');b.className='btn ghost';b.textContent=t+' PT';
+ QUEUE_SLOTS.forEach(t=>{const b=document.createElement('button');b.textContent=t+' PT';
b.onclick=()=>{ applyQueueSlot(t); }; q.appendChild(b);});
}
}
@@ -480,8 +1049,10 @@ function renderPreview(){
}
function pvShell(id, name, inner, extra, label){
return `<div class="pv ${id}">
- <div class="pvhead"><div class="pvav" style="background:${COLORS[id]}"></div>
- <div><div class="pvname">${name}</div><div class="pvsub">${label} preview</div></div></div>
+ <div class="pvhead">
+ <div class="pvav" style="background:${COLORS[id]}"></div>
+ <div><div class="pvname">${name}</div><div class="pvsub">${label} preview</div></div>
+ </div>
${inner}${extra}
<div class="pvactions"><span>♥ Like</span><span>💬 Comment</span><span>↗ Share</span></div>
</div>`;
@@ -560,7 +1131,7 @@ async function renderCalendar(){
cell.innerHTML=`<div class="dn">${d}</div>`;
events.filter(e=>{const dt=new Date(e.datetime);return dt.getFullYear()===y&&dt.getMonth()===mo&&dt.getDate()===d;})
.forEach(e=>{const ev=document.createElement('div');ev.className='ev';
- ev.style.borderLeftColor=COLORS[e.channel]||'#c8a96a';
+ ev.style.borderLeftColor=COLORS[e.channel]||'#c2a064';
ev.title=`${e.title}\n${e.channel} · ${new Date(e.datetime).toISOString()}\nstatus: ${e.status}`;
ev.textContent=`${new Date(e.datetime).toLocaleTimeString([], {hour:'numeric',minute:'2-digit'})} ${chanName(e.channel)}`;
cell.appendChild(ev);});
@@ -577,10 +1148,10 @@ async function renderDrafts(){
const created=l.created_at?new Date(l.created_at):null;
const whenStr=created?created.toLocaleString(undefined,{year:'numeric',month:'short',day:'numeric',hour:'numeric',minute:'2-digit'}):'—';
const m={draft:'b-draft',pending_approval:'b-pending',approved:'b-approved',published:'b-published'};
- d.innerHTML=`<div style="display:flex;justify-content:space-between">
+ d.innerHTML=`<div style="display:flex;justify-content:space-between;align-items:flex-start">
<strong>${esc(l.title)}</strong>
<span class="badge ${m[l.status]||'b-draft'}">${(l.status||'draft').replace('_',' ')}</span></div>
- <div class="muted">${(l.channelsSelected||[]).map(chanName).join(' · ')||'no channels'}</div>
+ <div class="muted" style="margin-top:4px">${(l.channelsSelected||[]).map(chanName).join(' · ')||'no channels'}</div>
<div class="when" title="${l.created_at||''}">🕓 ${whenStr}</div>`;
d.onclick=()=>{ state=l; activeChanTab=null; activePvTab=null; showView('compose'); hydrate(); toast('Loaded: '+l.title); };
el.appendChild(d);
← 6df89ad add README with run instructions, component map, and stub li
·
back to Dw Launches
·
add modal-rig.js drop-in (vanilla drag/resize/maximize/persi 8db4dc5 →