zero-query 1.2.12 → 1.2.14
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 +64 -45
- 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 +11 -4
- package/dist/zquery.min.js +3 -3
- package/package.json +1 -1
- package/src/webrtc/peer.js +8 -1
- package/tests/webrtc/peer.test.js +11 -8
|
@@ -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
|
});
|
|
@@ -660,64 +660,83 @@ $.component('video-room', {
|
|
|
660
660
|
if (!hasShare) hint.push('screen sharing unavailable in this browser');
|
|
661
661
|
const hintLine = hint.length ? `<div class="device-hint">${this._icon('alert', 14)}<span>${$.escapeHtml(hint.join(' · '))}</span></div>` : '';
|
|
662
662
|
|
|
663
|
+
const totalPeers = availableRooms.reduce((n, r) => n + (r.peerCount || 0), 0);
|
|
664
|
+
const liveBadge = `<span class="live-dot" aria-hidden="true"></span>
|
|
665
|
+
<span>${availableRooms.length} ${availableRooms.length === 1 ? 'room' : 'rooms'} · ${totalPeers} online</span>`;
|
|
666
|
+
|
|
663
667
|
const roomsBlock = availableRooms.length > 0
|
|
664
668
|
? `<ul class="room-list">
|
|
665
669
|
${availableRooms.map((r) => {
|
|
666
|
-
const active
|
|
667
|
-
const
|
|
670
|
+
const active = r.name === roomName ? ' active' : '';
|
|
671
|
+
const isAlone = (r.peerCount || 0) <= 1;
|
|
672
|
+
const count = (r.peerCount === 1) ? '1 here' : ((r.peerCount || 0) + ' here');
|
|
673
|
+
const dots = Math.min(r.peerCount || 0, 4);
|
|
674
|
+
const dotsHtml = Array.from({ length: dots }).map((_, i) =>
|
|
675
|
+
`<span class="pip pip-${i % 3}"></span>`
|
|
676
|
+
).join('') || '<span class="pip pip-empty"></span>';
|
|
668
677
|
return `<li>
|
|
669
|
-
<button type="button" class="room-
|
|
670
|
-
<span class="room-
|
|
671
|
-
<span class="room-
|
|
678
|
+
<button type="button" class="room-card${active}" data-room="${$.escapeHtml(r.name)}" @click="pickRoom" ${connecting ? 'disabled' : ''}>
|
|
679
|
+
<span class="room-card-pips">${dotsHtml}</span>
|
|
680
|
+
<span class="room-card-body">
|
|
681
|
+
<span class="room-card-name">#${$.escapeHtml(r.name)}</span>
|
|
682
|
+
<span class="room-card-count">${count}</span>
|
|
683
|
+
</span>
|
|
684
|
+
<span class="room-card-cta">${isAlone ? 'be the first' : 'jump in'} →</span>
|
|
672
685
|
</button>
|
|
673
686
|
</li>`;
|
|
674
687
|
}).join('')}
|
|
675
688
|
</ul>`
|
|
676
|
-
: `<
|
|
689
|
+
: `<div class="room-list-empty">
|
|
690
|
+
${loadingRooms ? 'Looking for live rooms\u2026' : 'No live rooms right now. Type a name below and start one.'}
|
|
691
|
+
</div>`;
|
|
677
692
|
|
|
678
693
|
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'}
|
|
694
|
+
<div class="lobby-shell">
|
|
695
|
+
<div class="lobby-bg" aria-hidden="true"></div>
|
|
696
|
+
<div class="lobby">
|
|
697
|
+
<div class="lobby-hero">
|
|
698
|
+
<span class="eyebrow">${liveBadge}</span>
|
|
699
|
+
<h1>Spin up a room. Share whatever.</h1>
|
|
700
|
+
<p class="lead">
|
|
701
|
+
A no-signup mesh call wired straight into
|
|
702
|
+
<code>$.webrtc.join()</code> on top of
|
|
703
|
+
<a href="https://github.com/tonywied17/zero-server" target="_blank" rel="noopener">zero-server</a>.
|
|
704
|
+
Drop a name, send the link to a friend, and you'll be on a peer-to-peer
|
|
705
|
+
video call in two clicks. Camera, mic, and screen share are <em>off</em>
|
|
706
|
+
until you say so.
|
|
707
|
+
</p>
|
|
708
|
+
</div>
|
|
709
|
+
|
|
710
|
+
<form class="join-form" @submit="join">
|
|
711
|
+
<label>
|
|
712
|
+
<span class="field-label">Room</span>
|
|
713
|
+
<span class="field-prefix">#</span>
|
|
714
|
+
<input class="has-prefix" type="text" value="${$.escapeHtml(roomName)}" @input="setRoom" placeholder="lobby" autocomplete="off" spellcheck="false" ${connecting ? 'disabled' : ''} />
|
|
715
|
+
</label>
|
|
716
|
+
<label>
|
|
717
|
+
<span class="field-label">Your name</span>
|
|
718
|
+
<input type="text" value="${$.escapeHtml(displayName)}" @input="setName" placeholder="how should peers see you?" autocomplete="off" ${connecting ? 'disabled' : ''} />
|
|
719
|
+
</label>
|
|
720
|
+
<button type="submit" class="primary join-btn" ${connecting ? 'disabled' : ''}>
|
|
721
|
+
${connecting ? 'Connecting...' : 'Join room'}
|
|
713
722
|
</button>
|
|
723
|
+
</form>
|
|
724
|
+
|
|
725
|
+
<div class="rooms-section">
|
|
726
|
+
<div class="rooms-head">
|
|
727
|
+
<h2>Live right now</h2>
|
|
728
|
+
<button type="button" class="ghost" @click="refreshRooms" ${loadingRooms ? 'disabled' : ''}>
|
|
729
|
+
${loadingRooms ? 'Refreshing\u2026' : 'Refresh'}
|
|
730
|
+
</button>
|
|
731
|
+
</div>
|
|
732
|
+
${roomsBlock}
|
|
714
733
|
</div>
|
|
715
|
-
|
|
734
|
+
|
|
735
|
+
${hintLine}
|
|
736
|
+
<p class="status ${error ? 'error' : ''}">
|
|
737
|
+
${error ? $.escapeHtml(error) : $.escapeHtml(status)}
|
|
738
|
+
</p>
|
|
716
739
|
</div>
|
|
717
|
-
${hintLine}
|
|
718
|
-
<p class="status ${error ? 'error' : ''}">
|
|
719
|
-
${error ? $.escapeHtml(error) : $.escapeHtml(status)}
|
|
720
|
-
</p>
|
|
721
740
|
</div>
|
|
722
741
|
`;
|
|
723
742
|
},
|
|
@@ -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.14
|
|
3
3
|
* Lightweight Frontend Library
|
|
4
4
|
* https://github.com/tonywied17/zero-query
|
|
5
5
|
* (c) 2026 Anthony Wiedman - MIT License
|
|
@@ -1415,6 +1415,11 @@ class Peer {
|
|
|
1415
1415
|
this._maxIceCandidates = options.maxIceCandidates || DEFAULT_MAX_ICE_CANDIDATES;
|
|
1416
1416
|
this._sentCandidates = 0;
|
|
1417
1417
|
this._sigUnsub = [];
|
|
1418
|
+
// Serialize incoming remote events (offer/answer/ice) so an ICE frame
|
|
1419
|
+
// can never call addIceCandidate() while a setRemoteDescription() is
|
|
1420
|
+
// still in flight - that race throws "The remote description was null"
|
|
1421
|
+
// and leaves the PeerConnection stuck in `have-local-offer` forever.
|
|
1422
|
+
this._opChain = Promise.resolve();
|
|
1418
1423
|
|
|
1419
1424
|
this._attachPc();
|
|
1420
1425
|
this._attachSignaling();
|
|
@@ -1605,7 +1610,9 @@ class Peer {
|
|
|
1605
1610
|
const guard = (cb) => (msg) => {
|
|
1606
1611
|
if (this.closed) return;
|
|
1607
1612
|
if (!msg || msg.from !== this.id) return;
|
|
1608
|
-
|
|
1613
|
+
// Chain every remote event behind any prior in-flight op so SDP
|
|
1614
|
+
// and ICE never race against each other.
|
|
1615
|
+
this._opChain = this._opChain.then(() => cb(msg)).catch(() => {});
|
|
1609
1616
|
};
|
|
1610
1617
|
this._sigUnsub.push(
|
|
1611
1618
|
this.signaling.on('offer', guard((m) => this._onRemoteDescription('offer', m.sdp))),
|
|
@@ -10476,9 +10483,9 @@ $.TurnError = TurnError;
|
|
|
10476
10483
|
$.E2eeError = E2eeError;
|
|
10477
10484
|
|
|
10478
10485
|
// --- Meta ------------------------------------------------------------------
|
|
10479
|
-
$.version = '1.2.
|
|
10486
|
+
$.version = '1.2.14';
|
|
10480
10487
|
$.libSize = '~130 KB';
|
|
10481
|
-
$.unitTests = {"passed":2348,"failed":0,"total":2534,"suites":620,"duration":
|
|
10488
|
+
$.unitTests = {"passed":2348,"failed":0,"total":2534,"suites":620,"duration":8121,"ok":true};
|
|
10482
10489
|
$.meta = {}; // populated at build time by CLI bundler
|
|
10483
10490
|
|
|
10484
10491
|
// --- Environment detection -------------------------------------------------
|