vibedate 0.8.1 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +382 -26
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -626,6 +626,187 @@ var webAppHtml = `<!DOCTYPE html>
626
626
  }
627
627
  .chat-panel .cp-send:hover{ filter: brightness(1.06); }
628
628
  .chat-panel .cp-send:active{ transform: scale(.95); }
629
+
630
+ /* ---- you identity chip (header) ---- */
631
+ .you-chip{
632
+ display:none; align-items:center; gap:10px;
633
+ padding: 6px 12px 6px 7px; border-radius: 999px;
634
+ background: rgba(0,0,0,.28); border: 1px solid var(--border-2);
635
+ max-width: min(420px, 52vw); min-width: 0;
636
+ }
637
+ .you-chip.is-on{ display:inline-flex; }
638
+ .you-chip .yc-avatar{
639
+ width:30px; height:30px; border-radius:50%; overflow:hidden; flex-shrink:0;
640
+ border: 1px solid var(--border-2); background: var(--bg-1);
641
+ }
642
+ .you-chip .yc-avatar svg{ width:100%; height:100%; display:block; }
643
+ .you-chip .yc-meta{ min-width:0; display:flex; flex-direction:column; gap:1px; }
644
+ .you-chip .yc-handle{
645
+ font-size:.78rem; font-weight:700; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
646
+ }
647
+ .you-chip .yc-sub{
648
+ font-size:.68rem; color: var(--muted-2); display:flex; align-items:center; gap:6px; flex-wrap:wrap;
649
+ }
650
+ .topbar-right{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; justify-content:flex-end; }
651
+
652
+ /* ---- identity / status marks ---- */
653
+ .mark{
654
+ display:inline-flex; align-items:center; gap:3px;
655
+ font-size:.66rem; font-weight:700; letter-spacing:.01em;
656
+ padding: 2px 7px; border-radius:999px; line-height:1.3; white-space:nowrap;
657
+ }
658
+ .mark-verified{
659
+ color: #bff2df; background: rgba(127,227,192,.12); border: 1px solid rgba(127,227,192,.32);
660
+ }
661
+ .mark-identity{
662
+ color: #e8d9ff; background: rgba(203,168,255,.12); border: 1px solid rgba(203,168,255,.32);
663
+ }
664
+ .mark-demo{
665
+ color: var(--muted-2); background: rgba(248,239,232,.05); border: 1px solid var(--border);
666
+ text-transform: uppercase; letter-spacing: .06em;
667
+ }
668
+ .mark-live{
669
+ color: #ffd0c8; background: rgba(255,122,104,.12); border: 1px solid rgba(255,122,104,.35);
670
+ }
671
+ .lg-pill{
672
+ display:inline-flex; align-items:center; gap:5px;
673
+ font-size:.7rem; font-weight:700; padding: 3px 9px; border-radius:999px;
674
+ }
675
+
676
+ /* ---- people column (live + demo) ---- */
677
+ .people-col{ display:flex; flex-direction:column; gap: 22px; min-width: 0; }
678
+ .section-head{
679
+ display:flex; align-items:baseline; justify-content:space-between; gap:10px;
680
+ margin: 0 0 12px;
681
+ }
682
+ .section-head h2{
683
+ font-size: 1.02rem; font-weight: 700; margin:0;
684
+ display:flex; align-items:center; gap:8px;
685
+ }
686
+ .section-head .count{
687
+ font-size:.72rem; font-weight:700; color: var(--muted-2);
688
+ background: rgba(0,0,0,.22); border: 1px solid var(--border);
689
+ padding: 3px 8px; border-radius:999px;
690
+ }
691
+ .section-sub{
692
+ font-size:.78rem; color: var(--muted-2); margin: -6px 0 12px; line-height:1.45;
693
+ }
694
+
695
+ /* Live now roster cards */
696
+ .live-roster{ display:flex; flex-direction:column; gap:10px; }
697
+ .live-card{
698
+ display:flex; align-items:center; gap:12px;
699
+ background: linear-gradient(165deg, rgba(255,122,104,.08), transparent 55%),
700
+ linear-gradient(180deg, var(--bg-card), var(--bg-card-2));
701
+ border: 1px solid rgba(255,122,104,.22);
702
+ border-radius: 16px; padding: 14px 14px;
703
+ box-shadow: var(--shadow-1);
704
+ transition: border-color var(--dur-fast) ease, transform var(--dur-fast) var(--ease-out);
705
+ }
706
+ .live-card:hover{ border-color: rgba(255,122,104,.4); transform: translateY(-1px); }
707
+ .live-card .lc-avatar{
708
+ width:48px; height:48px; border-radius:50%; overflow:hidden; flex-shrink:0;
709
+ border:1px solid var(--border-2); background:var(--bg-1);
710
+ }
711
+ .live-card .lc-avatar svg{ width:100%; height:100%; display:block; }
712
+ .live-card .lc-body{ flex:1; min-width:0; }
713
+ .live-card .lc-handle-row{
714
+ display:flex; align-items:center; gap:6px; flex-wrap:wrap; margin-bottom:4px;
715
+ }
716
+ .live-card .lc-handle{ font-weight:700; font-size:.92rem; word-break:break-all; }
717
+ .live-card .lc-meta{
718
+ display:flex; align-items:center; gap:6px; flex-wrap:wrap;
719
+ font-size:.72rem; color: var(--muted-2);
720
+ }
721
+ .live-card .lc-actions{ display:flex; flex-direction:column; gap:6px; flex-shrink:0; }
722
+ .live-card .lc-actions button{
723
+ border:0; border-radius:10px; padding:8px 14px; font-weight:700; font-size:.76rem;
724
+ min-width: 72px;
725
+ transition: transform var(--dur-fast) var(--ease-out), filter var(--dur-fast) ease, border-color var(--dur-fast) ease, color var(--dur-fast) ease;
726
+ }
727
+ .live-card .lc-actions button:active{ transform: scale(.96); }
728
+ .live-card .btn-call{
729
+ background: linear-gradient(180deg, var(--coral), var(--coral-dim)); color: #2a1109;
730
+ box-shadow: 0 8px 18px -10px rgba(255,122,104,.55);
731
+ }
732
+ .live-card .btn-call:hover{ filter: brightness(1.06); }
733
+ .live-card .btn-call:disabled{ opacity:.4; cursor:not-allowed; filter:none; box-shadow:none; }
734
+ .live-card .btn-chat{
735
+ background: transparent; color: var(--muted); border: 1px solid var(--border-2);
736
+ }
737
+ .live-card .btn-chat:hover{ color: var(--fg); border-color: var(--muted-2); }
738
+ .live-card .btn-chat.has-unread{
739
+ color: var(--coral); border-color: rgba(255,122,104,.45);
740
+ background: rgba(255,122,104,.08);
741
+ }
742
+
743
+ .live-empty{
744
+ border: 1px dashed var(--border-2); border-radius: 16px;
745
+ padding: 18px 16px; text-align:center;
746
+ background: rgba(0,0,0,.14);
747
+ }
748
+ .live-empty strong{ display:block; font-size:.9rem; margin-bottom:4px; }
749
+ .live-empty p{ margin:0; font-size:.8rem; color: var(--muted-2); line-height:1.45; }
750
+
751
+ .live-now-wrap.has-live .section-head h2 .pulse-live{
752
+ width:8px; height:8px; border-radius:50%; background: var(--coral);
753
+ box-shadow: 0 0 0 3px rgba(255,122,104,.2);
754
+ animation: pulse-dot 2s ease-in-out infinite; display:inline-block;
755
+ }
756
+
757
+ /* Demo badge on match cards */
758
+ .mc-demo-badge{
759
+ position:absolute; top:14px; right:14px; z-index:2;
760
+ }
761
+ .match-card{ position:absolute; /* ensure badge anchors */ }
762
+ .mc-handle-row{ display:flex; align-items:center; gap:7px; flex-wrap:wrap; }
763
+ .demo-note{
764
+ font-size:.74rem; color: var(--muted-2); margin: 0 0 10px; line-height:1.4;
765
+ }
766
+ .demo-note b{ color: var(--muted); font-weight:600; }
767
+
768
+ /* Stack polish when demo secondary */
769
+ .stack-wrap.is-secondary{ opacity: .92; }
770
+ .people-col .stack-wrap{ height: 430px; }
771
+
772
+ /* Video / room / chat polish */
773
+ .video-modal .vtile video{
774
+ box-shadow: 0 24px 60px -28px rgba(0,0,0,.8), 0 0 0 1px rgba(255,122,104,.12);
775
+ }
776
+ .video-modal .vlabel{
777
+ background: rgba(0,0,0,.35); padding: 4px 10px; border-radius:999px;
778
+ border: 1px solid var(--border);
779
+ }
780
+ .room-video .rv-tile video{
781
+ box-shadow: 0 14px 36px -20px rgba(0,0,0,.7);
782
+ }
783
+ .room-video .rv-head{
784
+ background: rgba(0,0,0,.28); border: 1px solid var(--border);
785
+ border-radius: 12px; padding: 10px 14px;
786
+ }
787
+ .chat-panel{
788
+ border-color: rgba(255,122,104,.18);
789
+ }
790
+ .chat-panel .cp-head{
791
+ background: rgba(0,0,0,.18);
792
+ }
793
+ .live-panel, .room-panel{
794
+ border-color: rgba(255,122,104,.18);
795
+ }
796
+ .live-panel .lp-title .lp-live-count,
797
+ .room-panel .rp-title .rp-count{
798
+ margin-left:auto; font-size:.68rem; font-weight:700; color: var(--muted-2);
799
+ }
800
+
801
+ @media (max-width: 1020px){
802
+ .you-chip{ max-width: 100%; }
803
+ }
804
+ @media (max-width: 480px){
805
+ .live-card{ flex-wrap: wrap; }
806
+ .live-card .lc-actions{ flex-direction: row; width: 100%; }
807
+ .live-card .lc-actions button{ flex:1; }
808
+ }
809
+
629
810
  </style>
630
811
  </head>
631
812
  <body>
@@ -635,7 +816,16 @@ var webAppHtml = `<!DOCTYPE html>
635
816
  <span class="name">vibe<span>dating</span></span>
636
817
  <span class="tag">matched by how hard you push the model</span>
637
818
  </div>
638
- <div class="local-badge"><span class="dot" aria-hidden="true"></span> raw usage stays local &middot; only league shared</div>
819
+ <div class="topbar-right">
820
+ <div class="you-chip" id="youChip" aria-live="polite">
821
+ <div class="yc-avatar" id="youChipAvatar"></div>
822
+ <div class="yc-meta">
823
+ <div class="yc-handle" id="youChipHandle">@you</div>
824
+ <div class="yc-sub" id="youChipSub"></div>
825
+ </div>
826
+ </div>
827
+ <div class="local-badge"><span class="dot" aria-hidden="true"></span> raw usage stays local &middot; only league shared</div>
828
+ </div>
639
829
  </header>
640
830
 
641
831
  <div class="hero">
@@ -729,12 +919,27 @@ var webAppHtml = `<!DOCTYPE html>
729
919
  </div>
730
920
  </section>
731
921
 
732
- <section class="panel" aria-label="Match stack">
733
- <h2 class="panel-title">Match stack</h2>
734
- <div class="stack-wrap">
735
- <div class="card-stack" id="cardStack"></div>
736
- </div>
737
- </section>
922
+ <div class="people-col panel" aria-label="People">
923
+ <section class="live-now-wrap" id="liveNowWrap" aria-label="Live now">
924
+ <div class="section-head">
925
+ <h2><span class="pulse-live" aria-hidden="true"></span> Live now</h2>
926
+ <span class="count" id="liveNowCount">0</span>
927
+ </div>
928
+ <p class="section-sub">Real discovered peers on the wire &mdash; usage-verified and identity marks from their hello.</p>
929
+ <div class="live-roster" id="liveRoster"></div>
930
+ </section>
931
+
932
+ <section aria-label="Demo profiles">
933
+ <div class="section-head">
934
+ <h2>Demo profiles</h2>
935
+ <span class="mark mark-demo" title="Seeded local pool \u2014 not real people">demo</span>
936
+ </div>
937
+ <p class="demo-note">Seeded local filler from <b>CANDIDATES</b> &mdash; not live peers. Swipe to practice matching by league.</p>
938
+ <div class="stack-wrap" id="demoStackWrap">
939
+ <div class="card-stack" id="cardStack"></div>
940
+ </div>
941
+ </section>
942
+ </div>
738
943
 
739
944
  <section class="panel" aria-label="League ladder">
740
945
  <h2 class="panel-title">League ladder</h2>
@@ -770,10 +975,10 @@ var webAppHtml = `<!DOCTYPE html>
770
975
  </div>
771
976
  </div>
772
977
 
773
- <aside class="live-panel" id="livePanel" aria-label="Live peers">
774
- <div class="lp-title"><span class="lp-dot" aria-hidden="true"></span> Live peers</div>
978
+ <aside class="live-panel" id="livePanel" aria-label="Live peers compact">
979
+ <div class="lp-title"><span class="lp-dot" aria-hidden="true"></span> Live now <span class="lp-live-count" id="lpLiveCount"></span></div>
775
980
  <div id="liveRows"></div>
776
- <div class="lp-legend">&#10003; usage verified &middot; &#128273; identity verified &mdash; call someone, or wait for a call</div>
981
+ <div class="lp-legend">&#10003; usage verified &middot; &#128273; identity verified &mdash; real peers only (not demo)</div>
777
982
  </aside>
778
983
 
779
984
  <div class="incoming-call" id="incomingCall" role="dialog" aria-modal="true" aria-label="Incoming call">
@@ -791,7 +996,7 @@ var webAppHtml = `<!DOCTYPE html>
791
996
  <div class="cp-head">
792
997
  <div>
793
998
  <div class="cp-title" id="chatTitle">@peer</div>
794
- <div class="cp-sub" id="chatSub">live over the P2P link &middot; text only</div>
999
+ <div class="cp-sub" id="chatSub">live peer &middot; P2P text</div>
795
1000
  </div>
796
1001
  <button class="cp-close" id="chatClose" type="button" aria-label="Close chat">&times;</button>
797
1002
  </div>
@@ -811,7 +1016,7 @@ var webAppHtml = `<!DOCTYPE html>
811
1016
  </div>
812
1017
 
813
1018
  <aside class="room-panel" id="roomPanel" aria-label="Room">
814
- <div class="rp-title"><span class="lp-dot" aria-hidden="true"></span> Room</div>
1019
+ <div class="rp-title"><span class="lp-dot" aria-hidden="true"></span> Room <span class="rp-count" id="rpCount"></span></div>
815
1020
  <div class="rp-meta" id="roomMeta">not in a room</div>
816
1021
  <div class="rp-actions">
817
1022
  <button class="rp-btn" id="roomJoinBtn" type="button">Join video</button>
@@ -868,6 +1073,12 @@ var webAppHtml = `<!DOCTYPE html>
868
1073
  function fmt(n){ return Math.round(n).toLocaleString("en-US"); }
869
1074
  function variantFor(handle){ var h=0; for (var i=0;i<handle.length;i++){ h=(h*31+handle.charCodeAt(i))>>>0; } return h % BLOB_PATHS.length; }
870
1075
 
1076
+ // Shared with the live A/V script (separate IIFE) \u2014 roster cards need avatars + league colors.
1077
+ window.__vdAvatarSVG = avatarSVG;
1078
+ window.__vdVariantFor = variantFor;
1079
+ window.__vdLeagueById = leagueById;
1080
+ window.__vdLeagueColorHex = leagueColorHex;
1081
+
871
1082
  var state = { connected:false };
872
1083
  var stackIndex = 0;
873
1084
  var provider = "claude-code";
@@ -932,17 +1143,54 @@ var webAppHtml = `<!DOCTYPE html>
932
1143
  state = s || { connected:false };
933
1144
  if (state.connected){
934
1145
  renderReveal(state);
1146
+ renderYouChip(state);
935
1147
  renderYouMarker(state.league);
936
1148
  stackIndex = 0;
937
1149
  renderStack();
938
1150
  showStep("reveal");
939
1151
  } else {
1152
+ renderYouChip(null);
940
1153
  stackIndex = 0;
941
1154
  renderStack();
942
1155
  showStep("idle");
943
1156
  }
944
1157
  }
945
1158
 
1159
+ function renderYouChip(s){
1160
+ var chip = document.getElementById("youChip");
1161
+ if (!chip) return;
1162
+ if (!s || !s.connected){
1163
+ chip.classList.remove("is-on");
1164
+ return;
1165
+ }
1166
+ var lg = leagueById(s.league);
1167
+ var hex = lg ? lg.hex : "#ffcf6b";
1168
+ document.getElementById("youChipAvatar").innerHTML = avatarSVG(3, hex);
1169
+ document.getElementById("youChipHandle").textContent = s.handle || "@you";
1170
+ var sub = document.getElementById("youChipSub");
1171
+ sub.innerHTML = "";
1172
+ var pill = document.createElement("span");
1173
+ pill.className = "lg-pill";
1174
+ pill.style.background = "color-mix(in srgb, " + hex + " 16%, transparent)";
1175
+ pill.style.border = "1px solid color-mix(in srgb, " + hex + " 40%, transparent)";
1176
+ pill.style.color = hex;
1177
+ pill.textContent = lg ? lg.label : ((s.league || "?") + " League");
1178
+ sub.appendChild(pill);
1179
+ if (s.verified){
1180
+ var vm = document.createElement("span");
1181
+ vm.className = "mark mark-verified";
1182
+ vm.textContent = "\u2713 verified";
1183
+ sub.appendChild(vm);
1184
+ } else {
1185
+ var lm = document.createElement("span");
1186
+ lm.className = "mark mark-demo";
1187
+ lm.textContent = "local";
1188
+ lm.title = "Usage read locally / self-reported";
1189
+ sub.appendChild(lm);
1190
+ }
1191
+ chip.classList.add("is-on");
1192
+ }
1193
+
946
1194
  function renderReveal(s){
947
1195
  var lg = leagueById(s.league);
948
1196
  var hex = lg ? lg.hex : "#ffcf6b";
@@ -992,6 +1240,7 @@ var webAppHtml = `<!DOCTYPE html>
992
1240
  rawToggle.setAttribute("aria-expanded","false");
993
1241
  rawToggle.textContent = "show raw usage (visible only to you)";
994
1242
  document.querySelectorAll(".you-marker").forEach(function(m){ m.remove(); });
1243
+ renderYouChip(null);
995
1244
  stackIndex = 0;
996
1245
  renderStack();
997
1246
  showStep("idle");
@@ -1039,8 +1288,8 @@ var webAppHtml = `<!DOCTYPE html>
1039
1288
  if (!state.connected){
1040
1289
  empty.innerHTML = "<strong>Connect to see matches.</strong><p>Read your usage to get sorted into a league and start matching.</p>";
1041
1290
  } else {
1042
- empty.innerHTML = "<strong>That's everyone in range.</strong><p>You've matched through today's pool. It refreshes with the next billing cycle.</p>" +
1043
- '<button class="btn btn-primary" id="btnRestart" type="button" style="width:auto;padding:11px 20px;">Start over</button>';
1291
+ empty.innerHTML = "<strong>End of demo pool.</strong><p>These were seeded demo profiles \u2014 not live peers. Real people show up under Live now.</p>" +
1292
+ '<button class="btn btn-primary" id="btnRestart" type="button" style="width:auto;padding:11px 20px;">Restart demo</button>';
1044
1293
  }
1045
1294
  cardStackEl.appendChild(empty);
1046
1295
  var restart = empty.querySelector("#btnRestart");
@@ -1056,12 +1305,13 @@ var webAppHtml = `<!DOCTYPE html>
1056
1305
  var label = lg ? lg.label : (cand.league + " League");
1057
1306
  var bio = cand.bio || [];
1058
1307
  el.innerHTML =
1308
+ '<span class="mc-demo-badge mark mark-demo" title="Seeded demo profile \u2014 not a live peer">demo</span>' +
1059
1309
  '<div class="mc-top">' +
1060
1310
  '<div class="mc-avatar">' + avatarSVG(variantFor(cand.handle), hex) + '</div>' +
1061
1311
  '<div>' +
1062
- '<div class="mc-handle">' + cand.handle + '</div>' +
1312
+ '<div class="mc-handle-row"><div class="mc-handle">' + cand.handle + '</div></div>' +
1063
1313
  '<span class="mc-league" style="background:color-mix(in srgb,' + hex + ' 16%, transparent);border:1px solid color-mix(in srgb,' + hex + ' 40%, transparent);color:' + hex + '">' + label + '</span>' +
1064
- '<div class="mc-verified">&#10003; usage verified</div>' +
1314
+ '<div class="mc-verified" style="color:var(--muted-2)">seeded local profile</div>' +
1065
1315
  '</div>' +
1066
1316
  '</div>' +
1067
1317
  '<div class="mc-bio"><p>' + (bio[0] || "") + '</p><p>' + (bio[1] || "") + '</p></div>' +
@@ -1069,7 +1319,7 @@ var webAppHtml = `<!DOCTYPE html>
1069
1319
  '<div class="mc-actions">' +
1070
1320
  '<button class="round-btn pass" type="button" title="Pass">&#10005;</button>' +
1071
1321
  '<button class="round-btn like" type="button" title="Like">&#9829;</button>' +
1072
- '<span class="hint"></span>' +
1322
+ '<span class="hint">demo</span>' +
1073
1323
  '</div>' : '');
1074
1324
  cardStackEl.appendChild(el);
1075
1325
  if (depth === 0){
@@ -1469,7 +1719,7 @@ var webAppHtml = `<!DOCTYPE html>
1469
1719
  delete unread[handle];
1470
1720
  chatTitle.textContent = handle;
1471
1721
  chatSub.textContent = peerKnown(handle)
1472
- ? "live over the P2P link \xB7 text only"
1722
+ ? "live peer \xB7 P2P text \xB7 not a demo profile"
1473
1723
  : "peer offline \xB7 messages will not deliver";
1474
1724
  chatPanel.classList.add("is-open");
1475
1725
  renderChat();
@@ -1531,14 +1781,117 @@ var webAppHtml = `<!DOCTYPE html>
1531
1781
  // Render the live-peers rows: one per connected peer with its verification
1532
1782
  // marks, a Chat button (opens the conversation), and a Call button that
1533
1783
  // rings THAT peer specifically.
1784
+ // Marks from the peer's hello: \u2713 usage verified (real logs), \u{1F511} identity (signed).
1785
+ function peerMarksHtml(p){
1786
+ var html = "";
1787
+ if (p.verified === true) html += '<span class="mark mark-verified" title="Usage verified from real local logs">\u2713 verified</span>';
1788
+ if (p.identityVerified === true) html += '<span class="mark mark-identity" title="Identity signature verified">\u{1F511} identity</span>';
1789
+ return html;
1790
+ }
1791
+
1792
+ function leaguePillHtml(leagueId){
1793
+ var FALLBACK = { "1M":"#d69a6e","5M":"#cfd8e6","10M":"#ffcf6b","100M":"#f0839c","1B+":"#cba8ff" };
1794
+ var LABELS = { "1M":"1M League","5M":"5M League","10M":"10M League","100M":"100M League","1B+":"1B+ League" };
1795
+ var lg = typeof window.__vdLeagueById === "function" ? window.__vdLeagueById(leagueId) : null;
1796
+ var hex = lg ? lg.hex : (FALLBACK[leagueId] || "#ffcf6b");
1797
+ var label = lg ? lg.label : (LABELS[leagueId] || ((leagueId || "?") + " League"));
1798
+ return '<span class="lg-pill" style="background:color-mix(in srgb,' + hex + ' 16%, transparent);border:1px solid color-mix(in srgb,' + hex + ' 40%, transparent);color:' + hex + '">' + label + '</span>';
1799
+ }
1800
+
1801
+ function avatarForPeer(handle, leagueId){
1802
+ var FALLBACK = { "1M":"#d69a6e","5M":"#cfd8e6","10M":"#ffcf6b","100M":"#f0839c","1B+":"#cba8ff" };
1803
+ var hex = (typeof window.__vdLeagueColorHex === "function")
1804
+ ? window.__vdLeagueColorHex(leagueId)
1805
+ : (FALLBACK[leagueId] || "#ffcf6b");
1806
+ if (typeof window.__vdAvatarSVG === "function" && typeof window.__vdVariantFor === "function") {
1807
+ return window.__vdAvatarSVG(window.__vdVariantFor(handle), hex);
1808
+ }
1809
+ return "";
1810
+ }
1811
+
1812
+ // Compact dock rows (floating panel) + full Live now roster cards (main column).
1534
1813
  function renderLiveRows(peers){
1535
1814
  liveRows.innerHTML = "";
1815
+ var roster = document.getElementById("liveRoster");
1816
+ var wrap = document.getElementById("liveNowWrap");
1817
+ var countEl = document.getElementById("liveNowCount");
1818
+ var lpCount = document.getElementById("lpLiveCount");
1819
+ var demoWrap = document.getElementById("demoStackWrap");
1820
+
1821
+ if (countEl) countEl.textContent = String(peers.length);
1822
+ if (lpCount) lpCount.textContent = peers.length ? (peers.length + " online") : "";
1823
+ if (wrap) wrap.classList.toggle("has-live", peers.length > 0);
1824
+ if (demoWrap) demoWrap.classList.toggle("is-secondary", peers.length > 0);
1825
+
1826
+ if (roster) {
1827
+ roster.innerHTML = "";
1828
+ if (peers.length === 0) {
1829
+ var empty = document.createElement("div");
1830
+ empty.className = "live-empty";
1831
+ empty.innerHTML = "<strong>No live peers right now</strong><p>When someone on your league topic connects, they show up here \u2014 clearly separate from the demo pool below.</p>";
1832
+ roster.appendChild(empty);
1833
+ } else {
1834
+ peers.forEach(function(p){
1835
+ var card = document.createElement("article");
1836
+ card.className = "live-card";
1837
+
1838
+ var av = document.createElement("div");
1839
+ av.className = "lc-avatar";
1840
+ av.innerHTML = avatarForPeer(p.handle, p.league);
1841
+
1842
+ var body = document.createElement("div");
1843
+ body.className = "lc-body";
1844
+ var handleRow = document.createElement("div");
1845
+ handleRow.className = "lc-handle-row";
1846
+ var handleEl = document.createElement("div");
1847
+ handleEl.className = "lc-handle";
1848
+ handleEl.textContent = p.handle;
1849
+ handleRow.appendChild(handleEl);
1850
+ var liveMark = document.createElement("span");
1851
+ liveMark.className = "mark mark-live";
1852
+ liveMark.textContent = "live";
1853
+ handleRow.appendChild(liveMark);
1854
+ body.appendChild(handleRow);
1855
+
1856
+ var meta = document.createElement("div");
1857
+ meta.className = "lc-meta";
1858
+ meta.innerHTML = leaguePillHtml(p.league) + peerMarksHtml(p) +
1859
+ '<span style="opacity:.75">' + (p.harness || "") + "</span>";
1860
+ body.appendChild(meta);
1861
+
1862
+ var actions = document.createElement("div");
1863
+ actions.className = "lc-actions";
1864
+ var chatBtn = document.createElement("button");
1865
+ chatBtn.className = "btn-chat" + (unread[p.handle] ? " has-unread" : "");
1866
+ chatBtn.type = "button";
1867
+ chatBtn.textContent = "Chat" + (unread[p.handle] ? " (" + unread[p.handle] + ")" : "");
1868
+ chatBtn.addEventListener("click", function(){ openChat(p.handle); });
1869
+ var callBtn = document.createElement("button");
1870
+ callBtn.className = "btn-call";
1871
+ callBtn.type = "button";
1872
+ callBtn.textContent = "Call";
1873
+ callBtn.disabled = !!(rtc.remoteHandle || pendingOffer);
1874
+ callBtn.addEventListener("click", function(){
1875
+ if (rtc.remoteHandle || pendingOffer) return;
1876
+ callBtn.disabled = true;
1877
+ startCallAsOfferer(p.handle).catch(function(){ hangup(); });
1878
+ });
1879
+ actions.appendChild(chatBtn);
1880
+ actions.appendChild(callBtn);
1881
+
1882
+ card.appendChild(av);
1883
+ card.appendChild(body);
1884
+ card.appendChild(actions);
1885
+ roster.appendChild(card);
1886
+ });
1887
+ }
1888
+ }
1889
+
1890
+ // Compact floating dock \u2014 still handy when scrolled away from the roster.
1536
1891
  peers.forEach(function(p){
1537
1892
  var row = document.createElement("div");
1538
1893
  row.className = "live-row";
1539
1894
  var who = document.createElement("div");
1540
- // Marks from the peer's hello, shown only when present: \u2713 usage
1541
- // verified (real local logs), \u{1F511} identity-verified (signed hello).
1542
1895
  var marks = (p.verified === true ? " \u2713" : "") + (p.identityVerified === true ? " \u{1F511}" : "");
1543
1896
  var h = document.createElement("div"); h.className = "h"; h.textContent = p.handle + marks;
1544
1897
  var s = document.createElement("div"); s.className = "s"; s.textContent = p.league + " \xB7 " + p.harness;
@@ -1553,9 +1906,8 @@ var webAppHtml = `<!DOCTYPE html>
1553
1906
  var btn = document.createElement("button");
1554
1907
  btn.className = "vbtn"; btn.type = "button"; btn.textContent = "Call";
1555
1908
  btn.addEventListener("click", function(){
1556
- if (rtc.remoteHandle || pendingOffer) return; // already in a call / prompt
1909
+ if (rtc.remoteHandle || pendingOffer) return;
1557
1910
  btn.disabled = true;
1558
- // DIRECTED call: the rtc-offer is relayed only to this peer's handle.
1559
1911
  startCallAsOfferer(p.handle).catch(function(){ hangup(); });
1560
1912
  });
1561
1913
  actions.appendChild(chatBtn);
@@ -1576,14 +1928,16 @@ var webAppHtml = `<!DOCTYPE html>
1576
1928
  peers.forEach(function(p){ ensureChatLoop(p.handle); });
1577
1929
  if (chatWith) {
1578
1930
  chatSub.textContent = peerKnown(chatWith)
1579
- ? "live over the P2P link \xB7 text only"
1931
+ ? "live peer \xB7 P2P text"
1580
1932
  : "peer offline \xB7 messages will not deliver";
1581
1933
  }
1582
- if (peers.length === 0) { livePanel.classList.remove("is-open"); return; }
1583
- livePanel.classList.add("is-open");
1584
1934
  renderLiveRows(peers);
1935
+ if (peers.length === 0) { livePanel.classList.remove("is-open"); }
1936
+ else { livePanel.classList.add("is-open"); }
1585
1937
  } catch(e){}
1586
1938
  }
1939
+ // Initial empty state for the main Live now section, then poll.
1940
+ renderLiveRows([]);
1587
1941
  refreshPeers();
1588
1942
  setInterval(refreshPeers, 4000);
1589
1943
 
@@ -1824,6 +2178,8 @@ var webAppHtml = `<!DOCTYPE html>
1824
2178
  roomMeta.textContent = roomName +
1825
2179
  (roomVid.self ? " \xB7 you " + roomVid.self : "") +
1826
2180
  " \xB7 " + n + " other" + (n === 1 ? "" : "s");
2181
+ var rpCount = document.getElementById("rpCount");
2182
+ if (rpCount) rpCount.textContent = n ? (n + " other" + (n === 1 ? "" : "s")) : "empty";
1827
2183
  roomPanel.classList.add("is-open");
1828
2184
  roomJoinBtn.disabled = roomVid.joined || !roomVid.self;
1829
2185
  roomLeaveBtn.disabled = !roomVid.joined;
@@ -2428,7 +2784,7 @@ async function handle(req, res, opts) {
2428
2784
  }
2429
2785
 
2430
2786
  // src/cli.ts
2431
- var VERSION = "0.8.1";
2787
+ var VERSION = "0.8.2";
2432
2788
  function parsePort(raw) {
2433
2789
  const n = Number(raw);
2434
2790
  if (!Number.isInteger(n) || n < 1 || n > 65535) return void 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibedate",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "Dating by tokens — matched by usage league across agentic CLIs. Raw usage stays local; only your league is shared. CLI + local web app + MCP. Local-first.",
5
5
  "type": "module",
6
6
  "license": "MIT",