[object Object]

← back to Dw Activation Calendar

Restyle DW Calendar to luxe Figma spec (Cormorant/Inter, plum/brass palette, white-card layout) — preserve all Google Calendar CRUD, 409 not-connected handling, tabs, modal-rig drag/resize, and SKU Activation grid

73ec16d403b8909f51d63a1a32e9eb60848e04a5 · 2026-06-24 14:03:44 -0700 · Steve

Files touched

Diff

commit 73ec16d403b8909f51d63a1a32e9eb60848e04a5
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Jun 24 14:03:44 2026 -0700

    Restyle DW Calendar to luxe Figma spec (Cormorant/Inter, plum/brass palette, white-card layout) — preserve all Google Calendar CRUD, 409 not-connected handling, tabs, modal-rig drag/resize, and SKU Activation grid
---
 public/index.html | 368 ++++++++++++++++++++++++++++++++++++++----------------
 1 file changed, 259 insertions(+), 109 deletions(-)

diff --git a/public/index.html b/public/index.html
index d65886f..fd7f57f 100644
--- a/public/index.html
+++ b/public/index.html
@@ -4,132 +4,235 @@
 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <title>DW Calendar</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,500;0,600;1,400;1,500&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
 <style>
   :root{
-    --bg:#0e0f12; --panel:#15171c; --panel2:#1b1e25; --line:#2a2e38;
-    --ink:#ece8df; --ink-2:#b8b3a6; --ink-3:#7c7768; --gold:#c2a064; --gold-dim:#8a7240;
-    --card:#16181d; --border:#2a2e38;
+    /* Luxe DW palette (Figma tokens — verbatim) */
+    --ground:#FAF8F3; --surface:#FFFFFF; --ink:#1F1B17; --muted:#8A8178;
+    --faint:#C2BBAE; --hairline:#E7E1D6; --plum:#5E2B3C; --brass:#B0894F;
+    --green:#3E7C5A; --red:#A23B3B;
+    --out-month:#FBF9F5;
+    /* chip tints */
+    --chip-brass-bg:#F4ECDD; --chip-brass-fg:#7A571C;
+    --chip-plum-bg:#F3E7EC;  --chip-plum-fg:#6E2A40;
+    --chip-green-bg:#E8F0EA; --chip-green-fg:#2F5C43;
+    /* not-connected banner */
+    --warn-bg:#FBF3E3; --warn-bd:#E8D6A8; --warn-fg:#7A571C;
+    --serif:"Cormorant Garamond",Georgia,"Times New Roman",serif;
+    --ui:"Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
   }
   *{box-sizing:border-box}
-  body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.4 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif}
-  header{display:flex;align-items:center;gap:20px;padding:14px 22px;border-bottom:1px solid var(--line);flex-wrap:wrap;position:sticky;top:0;background:var(--bg);z-index:5}
-  .wordmark{font-family:Georgia,"Times New Roman",serif;font-size:21px;letter-spacing:.3px}
-  .wordmark .dot{color:var(--gold)}
-  .sub{font-size:11px;color:var(--ink-3);text-transform:uppercase;letter-spacing:1.5px}
+  html,body{height:100%}
+  body{margin:0;background:var(--ground);color:var(--ink);font:14px/1.5 var(--ui);-webkit-font-smoothing:antialiased}
+  .wrap{max-width:1200px;margin:32px auto;background:var(--surface);border:1px solid var(--hairline);border-radius:20px;padding:40px 48px 48px;box-shadow:0 1px 2px rgba(31,27,23,.03)}
+
+  /* ---- header ---- */
+  header{display:flex;align-items:flex-start;gap:20px;padding-bottom:24px;border-bottom:1px solid var(--hairline)}
+  .brand{flex:1;min-width:0}
+  .wordmark{font-family:var(--serif);font-weight:600;font-size:32px;line-height:1.05;color:var(--ink);letter-spacing:.2px}
+  .sub{font-family:var(--ui);font-size:11px;font-weight:600;color:var(--brass);text-transform:uppercase;letter-spacing:4px;margin-top:4px}
   .spacer{flex:1}
-  .ctrl{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--ink-2)}
-  .ctrl input{background:var(--panel2);border:1px solid var(--line);border-radius:6px;color:var(--ink);font:inherit;font-size:12px;padding:5px 8px;width:64px}
-  .ctrl input[type=date]{width:130px}
-  button{background:var(--gold-dim);border:none;color:#1a1a1a;font:inherit;font-size:12px;font-weight:600;padding:6px 12px;border-radius:6px;cursor:pointer}
-  button:hover{background:var(--gold)}
-  button.ghost{background:none;border:1px solid var(--line);color:var(--ink-2)}
-  button.ghost:hover{color:var(--ink);border-color:var(--gold-dim)}
-  button.danger{background:#7a3a3a;color:#f3e6e6}
-  button.danger:hover{background:#9b4848}
-  /* tabs */
-  .tabs{display:flex;gap:4px;padding:0 22px;border-bottom:1px solid var(--line);background:var(--bg);position:sticky;top:53px;z-index:4}
-  .tab{background:none;border:none;border-bottom:2px solid transparent;color:var(--ink-3);font-weight:600;padding:11px 16px;cursor:pointer;font-size:13px;border-radius:0}
-  .tab:hover{color:var(--ink-2);background:none}
-  .tab.active{color:var(--gold);border-bottom-color:var(--gold)}
+  button{font:inherit;cursor:pointer;border:none}
+  .btn-primary{background:var(--plum);color:#fff;font-weight:600;font-size:14px;padding:11px 20px;border-radius:10px;box-shadow:0 1px 2px rgba(94,43,60,.25)}
+  .btn-primary:hover{background:#4d2230}
+  .btn-ghost{background:var(--surface);border:1px solid var(--hairline);color:var(--ink);font-size:13px;font-weight:500;padding:8px 16px;border-radius:999px}
+  .btn-ghost:hover{border-color:var(--faint)}
+  .btn-pill{background:var(--surface);border:1px solid var(--hairline);color:var(--ink);font-size:13px;font-weight:500;padding:8px 18px;border-radius:999px}
+  .btn-pill:hover{border-color:var(--faint)}
+  /* SKU-view inline controls */
+  #skuCtrls{display:none;align-items:center;gap:16px;margin-left:18px}
+  .ctrl{display:flex;flex-direction:column;gap:3px;font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px}
+  .ctrl input{font:inherit;font-size:13px;color:var(--ink);background:var(--surface);border:1px solid var(--hairline);border-radius:8px;padding:7px 9px;width:74px}
+  .ctrl input[type=date]{width:140px}
+  #apply{background:var(--plum);color:#fff;font-weight:600;font-size:13px;padding:9px 16px;border-radius:8px;align-self:flex-end}
+
+  /* ---- tab bar ---- */
+  .tabbar{display:flex;align-items:center;gap:8px;margin:24px 0 4px;padding:5px;background:var(--ground);border:1px solid var(--hairline);border-radius:14px}
+  .tab{background:transparent;color:var(--muted);font-weight:500;font-size:14px;padding:9px 18px;border-radius:10px;transition:all .12s}
+  .tab:hover{color:var(--ink)}
+  .tab.active{background:var(--surface);color:var(--ink);font-weight:600;box-shadow:0 1px 3px rgba(31,27,23,.08)}
+  .conn-pill{margin-left:auto;display:flex;align-items:center;gap:8px;font-size:13px;color:var(--muted);background:var(--surface);border:1px solid var(--hairline);border-radius:999px;padding:7px 16px}
+  .conn-pill .dot{width:8px;height:8px;border-radius:50%;background:var(--green);flex:0 0 auto}
+  .conn-pill.off .dot{background:var(--faint)}
+  .conn-pill code{font-family:var(--ui);font-size:12px;color:var(--ink)}
+  .conn-pill button{background:none;color:var(--muted);font-size:12px;text-decoration:underline;padding:0 0 0 6px}
+  .conn-pill a{text-decoration:none}
+  .conn-pill a button{color:var(--plum);text-decoration:underline}
+
   .view{display:none}
   .view.active{display:block}
-  /* connection banner */
-  .banner{margin:14px 22px;padding:11px 16px;border-radius:9px;border:1px solid var(--gold-dim);background:rgba(194,160,100,.08);color:var(--ink-2);font-size:13px;display:flex;align-items:center;gap:14px;flex-wrap:wrap}
-  .banner.ok{border-color:#3c6d3c;background:rgba(109,179,109,.08)}
-  .banner b{color:var(--ink)}
-  /* SKU view (existing) */
-  .summary{display:flex;gap:22px;padding:10px 22px;border-bottom:1px solid var(--line);font-size:12px;color:var(--ink-2);flex-wrap:wrap}
-  .summary b{color:var(--ink);font-size:15px}
-  .summary .k{display:flex;flex-direction:column;gap:1px}
-  .monthbar{display:flex;align-items:center;justify-content:center;gap:18px;padding:12px;font-size:15px}
-  .monthbar .ml{min-width:200px;text-align:center;font-family:Georgia,serif;font-size:18px}
-  .cal{display:grid;grid-template-columns:repeat(7,1fr);gap:8px;padding:0 22px 30px}
-  .dow{font-size:10px;color:var(--ink-3);text-transform:uppercase;letter-spacing:1px;text-align:center;padding:4px 0}
-  .cell{background:var(--panel);border:1px solid var(--line);border-radius:9px;min-height:150px;max-height:340px;display:flex;flex-direction:column;overflow:hidden}
-  .cell.empty{background:transparent;border:none;min-height:0}
-  .cell.blank{background:#101216;opacity:.5}
-  .cell .dh{display:flex;align-items:center;justify-content:space-between;padding:6px 9px;border-bottom:1px solid var(--line)}
-  .cell .dn{font-size:13px;color:var(--ink)}
-  .cell .cnt{font-size:10px;color:var(--gold);background:var(--panel2);border:1px solid var(--gold-dim);border-radius:10px;padding:1px 7px}
-  .chips{flex:1;overflow-y:auto;padding:7px;display:grid;grid-template-columns:repeat(auto-fill,minmax(58px,1fr));gap:5px;align-content:start}
-  .chip{position:relative;border-radius:6px;overflow:hidden;background:var(--panel2);border:1px solid var(--line);aspect-ratio:1/1;cursor:default}
-  .chip img{width:100%;height:100%;object-fit:cover;display:block;background:#222}
-  .chip .ph{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:700;color:#1a1a1a}
-  .chip .vn{position:absolute;left:0;right:0;bottom:0;font-size:7px;line-height:1.15;background:rgba(0,0,0,.72);color:#e9d9a8;padding:2px 3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;letter-spacing:.2px}
-  .chip .fx{position:absolute;top:2px;left:2px;width:6px;height:6px;border-radius:50%}
-  .chip .fx.s{background:#6db36d}.chip .fx.r{background:#c2a064}
-  .chips::-webkit-scrollbar{width:7px}.chips::-webkit-scrollbar-thumb{background:var(--line);border-radius:4px}
-  .legend{display:flex;gap:10px;flex-wrap:wrap;padding:8px 22px 18px;font-size:11px;color:var(--ink-3)}
+
+  /* ---- not-connected banner ---- */
+  .banner{margin:18px 0 0;padding:13px 18px;border-radius:12px;border:1px solid var(--warn-bd);background:var(--warn-bg);color:var(--warn-fg);font-size:13.5px;display:flex;align-items:center;gap:14px;flex-wrap:wrap}
+  .banner.hidden{display:none}
+  .banner b{color:var(--warn-fg);font-weight:600}
+  .banner a{text-decoration:none}
+
+  /* ---- month toolbar ---- */
+  .monthbar{display:flex;align-items:center;gap:16px;margin:22px 0 14px}
+  .monthbar .nav{background:none;color:var(--muted);font-size:22px;line-height:1;padding:2px 6px;border-radius:8px}
+  .monthbar .nav:hover{color:var(--ink);background:var(--ground)}
+  .monthbar .ml{font-family:var(--serif);font-weight:500;font-size:26px;color:var(--ink);min-width:200px;letter-spacing:.2px}
+  .monthbar .spacer{flex:1}
+
+  /* ---- month card grid ---- */
+  .cal{border:1px solid var(--hairline);border-radius:14px;overflow:hidden;background:var(--surface)}
+  .cal .dow-row{display:grid;grid-template-columns:repeat(7,1fr);border-bottom:1px solid var(--hairline)}
+  .dow{height:40px;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:1.5px}
+  .grid{display:grid;grid-template-columns:repeat(7,1fr)}
+  .cell{min-height:112px;border-right:1px solid var(--hairline);border-bottom:1px solid var(--hairline);padding:8px 9px;display:flex;flex-direction:column;gap:5px;overflow:hidden}
+  .cell:nth-child(7n){border-right:none}
+  .grid .cell:nth-last-child(-n+7){border-bottom:none}
+  .cell.out{background:var(--out-month)}
+  .cell.out .dn{color:var(--faint)}
+  .cell .dn{font-size:13px;font-weight:500;color:var(--ink);width:28px;height:28px;display:flex;align-items:center;justify-content:center}
+  .cell.today .dn{background:var(--plum);color:#fff;border-radius:50%}
+  .cell .cnt{font-size:10px;font-weight:600;color:var(--brass)}
+  .cell .dh{display:flex;align-items:center;justify-content:space-between}
+
+  /* Google event chips */
+  .gcell .evs{display:flex;flex-direction:column;gap:4px;overflow-y:auto}
+  .ev{display:block;width:100%;text-align:left;border:none;border-radius:6px;padding:4px 8px;cursor:pointer;font-size:11.5px;line-height:1.3;overflow:hidden;
+      background:var(--chip-brass-bg);color:var(--chip-brass-fg)}
+  .ev.cat-plum{background:var(--chip-plum-bg);color:var(--chip-plum-fg)}
+  .ev.cat-green{background:var(--chip-green-bg);color:var(--chip-green-fg)}
+  .ev.cat-brass{background:var(--chip-brass-bg);color:var(--chip-brass-fg)}
+  .ev:hover{filter:brightness(.97)}
+  .ev .et{font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
+  .ev .ewhen{opacity:.85;font-size:10.5px}
+  .ev .ecreated{opacity:.65;font-size:9px;margin-top:1px}
+  .addbtn{margin-top:auto;align-self:flex-start;font-size:11px;color:var(--faint);background:none;padding:2px 0;opacity:0;transition:opacity .12s}
+  .gcell:hover .addbtn{opacity:1}
+  .addbtn:hover{color:var(--muted)}
+
+  /* ---- SKU activation view ---- */
+  .summary{display:flex;gap:32px;margin:18px 0 6px;flex-wrap:wrap}
+  .summary .k{display:flex;flex-direction:column;gap:2px}
+  .summary .k span{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.6px}
+  .summary .k b{font-family:var(--serif);font-weight:500;font-size:24px;color:var(--ink)}
+  .note{font-size:12px;color:var(--muted);margin:6px 0 0;line-height:1.5}
+  .note b{color:var(--ink);font-weight:600}
+  .legend{display:flex;gap:12px;flex-wrap:wrap;margin-top:16px;font-size:11px;color:var(--muted)}
   .legend .li{display:flex;align-items:center;gap:5px}
   .legend .sw{width:10px;height:10px;border-radius:3px;display:inline-block}
-  .note{padding:0 22px 10px;font-size:11px;color:var(--ink-3)}
-  .loading{padding:60px;text-align:center;color:var(--ink-3)}
-  /* Google cal events */
-  .gcell .ev{display:block;width:100%;text-align:left;background:var(--panel2);border:1px solid var(--gold-dim);border-radius:5px;color:var(--ink);font-size:10px;line-height:1.3;padding:3px 5px;margin:0 0 4px;cursor:pointer;overflow:hidden}
-  .gcell .ev:hover{border-color:var(--gold);background:#23262e}
-  .gcell .ev .et{font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
-  .gcell .ev .ewhen{color:var(--gold)}
-  .gcell .ev .ecreated{color:var(--ink-3);font-size:8px;margin-top:1px}
-  .gcell .addbtn{margin:4px 0 0;font-size:10px;padding:2px 8px;background:none;border:1px dashed var(--line);color:var(--ink-3)}
-  .gcell .addbtn:hover{border-color:var(--gold-dim);color:var(--ink-2);background:none}
-  /* modal form */
-  .mform label{display:block;font-size:11px;color:var(--ink-3);text-transform:uppercase;letter-spacing:.5px;margin:12px 0 4px}
+  .loading{padding:60px;text-align:center;color:var(--muted)}
+
+  /* SKU chips (image tiles) */
+  .scell .chips{display:grid;grid-template-columns:repeat(auto-fill,minmax(52px,1fr));gap:5px;overflow-y:auto;align-content:start}
+  .chip{position:relative;border-radius:6px;overflow:hidden;background:var(--ground);border:1px solid var(--hairline);aspect-ratio:1/1}
+  .chip img{width:100%;height:100%;object-fit:cover;display:block}
+  .chip .ph{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:700;color:#fff}
+  .chip .vn{position:absolute;left:0;right:0;bottom:0;font-size:7px;line-height:1.15;background:rgba(31,27,23,.72);color:#f3ead7;padding:2px 3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;letter-spacing:.2px}
+  .chip .fx{position:absolute;top:2px;left:2px;width:6px;height:6px;border-radius:50%}
+  .chip .fx.s{background:var(--green)}.chip .fx.r{background:var(--brass)}
+  .chips::-webkit-scrollbar,.evs::-webkit-scrollbar{width:7px}
+  .chips::-webkit-scrollbar-thumb,.evs::-webkit-scrollbar-thumb{background:var(--hairline);border-radius:4px}
+
+  /* ===== modal (rig-built panel restyled to the luxe Figma look) ===== */
+  /* The rig sets inline cssText on backdrop/panel/head/body; override with high
+     specificity + !important so drag/resize/close wiring stays intact. */
+  [id^="modalrig-bd-"]{background:rgba(31,27,23,.28)!important;backdrop-filter:blur(1px)}
+  [id^="modalrig-bd-"] > div{           /* the panel */
+    background:var(--surface)!important;border:1px solid var(--hairline)!important;
+    border-radius:16px!important;box-shadow:0 24px 64px rgba(31,27,23,.22)!important;overflow:hidden!important}
+  [id^="modalrig-bd-"] > div > div:first-child{  /* the rig head → grab-handle bar */
+    background:var(--surface)!important;border-bottom:none!important;border-radius:16px 16px 0 0!important;
+    justify-content:center!important;padding:10px 0 0!important;min-height:18px}
+  [id^="modalrig-bd-"] > div > div:first-child span:first-child{display:none!important} /* hide rig title */
+  [id^="modalrig-bd-"] > div > div:first-child::before{
+    content:"";width:40px;height:4px;border-radius:2px;background:var(--hairline);display:block}
+  [id^="modalrig-bd-"] [data-rig-btn]{display:none!important} /* hide rig × + maximize; we add our own close */
+  [id^="modalrig-bd-"] > div > div:nth-child(2){padding:8px 28px 24px!important} /* body */
+
+  .mform{font-family:var(--ui)}
+  .mform .mhead{display:flex;align-items:center;justify-content:space-between;margin:4px 0 18px}
+  .mform .mtitle{font-family:var(--serif);font-weight:600;font-size:24px;color:var(--ink)}
+  .mform .mclose{width:30px;height:30px;border-radius:50%;background:var(--ground);color:var(--muted);font-size:16px;line-height:1;display:flex;align-items:center;justify-content:center;cursor:pointer;border:none}
+  .mform .mclose:hover{background:var(--hairline);color:var(--ink)}
+  .mform label{display:block;font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.7px;font-weight:600;margin:14px 0 5px}
   .mform input[type=text],.mform textarea,.mform input[type=datetime-local],.mform input[type=date]{
-    width:100%;background:var(--panel2);border:1px solid var(--line);border-radius:6px;color:var(--ink);font:inherit;font-size:13px;padding:8px 10px}
-  .mform textarea{min-height:64px;resize:vertical}
-  .mform .row{display:flex;gap:12px}.mform .row>div{flex:1}
-  .mform .chk{display:flex;align-items:center;gap:8px;margin-top:12px;font-size:13px;color:var(--ink-2)}
-  .mform .actions{display:flex;justify-content:space-between;align-items:center;margin-top:18px;gap:10px}
-  .mform .msg{font-size:12px;min-height:16px}
-  .mform .msg.err{color:#e08a8a}.mform .msg.ok{color:#8fd08f}
+    width:100%;font:inherit;font-size:14px;color:var(--ink);background:var(--surface);border:1px solid var(--hairline);border-radius:8px;padding:10px 12px}
+  .mform input:focus,.mform textarea:focus{outline:none;border-color:var(--brass)}
+  .mform textarea{min-height:86px;resize:vertical;line-height:1.45}
+  .mform .row{display:flex;gap:16px}.mform .row>div{flex:1}
+  /* all-day pill toggle */
+  .mform .chk{display:flex;align-items:center;justify-content:space-between;margin-top:16px;font-size:14px;font-weight:500;color:var(--ink)}
+  .mform .chk input{position:absolute;opacity:0;pointer-events:none}
+  .mform .toggle{width:44px;height:24px;border-radius:999px;background:var(--hairline);position:relative;transition:background .15s;cursor:pointer;flex:0 0 auto}
+  .mform .toggle::after{content:"";position:absolute;top:2px;left:2px;width:20px;height:20px;border-radius:50%;background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.2);transition:transform .15s}
+  .mform .chk input:checked + .toggle{background:var(--plum)}
+  .mform .chk input:checked + .toggle::after{transform:translateX(20px)}
+  /* color picker dots */
+  .mform .colorpick{display:flex;align-items:center;justify-content:space-between;margin-top:18px}
+  .mform .colorpick .lab{font-size:14px;font-weight:500;color:var(--ink)}
+  .mform .dots{display:flex;gap:10px}
+  .mform .dot{width:22px;height:22px;border-radius:50%;cursor:pointer;border:2px solid transparent;box-shadow:0 0 0 1px rgba(31,27,23,.06) inset}
+  .mform .dot.sel{box-shadow:0 0 0 2px var(--surface) inset,0 0 0 4px currentColor}
+  /* footer */
+  .mform .actions{display:flex;justify-content:space-between;align-items:center;margin-top:24px;padding-top:20px;border-top:1px solid var(--hairline)}
+  .mform .actions .right{display:flex;gap:10px}
+  .mform .btn-del{background:none;color:var(--red);font-weight:600;font-size:14px;padding:9px 4px}
+  .mform .btn-del:hover{text-decoration:underline}
+  .mform .btn-cancel{background:var(--surface);border:1px solid var(--hairline);color:var(--ink);font-weight:500;font-size:14px;padding:9px 20px;border-radius:10px}
+  .mform .btn-cancel:hover{border-color:var(--faint)}
+  .mform .btn-save{background:var(--plum);color:#fff;font-weight:600;font-size:14px;padding:9px 24px;border-radius:10px}
+  .mform .btn-save:hover{background:#4d2230}
+  .mform .msg{font-size:12.5px;min-height:16px;margin-top:10px}
+  .mform .msg.err{color:var(--red)}.mform .msg.ok{color:var(--green)}
 </style>
 </head>
 <body>
+<div class="wrap">
 <header>
-  <div>
-    <div class="sub">Designer Wallcoverings</div>
-    <div class="wordmark">DW Calendar<span class="dot">.</span></div>
+  <div class="brand">
+    <div class="wordmark">Designer Wallcoverings</div>
+    <div class="sub">Calendar</div>
   </div>
   <div class="spacer"></div>
   <!-- SKU-view controls (only shown on the SKU tab) -->
-  <div id="skuCtrls" style="display:none;gap:18px">
+  <div id="skuCtrls">
     <div class="ctrl"><label>Per day</label><input id="perDay" type="number" value="500" min="1"></div>
     <div class="ctrl"><label>Per vendor/day</label><input id="perVendorDay" type="number" value="500" min="1"></div>
     <div class="ctrl"><label>Start</label><input id="start" type="date"></div>
     <button id="apply">Apply</button>
   </div>
+  <button class="btn-primary" id="gnew">+ New Event</button>
 </header>
 
-<div class="tabs">
+<div class="tabbar">
   <button class="tab" id="tab-gcal" data-view="gcal">Google Calendar</button>
   <button class="tab" id="tab-sku" data-view="sku">SKU Activation</button>
+  <span class="conn-pill off" id="connPill"><span class="dot"></span><span id="connTxt">Checking…</span></span>
 </div>
 
 <!-- ====================== GOOGLE CALENDAR VIEW ====================== -->
 <div class="view" id="view-gcal">
-  <div class="banner" id="gbanner">Checking calendar connection…</div>
+  <div class="banner hidden" id="gbanner"></div>
   <div class="monthbar">
-    <button class="ghost" id="gprev">‹ Prev</button>
+    <button class="nav" id="gprev" aria-label="Previous month">‹</button>
     <div class="ml" id="gmlabel">—</div>
-    <button class="ghost" id="gnext">Next ›</button>
-    <button id="gnew" style="margin-left:18px">+ New Event</button>
+    <button class="nav" id="gnext" aria-label="Next month">›</button>
+    <div class="spacer"></div>
+    <button class="btn-pill" id="gtoday">Today</button>
   </div>
   <div id="gcal"><div class="loading">Loading events…</div></div>
-  <div class="note">Live, editable view of the shared DW Google Calendar. Click any day or an event to create / edit. Events are written straight to Google Calendar (events.insert / patch / delete).</div>
 </div>
 
-<!-- ====================== SKU ACTIVATION VIEW (existing) ====================== -->
+<!-- ====================== SKU ACTIVATION VIEW ====================== -->
 <div class="view" id="view-sku">
   <div class="summary" id="summary"></div>
-  <div class="note">Projected go-active schedule for every SKU staged for activation — published at <b>500 variants/day</b> (the 5-field backlog half of the 1,000/day cap), round-robin breadth-first across all vendors, in date order, until the list is cleared. Each chip is a SKU: product image + vendor. <span style="color:#6db36d">●</span> sample-add · <span style="color:#c2a064">●</span> roll/price build.</div>
+  <div class="note">Projected go-active schedule for every SKU staged for activation — published at <b>500 variants/day</b> (the 5-field backlog half of the 1,000/day cap), round-robin breadth-first across all vendors, in date order, until the list is cleared. Each chip is a SKU: product image + vendor. <span style="color:var(--green)">●</span> sample-add · <span style="color:var(--brass)">●</span> roll/price build.</div>
   <div class="monthbar">
-    <button class="ghost" id="prev">‹ Prev</button>
+    <button class="nav" id="prev" aria-label="Previous month">‹</button>
     <div class="ml" id="mlabel">—</div>
-    <button class="ghost" id="next">Next ›</button>
+    <button class="nav" id="next" aria-label="Next month">›</button>
   </div>
   <div id="cal"><div class="loading">Loading schedule…</div></div>
   <div class="legend" id="legend"></div>
 </div>
+</div><!-- /wrap -->
 
 <script src="/modal-rig.js"></script>
 <script>
@@ -139,6 +242,7 @@ function setTab(v){
   document.getElementById('view-gcal').classList.toggle('active',v==='gcal');
   document.getElementById('view-sku').classList.toggle('active',v==='sku');
   document.getElementById('skuCtrls').style.display = v==='sku' ? 'flex':'none';
+  document.getElementById('gnew').style.display = v==='gcal' ? '' : 'none';
   localStorage.setItem('dwcal_tab',v);
   if(v==='gcal' && !GDATA) loadGcal();
   if(v==='sku' && !DATA) load();
@@ -158,22 +262,34 @@ function fmtWhen(ev){
   try{return new Date(ev.start).toLocaleTimeString(undefined,{hour:'numeric',minute:'2-digit'});}
   catch(e){return '';}
 }
+/* category tint: map a Google colorId / a guessed bucket to a chip tint class */
+function evCat(ev){
+  const c=String(ev.colorId||'');
+  // Google green-ish ids → green, red/plum-ish → plum, else brass (default)
+  if(['2','10','5'].includes(c)) return 'cat-green';
+  if(['3','4','11','6'].includes(c)) return 'cat-plum';
+  return 'cat-brass';
+}
 async function refreshStatus(){
   try{ GSTATUS = await (await fetch('/api/calendar/status')).json(); }
   catch(e){ GSTATUS={connected:false,oauth_configured:false,error:e.message}; }
+  const pill=document.getElementById('connPill'), txt=document.getElementById('connTxt');
   const b=document.getElementById('gbanner');
   if(GSTATUS.connected){
-    b.className='banner ok';
-    b.innerHTML='<b>● Connected</b> — editing calendar <code>'+(GSTATUS.calendar_id||'primary')+'</code>. '+
-      '<button class="ghost" id="gdisc">Disconnect</button>';
+    pill.className='conn-pill';
+    txt.innerHTML='Connected · <code>'+(GSTATUS.calendar_id||'info@designerwallcoverings.com')+'</code> '+
+      '<button id="gdisc">Disconnect</button>';
     document.getElementById('gdisc').onclick=async()=>{await fetch('/api/oauth/revoke',{method:'POST'});refreshStatus();loadGcal();};
+    b.className='banner hidden'; b.innerHTML='';
   }else{
+    pill.className='conn-pill off';
+    txt.textContent='Not connected';
     b.className='banner';
     b.innerHTML='<b>Calendar not connected — Steve must authorize.</b> '+
       (GSTATUS.oauth_configured
         ? 'OAuth is configured — click to grant access. '
         : 'The Google OAuth credential has not been added yet (queued in pending-approval). ')+
-      '<a href="/api/oauth/start"><button>Connect</button></a>';
+      '<a href="/api/oauth/start"><button class="btn-primary">Connect</button></a>';
   }
 }
 async function loadGcal(){
@@ -194,17 +310,24 @@ function renderGcal(){
   (GDATA.events||[]).forEach(ev=>{ const d=(ev.start||'').slice(0,10); (byDate[d]=byDate[d]||[]).push(ev); });
   const y=gMonth.getFullYear(), m=gMonth.getMonth();
   const cal=document.getElementById('gcal'); cal.className='cal'; cal.innerHTML='';
-  ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'].forEach(d=>{const h=document.createElement('div');h.className='dow';h.textContent=d;cal.appendChild(h);});
+  const dowRow=document.createElement('div'); dowRow.className='dow-row';
+  ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'].forEach(d=>{const h=document.createElement('div');h.className='dow';h.textContent=d;dowRow.appendChild(h);});
+  cal.appendChild(dowRow);
+  const grid=document.createElement('div'); grid.className='grid'; cal.appendChild(grid);
+
   const first=new Date(y,m,1), startDow=first.getDay(), days=new Date(y,m+1,0).getDate();
-  for(let i=0;i<startDow;i++){const c=document.createElement('div');c.className='cell empty';cal.appendChild(c);}
+  const prevDays=new Date(y,m,0).getDate();
+  const todayISO=new Date().toISOString().slice(0,10);
+  // leading out-of-month days
+  for(let i=startDow-1;i>=0;i--){ grid.appendChild(outCell(prevDays-i)); }
   for(let dn=1;dn<=days;dn++){
     const iso=`${y}-${String(m+1).padStart(2,'0')}-${String(dn).padStart(2,'0')}`;
-    const cell=document.createElement('div'); cell.className='cell gcell';
+    const cell=document.createElement('div'); cell.className='cell gcell'+(iso===todayISO?' today':'');
     const evs=byDate[iso]||[];
     cell.innerHTML=`<div class="dh"><span class="dn">${dn}</span>${evs.length?`<span class="cnt">${evs.length}</span>`:''}</div>`;
-    const body=document.createElement('div'); body.className='chips'; body.style.gridTemplateColumns='1fr'; body.style.display='block';
+    const body=document.createElement('div'); body.className='evs';
     evs.forEach(ev=>{
-      const b=document.createElement('button'); b.className='ev';
+      const b=document.createElement('button'); b.className='ev '+evCat(ev);
       b.innerHTML=`<div class="et">${esc(ev.summary)}</div>`+
         `<div class="ewhen">${fmtWhen(ev)}</div>`+
         `<div class="ecreated" title="${ev.created||''}">🕓 ${fmtCreated(ev.created)}</div>`;
@@ -215,8 +338,16 @@ function renderGcal(){
     add.onclick=(e)=>{e.stopPropagation();openEventModal(null,iso);};
     body.appendChild(add);
     cell.appendChild(body);
-    cal.appendChild(cell);
+    grid.appendChild(cell);
   }
+  // trailing out-of-month days to fill the last row
+  const totalCells=startDow+days; const trail=(7-(totalCells%7))%7;
+  for(let dn=1;dn<=trail;dn++){ grid.appendChild(outCell(dn)); }
+}
+function outCell(dn){
+  const c=document.createElement('div'); c.className='cell out';
+  c.innerHTML=`<div class="dh"><span class="dn">${dn}</span></div>`;
+  return c;
 }
 function esc(s){return String(s==null?'':s).replace(/[&<>"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));}
 
@@ -227,32 +358,44 @@ function toLocalInput(iso){ // ISO -> value for datetime-local (local tz)
   const p=n=>String(n).padStart(2,'0');
   return `${d.getFullYear()}-${p(d.getMonth()+1)}-${p(d.getDate())}T${p(d.getHours())}:${p(d.getMinutes())}`;
 }
+const CAL_DOTS=[{id:'plum',c:'#5E2B3C'},{id:'brass',c:'#B0894F'},{id:'green',c:'#3E7C5A'},{id:'ink',c:'#1F1B17'}];
 function openEventModal(ev, dayISO){
   const isEdit=!!ev;
   const startVal = ev ? (ev.allDay ? '' : toLocalInput(ev.start)) : (dayISO+'T09:00');
   const endVal   = ev ? (ev.allDay ? '' : toLocalInput(ev.end))   : (dayISO+'T10:00');
   const allDay = ev ? !!ev.allDay : false;
+  const dots=CAL_DOTS.map((d,i)=>`<span class="dot${i===0?' sel':''}" data-color="${d.id}" style="background:${d.c};color:${d.c}"></span>`).join('');
   const bodyHtml=`<form class="mform" id="evForm" onsubmit="return false">
+    <div class="mhead">
+      <div class="mtitle">${isEdit?'Edit Event':'New Event'}</div>
+      <button type="button" class="mclose" id="f_x" aria-label="Close">✕</button>
+    </div>
     <label>Title *</label><input type="text" id="f_summary" value="${esc(ev?ev.summary:'')}" placeholder="Event title" required>
-    <label>Description</label><textarea id="f_desc" placeholder="Notes">${esc(ev?ev.description:'')}</textarea>
-    <label>Location</label><input type="text" id="f_loc" value="${esc(ev?ev.location:'')}" placeholder="Optional">
-    <label class="chk"><input type="checkbox" id="f_allday" ${allDay?'checked':''}> All-day event</label>
     <div class="row" id="timedRow" style="${allDay?'display:none':''}">
-      <div><label>Start</label><input type="datetime-local" id="f_start" value="${startVal}"></div>
-      <div><label>End</label><input type="datetime-local" id="f_end" value="${endVal}"></div>
+      <div><label>Starts</label><input type="datetime-local" id="f_start" value="${startVal}"></div>
+      <div><label>Ends</label><input type="datetime-local" id="f_end" value="${endVal}"></div>
     </div>
     <div class="row" id="dayRow" style="${allDay?'':'display:none'}">
       <div><label>Date</label><input type="date" id="f_date" value="${(ev?ev.start:dayISO||'').slice(0,10)}"></div>
     </div>
+    <label class="chk">All-day event<input type="checkbox" id="f_allday" ${allDay?'checked':''}><span class="toggle"></span></label>
+    <label>Location</label><input type="text" id="f_loc" value="${esc(ev?ev.location:'')}" placeholder="Optional">
+    <label>Description</label><textarea id="f_desc" placeholder="Notes">${esc(ev?ev.description:'')}</textarea>
+    <div class="colorpick"><span class="lab">Calendar color</span><div class="dots" id="f_dots">${dots}</div></div>
     <div class="msg" id="f_msg"></div>
     <div class="actions">
-      <div>${isEdit?'<button type="button" class="danger" id="f_del">Delete</button>':''}</div>
-      <div><button type="button" class="ghost" id="f_cancel">Cancel</button> <button type="button" id="f_save">${isEdit?'Save':'Create'}</button></div>
+      <div>${isEdit?'<button type="button" class="btn-del" id="f_del">Delete</button>':'<span></span>'}</div>
+      <div class="right"><button type="button" class="btn-cancel" id="f_cancel">Cancel</button> <button type="button" class="btn-save" id="f_save">${isEdit?'Save':'Create'}</button></div>
     </div>
   </form>`;
-  const M=ModalRig.open({key:'dwcal-event',title:isEdit?'Edit Event':'New Event',bodyHtml,width:440,height:520,bg:'var(--card)'});
+  const M=ModalRig.open({key:'dwcal-event',title:isEdit?'Edit Event':'New Event',bodyHtml,width:460,height:660,bg:'var(--surface)'});
   const $=id=>M.body.querySelector('#'+id);
+  $('f_x').onclick=M.close;
+  // wire the all-day toggle (the visible toggle is the styled label; the checkbox drives it)
   $('f_allday').onchange=()=>{const on=$('f_allday').checked;$('timedRow').style.display=on?'none':'';$('dayRow').style.display=on?'':'none';};
+  // color dot selection (cosmetic ring)
+  M.body.querySelectorAll('#f_dots .dot').forEach(d=>d.onclick=()=>{
+    M.body.querySelectorAll('#f_dots .dot').forEach(x=>x.classList.remove('sel'));d.classList.add('sel');});
   $('f_cancel').onclick=M.close;
   if(isEdit) $('f_del').onclick=async()=>{
     if(!confirm('Delete this event?')) return;
@@ -283,15 +426,16 @@ function showMsg($,t,err){const m=$('f_msg');m.textContent=t;m.className='msg '+
 
 document.getElementById('gprev').onclick=()=>{gMonth=new Date(gMonth.getFullYear(),gMonth.getMonth()-1,1);loadGcal();};
 document.getElementById('gnext').onclick=()=>{gMonth=new Date(gMonth.getFullYear(),gMonth.getMonth()+1,1);loadGcal();};
+document.getElementById('gtoday').onclick=()=>{const n=new Date();gMonth=new Date(n.getFullYear(),n.getMonth(),1);loadGcal();};
 document.getElementById('gnew').onclick=()=>{const n=new Date();openEventModal(null,n.toISOString().slice(0,10));};
 
-/* ======================= SKU ACTIVATION (existing, unchanged logic) ======================= */
+/* ======================= SKU ACTIVATION (existing logic) ======================= */
 let DATA=null, viewMonth=null;
 const VCOLORS={};
 function vcolor(v){
   if(VCOLORS[v]) return VCOLORS[v];
   let h=0; for(const c of v) h=(h*31+c.charCodeAt(0))%360;
-  return VCOLORS[v]=`hsl(${h},42%,58%)`;
+  return VCOLORS[v]=`hsl(${h},38%,52%)`;
 }
 async function load(){
   const perDay=document.getElementById('perDay').value||500;
@@ -312,26 +456,31 @@ function renderSummary(){
     `<div class="k"><span>Vendors</span><b>${Object.keys(s.vendorTotals).length}</b></div>`+
     `<div class="k"><span>Days to clear</span><b>${s.totalDays}</b></div>`+
     `<div class="k"><span>Finishes</span><b>${s.endDate||'—'}</b></div>`+
-    `<div class="k"><span>Cadence</span><b>${s.perDay}/day · ${s.perVendorDay}/vendor</b></div>`;
+    `<div class="k"><span>Cadence</span><b>${s.perDay}/day</b></div>`;
 }
 function renderLegend(){
   const vt=DATA.vendorTotals;
   const top=Object.entries(vt).sort((a,b)=>b[1]-a[1]);
   document.getElementById('legend').innerHTML=top.map(([v,n])=>
-    `<span class="li"><span class="sw" style="background:${vcolor(v)}"></span>${v} <span style="color:var(--ink-2)">${n.toLocaleString()}</span></span>`).join('');
+    `<span class="li"><span class="sw" style="background:${vcolor(v)}"></span>${v} <span style="color:var(--ink)">${n.toLocaleString()}</span></span>`).join('');
 }
 function renderMonth(){
   const byDate={}; DATA.days.forEach(d=>byDate[d.date]=d);
   const y=viewMonth.getFullYear(), m=viewMonth.getMonth();
   document.getElementById('mlabel').textContent=fmtMonth(viewMonth);
   const cal=document.getElementById('cal'); cal.className='cal'; cal.innerHTML='';
-  ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'].forEach(d=>{const h=document.createElement('div');h.className='dow';h.textContent=d;cal.appendChild(h);});
+  const dowRow=document.createElement('div'); dowRow.className='dow-row';
+  ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'].forEach(d=>{const h=document.createElement('div');h.className='dow';h.textContent=d;dowRow.appendChild(h);});
+  cal.appendChild(dowRow);
+  const grid=document.createElement('div'); grid.className='grid'; cal.appendChild(grid);
   const first=new Date(y,m,1), startDow=first.getDay(), days=new Date(y,m+1,0).getDate();
-  for(let i=0;i<startDow;i++){const c=document.createElement('div');c.className='cell empty';cal.appendChild(c);}
+  const prevDays=new Date(y,m,0).getDate();
+  const todayISO=new Date().toISOString().slice(0,10);
+  for(let i=startDow-1;i>=0;i--){ grid.appendChild(outCell(prevDays-i)); }
   for(let dn=1;dn<=days;dn++){
     const iso=`${y}-${String(m+1).padStart(2,'0')}-${String(dn).padStart(2,'0')}`;
     const day=byDate[iso];
-    const cell=document.createElement('div'); cell.className='cell'+(day?'':' blank');
+    const cell=document.createElement('div'); cell.className='cell scell'+(iso===todayISO?' today':'');
     cell.innerHTML=`<div class="dh"><span class="dn">${dn}</span>${day?`<span class="cnt">${day.count}</span>`:''}</div>`;
     if(day){
       const chips=document.createElement('div'); chips.className='chips';
@@ -348,15 +497,16 @@ function renderMonth(){
       });
       cell.appendChild(chips);
     }
-    cal.appendChild(cell);
+    grid.appendChild(cell);
   }
+  const totalCells=startDow+days; const trail=(7-(totalCells%7))%7;
+  for(let dn=1;dn<=trail;dn++){ grid.appendChild(outCell(dn)); }
 }
 document.getElementById('prev').onclick=()=>{viewMonth=new Date(viewMonth.getFullYear(),viewMonth.getMonth()-1,1);renderMonth();};
 document.getElementById('next').onclick=()=>{viewMonth=new Date(viewMonth.getFullYear(),viewMonth.getMonth()+1,1);renderMonth();};
 document.getElementById('apply').onclick=load;
 
 /* ======================= boot ======================= */
-// if landing via OAuth callback (?connected=1), open the gcal tab
 const initialTab = (new URLSearchParams(location.search).has('connected')) ? 'gcal'
   : (localStorage.getItem('dwcal_tab') || 'gcal');
 setTab(initialTab);

← c3c31ac Document Google Calendar layer: README + updated server head  ·  back to Dw Activation Calendar  ·  Default calendar to populated SKU Activation tab on first lo b585adf →