zero-query 1.2.13 → 1.2.15
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.
- package/cli/scaffold/webrtc/app/app.js +1 -1
- package/cli/scaffold/webrtc/app/components/video-room.js +69 -46
- package/cli/scaffold/webrtc/global.css +244 -61
- package/cli/scaffold/webrtc/index.html +14 -1
- package/cli/scaffold/webrtc/server/index.js +1 -1
- package/dist/zquery.dist.zip +0 -0
- package/dist/zquery.js +17 -11
- package/dist/zquery.min.js +3 -3
- package/package.json +1 -1
- package/src/webrtc/peer.js +14 -8
- package/tests/webrtc/peer.test.js +13 -17
|
@@ -7,5 +7,5 @@ $.ready(() => {
|
|
|
7
7
|
// Instantiate every registered <component-tag> already in the DOM.
|
|
8
8
|
// (No $.router() is wiring it up for us in this single-page scaffold.)
|
|
9
9
|
$.mountAll();
|
|
10
|
-
console.log('
|
|
10
|
+
console.log('{{NAME}} · zQuery v' + $.version + ' WebRTC demo');
|
|
11
11
|
});
|
|
@@ -121,7 +121,11 @@ $.component('video-room', {
|
|
|
121
121
|
},
|
|
122
122
|
|
|
123
123
|
pickRoom(e) {
|
|
124
|
-
|
|
124
|
+
// zero-query @click is delegated (one listener on the component root),
|
|
125
|
+
// so e.currentTarget is that root - NOT the room-card button. Walk up
|
|
126
|
+
// from the actual click target to find the button carrying data-room.
|
|
127
|
+
const target = e && e.target;
|
|
128
|
+
const btn = target && target.closest && target.closest('[data-room]');
|
|
125
129
|
const name = btn && btn.getAttribute('data-room');
|
|
126
130
|
if (!name) return;
|
|
127
131
|
if (this.state.joined || this.state.connecting) return;
|
|
@@ -660,64 +664,83 @@ $.component('video-room', {
|
|
|
660
664
|
if (!hasShare) hint.push('screen sharing unavailable in this browser');
|
|
661
665
|
const hintLine = hint.length ? `<div class="device-hint">${this._icon('alert', 14)}<span>${$.escapeHtml(hint.join(' · '))}</span></div>` : '';
|
|
662
666
|
|
|
667
|
+
const totalPeers = availableRooms.reduce((n, r) => n + (r.peerCount || 0), 0);
|
|
668
|
+
const liveBadge = `<span class="live-dot" aria-hidden="true"></span>
|
|
669
|
+
<span>${availableRooms.length} ${availableRooms.length === 1 ? 'room' : 'rooms'} · ${totalPeers} online</span>`;
|
|
670
|
+
|
|
663
671
|
const roomsBlock = availableRooms.length > 0
|
|
664
672
|
? `<ul class="room-list">
|
|
665
673
|
${availableRooms.map((r) => {
|
|
666
|
-
const active
|
|
667
|
-
const
|
|
674
|
+
const active = r.name === roomName ? ' active' : '';
|
|
675
|
+
const isAlone = (r.peerCount || 0) <= 1;
|
|
676
|
+
const count = (r.peerCount === 1) ? '1 here' : ((r.peerCount || 0) + ' here');
|
|
677
|
+
const dots = Math.min(r.peerCount || 0, 4);
|
|
678
|
+
const dotsHtml = Array.from({ length: dots }).map((_, i) =>
|
|
679
|
+
`<span class="pip pip-${i % 3}"></span>`
|
|
680
|
+
).join('') || '<span class="pip pip-empty"></span>';
|
|
668
681
|
return `<li>
|
|
669
|
-
<button type="button" class="room-
|
|
670
|
-
<span class="room-
|
|
671
|
-
<span class="room-
|
|
682
|
+
<button type="button" class="room-card${active}" data-room="${$.escapeHtml(r.name)}" @click="pickRoom" ${connecting ? 'disabled' : ''}>
|
|
683
|
+
<span class="room-card-pips">${dotsHtml}</span>
|
|
684
|
+
<span class="room-card-body">
|
|
685
|
+
<span class="room-card-name">#${$.escapeHtml(r.name)}</span>
|
|
686
|
+
<span class="room-card-count">${count}</span>
|
|
687
|
+
</span>
|
|
688
|
+
<span class="room-card-cta">${isAlone ? 'be the first' : 'jump in'} →</span>
|
|
672
689
|
</button>
|
|
673
690
|
</li>`;
|
|
674
691
|
}).join('')}
|
|
675
692
|
</ul>`
|
|
676
|
-
: `<
|
|
693
|
+
: `<div class="room-list-empty">
|
|
694
|
+
${loadingRooms ? 'Looking for live rooms\u2026' : 'No live rooms right now. Type a name below and start one.'}
|
|
695
|
+
</div>`;
|
|
677
696
|
|
|
678
697
|
return `
|
|
679
|
-
<div class="lobby">
|
|
680
|
-
<
|
|
681
|
-
<
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
${connecting ? '
|
|
706
|
-
|
|
707
|
-
</form>
|
|
708
|
-
<div class="rooms-section">
|
|
709
|
-
<div class="rooms-head">
|
|
710
|
-
<h2>Active rooms</h2>
|
|
711
|
-
<button type="button" class="ghost" @click="refreshRooms" ${loadingRooms ? 'disabled' : ''}>
|
|
712
|
-
${loadingRooms ? 'Refreshing\u2026' : 'Refresh'}
|
|
698
|
+
<div class="lobby-shell">
|
|
699
|
+
<div class="lobby-bg" aria-hidden="true"></div>
|
|
700
|
+
<div class="lobby">
|
|
701
|
+
<div class="lobby-hero">
|
|
702
|
+
<span class="eyebrow">${liveBadge}</span>
|
|
703
|
+
<h1>Spin up a room. Share whatever.</h1>
|
|
704
|
+
<p class="lead">
|
|
705
|
+
A no-signup mesh call wired straight into
|
|
706
|
+
<code>$.webrtc.join()</code> on top of
|
|
707
|
+
<a href="https://github.com/tonywied17/zero-server" target="_blank" rel="noopener">zero-server</a>.
|
|
708
|
+
Drop a name, send the link to a friend, and you'll be on a peer-to-peer
|
|
709
|
+
video call in two clicks. Camera, mic, and screen share are <em>off</em>
|
|
710
|
+
until you say so.
|
|
711
|
+
</p>
|
|
712
|
+
</div>
|
|
713
|
+
|
|
714
|
+
<form class="join-form" @submit="join">
|
|
715
|
+
<label>
|
|
716
|
+
<span class="field-label">Room</span>
|
|
717
|
+
<span class="field-prefix">#</span>
|
|
718
|
+
<input class="has-prefix" type="text" value="${$.escapeHtml(roomName)}" @input="setRoom" placeholder="lobby" autocomplete="off" spellcheck="false" ${connecting ? 'disabled' : ''} />
|
|
719
|
+
</label>
|
|
720
|
+
<label>
|
|
721
|
+
<span class="field-label">Your name</span>
|
|
722
|
+
<input type="text" value="${$.escapeHtml(displayName)}" @input="setName" placeholder="how should peers see you?" autocomplete="off" ${connecting ? 'disabled' : ''} />
|
|
723
|
+
</label>
|
|
724
|
+
<button type="submit" class="primary join-btn" ${connecting ? 'disabled' : ''}>
|
|
725
|
+
${connecting ? 'Connecting...' : 'Join room'}
|
|
713
726
|
</button>
|
|
727
|
+
</form>
|
|
728
|
+
|
|
729
|
+
<div class="rooms-section">
|
|
730
|
+
<div class="rooms-head">
|
|
731
|
+
<h2>Live right now</h2>
|
|
732
|
+
<button type="button" class="ghost" @click="refreshRooms" ${loadingRooms ? 'disabled' : ''}>
|
|
733
|
+
${loadingRooms ? 'Refreshing\u2026' : 'Refresh'}
|
|
734
|
+
</button>
|
|
735
|
+
</div>
|
|
736
|
+
${roomsBlock}
|
|
714
737
|
</div>
|
|
715
|
-
|
|
738
|
+
|
|
739
|
+
${hintLine}
|
|
740
|
+
<p class="status ${error ? 'error' : ''}">
|
|
741
|
+
${error ? $.escapeHtml(error) : $.escapeHtml(status)}
|
|
742
|
+
</p>
|
|
716
743
|
</div>
|
|
717
|
-
${hintLine}
|
|
718
|
-
<p class="status ${error ? 'error' : ''}">
|
|
719
|
-
${error ? $.escapeHtml(error) : $.escapeHtml(status)}
|
|
720
|
-
</p>
|
|
721
744
|
</div>
|
|
722
745
|
`;
|
|
723
746
|
},
|
|
@@ -2,17 +2,20 @@
|
|
|
2
2
|
* { box-sizing: border-box; }
|
|
3
3
|
|
|
4
4
|
:root {
|
|
5
|
-
--bg: #
|
|
6
|
-
--
|
|
7
|
-
--panel
|
|
8
|
-
--
|
|
5
|
+
--bg: #0a0b10;
|
|
6
|
+
--bg-2: #0f1018;
|
|
7
|
+
--panel: #14161f;
|
|
8
|
+
--panel-2: #1c1f2c;
|
|
9
|
+
--border: #242838;
|
|
9
10
|
--border-hi: #3a4258;
|
|
10
|
-
--text: #
|
|
11
|
+
--text: #eef1f8;
|
|
11
12
|
--text-muted: #8a93a6;
|
|
12
|
-
--accent: #
|
|
13
|
-
--accent-2: #
|
|
13
|
+
--accent: #7c5cff;
|
|
14
|
+
--accent-2: #22d3ee;
|
|
15
|
+
--accent-3: #16a34a;
|
|
14
16
|
--danger: #e1455c;
|
|
15
17
|
--warn: #f59e0b;
|
|
18
|
+
--gradient: linear-gradient(135deg, #7c5cff 0%, #22d3ee 100%);
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
html, body {
|
|
@@ -23,6 +26,7 @@ html, body {
|
|
|
23
26
|
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
24
27
|
height: 100vh;
|
|
25
28
|
overflow: hidden;
|
|
29
|
+
-webkit-font-smoothing: antialiased;
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
body { display: flex; flex-direction: column; }
|
|
@@ -30,64 +34,194 @@ body { display: flex; flex-direction: column; }
|
|
|
30
34
|
.topbar {
|
|
31
35
|
flex: 0 0 auto;
|
|
32
36
|
display: flex;
|
|
33
|
-
align-items:
|
|
37
|
+
align-items: center;
|
|
34
38
|
justify-content: space-between;
|
|
35
|
-
padding: 0.
|
|
39
|
+
padding: 0.65rem 1.1rem;
|
|
36
40
|
border-bottom: 1px solid var(--border);
|
|
37
|
-
background: var(--panel);
|
|
41
|
+
background: linear-gradient(180deg, rgba(124, 92, 255, 0.05), transparent 60%), var(--panel);
|
|
42
|
+
position: relative;
|
|
43
|
+
}
|
|
44
|
+
.topbar::after {
|
|
45
|
+
content: '';
|
|
46
|
+
position: absolute; left: 0; right: 0; bottom: -1px; height: 1px;
|
|
47
|
+
background: linear-gradient(90deg, transparent, rgba(124, 92, 255, 0.4), rgba(34, 211, 238, 0.4), transparent);
|
|
48
|
+
}
|
|
49
|
+
.topbar .brand {
|
|
50
|
+
display: inline-flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
gap: 0.55rem;
|
|
53
|
+
font-weight: 700;
|
|
54
|
+
font-size: 1rem;
|
|
55
|
+
letter-spacing: -0.01em;
|
|
38
56
|
}
|
|
39
|
-
.topbar .brand {
|
|
40
|
-
.topbar
|
|
57
|
+
.topbar .brand-mark { display: block; }
|
|
58
|
+
.topbar .brand-text { background: var(--gradient); -webkit-background-clip: text; background-clip: text; color: transparent; }
|
|
59
|
+
.topbar small { color: var(--text-muted); font-size: 0.78rem; letter-spacing: 0.02em; }
|
|
41
60
|
|
|
42
|
-
main { flex: 1 1 auto; min-height: 0; }
|
|
61
|
+
main { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
|
|
43
62
|
|
|
44
63
|
/* ---- Lobby (pre-join) -------------------------------------------------- */
|
|
45
64
|
|
|
65
|
+
.lobby-shell {
|
|
66
|
+
position: relative;
|
|
67
|
+
min-height: 100%;
|
|
68
|
+
display: flex;
|
|
69
|
+
align-items: flex-start;
|
|
70
|
+
justify-content: center;
|
|
71
|
+
padding: 3.5rem 1.25rem 4rem;
|
|
72
|
+
overflow: hidden;
|
|
73
|
+
}
|
|
74
|
+
.lobby-bg {
|
|
75
|
+
position: absolute; inset: 0;
|
|
76
|
+
pointer-events: none;
|
|
77
|
+
background:
|
|
78
|
+
radial-gradient(60rem 30rem at 15% -10%, rgba(124, 92, 255, 0.18), transparent 60%),
|
|
79
|
+
radial-gradient(50rem 25rem at 110% 10%, rgba(34, 211, 238, 0.14), transparent 60%),
|
|
80
|
+
radial-gradient(40rem 20rem at 50% 110%, rgba(22, 163, 74, 0.10), transparent 60%);
|
|
81
|
+
animation: bgDrift 24s ease-in-out infinite alternate;
|
|
82
|
+
}
|
|
83
|
+
@keyframes bgDrift {
|
|
84
|
+
0% { transform: translate3d(0, 0, 0); }
|
|
85
|
+
100% { transform: translate3d(-2%, 2%, 0); }
|
|
86
|
+
}
|
|
87
|
+
|
|
46
88
|
.lobby {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
padding: 2rem;
|
|
50
|
-
background:
|
|
89
|
+
position: relative;
|
|
90
|
+
width: min(680px, 100%);
|
|
91
|
+
padding: 2.25rem 2.25rem 2rem;
|
|
92
|
+
background: linear-gradient(180deg, rgba(28, 31, 44, 0.85), rgba(20, 22, 31, 0.85));
|
|
51
93
|
border: 1px solid var(--border);
|
|
52
|
-
border-radius:
|
|
94
|
+
border-radius: 1.1rem;
|
|
95
|
+
backdrop-filter: blur(10px);
|
|
96
|
+
-webkit-backdrop-filter: blur(10px);
|
|
97
|
+
box-shadow: 0 25px 60px -25px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(124, 92, 255, 0.05);
|
|
98
|
+
}
|
|
99
|
+
.lobby::before {
|
|
100
|
+
content: '';
|
|
101
|
+
position: absolute; inset: -1px;
|
|
102
|
+
border-radius: inherit;
|
|
103
|
+
padding: 1px;
|
|
104
|
+
background: var(--gradient);
|
|
105
|
+
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
|
|
106
|
+
-webkit-mask-composite: xor;
|
|
107
|
+
mask-composite: exclude;
|
|
108
|
+
opacity: 0.35;
|
|
109
|
+
pointer-events: none;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.lobby-hero { margin-bottom: 1.5rem; }
|
|
113
|
+
.lobby-hero .eyebrow {
|
|
114
|
+
display: inline-flex;
|
|
115
|
+
align-items: center;
|
|
116
|
+
gap: 0.45rem;
|
|
117
|
+
font-size: 0.72rem;
|
|
118
|
+
color: var(--text-muted);
|
|
119
|
+
background: rgba(124, 92, 255, 0.08);
|
|
120
|
+
border: 1px solid rgba(124, 92, 255, 0.18);
|
|
121
|
+
padding: 0.3rem 0.65rem;
|
|
122
|
+
border-radius: 999px;
|
|
123
|
+
letter-spacing: 0.04em;
|
|
124
|
+
text-transform: uppercase;
|
|
125
|
+
font-weight: 600;
|
|
126
|
+
}
|
|
127
|
+
.lobby-hero .live-dot {
|
|
128
|
+
width: 7px; height: 7px;
|
|
129
|
+
border-radius: 50%;
|
|
130
|
+
background: var(--accent-3);
|
|
131
|
+
box-shadow: 0 0 0 3px rgba(22, 163, 74, 0.22);
|
|
132
|
+
animation: livePulse 1.8s ease-in-out infinite;
|
|
133
|
+
}
|
|
134
|
+
@keyframes livePulse {
|
|
135
|
+
0%, 100% { box-shadow: 0 0 0 3px rgba(22, 163, 74, 0.22); }
|
|
136
|
+
50% { box-shadow: 0 0 0 5px rgba(22, 163, 74, 0.05); }
|
|
53
137
|
}
|
|
54
|
-
|
|
55
|
-
.lobby
|
|
56
|
-
|
|
138
|
+
|
|
139
|
+
.lobby h1 {
|
|
140
|
+
margin: 0.8rem 0 0.5rem;
|
|
141
|
+
font-size: clamp(1.6rem, 3.5vw, 2rem);
|
|
142
|
+
font-weight: 700;
|
|
143
|
+
letter-spacing: -0.02em;
|
|
144
|
+
line-height: 1.15;
|
|
145
|
+
}
|
|
146
|
+
.lobby .lead {
|
|
147
|
+
color: var(--text-muted);
|
|
148
|
+
line-height: 1.6;
|
|
149
|
+
margin: 0.4rem 0;
|
|
150
|
+
font-size: 0.95rem;
|
|
151
|
+
}
|
|
152
|
+
.lobby .lead em { color: var(--text); font-style: normal; font-weight: 600; }
|
|
153
|
+
.lobby code {
|
|
154
|
+
background: rgba(124, 92, 255, 0.1);
|
|
155
|
+
color: #d4cdff;
|
|
156
|
+
padding: 0.1rem 0.4rem;
|
|
157
|
+
border-radius: 0.3rem;
|
|
158
|
+
font-size: 0.85em;
|
|
159
|
+
}
|
|
160
|
+
.lobby a { color: var(--accent-2); text-decoration: none; }
|
|
161
|
+
.lobby a:hover { text-decoration: underline; }
|
|
57
162
|
|
|
58
163
|
.join-form {
|
|
59
|
-
display:
|
|
60
|
-
|
|
61
|
-
gap: 0.
|
|
62
|
-
margin: 1.
|
|
164
|
+
display: grid;
|
|
165
|
+
grid-template-columns: 1.4fr 1fr auto;
|
|
166
|
+
gap: 0.6rem;
|
|
167
|
+
margin: 1.5rem 0 1rem;
|
|
168
|
+
align-items: end;
|
|
63
169
|
}
|
|
64
170
|
.join-form label {
|
|
171
|
+
position: relative;
|
|
65
172
|
display: flex;
|
|
66
173
|
flex-direction: column;
|
|
67
174
|
gap: 0.3rem;
|
|
68
|
-
|
|
175
|
+
}
|
|
176
|
+
.join-form .field-label {
|
|
177
|
+
font-size: 0.72rem;
|
|
69
178
|
color: var(--text-muted);
|
|
179
|
+
letter-spacing: 0.04em;
|
|
180
|
+
text-transform: uppercase;
|
|
181
|
+
font-weight: 600;
|
|
182
|
+
}
|
|
183
|
+
.join-form .field-prefix {
|
|
184
|
+
position: absolute;
|
|
185
|
+
left: 0.85rem;
|
|
186
|
+
bottom: 0.7rem;
|
|
187
|
+
color: var(--text-muted);
|
|
188
|
+
font-weight: 600;
|
|
189
|
+
pointer-events: none;
|
|
190
|
+
font-size: 0.95rem;
|
|
70
191
|
}
|
|
71
192
|
.join-form input {
|
|
72
|
-
background:
|
|
193
|
+
background: rgba(10, 11, 16, 0.7);
|
|
73
194
|
color: var(--text);
|
|
74
195
|
border: 1px solid var(--border);
|
|
75
|
-
border-radius: 0.
|
|
76
|
-
padding: 0.
|
|
196
|
+
border-radius: 0.55rem;
|
|
197
|
+
padding: 0.65rem 0.85rem;
|
|
77
198
|
font-family: inherit;
|
|
78
199
|
font-size: 0.95rem;
|
|
200
|
+
transition: border-color .15s, box-shadow .15s, background .15s;
|
|
201
|
+
}
|
|
202
|
+
.join-form input.has-prefix { padding-left: 1.5rem; }
|
|
203
|
+
.join-form input:focus {
|
|
204
|
+
outline: none;
|
|
205
|
+
border-color: rgba(124, 92, 255, 0.55);
|
|
206
|
+
box-shadow: 0 0 0 3px rgba(124, 92, 255, 0.18);
|
|
207
|
+
background: rgba(10, 11, 16, 0.95);
|
|
208
|
+
}
|
|
209
|
+
.join-form .join-btn {
|
|
210
|
+
padding: 0.7rem 1.25rem;
|
|
211
|
+
height: fit-content;
|
|
212
|
+
align-self: stretch;
|
|
79
213
|
}
|
|
80
214
|
|
|
81
215
|
button {
|
|
82
216
|
background: var(--panel-2);
|
|
83
217
|
color: var(--text);
|
|
84
218
|
border: 1px solid var(--border);
|
|
85
|
-
border-radius: 0.
|
|
219
|
+
border-radius: 0.55rem;
|
|
86
220
|
padding: 0.5rem 0.9rem;
|
|
87
221
|
font-weight: 600;
|
|
88
222
|
cursor: pointer;
|
|
89
223
|
font-family: inherit;
|
|
90
|
-
transition: background .15s, border-color .15s, transform .05s;
|
|
224
|
+
transition: background .15s, border-color .15s, transform .05s, box-shadow .15s;
|
|
91
225
|
display: inline-flex;
|
|
92
226
|
align-items: center;
|
|
93
227
|
gap: 0.4rem;
|
|
@@ -97,24 +231,29 @@ button .icon { flex: 0 0 auto; display: block; }
|
|
|
97
231
|
button:hover { background: #232939; border-color: var(--border-hi); }
|
|
98
232
|
button:active { transform: translateY(1px); }
|
|
99
233
|
button:disabled { opacity: 0.55; cursor: not-allowed; }
|
|
100
|
-
button.primary {
|
|
101
|
-
|
|
234
|
+
button.primary {
|
|
235
|
+
background: var(--gradient);
|
|
236
|
+
border-color: transparent;
|
|
237
|
+
color: #fff;
|
|
238
|
+
box-shadow: 0 8px 20px -8px rgba(124, 92, 255, 0.55);
|
|
239
|
+
}
|
|
240
|
+
button.primary:hover { filter: brightness(1.08); box-shadow: 0 10px 24px -8px rgba(124, 92, 255, 0.7); }
|
|
102
241
|
button.leave { background: var(--danger); border-color: transparent; color: #fff; }
|
|
103
242
|
button.off { background: #3a2030; color: #f3b6c2; }
|
|
104
|
-
button.active { background: var(--accent-
|
|
243
|
+
button.active { background: var(--accent-3); border-color: transparent; color: #fff; }
|
|
105
244
|
button.ghost { background: transparent; }
|
|
106
245
|
|
|
107
|
-
.status { color: var(--text-muted); font-size: 0.
|
|
246
|
+
.status { color: var(--text-muted); font-size: 0.88rem; margin: 0.5rem 0 0; }
|
|
108
247
|
.status.error { color: var(--danger); }
|
|
109
248
|
|
|
110
249
|
.device-hint {
|
|
111
250
|
color: var(--warn);
|
|
112
251
|
background: rgba(245, 158, 11, 0.08);
|
|
113
252
|
border: 1px solid rgba(245, 158, 11, 0.25);
|
|
114
|
-
padding: 0.
|
|
115
|
-
border-radius: 0.
|
|
253
|
+
padding: 0.55rem 0.8rem;
|
|
254
|
+
border-radius: 0.55rem;
|
|
116
255
|
font-size: 0.85rem;
|
|
117
|
-
margin-top:
|
|
256
|
+
margin-top: 1rem;
|
|
118
257
|
display: flex;
|
|
119
258
|
align-items: center;
|
|
120
259
|
gap: 0.5rem;
|
|
@@ -124,53 +263,93 @@ button.ghost { background: transparent; }
|
|
|
124
263
|
/* ---- Active rooms picker --------------------------------------------- */
|
|
125
264
|
|
|
126
265
|
.rooms-section {
|
|
127
|
-
margin-top: 1.
|
|
266
|
+
margin-top: 1.5rem;
|
|
267
|
+
padding-top: 1.25rem;
|
|
128
268
|
border-top: 1px solid var(--border);
|
|
129
|
-
padding-top: 1rem;
|
|
130
269
|
}
|
|
131
270
|
.rooms-head {
|
|
132
271
|
display: flex;
|
|
133
272
|
align-items: center;
|
|
134
273
|
justify-content: space-between;
|
|
135
|
-
margin-bottom: 0.
|
|
274
|
+
margin-bottom: 0.75rem;
|
|
136
275
|
}
|
|
137
276
|
.rooms-head h2 {
|
|
138
277
|
margin: 0;
|
|
139
|
-
font-size: 0.
|
|
278
|
+
font-size: 0.72rem;
|
|
140
279
|
color: var(--text-muted);
|
|
141
|
-
font-weight:
|
|
142
|
-
letter-spacing: 0.
|
|
280
|
+
font-weight: 700;
|
|
281
|
+
letter-spacing: 0.08em;
|
|
282
|
+
text-transform: uppercase;
|
|
143
283
|
}
|
|
144
284
|
.room-list {
|
|
145
285
|
list-style: none;
|
|
146
286
|
margin: 0;
|
|
147
287
|
padding: 0;
|
|
148
|
-
display:
|
|
149
|
-
|
|
150
|
-
gap: 0.
|
|
288
|
+
display: grid;
|
|
289
|
+
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
|
290
|
+
gap: 0.55rem;
|
|
151
291
|
}
|
|
152
292
|
.room-list li { margin: 0; }
|
|
153
|
-
.room-
|
|
154
|
-
|
|
293
|
+
.room-card {
|
|
294
|
+
width: 100%;
|
|
295
|
+
display: grid;
|
|
296
|
+
grid-template-columns: auto 1fr auto;
|
|
155
297
|
align-items: center;
|
|
156
|
-
gap: 0.
|
|
157
|
-
padding: 0.
|
|
158
|
-
border-radius:
|
|
159
|
-
background:
|
|
298
|
+
gap: 0.7rem;
|
|
299
|
+
padding: 0.7rem 0.85rem;
|
|
300
|
+
border-radius: 0.7rem;
|
|
301
|
+
background: rgba(10, 11, 16, 0.5);
|
|
160
302
|
border: 1px solid var(--border);
|
|
161
303
|
color: var(--text);
|
|
162
|
-
font-size: 0.
|
|
304
|
+
font-size: 0.88rem;
|
|
163
305
|
cursor: pointer;
|
|
306
|
+
text-align: left;
|
|
307
|
+
transition: background .15s, border-color .15s, transform .05s;
|
|
308
|
+
}
|
|
309
|
+
.room-card:hover {
|
|
310
|
+
border-color: rgba(124, 92, 255, 0.5);
|
|
311
|
+
background: rgba(124, 92, 255, 0.06);
|
|
312
|
+
}
|
|
313
|
+
.room-card:hover .room-card-cta { color: var(--accent-2); }
|
|
314
|
+
.room-card.active {
|
|
315
|
+
border-color: transparent;
|
|
316
|
+
background: linear-gradient(135deg, rgba(124, 92, 255, 0.22), rgba(34, 211, 238, 0.18));
|
|
317
|
+
box-shadow: inset 0 0 0 1px rgba(124, 92, 255, 0.4);
|
|
318
|
+
}
|
|
319
|
+
.room-card-pips {
|
|
320
|
+
display: inline-flex;
|
|
321
|
+
align-items: center;
|
|
322
|
+
gap: 2px;
|
|
323
|
+
padding: 0.35rem 0.4rem;
|
|
324
|
+
border-radius: 0.4rem;
|
|
325
|
+
background: rgba(255, 255, 255, 0.03);
|
|
326
|
+
}
|
|
327
|
+
.room-card-pips .pip {
|
|
328
|
+
width: 6px; height: 6px;
|
|
329
|
+
border-radius: 50%;
|
|
330
|
+
display: inline-block;
|
|
331
|
+
}
|
|
332
|
+
.room-card-pips .pip-0 { background: var(--accent); }
|
|
333
|
+
.room-card-pips .pip-1 { background: var(--accent-2); }
|
|
334
|
+
.room-card-pips .pip-2 { background: var(--accent-3); }
|
|
335
|
+
.room-card-pips .pip-empty { background: var(--border-hi); }
|
|
336
|
+
.room-card-body { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
|
|
337
|
+
.room-card-name { font-weight: 700; letter-spacing: -0.01em; }
|
|
338
|
+
.room-card-count { color: var(--text-muted); font-size: 0.75rem; }
|
|
339
|
+
.room-card-cta {
|
|
340
|
+
font-size: 0.75rem;
|
|
341
|
+
color: var(--text-muted);
|
|
342
|
+
font-weight: 600;
|
|
343
|
+
letter-spacing: 0.02em;
|
|
344
|
+
transition: color .15s;
|
|
164
345
|
}
|
|
165
|
-
.room-pill:hover { border-color: var(--accent); }
|
|
166
|
-
.room-pill.active { background: var(--accent); border-color: transparent; color: #fff; }
|
|
167
|
-
.room-pill-name { font-weight: 600; }
|
|
168
|
-
.room-pill-count { font-size: 0.75rem; color: var(--text-muted); }
|
|
169
|
-
.room-pill.active .room-pill-count { color: rgba(255,255,255,0.85); }
|
|
170
346
|
.room-list-empty {
|
|
171
347
|
color: var(--text-muted);
|
|
172
|
-
font-size: 0.
|
|
173
|
-
|
|
348
|
+
font-size: 0.88rem;
|
|
349
|
+
padding: 0.9rem 1rem;
|
|
350
|
+
border: 1px dashed var(--border);
|
|
351
|
+
border-radius: 0.7rem;
|
|
352
|
+
text-align: center;
|
|
174
353
|
}
|
|
175
354
|
|
|
176
355
|
/* ---- Room layout (in-call) -------------------------------------------- */
|
|
@@ -496,4 +675,8 @@ button.ghost { background: transparent; }
|
|
|
496
675
|
.stage-main-6,
|
|
497
676
|
.stage-main-9 { grid-template-columns: 1fr 1fr; grid-auto-rows: minmax(140px, 1fr); grid-template-rows: none; }
|
|
498
677
|
.tile.thumb { width: 120px; }
|
|
678
|
+
.lobby-shell { padding: 1.5rem 0.75rem 2.5rem; }
|
|
679
|
+
.lobby { padding: 1.5rem 1.25rem; border-radius: 0.9rem; }
|
|
680
|
+
.join-form { grid-template-columns: 1fr; }
|
|
681
|
+
.join-form .join-btn { width: 100%; justify-content: center; }
|
|
499
682
|
}
|
|
@@ -11,7 +11,20 @@
|
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
<header class="topbar">
|
|
14
|
-
<span class="brand"
|
|
14
|
+
<span class="brand">
|
|
15
|
+
<svg class="brand-mark" width="22" height="22" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
16
|
+
<defs>
|
|
17
|
+
<linearGradient id="zq-grad" x1="0" y1="0" x2="24" y2="24" gradientUnits="userSpaceOnUse">
|
|
18
|
+
<stop offset="0" stop-color="#7c5cff"/>
|
|
19
|
+
<stop offset="1" stop-color="#22d3ee"/>
|
|
20
|
+
</linearGradient>
|
|
21
|
+
</defs>
|
|
22
|
+
<rect x="2" y="4" width="20" height="16" rx="4" fill="url(#zq-grad)"/>
|
|
23
|
+
<path d="M9 9h6l-6 6h6" stroke="#0b0c10" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
|
24
|
+
<circle cx="19.5" cy="6.5" r="2.5" fill="#16a34a" stroke="#0b0c10" stroke-width="1.5"/>
|
|
25
|
+
</svg>
|
|
26
|
+
<span class="brand-text">{{NAME}}</span>
|
|
27
|
+
</span>
|
|
15
28
|
<small>zQuery <span id="nav-version"></span> · WebRTC demo</small>
|
|
16
29
|
</header>
|
|
17
30
|
<main>
|
|
@@ -184,7 +184,7 @@ async function main() {
|
|
|
184
184
|
|
|
185
185
|
// ---- Listen ----
|
|
186
186
|
app.listen(PORT, () => {
|
|
187
|
-
console.log('\n
|
|
187
|
+
console.log('\n WebRTC server → http://localhost:' + PORT);
|
|
188
188
|
console.log(' • signaling ws://localhost:' + PORT + '/rtc');
|
|
189
189
|
console.log(' • turn creds http://localhost:' + PORT + '/rtc/turn');
|
|
190
190
|
if (JWT) {
|
package/dist/zquery.dist.zip
CHANGED
|
Binary file
|
package/dist/zquery.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* zQuery (zeroQuery) v1.2.
|
|
2
|
+
* zQuery (zeroQuery) v1.2.15
|
|
3
3
|
* Lightweight Frontend Library
|
|
4
4
|
* https://github.com/tonywied17/zero-query
|
|
5
5
|
* (c) 2026 Anthony Wiedman - MIT License
|
|
@@ -1564,11 +1564,12 @@ class Peer {
|
|
|
1564
1564
|
this.pc.onicecandidate = (event) => {
|
|
1565
1565
|
if (this.closed) return;
|
|
1566
1566
|
const candidate = event && event.candidate;
|
|
1567
|
-
// End-of-candidates
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1567
|
+
// End-of-candidates: the @zero-server/webrtc SignalingHub rejects
|
|
1568
|
+
// any ice frame whose `candidate` is not a non-empty string
|
|
1569
|
+
// (BAD_FRAME -> counts toward the protocol-error budget that can
|
|
1570
|
+
// disconnect the peer). Browsers infer end-of-candidates from
|
|
1571
|
+
// iceGatheringState anyway, so we simply drop the marker.
|
|
1572
|
+
if (!candidate) return;
|
|
1572
1573
|
const cand = typeof candidate === 'string' ? candidate : candidate.candidate;
|
|
1573
1574
|
if (!cand) return;
|
|
1574
1575
|
// NOTE: do NOT filter mDNS (.local) candidates. Firefox emits them by
|
|
@@ -1673,12 +1674,17 @@ class Peer {
|
|
|
1673
1674
|
}
|
|
1674
1675
|
// Older zero-query clients relayed only the bare `a=candidate:` string,
|
|
1675
1676
|
// which browsers reject with "missing values for both sdpMid and
|
|
1676
|
-
// sdpMLineIndex".
|
|
1677
|
+
// sdpMLineIndex". Pass sdpMLineIndex:0 and leave sdpMid null so
|
|
1678
|
+
// the browser binds the candidate to the first (and, under
|
|
1679
|
+
// bundlePolicy:'max-bundle', only) m-section. Passing sdpMid:''
|
|
1680
|
+
// is NOT equivalent to omitting it - the browser treats empty
|
|
1681
|
+
// string as a real mid and fails with "Failed to execute
|
|
1682
|
+
// 'addIceCandidate'" because no m-section has mid="".
|
|
1677
1683
|
const init = (typeof candidate === 'string')
|
|
1678
|
-
? { candidate, sdpMid:
|
|
1684
|
+
? { candidate, sdpMid: null, sdpMLineIndex: 0 }
|
|
1679
1685
|
: {
|
|
1680
1686
|
candidate: candidate.candidate,
|
|
1681
|
-
sdpMid: candidate.sdpMid != null ? candidate.sdpMid :
|
|
1687
|
+
sdpMid: candidate.sdpMid != null ? candidate.sdpMid : null,
|
|
1682
1688
|
sdpMLineIndex: candidate.sdpMLineIndex != null ? candidate.sdpMLineIndex : 0,
|
|
1683
1689
|
usernameFragment: candidate.usernameFragment != null ? candidate.usernameFragment : undefined,
|
|
1684
1690
|
};
|
|
@@ -10483,9 +10489,9 @@ $.TurnError = TurnError;
|
|
|
10483
10489
|
$.E2eeError = E2eeError;
|
|
10484
10490
|
|
|
10485
10491
|
// --- Meta ------------------------------------------------------------------
|
|
10486
|
-
$.version = '1.2.
|
|
10492
|
+
$.version = '1.2.15';
|
|
10487
10493
|
$.libSize = '~130 KB';
|
|
10488
|
-
$.unitTests = {"passed":2348,"failed":0,"total":2534,"suites":620,"duration":
|
|
10494
|
+
$.unitTests = {"passed":2348,"failed":0,"total":2534,"suites":620,"duration":7548,"ok":true};
|
|
10489
10495
|
$.meta = {}; // populated at build time by CLI bundler
|
|
10490
10496
|
|
|
10491
10497
|
// --- Environment detection -------------------------------------------------
|