@hadoku/watchparty-ui 0.44.20 → 0.45.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 (29) hide show
  1. package/dist/chunks/{AdminPanel-CYbxabgY.js → AdminPanel-BGVD6Qlb.js} +4 -4
  2. package/dist/chunks/{AdminPanel-CYbxabgY.js.map → AdminPanel-BGVD6Qlb.js.map} +1 -1
  3. package/dist/chunks/{GameModule-Df1rYrJo.js → GameModule-Dx3GHtRk.js} +244 -247
  4. package/dist/chunks/GameModule-Dx3GHtRk.js.map +1 -0
  5. package/dist/chunks/{MapEditor-DgbOaCT2.js → MapEditor-yvJbXXgw.js} +319 -325
  6. package/dist/chunks/MapEditor-yvJbXXgw.js.map +1 -0
  7. package/dist/chunks/{MediaDrawer-Da6-fLeV.js → MediaDrawer-DLFj0-te.js} +293 -297
  8. package/dist/chunks/MediaDrawer-DLFj0-te.js.map +1 -0
  9. package/dist/chunks/{PlaylistPanel-fy0YV4nH.js → PlaylistPanel-B0SWGlFy.js} +124 -126
  10. package/dist/chunks/PlaylistPanel-B0SWGlFy.js.map +1 -0
  11. package/dist/chunks/Room-BVyJK3zM.js +6165 -0
  12. package/dist/chunks/Room-BVyJK3zM.js.map +1 -0
  13. package/dist/chunks/{index-B92W398r.js → index-DOLbMWw6.js} +1626 -1618
  14. package/dist/chunks/index-DOLbMWw6.js.map +1 -0
  15. package/dist/chunks/{submissionsApi-CHcpF-It.js → submissionsApi-CXZf_xlm.js} +2 -2
  16. package/dist/chunks/{submissionsApi-CHcpF-It.js.map → submissionsApi-CXZf_xlm.js.map} +1 -1
  17. package/dist/index.d.ts +11 -2
  18. package/dist/index.js +1 -1
  19. package/dist/room-chrome.js +3 -2
  20. package/dist/room-chrome.js.map +1 -1
  21. package/dist/watchparty-ui.css +1 -1
  22. package/package.json +2 -2
  23. package/dist/chunks/GameModule-Df1rYrJo.js.map +0 -1
  24. package/dist/chunks/MapEditor-DgbOaCT2.js.map +0 -1
  25. package/dist/chunks/MediaDrawer-Da6-fLeV.js.map +0 -1
  26. package/dist/chunks/PlaylistPanel-fy0YV4nH.js.map +0 -1
  27. package/dist/chunks/Room-Ci6DN1Oi.js +0 -6137
  28. package/dist/chunks/Room-Ci6DN1Oi.js.map +0 -1
  29. package/dist/chunks/index-B92W398r.js.map +0 -1
@@ -1,51 +1,51 @@
1
- import { jsx as e, jsxs as c } from "react/jsx-runtime";
2
- import I, { useState as w, useEffect as $ } from "react";
3
- import { h as b, s as C, j as M, P as _, aN as L, aO as k, aP as S, aL as T, d as O, aQ as V, am as D, aR as A, U as B, aS as U, aD as j, aB as W } from "./index-B92W398r.js";
4
- import { d as E, S as P } from "./Room-Ci6DN1Oi.js";
5
- const F = 3e3, K = {
1
+ import { jsx as e, jsxs as i } from "react/jsx-runtime";
2
+ import P, { useState as y, useEffect as _ } from "react";
3
+ import { h as v, s as O, j as L, O as T, aN as D, aO as I, aP as C, aL as A, d as R, aQ as V, am as B, aR as U, U as W, aS as F, aD as j, aB as K } from "./index-DOLbMWw6.js";
4
+ import { d as $, S as M } from "./Room-BVyJK3zM.js";
5
+ const z = 3e3, G = {
6
6
  super_upvote: 4,
7
7
  upvote: 3,
8
8
  mid: 2,
9
9
  downvote: 1
10
- }, z = {
10
+ }, q = {
11
11
  // Bigger than the other marks on purpose: a super-upvote is the strongest
12
12
  // vote and was the hardest to see (a 10px star under the avatar).
13
- super_upvote: { icon: /* @__PURE__ */ e(P, { className: "w-4 h-4" }), color: "bg-warning-bg text-on-warning-bg rounded" },
13
+ super_upvote: { icon: /* @__PURE__ */ e(M, { className: "w-4 h-4" }), color: "bg-warning-bg text-on-warning-bg rounded" },
14
14
  upvote: { icon: "▲", color: "bg-success-bg text-on-success-bg rounded px-0.5" },
15
15
  mid: { icon: "─", color: "text-text-secondary" },
16
16
  downvote: { icon: "▼", color: "bg-danger-bg text-on-danger-bg rounded px-0.5" }
17
17
  };
18
- function G(o) {
18
+ function H(o) {
19
19
  const { playlist: a, votes: u } = o;
20
- return a.videos.map((i) => {
21
- let d = 0, r = 0;
22
- for (const m of Object.values(u)) {
23
- const g = m[i.mediaId];
24
- g && (d += K[g], r++);
20
+ return a.videos.map((l) => {
21
+ let c = 0, r = 0;
22
+ for (const d of Object.values(u)) {
23
+ const f = d[l.mediaId];
24
+ f && (c += G[f], r++);
25
25
  }
26
- const h = r > 0 ? d / r : 0;
27
- return { video: i, avgScore: h, voterCount: r };
28
- }).sort((i, d) => d.avgScore - i.avgScore);
26
+ const h = r > 0 ? c / r : 0;
27
+ return { video: l, avgScore: h, voterCount: r };
28
+ }).sort((l, c) => c.avgScore - l.avgScore);
29
29
  }
30
- function q({ results: o, onClose: a }) {
31
- const u = b((s) => s.roster), i = E(), d = G(o), [r, h] = w(null);
32
- $(() => {
30
+ function Q({ results: o, onClose: a }) {
31
+ const u = v((s) => s.roster), l = $(), c = H(o), [r, h] = y(null);
32
+ _(() => {
33
33
  if (!r) return;
34
- const s = setTimeout(() => h(null), F);
34
+ const s = setTimeout(() => h(null), z);
35
35
  return () => clearTimeout(s);
36
36
  }, [r]);
37
- const m = Object.keys(o.votes).filter(
37
+ const d = Object.keys(o.votes).filter(
38
38
  (s) => Object.keys(o.votes[s]).length > 0
39
- ), g = (s) => {
40
- const v = u[s]?.sprite ?? o.sprites?.[s];
41
- return _(v) ? v : void 0;
39
+ ), f = (s) => {
40
+ const x = u[s]?.sprite ?? o.sprites?.[s];
41
+ return T(x) ? x : void 0;
42
42
  };
43
- function N(s) {
43
+ function k(s) {
44
44
  if (r !== s) {
45
45
  h(s);
46
46
  return;
47
47
  }
48
- h(null), L(s);
48
+ h(null), D(s);
49
49
  }
50
50
  return /* @__PURE__ */ e(
51
51
  "div",
@@ -54,9 +54,9 @@ function q({ results: o, onClose: a }) {
54
54
  onClick: (s) => {
55
55
  s.target === s.currentTarget && a();
56
56
  },
57
- children: /* @__PURE__ */ c("div", { "data-testid": "reveal-popup", className: "bg-bg-card border border-border rounded-lg shadow-xl w-full max-w-2xl max-h-[80vh] flex flex-col mx-4", children: [
58
- /* @__PURE__ */ c("div", { className: "flex items-center justify-between px-4 py-3 border-b border-border shrink-0", children: [
59
- /* @__PURE__ */ c("h2", { className: "text-sm font-semibold text-text", children: [
57
+ children: /* @__PURE__ */ i("div", { "data-testid": "reveal-popup", className: "bg-bg-card border border-border rounded-lg shadow-xl w-full max-w-2xl max-h-[80vh] flex flex-col mx-4", children: [
58
+ /* @__PURE__ */ i("div", { className: "flex items-center justify-between px-4 py-3 border-b border-border shrink-0", children: [
59
+ /* @__PURE__ */ i("h2", { className: "text-sm font-semibold text-text", children: [
60
60
  o.playlist.name,
61
61
  " — Results"
62
62
  ] }),
@@ -70,18 +70,18 @@ function q({ results: o, onClose: a }) {
70
70
  }
71
71
  )
72
72
  ] }),
73
- m.length > 0 && /* @__PURE__ */ e("div", { className: "flex flex-wrap items-center gap-1.5 px-4 py-2 border-b border-border shrink-0", children: m.map((s) => {
74
- const v = g(s);
75
- return /* @__PURE__ */ c("span", { className: "reveal-voter", children: [
76
- v ? /* @__PURE__ */ e("img", { src: C(v), alt: "", crossOrigin: "anonymous", className: "w-4 h-4 rounded-full object-cover" }) : /* @__PURE__ */ e("span", { className: "w-4 h-4 rounded-full bg-bg-alt" }),
73
+ d.length > 0 && /* @__PURE__ */ e("div", { className: "flex flex-wrap items-center gap-1.5 px-4 py-2 border-b border-border shrink-0", children: d.map((s) => {
74
+ const x = f(s);
75
+ return /* @__PURE__ */ i("span", { className: "reveal-voter", children: [
76
+ x ? /* @__PURE__ */ e("img", { src: O(x), alt: "", crossOrigin: "anonymous", className: "w-4 h-4 rounded-full object-cover" }) : /* @__PURE__ */ e("span", { className: "w-4 h-4 rounded-full bg-bg-alt" }),
77
77
  /* @__PURE__ */ e("span", { className: "text-xs text-text truncate max-w-[8rem]", children: o.names[s] || s }),
78
- i && /* @__PURE__ */ e(
78
+ l && /* @__PURE__ */ e(
79
79
  "button",
80
80
  {
81
81
  type: "button",
82
82
  className: "reveal-voter-remove",
83
83
  "data-armed": r === s ? "" : void 0,
84
- onClick: () => N(s),
84
+ onClick: () => k(s),
85
85
  "aria-label": `Remove ${o.names[s] || s}'s votes`,
86
86
  title: r === s ? "Click again to remove" : `Remove ${o.names[s] || s}'s votes`,
87
87
  children: r === s ? "remove?" : "✕"
@@ -89,44 +89,44 @@ function q({ results: o, onClose: a }) {
89
89
  )
90
90
  ] }, s);
91
91
  }) }),
92
- /* @__PURE__ */ e("div", { className: "overflow-y-auto flex-1 py-1", children: d.map(({ video: s, avgScore: v, voterCount: t }, n) => /* @__PURE__ */ c(
92
+ /* @__PURE__ */ e("div", { className: "overflow-y-auto flex-1 py-1", children: c.map(({ video: s, avgScore: x, voterCount: S }, w) => /* @__PURE__ */ i(
93
93
  "div",
94
94
  {
95
- className: `flex items-center gap-3 px-4 py-2 ${n % 2 === 0 ? "" : "bg-bg-alt/20"}`,
95
+ className: `flex items-center gap-3 px-4 py-2 ${w % 2 === 0 ? "" : "bg-bg-alt/20"}`,
96
96
  children: [
97
- /* @__PURE__ */ e("span", { className: "text-xs text-text-muted w-5 shrink-0 text-right", children: n + 1 }),
97
+ /* @__PURE__ */ e("span", { className: "text-xs text-text-muted w-5 shrink-0 text-right", children: w + 1 }),
98
98
  /* @__PURE__ */ e("span", { className: "text-xs text-text truncate flex-1 min-w-0", children: s.displayName }),
99
- /* @__PURE__ */ c("div", { className: "flex items-center gap-1.5 shrink-0", children: [
100
- m.map((l) => {
101
- const x = o.votes[l]?.[s.mediaId];
102
- if (!x) return null;
103
- const f = g(l), p = z[x], y = M(f ?? null);
104
- return /* @__PURE__ */ c("div", { className: "flex flex-col items-center gap-0.5", children: [
105
- /* @__PURE__ */ e("div", { className: "relative w-6 h-6 shrink-0", children: f ? /* @__PURE__ */ e(
99
+ /* @__PURE__ */ i("div", { className: "flex items-center gap-1.5 shrink-0", children: [
100
+ d.map((t) => {
101
+ const n = o.votes[t]?.[s.mediaId];
102
+ if (!n) return null;
103
+ const m = f(t), p = q[n], b = L(m ?? null);
104
+ return /* @__PURE__ */ i("div", { className: "flex flex-col items-center gap-0.5", children: [
105
+ /* @__PURE__ */ e("div", { className: "relative w-6 h-6 shrink-0", children: m ? /* @__PURE__ */ e(
106
106
  "img",
107
107
  {
108
- src: C(f),
109
- alt: o.names[l] || l,
108
+ src: O(m),
109
+ alt: o.names[t] || t,
110
110
  crossOrigin: "anonymous",
111
111
  className: "w-6 h-6 rounded-full object-cover",
112
- style: { border: `1.5px solid ${y}` }
112
+ style: { border: `1.5px solid ${b}` }
113
113
  }
114
114
  ) : /* @__PURE__ */ e(
115
115
  "div",
116
116
  {
117
117
  className: "w-6 h-6 rounded-full bg-bg-alt",
118
- style: { border: `1.5px solid ${y}` }
118
+ style: { border: `1.5px solid ${b}` }
119
119
  }
120
120
  ) }),
121
121
  /* @__PURE__ */ e("span", { className: `text-[9px] leading-none font-bold ${p.color}`, children: p.icon })
122
- ] }, l);
122
+ ] }, t);
123
123
  }),
124
- m.map((l) => o.votes[l]?.[s.mediaId] ? null : /* @__PURE__ */ c("div", { className: "flex flex-col items-center gap-0.5 opacity-20", children: [
124
+ d.map((t) => o.votes[t]?.[s.mediaId] ? null : /* @__PURE__ */ i("div", { className: "flex flex-col items-center gap-0.5 opacity-20", children: [
125
125
  /* @__PURE__ */ e("div", { className: "w-6 h-6 rounded-full bg-bg-alt border border-border" }),
126
126
  /* @__PURE__ */ e("span", { className: "text-[9px] leading-none text-text-muted", children: "·" })
127
- ] }, `no-${l}`))
127
+ ] }, `no-${t}`))
128
128
  ] }),
129
- /* @__PURE__ */ e("span", { className: "text-xs text-text-secondary w-8 text-right shrink-0 font-medium", children: t > 0 ? v.toFixed(1) : "—" })
129
+ /* @__PURE__ */ e("span", { className: "text-xs text-text-secondary w-8 text-right shrink-0 font-medium", children: S > 0 ? x.toFixed(1) : "—" })
130
130
  ]
131
131
  },
132
132
  s.mediaId
@@ -135,40 +135,40 @@ function q({ results: o, onClose: a }) {
135
135
  }
136
136
  );
137
137
  }
138
- function H() {
138
+ function Y() {
139
139
  return /* @__PURE__ */ e("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.5, children: /* @__PURE__ */ e("polyline", { points: "18 15 12 9 6 15" }) });
140
140
  }
141
- function Q() {
141
+ function J() {
142
142
  return /* @__PURE__ */ e("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.5, children: /* @__PURE__ */ e("line", { x1: "5", y1: "12", x2: "19", y2: "12" }) });
143
143
  }
144
- function Y() {
144
+ function X() {
145
145
  return /* @__PURE__ */ e("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.5, children: /* @__PURE__ */ e("polyline", { points: "6 9 12 15 18 9" }) });
146
146
  }
147
- const R = [
148
- { value: "super_upvote", icon: /* @__PURE__ */ e(P, { className: "w-4 h-4" }), label: "Super" },
149
- { value: "upvote", icon: /* @__PURE__ */ e(H, {}), label: "Up" },
150
- { value: "mid", icon: /* @__PURE__ */ e(Q, {}), label: "Mid" },
151
- { value: "downvote", icon: /* @__PURE__ */ e(Y, {}), label: "Down" }
147
+ const E = [
148
+ { value: "super_upvote", icon: /* @__PURE__ */ e(M, { className: "w-4 h-4" }), label: "Super" },
149
+ { value: "upvote", icon: /* @__PURE__ */ e(Y, {}), label: "Up" },
150
+ { value: "mid", icon: /* @__PURE__ */ e(J, {}), label: "Mid" },
151
+ { value: "downvote", icon: /* @__PURE__ */ e(X, {}), label: "Down" }
152
152
  ];
153
- function J(o) {
154
- const a = Math.round(o / 1e3), u = Math.floor(a / 3600), i = Math.floor(a % 3600 / 60), d = a % 60;
155
- return u > 0 ? `${u}:${String(i).padStart(2, "0")}:${String(d).padStart(2, "0")}` : `${i}:${String(d).padStart(2, "0")}`;
153
+ function Z(o) {
154
+ const a = Math.round(o / 1e3), u = Math.floor(a / 3600), l = Math.floor(a % 3600 / 60), c = a % 60;
155
+ return u > 0 ? `${u}:${String(l).padStart(2, "0")}:${String(c).padStart(2, "0")}` : `${l}:${String(c).padStart(2, "0")}`;
156
156
  }
157
- function X() {
158
- const [o, a] = w(""), [u, i] = w(!1), [d, r] = w(null), h = () => {
159
- const m = o.trim();
160
- m && (i(!0), r(null), W(m), setTimeout(() => i(!1), 1500));
157
+ function ee() {
158
+ const [o, a] = y(""), [u, l] = y(!1), [c, r] = y(null), h = () => {
159
+ const d = o.trim();
160
+ d && (l(!0), r(null), K(d), setTimeout(() => l(!1), 1500));
161
161
  };
162
- return /* @__PURE__ */ c("div", { className: "p-4 flex flex-col gap-3", children: [
162
+ return /* @__PURE__ */ i("div", { className: "p-4 flex flex-col gap-3", children: [
163
163
  /* @__PURE__ */ e("p", { className: "text-xs text-text-muted", children: "Enter a playlist ID to load it for the room." }),
164
- /* @__PURE__ */ c("div", { className: "flex gap-2", children: [
164
+ /* @__PURE__ */ i("div", { className: "flex gap-2", children: [
165
165
  /* @__PURE__ */ e(
166
166
  "input",
167
167
  {
168
168
  type: "text",
169
169
  value: o,
170
- onChange: (m) => a(m.target.value),
171
- onKeyDown: (m) => m.key === "Enter" && h(),
170
+ onChange: (d) => a(d.target.value),
171
+ onKeyDown: (d) => d.key === "Enter" && h(),
172
172
  placeholder: "Playlist ID (e.g. april-4)",
173
173
  className: "modal-input flex-1 text-xs px-2 py-1.5",
174
174
  disabled: u
@@ -184,66 +184,64 @@ function X() {
184
184
  }
185
185
  )
186
186
  ] }),
187
- d && /* @__PURE__ */ e("p", { className: "text-xs bg-danger-bg text-on-danger-bg px-2 py-1 rounded", children: d })
187
+ c && /* @__PURE__ */ e("p", { className: "text-xs bg-danger-bg text-on-danger-bg px-2 py-1 rounded", children: c })
188
188
  ] });
189
189
  }
190
- function Z() {
190
+ function te() {
191
191
  return /* @__PURE__ */ e("div", { className: "flex-1 flex items-center justify-center p-4", children: /* @__PURE__ */ e("p", { className: "text-xs text-text-muted text-center", children: "No playlist loaded yet." }) });
192
192
  }
193
- function oe() {
194
- const o = E(), a = b((t) => t.playlist), u = b((t) => t.activePlaylistIndex), i = b((t) => t.myVotes), d = b((t) => t.voteProgress), r = b((t) => t.playlistResults), h = b((t) => t.snapshot.mediaId), [m, g] = w(!1);
195
- if (I.useEffect(() => {
196
- r && !r.update && g(!0);
197
- }, [r]), I.useEffect(() => {
193
+ function ne() {
194
+ const o = $(), a = v((t) => t.playlist), u = v((t) => t.activePlaylistIndex), l = v((t) => t.myVotes), c = v((t) => t.voteProgress), r = v((t) => t.playlistResults), h = v((t) => t.snapshot.mediaId), [d, f] = y(() => !!r && !r.update), [k, s] = y(r);
195
+ if (r !== k && (s(r), r && !r.update && f(!0)), P.useEffect(() => {
198
196
  if (!a || a.type !== "vote") return;
199
197
  const t = {};
200
198
  for (const n of a.videos) {
201
- const l = k.voteKey(S(), a.id, n.mediaId), x = T(l);
202
- x && R.some((f) => f.value === x) && (t[n.mediaId] = x);
199
+ const m = I.voteKey(C(), a.id, n.mediaId), p = A(m);
200
+ p && E.some((b) => b.value === p) && (t[n.mediaId] = p);
203
201
  }
204
- if (O("playlist", "Checked localStorage votes", { playlistId: a.id, found: Object.keys(t).length }), Object.keys(t).length !== 0) {
205
- b.getState().applyPlaylistState({ playlist: a, activeIndex: u, myVotes: { ...i, ...t } });
206
- for (const [n, l] of Object.entries(t))
207
- V(a.id, n, l);
202
+ if (R("playlist", "Checked localStorage votes", { playlistId: a.id, found: Object.keys(t).length }), Object.keys(t).length !== 0) {
203
+ v.getState().applyPlaylistState({ playlist: a, activeIndex: u, myVotes: { ...l, ...t } });
204
+ for (const [n, m] of Object.entries(t))
205
+ V(a.id, n, m);
208
206
  }
209
207
  }, [a?.id]), !a)
210
- return o ? /* @__PURE__ */ e(X, {}) : /* @__PURE__ */ e(Z, {});
211
- const N = a.type === "vote", s = d.length, v = (t, n) => {
212
- V(a.id, t, n), O("playlist", "Vote emitted", { playlistId: a.id, mediaId: t, vote: n }), j(k.voteKey(S(), a.id, t), n);
213
- const l = { ...i, [t]: n };
208
+ return o ? /* @__PURE__ */ e(ee, {}) : /* @__PURE__ */ e(te, {});
209
+ const x = a.type === "vote", S = c.length, w = (t, n) => {
210
+ V(a.id, t, n), R("playlist", "Vote emitted", { playlistId: a.id, mediaId: t, vote: n }), j(I.voteKey(C(), a.id, t), n);
211
+ const m = { ...l, [t]: n };
214
212
  if (n === "super_upvote")
215
- for (const [x, f] of Object.entries(l))
216
- x !== t && f === "super_upvote" && (l[x] = "upvote", j(k.voteKey(S(), a.id, x), "upvote"));
217
- b.getState().applyPlaylistState({
213
+ for (const [p, b] of Object.entries(m))
214
+ p !== t && b === "super_upvote" && (m[p] = "upvote", j(I.voteKey(C(), a.id, p), "upvote"));
215
+ v.getState().applyPlaylistState({
218
216
  playlist: a,
219
217
  activeIndex: u,
220
- myVotes: l
218
+ myVotes: m
221
219
  });
222
220
  };
223
- return /* @__PURE__ */ c("div", { className: "flex flex-col flex-1 min-h-0", children: [
224
- /* @__PURE__ */ c("div", { className: "flex items-center gap-2 px-3 py-2 border-b border-border shrink-0", children: [
221
+ return /* @__PURE__ */ i("div", { className: "flex flex-col flex-1 min-h-0", children: [
222
+ /* @__PURE__ */ i("div", { className: "flex items-center gap-2 px-3 py-2 border-b border-border shrink-0", children: [
225
223
  a.cover && /* @__PURE__ */ e(
226
224
  "img",
227
225
  {
228
- src: D(a.cover),
226
+ src: B(a.cover),
229
227
  alt: "",
230
228
  className: "w-8 h-8 object-cover rounded shrink-0 bg-bg-alt"
231
229
  }
232
230
  ),
233
231
  /* @__PURE__ */ e("span", { className: "text-xs font-medium text-text truncate flex-1", children: a.name }),
234
- o && N && /* @__PURE__ */ e(
232
+ o && x && /* @__PURE__ */ e(
235
233
  "button",
236
234
  {
237
- onClick: () => A(),
235
+ onClick: () => U(),
238
236
  className: "text-xs text-text-muted hover:text-text transition-colors cursor-pointer shrink-0",
239
237
  title: "Reveal votes to all",
240
238
  children: "Reveal"
241
239
  }
242
240
  )
243
241
  ] }),
244
- o && N && s > 0 && /* @__PURE__ */ e("div", { className: "px-3 py-1.5 border-b border-border shrink-0", children: /* @__PURE__ */ c("div", { className: "flex items-center gap-1.5", children: [
242
+ o && x && S > 0 && /* @__PURE__ */ e("div", { className: "px-3 py-1.5 border-b border-border shrink-0", children: /* @__PURE__ */ i("div", { className: "flex items-center gap-1.5", children: [
245
243
  /* @__PURE__ */ e("span", { className: "text-[10px] text-text-muted shrink-0", children: "Votes:" }),
246
- /* @__PURE__ */ e("div", { className: "flex gap-1 flex-wrap flex-1", children: d.map((t) => /* @__PURE__ */ c("span", { className: "text-[10px] text-text-secondary", children: [
244
+ /* @__PURE__ */ e("div", { className: "flex gap-1 flex-wrap flex-1", children: c.map((t) => /* @__PURE__ */ i("span", { className: "text-[10px] text-text-secondary", children: [
247
245
  t.name,
248
246
  " ",
249
247
  t.votedCount,
@@ -252,53 +250,53 @@ function oe() {
252
250
  ] }, t.clientId)) })
253
251
  ] }) }),
254
252
  /* @__PURE__ */ e("div", { className: "flex-1 overflow-y-auto", children: a.videos.map((t, n) => {
255
- const l = u === n, x = i[t.mediaId], f = h === t.mediaId;
256
- return /* @__PURE__ */ c(
253
+ const m = u === n, p = l[t.mediaId], b = h === t.mediaId;
254
+ return /* @__PURE__ */ i(
257
255
  "div",
258
256
  {
259
- className: `flex items-center gap-2 px-3 py-2 border-b border-border/50 group ${l ? "bg-primary/10" : "hover:bg-bg-alt/50"}`,
257
+ className: `flex items-center gap-2 px-3 py-2 border-b border-border/50 group ${m ? "bg-primary/10" : "hover:bg-bg-alt/50"}`,
260
258
  children: [
261
- /* @__PURE__ */ e("div", { className: "w-3.5 shrink-0 text-text-muted", children: f ? /* @__PURE__ */ e("svg", { className: "w-3.5 h-3.5 text-text", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ e("polygon", { points: "5 3 19 12 5 21 5 3" }) }) : /* @__PURE__ */ e("span", { className: "text-[10px] text-text-muted", children: n + 1 }) }),
262
- /* @__PURE__ */ c("div", { className: "relative shrink-0", children: [
259
+ /* @__PURE__ */ e("div", { className: "w-3.5 shrink-0 text-text-muted", children: b ? /* @__PURE__ */ e("svg", { className: "w-3.5 h-3.5 text-text", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ e("polygon", { points: "5 3 19 12 5 21 5 3" }) }) : /* @__PURE__ */ e("span", { className: "text-[10px] text-text-muted", children: n + 1 }) }),
260
+ /* @__PURE__ */ i("div", { className: "relative shrink-0", children: [
263
261
  /* @__PURE__ */ e(
264
262
  "img",
265
263
  {
266
- src: B(t.mediaId, t.thumbnail),
264
+ src: W(t.mediaId, t.thumbnail),
267
265
  alt: "",
268
266
  loading: "lazy",
269
- onError: (p) => {
270
- const y = p.currentTarget;
271
- (y.parentElement ?? y).style.display = "none";
267
+ onError: (g) => {
268
+ const N = g.currentTarget;
269
+ (N.parentElement ?? N).style.display = "none";
272
270
  },
273
271
  className: "w-14 h-10 object-cover rounded bg-bg-alt"
274
272
  }
275
273
  ),
276
- t.durationMs ? /* @__PURE__ */ e("span", { className: "absolute bottom-0 right-0 px-1 text-[9px] leading-tight rounded-tl rounded-br bg-bg-card/90 text-text-secondary", children: J(t.durationMs) }) : null
274
+ t.durationMs ? /* @__PURE__ */ e("span", { className: "absolute bottom-0 right-0 px-1 text-[9px] leading-tight rounded-tl rounded-br bg-bg-card/90 text-text-secondary", children: Z(t.durationMs) }) : null
277
275
  ] }),
278
276
  /* @__PURE__ */ e(
279
277
  "button",
280
278
  {
281
- className: `flex-1 text-left text-xs truncate cursor-pointer transition-colors min-w-0 ${o ? "hover:text-text text-text-secondary" : "text-text-secondary cursor-default"} ${l ? "text-text font-medium" : ""}`,
282
- onClick: o ? () => U(n) : void 0,
279
+ className: `flex-1 text-left text-xs truncate cursor-pointer transition-colors min-w-0 ${o ? "hover:text-text text-text-secondary" : "text-text-secondary cursor-default"} ${m ? "text-text font-medium" : ""}`,
280
+ onClick: o ? () => F(n) : void 0,
283
281
  title: o ? `Play: ${t.displayName}` : t.displayName,
284
282
  disabled: !o,
285
283
  children: t.displayName
286
284
  }
287
285
  ),
288
- N && /* @__PURE__ */ e("div", { className: "flex gap-0.5 shrink-0", children: R.map((p) => {
289
- const y = x === p.value;
286
+ x && /* @__PURE__ */ e("div", { className: "flex gap-0.5 shrink-0", children: E.map((g) => {
287
+ const N = p === g.value;
290
288
  return /* @__PURE__ */ e(
291
289
  "button",
292
290
  {
293
- onClick: () => v(t.mediaId, p.value),
294
- title: p.label,
291
+ onClick: () => w(t.mediaId, g.value),
292
+ title: g.label,
295
293
  className: "vote-btn",
296
- "data-vote": p.value,
297
- "data-selected": y ? "" : void 0,
298
- "aria-pressed": y,
299
- children: p.icon
294
+ "data-vote": g.value,
295
+ "data-selected": N ? "" : void 0,
296
+ "aria-pressed": N,
297
+ children: g.icon
300
298
  },
301
- p.value
299
+ g.value
302
300
  );
303
301
  }) })
304
302
  ]
@@ -306,16 +304,16 @@ function oe() {
306
304
  t.mediaId
307
305
  );
308
306
  }) }),
309
- N && m && r && /* @__PURE__ */ e(
310
- q,
307
+ x && d && r && /* @__PURE__ */ e(
308
+ Q,
311
309
  {
312
310
  results: r,
313
- onClose: () => g(!1)
311
+ onClose: () => f(!1)
314
312
  }
315
313
  )
316
314
  ] });
317
315
  }
318
316
  export {
319
- oe as default
317
+ ne as default
320
318
  };
321
- //# sourceMappingURL=PlaylistPanel-fy0YV4nH.js.map
319
+ //# sourceMappingURL=PlaylistPanel-B0SWGlFy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PlaylistPanel-B0SWGlFy.js","sources":["../../src/components/PlaylistRevealPopup.tsx","../../src/components/PlaylistPanel.tsx"],"sourcesContent":["import { useEffect, useState, type ReactNode } from 'react';\nimport type { TPlaylistResultsOut, TVoteValue } from '@watchparty/shared';\nimport { useAppStore } from '../lib/store';\nimport { spriteUrlFor, colorForSprite, isCharacterId } from '../lib/characters';\nimport { StarIcon } from './controls/Icons';\nimport { useIsLeader } from '../hooks/useStoreSelectors';\nimport { emitVoteRemove } from '../lib/socket';\n\n/** How long the ✕ stays armed after the first click before it disarms. */\nconst REMOVE_ARM_MS = 3000;\n\ninterface Props {\n results: TPlaylistResultsOut;\n onClose: () => void;\n}\n\nconst VOTE_WEIGHTS: Record<TVoteValue, number> = {\n super_upvote: 4,\n upvote: 3,\n mid: 2,\n downvote: 1,\n};\n\nconst VOTE_ICONS: Record<TVoteValue, { icon: ReactNode; color: string }> = {\n // Bigger than the other marks on purpose: a super-upvote is the strongest\n // vote and was the hardest to see (a 10px star under the avatar).\n super_upvote: { icon: <StarIcon className=\"w-4 h-4\" />, color: 'bg-warning-bg text-on-warning-bg rounded' },\n upvote: { icon: '▲', color: 'bg-success-bg text-on-success-bg rounded px-0.5' },\n mid: { icon: '─', color: 'text-text-secondary' },\n downvote: { icon: '▼', color: 'bg-danger-bg text-on-danger-bg rounded px-0.5' },\n};\n\nfunction computeScores(results: TPlaylistResultsOut) {\n const { playlist, votes } = results;\n return playlist.videos.map(video => {\n let totalScore = 0;\n let voterCount = 0;\n for (const clientVotes of Object.values(votes)) {\n const v = clientVotes[video.mediaId] as TVoteValue | undefined;\n if (v) { totalScore += VOTE_WEIGHTS[v]; voterCount++; }\n }\n const avgScore = voterCount > 0 ? totalScore / voterCount : 0;\n return { video, avgScore, voterCount };\n }).sort((a, b) => b.avgScore - a.avgScore);\n}\n\nexport default function PlaylistRevealPopup({ results, onClose }: Props) {\n const roster = useAppStore(s => s.roster);\n const isLeader = useIsLeader();\n const scored = computeScores(results);\n // Two clicks to remove: the first arms the ✕, the second sends. A stray\n // click mid-party must not silently drop someone's ballot.\n const [armed, setArmed] = useState<string | null>(null);\n useEffect(() => {\n if (!armed) return;\n const t = setTimeout(() => setArmed(null), REMOVE_ARM_MS);\n return () => clearTimeout(t);\n }, [armed]);\n\n // Ordered list of clients who voted\n const voters = Object.keys(results.votes).filter(\n id => Object.keys(results.votes[id]).length > 0\n );\n // Frozen at reveal by the server, so someone who has since LEFT (and is\n // gone from the live roster) still has an avatar.\n const spriteOf = (id: string) => {\n const sprite = roster[id]?.sprite ?? results.sprites?.[id];\n return isCharacterId(sprite) ? sprite : undefined;\n };\n\n function onRemove(voterKey: string) {\n if (armed !== voterKey) { setArmed(voterKey); return; }\n setArmed(null);\n emitVoteRemove(voterKey);\n }\n\n return (\n <div\n className=\"fixed inset-0 bg-black/70 flex items-center justify-center z-50\"\n onClick={e => { if (e.target === e.currentTarget) onClose(); }}\n >\n <div data-testid=\"reveal-popup\" className=\"bg-bg-card border border-border rounded-lg shadow-xl w-full max-w-2xl max-h-[80vh] flex flex-col mx-4\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-4 py-3 border-b border-border shrink-0\">\n <h2 className=\"text-sm font-semibold text-text\">\n {results.playlist.name} — Results\n </h2>\n <button\n onClick={onClose}\n aria-label=\"Close results\"\n className=\"p-1 text-text-muted hover:text-text transition-colors cursor-pointer\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n {/* Voters — the leader can drop one; the table regenerates for everyone */}\n {voters.length > 0 && (\n <div className=\"flex flex-wrap items-center gap-1.5 px-4 py-2 border-b border-border shrink-0\">\n {voters.map(id => {\n const spriteId = spriteOf(id);\n return (\n <span key={id} className=\"reveal-voter\">\n {spriteId\n ? <img src={spriteUrlFor(spriteId)} alt=\"\" crossOrigin=\"anonymous\" className=\"w-4 h-4 rounded-full object-cover\" />\n : <span className=\"w-4 h-4 rounded-full bg-bg-alt\" />}\n <span className=\"text-xs text-text truncate max-w-[8rem]\">{results.names[id] || id}</span>\n {isLeader && (\n <button\n type=\"button\"\n className=\"reveal-voter-remove\"\n data-armed={armed === id ? '' : undefined}\n onClick={() => onRemove(id)}\n aria-label={`Remove ${results.names[id] || id}'s votes`}\n title={armed === id ? 'Click again to remove' : `Remove ${results.names[id] || id}'s votes`}\n >\n {armed === id ? 'remove?' : '✕'}\n </button>\n )}\n </span>\n );\n })}\n </div>\n )}\n\n {/* Results list */}\n <div className=\"overflow-y-auto flex-1 py-1\">\n {scored.map(({ video, avgScore, voterCount }, rank) => (\n <div\n key={video.mediaId}\n className={`flex items-center gap-3 px-4 py-2 ${rank % 2 === 0 ? '' : 'bg-bg-alt/20'}`}\n >\n {/* Rank */}\n <span className=\"text-xs text-text-muted w-5 shrink-0 text-right\">{rank + 1}</span>\n\n {/* Video name */}\n <span className=\"text-xs text-text truncate flex-1 min-w-0\">\n {video.displayName}\n </span>\n\n {/* Per-voter avatars + vote icon */}\n <div className=\"flex items-center gap-1.5 shrink-0\">\n {voters.map(clientId => {\n const vote = results.votes[clientId]?.[video.mediaId] as TVoteValue | undefined;\n if (!vote) return null;\n const spriteId = spriteOf(clientId);\n const voteStyle = VOTE_ICONS[vote];\n const borderColor = colorForSprite(spriteId ?? null);\n\n return (\n <div key={clientId} className=\"flex flex-col items-center gap-0.5\">\n {/* Avatar */}\n <div className=\"relative w-6 h-6 shrink-0\">\n {spriteId ? (\n <img\n src={spriteUrlFor(spriteId)}\n alt={results.names[clientId] || clientId}\n crossOrigin=\"anonymous\"\n className=\"w-6 h-6 rounded-full object-cover\"\n style={{ border: `1.5px solid ${borderColor}` }}\n />\n ) : (\n <div\n className=\"w-6 h-6 rounded-full bg-bg-alt\"\n style={{ border: `1.5px solid ${borderColor}` }}\n />\n )}\n </div>\n {/* Vote icon */}\n <span className={`text-[9px] leading-none font-bold ${voteStyle.color}`}>\n {voteStyle.icon}\n </span>\n </div>\n );\n })}\n {/* Show dashes for non-voters */}\n {voters.map(clientId => {\n const voted = !!results.votes[clientId]?.[video.mediaId];\n if (voted) return null;\n return (\n <div key={`no-${clientId}`} className=\"flex flex-col items-center gap-0.5 opacity-20\">\n <div className=\"w-6 h-6 rounded-full bg-bg-alt border border-border\" />\n <span className=\"text-[9px] leading-none text-text-muted\">·</span>\n </div>\n );\n })}\n </div>\n\n {/* Avg score */}\n <span className=\"text-xs text-text-secondary w-8 text-right shrink-0 font-medium\">\n {voterCount > 0 ? avgScore.toFixed(1) : '—'}\n </span>\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n","import React, { useState } from 'react';\nimport { useAppStore } from '../lib/store';\nimport { useIsLeader } from '../hooks/useStoreSelectors';\nimport { emitPlaylistLoad, emitPlaylistSelect, emitVoteUpdate, emitRevealRequest } from '../lib/socket';\nimport { resolveMediaAsset, resolveCardImage } from '../lib/mediaUrls';\nimport { debug } from '../lib/debug';\nimport { STORAGE_KEYS } from '../lib/storageKeys';\nimport { voteOwnerScope } from '../lib/auth';\nimport type { TVoteValue } from '@watchparty/shared';\nimport PlaylistRevealPopup from './PlaylistRevealPopup';\nimport { StarIcon } from './controls/Icons';\nimport { readStored, writeStored } from '../lib/browserStorage';\n\n// ── Vote icons ────────────────────────────────────────────────────────────────\n\n\nfunction UpvoteIcon() {\n return (\n <svg className=\"w-3.5 h-3.5\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2.5}>\n <polyline points=\"18 15 12 9 6 15\" />\n </svg>\n );\n}\n\nfunction MidIcon() {\n return (\n <svg className=\"w-3.5 h-3.5\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2.5}>\n <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\" />\n </svg>\n );\n}\n\nfunction DownvoteIcon() {\n return (\n <svg className=\"w-3.5 h-3.5\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2.5}>\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n );\n}\n\n// Colours live in `.vote-btn` (styles.css), keyed by `data-vote`: a <button>'s\n// colour/background utilities are discarded by the unlayered `button` reset, so\n// the per-vote `text-*` classes these carried never rendered — every vote icon\n// was the same white, selected or not.\nconst VOTE_OPTIONS: { value: TVoteValue; icon: React.ReactNode; label: string }[] = [\n { value: 'super_upvote', icon: <StarIcon className=\"w-4 h-4\" />, label: 'Super' },\n { value: 'upvote', icon: <UpvoteIcon />, label: 'Up' },\n { value: 'mid', icon: <MidIcon />, label: 'Mid' },\n { value: 'downvote', icon: <DownvoteIcon />, label: 'Down' },\n];\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction formatDuration(ms: number): string {\n const total = Math.round(ms / 1000);\n const h = Math.floor(total / 3600);\n const m = Math.floor((total % 3600) / 60);\n const s = total % 60;\n return h > 0\n ? `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`\n : `${m}:${String(s).padStart(2, '0')}`;\n}\n\n// ── Load form (leader, no playlist loaded) ────────────────────────────────────\n\nfunction LoadForm() {\n const [input, setInput] = useState('');\n const [loading, setLoading] = useState(false);\n const [err, setErr] = useState<string | null>(null);\n\n const handleLoad = () => {\n const id = input.trim();\n if (!id) return;\n setLoading(true);\n setErr(null);\n emitPlaylistLoad(id);\n // Error will arrive via the 'error' socket event and show as a toast.\n // Reset loading state after a short delay.\n setTimeout(() => setLoading(false), 1500);\n };\n\n return (\n <div className=\"p-4 flex flex-col gap-3\">\n <p className=\"text-xs text-text-muted\">Enter a playlist ID to load it for the room.</p>\n <div className=\"flex gap-2\">\n <input\n type=\"text\"\n value={input}\n onChange={e => setInput(e.target.value)}\n onKeyDown={e => e.key === 'Enter' && handleLoad()}\n placeholder=\"Playlist ID (e.g. april-4)\"\n className=\"modal-input flex-1 text-xs px-2 py-1.5\"\n disabled={loading}\n />\n <button\n onClick={handleLoad}\n disabled={loading || !input.trim()}\n className=\"modal-btn-update text-xs px-3 py-1.5 rounded disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n Load\n </button>\n </div>\n {err && <p className=\"text-xs bg-danger-bg text-on-danger-bg px-2 py-1 rounded\">{err}</p>}\n </div>\n );\n}\n\n// ── Empty state (viewer, no playlist) ─────────────────────────────────────────\n\nfunction EmptyState() {\n return (\n <div className=\"flex-1 flex items-center justify-center p-4\">\n <p className=\"text-xs text-text-muted text-center\">No playlist loaded yet.</p>\n </div>\n );\n}\n\n// ── Main panel ────────────────────────────────────────────────────────────────\n\nexport default function PlaylistPanel() {\n const isLeader = useIsLeader();\n const playlist = useAppStore(s => s.playlist);\n const activeIndex = useAppStore(s => s.activePlaylistIndex);\n const myVotes = useAppStore(s => s.myVotes);\n const voteProgress = useAppStore(s => s.voteProgress);\n const results = useAppStore(s => s.playlistResults);\n const currentMediaId = useAppStore(s => s.snapshot.mediaId);\n\n // Show results popup when a reveal arrives. NOT on an `update` (a voter was\n // removed): that re-sends the table to everyone, and reopening it would put\n // the popup back in front of people who had already closed it.\n const [showResults, setShowResults] = useState(() => !!results && !results.update);\n const [seenResults, setSeenResults] = useState(results);\n if (results !== seenResults) {\n setSeenResults(results);\n if (results && !results.update) setShowResults(true);\n }\n\n // Restore votes from localStorage when a playlist is first loaded\n React.useEffect(() => {\n if (!playlist || playlist.type !== 'vote') return;\n const savedVotes: Record<string, TVoteValue> = {};\n for (const video of playlist.videos) {\n const key = STORAGE_KEYS.voteKey(voteOwnerScope(), playlist.id, video.mediaId);\n const saved = readStored(key) as TVoteValue | null;\n if (saved && VOTE_OPTIONS.some(o => o.value === saved)) {\n savedVotes[video.mediaId] = saved;\n }\n }\n debug('playlist', 'Checked localStorage votes', { playlistId: playlist.id, found: Object.keys(savedVotes).length });\n if (Object.keys(savedVotes).length === 0) return;\n useAppStore.getState().applyPlaylistState({ playlist, activeIndex, myVotes: { ...myVotes, ...savedVotes } });\n for (const [mediaId, vote] of Object.entries(savedVotes)) {\n emitVoteUpdate(playlist.id, mediaId, vote);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [playlist?.id]);\n\n if (!playlist) {\n return isLeader ? <LoadForm /> : <EmptyState />;\n }\n\n // `basic` playlists are a plain watch-through — no vote buttons, progress,\n // or reveal. Only `vote` playlists show the rating mechanic.\n const isVoting = playlist.type === 'vote';\n const totalUsers = voteProgress.length;\n\n const handleVote = (mediaId: string, vote: TVoteValue) => {\n emitVoteUpdate(playlist.id, mediaId, vote);\n debug('playlist', 'Vote emitted', { playlistId: playlist.id, mediaId, vote });\n writeStored(STORAGE_KEYS.voteKey(voteOwnerScope(), playlist.id, mediaId), vote);\n\n // Mirror the server's super_upvote exclusivity rule: if this vote is a star,\n // downgrade any other video's star to upvote so the visual ring updates immediately.\n const updatedVotes = { ...myVotes, [mediaId]: vote };\n if (vote === 'super_upvote') {\n for (const [otherId, otherVote] of Object.entries(updatedVotes)) {\n if (otherId !== mediaId && otherVote === 'super_upvote') {\n updatedVotes[otherId] = 'upvote';\n writeStored(STORAGE_KEYS.voteKey(voteOwnerScope(), playlist.id, otherId), 'upvote');\n }\n }\n }\n\n useAppStore.getState().applyPlaylistState({\n playlist,\n activeIndex,\n myVotes: updatedVotes,\n });\n };\n\n return (\n <div className=\"flex flex-col flex-1 min-h-0\">\n {/* Header */}\n <div className=\"flex items-center gap-2 px-3 py-2 border-b border-border shrink-0\">\n {playlist.cover && (\n <img\n src={resolveMediaAsset(playlist.cover)}\n alt=\"\"\n className=\"w-8 h-8 object-cover rounded shrink-0 bg-bg-alt\"\n />\n )}\n <span className=\"text-xs font-medium text-text truncate flex-1\">\n {playlist.name}\n </span>\n {isLeader && isVoting && (\n <button\n onClick={() => emitRevealRequest()}\n className=\"text-xs text-text-muted hover:text-text transition-colors cursor-pointer shrink-0\"\n title=\"Reveal votes to all\"\n >\n Reveal\n </button>\n )}\n </div>\n\n {/* Vote progress bar (leader only, vote playlists only) */}\n {isLeader && isVoting && totalUsers > 0 && (\n <div className=\"px-3 py-1.5 border-b border-border shrink-0\">\n <div className=\"flex items-center gap-1.5\">\n <span className=\"text-[10px] text-text-muted shrink-0\">Votes:</span>\n <div className=\"flex gap-1 flex-wrap flex-1\">\n {voteProgress.map(p => (\n <span key={p.clientId} className=\"text-[10px] text-text-secondary\">\n {p.name} {p.votedCount}/{p.totalVideos}\n </span>\n ))}\n </div>\n </div>\n </div>\n )}\n\n {/* Video list */}\n <div className=\"flex-1 overflow-y-auto\">\n {playlist.videos.map((video, i) => {\n const isActive = activeIndex === i;\n const myVote = myVotes[video.mediaId] as TVoteValue | undefined;\n const isCurrentMedia = currentMediaId === video.mediaId;\n\n return (\n <div\n key={video.mediaId}\n className={`flex items-center gap-2 px-3 py-2 border-b border-border/50 group ${\n isActive ? 'bg-primary/10' : 'hover:bg-bg-alt/50'\n }`}\n >\n {/* Play indicator */}\n <div className=\"w-3.5 shrink-0 text-text-muted\">\n {isCurrentMedia ? (\n <svg className=\"w-3.5 h-3.5 text-text\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <polygon points=\"5 3 19 12 5 21 5 3\" />\n </svg>\n ) : (\n <span className=\"text-[10px] text-text-muted\">{i + 1}</span>\n )}\n </div>\n\n {/* Thumbnail. No longer flat-playlist-only: `poster.jpg` is\n derived by convention when a clip carries no explicit\n thumbnail (see resolveCardImage), and a 404 hides the image. */}\n <div className=\"relative shrink-0\">\n <img\n src={resolveCardImage(video.mediaId, video.thumbnail)}\n alt=\"\"\n loading=\"lazy\"\n onError={e => {\n const el = e.currentTarget as HTMLImageElement;\n // Hide the whole slot, not just the img — the duration\n // badge is positioned against it.\n (el.parentElement ?? el).style.display = 'none';\n }}\n className=\"w-14 h-10 object-cover rounded bg-bg-alt\"\n />\n {video.durationMs ? (\n <span className=\"absolute bottom-0 right-0 px-1 text-[9px] leading-tight rounded-tl rounded-br bg-bg-card/90 text-text-secondary\">\n {formatDuration(video.durationMs)}\n </span>\n ) : null}\n </div>\n\n {/* Video name — leader can click to select */}\n <button\n className={`flex-1 text-left text-xs truncate cursor-pointer transition-colors min-w-0 ${\n isLeader\n ? 'hover:text-text text-text-secondary'\n : 'text-text-secondary cursor-default'\n } ${isActive ? 'text-text font-medium' : ''}`}\n onClick={isLeader ? () => emitPlaylistSelect(i) : undefined}\n title={isLeader ? `Play: ${video.displayName}` : video.displayName}\n disabled={!isLeader}\n >\n {video.displayName}\n </button>\n\n {/* Vote buttons (vote playlists only) */}\n {isVoting && (\n <div className=\"flex gap-0.5 shrink-0\">\n {VOTE_OPTIONS.map(opt => {\n const isSelected = myVote === opt.value;\n return (\n <button\n key={opt.value}\n onClick={() => handleVote(video.mediaId, opt.value)}\n title={opt.label}\n className=\"vote-btn\"\n data-vote={opt.value}\n data-selected={isSelected ? '' : undefined}\n aria-pressed={isSelected}\n >\n {opt.icon}\n </button>\n );\n })}\n </div>\n )}\n </div>\n );\n })}\n </div>\n\n {/* Results popup (vote playlists only) */}\n {isVoting && showResults && results && (\n <PlaylistRevealPopup\n results={results}\n onClose={() => setShowResults(false)}\n />\n )}\n </div>\n );\n}\n"],"names":["REMOVE_ARM_MS","VOTE_WEIGHTS","VOTE_ICONS","jsx","StarIcon","computeScores","results","playlist","votes","video","totalScore","voterCount","clientVotes","v","avgScore","a","b","PlaylistRevealPopup","onClose","roster","useAppStore","isLeader","useIsLeader","scored","armed","setArmed","useState","useEffect","t","voters","id","spriteOf","sprite","isCharacterId","onRemove","voterKey","emitVoteRemove","e","jsxs","spriteId","spriteUrlFor","rank","clientId","vote","voteStyle","borderColor","colorForSprite","UpvoteIcon","MidIcon","DownvoteIcon","VOTE_OPTIONS","formatDuration","ms","total","h","m","s","LoadForm","input","setInput","loading","setLoading","err","setErr","handleLoad","emitPlaylistLoad","EmptyState","PlaylistPanel","activeIndex","myVotes","voteProgress","currentMediaId","showResults","setShowResults","seenResults","setSeenResults","React","savedVotes","key","STORAGE_KEYS","voteOwnerScope","saved","readStored","o","debug","mediaId","emitVoteUpdate","isVoting","totalUsers","handleVote","writeStored","updatedVotes","otherId","otherVote","resolveMediaAsset","emitRevealRequest","p","i","isActive","myVote","isCurrentMedia","resolveCardImage","el","emitPlaylistSelect","opt","isSelected"],"mappings":"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"}