@bunnyland/ui-web 0.2.0

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 (57) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +211 -0
  3. package/assets/bunnyland-api.js +345 -0
  4. package/assets/bunnyland-play.js +1223 -0
  5. package/assets/bunnyland-ui.css +1634 -0
  6. package/assets/bunnyland-ui.js +795 -0
  7. package/dist/admin-widgets.d.ts +31 -0
  8. package/dist/admin-widgets.d.ts.map +1 -0
  9. package/dist/admin-widgets.js +100 -0
  10. package/dist/admin-widgets.js.map +1 -0
  11. package/dist/api.d.ts +37 -0
  12. package/dist/api.d.ts.map +1 -0
  13. package/dist/api.js +125 -0
  14. package/dist/api.js.map +1 -0
  15. package/dist/index.d.ts +7 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +7 -0
  18. package/dist/play.d.ts +265 -0
  19. package/dist/play.d.ts.map +1 -0
  20. package/dist/play.js +806 -0
  21. package/dist/play.js.map +1 -0
  22. package/dist/player-widgets.d.ts +11 -0
  23. package/dist/player-widgets.d.ts.map +1 -0
  24. package/dist/player-widgets.js +21 -0
  25. package/dist/player-widgets.js.map +1 -0
  26. package/dist/preact/auth.d.ts +37 -0
  27. package/dist/preact/auth.d.ts.map +1 -0
  28. package/dist/preact/components.d.ts +58 -0
  29. package/dist/preact/components.d.ts.map +1 -0
  30. package/dist/preact/theme.d.ts +16 -0
  31. package/dist/preact/theme.d.ts.map +1 -0
  32. package/dist/preact.d.ts +4 -0
  33. package/dist/preact.d.ts.map +1 -0
  34. package/dist/preact.js +488 -0
  35. package/dist/preact.js.map +1 -0
  36. package/dist/theme.d.ts +31 -0
  37. package/dist/theme.d.ts.map +1 -0
  38. package/dist/theme.js +165 -0
  39. package/dist/theme.js.map +1 -0
  40. package/dist/widgets.d.ts +7 -0
  41. package/dist/widgets.d.ts.map +1 -0
  42. package/dist/widgets.js +31 -0
  43. package/dist/widgets.js.map +1 -0
  44. package/docs/admin/custom-web-themes.md +112 -0
  45. package/docs/developer/design-language.md +116 -0
  46. package/package.json +101 -0
  47. package/src/admin-widgets.ts +189 -0
  48. package/src/api.ts +193 -0
  49. package/src/index.ts +6 -0
  50. package/src/play.ts +1151 -0
  51. package/src/player-widgets.ts +29 -0
  52. package/src/preact/auth.tsx +338 -0
  53. package/src/preact/components.tsx +249 -0
  54. package/src/preact/theme.tsx +72 -0
  55. package/src/preact.ts +3 -0
  56. package/src/theme.ts +225 -0
  57. package/src/widgets.ts +41 -0
package/dist/preact.js ADDED
@@ -0,0 +1,488 @@
1
+ import { ApiError as e, authMe as t, login as n, logout as r, rotateAuth as i, setPlayerAuth as a, subscribePlayerAuth as o } from "./api.js";
2
+ import { DEFAULT_THEME as s, THEME_CHANGE_EVENT as c, initTheme as l, setTheme as u, themeOptions as d } from "./theme.js";
3
+ import { useCallback as f, useContext as p, useEffect as m, useId as h, useMemo as g, useRef as _, useState as v } from "preact/hooks";
4
+ import { Fragment as y, jsx as b, jsxs as x } from "preact/jsx-runtime";
5
+ import { createContext as S } from "preact";
6
+ //#region src/preact/components.tsx
7
+ function C(...e) {
8
+ return e.filter(Boolean).join(" ");
9
+ }
10
+ function w({ class: e, variant: t, type: n = "button", ...r }) {
11
+ return /* @__PURE__ */ b("button", {
12
+ ...r,
13
+ type: n,
14
+ class: C(e, t && `bl-button-${t}`)
15
+ });
16
+ }
17
+ function T({ children: e, class: t, tone: n = "muted", ...r }) {
18
+ return /* @__PURE__ */ b("span", {
19
+ ...r,
20
+ class: C(t, "bl-status", `bl-status-${n}`),
21
+ children: e
22
+ });
23
+ }
24
+ function E({ children: e, class: t, ...n }) {
25
+ return /* @__PURE__ */ b("header", {
26
+ ...n,
27
+ class: C(t, "bl-toolbar"),
28
+ children: e
29
+ });
30
+ }
31
+ function D({ children: e, class: t, ...n }) {
32
+ return /* @__PURE__ */ b("div", {
33
+ ...n,
34
+ class: C(t, "toolbar-row"),
35
+ children: e
36
+ });
37
+ }
38
+ function O({ children: e, class: t, icon: n = "🐰", ...r }) {
39
+ return /* @__PURE__ */ x("span", {
40
+ ...r,
41
+ class: C(t, "toolbar-brand"),
42
+ children: [n, e]
43
+ });
44
+ }
45
+ function k({ children: e, class: t, title: n, tools: r, ...i }) {
46
+ return /* @__PURE__ */ x("section", {
47
+ ...i,
48
+ class: C(t, "pane"),
49
+ children: [(n || r) && /* @__PURE__ */ x("header", {
50
+ class: "pane-header",
51
+ children: [n && /* @__PURE__ */ b("span", {
52
+ class: "pane-title",
53
+ children: n
54
+ }), r && /* @__PURE__ */ b("span", {
55
+ class: "pane-tools",
56
+ children: r
57
+ })]
58
+ }), e]
59
+ });
60
+ }
61
+ function A({ children: e, class: t, description: n, label: r, wide: i, ...a }) {
62
+ return /* @__PURE__ */ x("label", {
63
+ ...a,
64
+ class: C(t, "field", i && "wide"),
65
+ children: [
66
+ /* @__PURE__ */ b("span", {
67
+ class: "field-label",
68
+ children: r
69
+ }),
70
+ e,
71
+ n && /* @__PURE__ */ b("span", {
72
+ class: "hint",
73
+ children: n
74
+ })
75
+ ]
76
+ });
77
+ }
78
+ function j({ children: e, class: t, ...n }) {
79
+ return /* @__PURE__ */ b("span", {
80
+ ...n,
81
+ class: C(t, "pill"),
82
+ children: e
83
+ });
84
+ }
85
+ function M({ children: e, class: t, ...n }) {
86
+ return /* @__PURE__ */ b("div", {
87
+ ...n,
88
+ class: C(t, "empty"),
89
+ children: e
90
+ });
91
+ }
92
+ function N({ disabled: e = !1, emptyLabel: t = "No matches", id: n, label: r, onChange: i, options: a, placeholder: o = "search...", value: s = "" }) {
93
+ let c = h(), l = n || c, u = `${l}-listbox`, d = a.find((e) => e.value === s) || null, [f, p] = v(d?.label || ""), [_, y] = v(!1), [S, w] = v(0), T = g(() => {
94
+ let e = f.trim().toLowerCase();
95
+ return e ? a.filter((t) => t.label.toLowerCase().includes(e) || t.value.toLowerCase().includes(e)) : a;
96
+ }, [a, f]), E = d?.label || "";
97
+ m(() => p(E), [E, s]);
98
+ let D = (e) => {
99
+ p(e.label), y(!1), i(e.value, e);
100
+ };
101
+ return /* @__PURE__ */ x("span", {
102
+ class: "search-dropdown",
103
+ children: [
104
+ r && /* @__PURE__ */ b("label", {
105
+ for: l,
106
+ children: r
107
+ }),
108
+ /* @__PURE__ */ b("input", {
109
+ id: l,
110
+ class: "search-dropdown-input",
111
+ type: "text",
112
+ value: f,
113
+ placeholder: o,
114
+ disabled: e,
115
+ autocomplete: "off",
116
+ role: "combobox",
117
+ "aria-autocomplete": "list",
118
+ "aria-controls": u,
119
+ "aria-expanded": _,
120
+ onFocus: () => y(!0),
121
+ onBlur: () => y(!1),
122
+ onInput: (e) => {
123
+ p(e.currentTarget.value), w(0), y(!0), e.currentTarget.value || i("", null);
124
+ },
125
+ onKeyDown: (e) => {
126
+ e.key === "ArrowDown" ? (e.preventDefault(), w((e) => Math.min(e + 1, T.length - 1))) : e.key === "ArrowUp" ? (e.preventDefault(), w((e) => Math.max(e - 1, 0))) : e.key === "Enter" && T[S] ? (e.preventDefault(), D(T[S])) : e.key === "Escape" && y(!1);
127
+ }
128
+ }),
129
+ _ && /* @__PURE__ */ b("div", {
130
+ id: u,
131
+ class: "search-dropdown-menu",
132
+ role: "listbox",
133
+ children: T.length ? T.map((e, t) => /* @__PURE__ */ b("button", {
134
+ type: "button",
135
+ class: C("search-dropdown-option", t === S && "active"),
136
+ role: "option",
137
+ "aria-selected": e.value === s,
138
+ onMouseDown: (e) => e.preventDefault(),
139
+ onClick: () => D(e),
140
+ children: e.label
141
+ }, e.value)) : /* @__PURE__ */ b("div", {
142
+ class: "search-dropdown-empty",
143
+ children: t
144
+ })
145
+ })
146
+ ]
147
+ });
148
+ }
149
+ function P({ addLabel: e = "Add Tag", disabled: t = !1, emptyLabel: n = "No tags.", onChange: r, placeholder: i = "add tag...", value: a }) {
150
+ let [o, s] = v(""), c = () => {
151
+ let e = o.trim();
152
+ e && (a.includes(e) || r([...a, e]), s(""));
153
+ };
154
+ return /* @__PURE__ */ x("div", {
155
+ class: "tag-editor",
156
+ children: [/* @__PURE__ */ b("div", {
157
+ class: "tag-list",
158
+ children: a.length ? a.map((e) => /* @__PURE__ */ x("span", {
159
+ class: "tag-pill",
160
+ children: [/* @__PURE__ */ b("span", { children: e }), /* @__PURE__ */ b(w, {
161
+ disabled: t,
162
+ "aria-label": `Remove tag ${e}`,
163
+ onClick: () => r(a.filter((t) => t !== e)),
164
+ children: "x"
165
+ })]
166
+ }, e)) : /* @__PURE__ */ b("span", {
167
+ class: "tiny",
168
+ children: n
169
+ })
170
+ }), /* @__PURE__ */ x("div", {
171
+ class: "tag-entry",
172
+ children: [/* @__PURE__ */ b("input", {
173
+ type: "text",
174
+ value: o,
175
+ placeholder: i,
176
+ disabled: t,
177
+ onInput: (e) => s(e.currentTarget.value),
178
+ onKeyDown: (e) => {
179
+ e.key === "Enter" && (e.preventDefault(), c());
180
+ }
181
+ }), /* @__PURE__ */ b(w, {
182
+ disabled: t,
183
+ onClick: c,
184
+ children: e
185
+ })]
186
+ })]
187
+ });
188
+ }
189
+ //#endregion
190
+ //#region src/preact/auth.tsx
191
+ var F = [
192
+ "character:profile",
193
+ "character:chat",
194
+ "world:play",
195
+ "world:admin"
196
+ ], I = {
197
+ "character:profile": "Character profile",
198
+ "character:chat": "Character chat",
199
+ "world:play": "World play",
200
+ "world:admin": "World administration"
201
+ }, L = S(null);
202
+ function R(e) {
203
+ return typeof e == "string" && F.includes(e);
204
+ }
205
+ function z(e) {
206
+ if (!e || typeof e != "object") throw Error("Invalid authentication response");
207
+ let t = e;
208
+ if (typeof t.subject != "string" || !Array.isArray(t.scopes)) throw Error("Invalid authentication response");
209
+ return {
210
+ subject: t.subject,
211
+ scopes: t.scopes.filter(R),
212
+ expires_at: Number(t.expires_at || 0),
213
+ rotate_after: t.rotate_after == null ? null : Number(t.rotate_after),
214
+ rotation_eligible: !!t.rotation_eligible
215
+ };
216
+ }
217
+ function B(e) {
218
+ let t = new Set(e);
219
+ return t.has("world:admin") && t.add("world:play"), t.has("world:play") && (t.add("character:profile"), t.add("character:chat")), t;
220
+ }
221
+ function V(e, t) {
222
+ let n = B(e);
223
+ return t.every((e) => n.has(e));
224
+ }
225
+ function H(e) {
226
+ return e instanceof Error ? e.message : String(e);
227
+ }
228
+ function U({ close: e, login: t, scopes: n }) {
229
+ let r = _(null), [i, a] = v(""), [o, s] = v(""), [c, l] = v(""), [u, d] = v(!1);
230
+ m(() => {
231
+ let e = r.current;
232
+ if (e) return typeof e.showModal == "function" ? e.showModal() : e.setAttribute("open", ""), () => {
233
+ typeof e.close == "function" && e.open && e.close();
234
+ };
235
+ }, []);
236
+ let f = async (n) => {
237
+ if (n.preventDefault(), !(!i.trim() || !o || u)) {
238
+ d(!0), l("");
239
+ try {
240
+ await t(i.trim(), o), e();
241
+ } catch (e) {
242
+ l(H(e));
243
+ } finally {
244
+ d(!1);
245
+ }
246
+ }
247
+ };
248
+ return /* @__PURE__ */ b("dialog", {
249
+ "aria-labelledby": "bl-auth-title",
250
+ class: "bl-auth-dialog",
251
+ onCancel: (t) => {
252
+ t.preventDefault(), e();
253
+ },
254
+ ref: r,
255
+ children: /* @__PURE__ */ x("form", {
256
+ class: "bl-auth-form",
257
+ onSubmit: (e) => {
258
+ f(e);
259
+ },
260
+ children: [
261
+ /* @__PURE__ */ b("h2", {
262
+ id: "bl-auth-title",
263
+ children: "Sign in to Bunnyland"
264
+ }),
265
+ /* @__PURE__ */ x("label", { children: [/* @__PURE__ */ b("span", { children: "Username" }), /* @__PURE__ */ b("input", {
266
+ autocomplete: "username",
267
+ autofocus: !0,
268
+ disabled: u,
269
+ onInput: (e) => a(e.currentTarget.value),
270
+ required: !0,
271
+ value: i
272
+ })] }),
273
+ /* @__PURE__ */ x("label", { children: [/* @__PURE__ */ b("span", { children: "Password" }), /* @__PURE__ */ b("input", {
274
+ autocomplete: "current-password",
275
+ disabled: u,
276
+ onInput: (e) => s(e.currentTarget.value),
277
+ required: !0,
278
+ type: "password",
279
+ value: o
280
+ })] }),
281
+ c && /* @__PURE__ */ b(T, {
282
+ role: "alert",
283
+ tone: "error",
284
+ children: c
285
+ }),
286
+ /* @__PURE__ */ x("p", {
287
+ class: "bl-auth-scopes",
288
+ children: [
289
+ "Requested access: ",
290
+ n.map((e) => `${I[e]} (${e})`).join(", "),
291
+ ". Your account determines the access granted."
292
+ ]
293
+ }),
294
+ /* @__PURE__ */ x("div", {
295
+ class: "bl-auth-actions",
296
+ children: [/* @__PURE__ */ b(w, {
297
+ disabled: u,
298
+ onClick: e,
299
+ children: "Cancel"
300
+ }), /* @__PURE__ */ b(w, {
301
+ disabled: u || !i.trim() || !o,
302
+ type: "submit",
303
+ variant: "primary",
304
+ children: u ? "Signing in…" : "Sign in"
305
+ })]
306
+ })
307
+ ]
308
+ })
309
+ });
310
+ }
311
+ function W({ base: s, children: c }) {
312
+ let [l, u] = v(s ? "checking" : "idle"), [d, p] = v(null), [h, y] = v(""), [S, C] = v(null), w = _(0), T = _(""), E = f(async () => {
313
+ let n = ++w.current;
314
+ if (!s) {
315
+ p(null), y(""), u("idle");
316
+ return;
317
+ }
318
+ u("checking"), y("");
319
+ try {
320
+ let e = z(await t(s));
321
+ if (n !== w.current) return;
322
+ p(e), u("authenticated");
323
+ } catch (t) {
324
+ if (n !== w.current) return;
325
+ p(null), t instanceof e && t.status === 401 ? u("anonymous") : (y(H(t)), u("error"));
326
+ }
327
+ }, [s]);
328
+ m(() => (E(), o(() => {
329
+ E();
330
+ })), [E]);
331
+ let D = f(async (e, t) => {
332
+ let r = z(await n(s, e, t));
333
+ a(""), w.current += 1, p(r), y(""), u("authenticated");
334
+ }, [s]), O = f(async () => {
335
+ await r(s), a(""), w.current += 1, p(null), y(""), u("anonymous");
336
+ }, [s]);
337
+ m(() => {
338
+ if (l !== "authenticated" || !d) return;
339
+ let e = Date.now(), t = Math.max(0, d.expires_at * 1e3 - e), n = d.rotate_after == null ? Infinity : Math.max(0, d.rotate_after * 1e3 - e), r = Math.min(t, n, 2147483647), a = window.setTimeout(() => {
340
+ if (n <= t && d.rotate_after != null) {
341
+ let e = `${s}:${d.subject}:${d.rotate_after}`;
342
+ if (T.current === e) return;
343
+ T.current = e, i(s).then((e) => {
344
+ let t = z(e);
345
+ p(t), u("authenticated");
346
+ }).catch(() => {
347
+ E();
348
+ });
349
+ } else E();
350
+ }, r);
351
+ return () => window.clearTimeout(a);
352
+ }, [
353
+ s,
354
+ E,
355
+ d,
356
+ l
357
+ ]);
358
+ let k = f((e) => !!(d && V(d.scopes, e)), [d]), A = f((e) => C([...e]), []), j = f(() => C(null), []), M = g(() => ({
359
+ closeLogin: j,
360
+ error: h,
361
+ hasScopes: k,
362
+ login: D,
363
+ logout: O,
364
+ openLogin: A,
365
+ refresh: E,
366
+ session: d,
367
+ status: l
368
+ }), [
369
+ j,
370
+ h,
371
+ k,
372
+ D,
373
+ O,
374
+ A,
375
+ E,
376
+ d,
377
+ l
378
+ ]);
379
+ return /* @__PURE__ */ x(L.Provider, {
380
+ value: M,
381
+ children: [c, S && /* @__PURE__ */ b(U, {
382
+ close: j,
383
+ login: D,
384
+ scopes: S
385
+ })]
386
+ });
387
+ }
388
+ function G() {
389
+ let e = p(L);
390
+ if (!e) throw Error("useAuth must be used within an AuthProvider");
391
+ return e;
392
+ }
393
+ function K({ children: e, scopes: t }) {
394
+ let n = G();
395
+ if (n.status === "authenticated" && n.hasScopes(t)) return /* @__PURE__ */ b(y, { children: e });
396
+ if (n.status === "checking") return /* @__PURE__ */ b(M, {
397
+ class: "bl-auth-gate",
398
+ children: "Checking access…"
399
+ });
400
+ if (n.status === "idle") return /* @__PURE__ */ b(M, {
401
+ class: "bl-auth-gate",
402
+ children: "Choose a server to continue."
403
+ });
404
+ if (n.status === "error") return /* @__PURE__ */ x(M, {
405
+ class: "bl-auth-gate",
406
+ children: [/* @__PURE__ */ x(T, {
407
+ tone: "error",
408
+ children: ["Authentication check failed: ", n.error]
409
+ }), /* @__PURE__ */ b(w, {
410
+ onClick: () => {
411
+ n.refresh();
412
+ },
413
+ children: "Retry"
414
+ })]
415
+ });
416
+ if (n.status === "authenticated") {
417
+ let e = t.filter((e) => !B(n.session?.scopes || []).has(e));
418
+ return /* @__PURE__ */ x(M, {
419
+ class: "bl-auth-gate",
420
+ children: [
421
+ /* @__PURE__ */ x(T, {
422
+ tone: "error",
423
+ children: [
424
+ "Insufficient access for ",
425
+ n.session?.subject || "this account",
426
+ "."
427
+ ]
428
+ }),
429
+ /* @__PURE__ */ x("small", { children: [
430
+ "Missing: ",
431
+ e.map((e) => `${I[e]} (${e})`).join(", "),
432
+ "."
433
+ ] }),
434
+ /* @__PURE__ */ b(w, {
435
+ onClick: () => n.openLogin(t),
436
+ children: "Sign in as another user"
437
+ })
438
+ ]
439
+ });
440
+ }
441
+ return /* @__PURE__ */ x(M, {
442
+ class: "bl-auth-gate",
443
+ children: [/* @__PURE__ */ b("span", { children: "Sign in to continue." }), /* @__PURE__ */ b(w, {
444
+ onClick: () => n.openLogin(t),
445
+ variant: "primary",
446
+ children: "Login"
447
+ })]
448
+ });
449
+ }
450
+ //#endregion
451
+ //#region src/preact/theme.tsx
452
+ function q(e = document.documentElement, t = s) {
453
+ let [n, r] = v(() => l(e, t));
454
+ m(() => {
455
+ let t = (e) => {
456
+ let t = e.detail;
457
+ t?.theme && r(t.theme);
458
+ };
459
+ return e.addEventListener(c, t), () => e.removeEventListener(c, t);
460
+ }, [e]);
461
+ let i = f((t) => {
462
+ r(u(t, e));
463
+ }, [e]);
464
+ return {
465
+ options: d(),
466
+ setTheme: i,
467
+ theme: n
468
+ };
469
+ }
470
+ function J({ controller: e, defaultTheme: t = s, onChange: n, root: r, ...i }) {
471
+ let a = q(r || document.documentElement, t), o = e || a;
472
+ return /* @__PURE__ */ b("select", {
473
+ ...i,
474
+ value: o.theme,
475
+ onChange: (e) => {
476
+ let t = e.currentTarget.value;
477
+ o.setTheme(t), n?.(t);
478
+ },
479
+ children: o.options.map((e) => /* @__PURE__ */ b("option", {
480
+ value: e.value,
481
+ children: e.label
482
+ }, e.value))
483
+ });
484
+ }
485
+ //#endregion
486
+ export { F as AUTH_SCOPES, K as AuthGate, W as AuthProvider, w as Button, M as EmptyState, A as Field, k as Pane, j as Pill, N as SearchSelect, T as StatusText, P as TagEditor, J as ThemeSelect, E as Toolbar, O as ToolbarBrand, D as ToolbarRow, B as effectiveAuthScopes, V as hasAuthScopes, G as useAuth, q as useTheme };
487
+
488
+ //# sourceMappingURL=preact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preact.js","names":[],"sources":["../src/preact/components.tsx","../src/preact/auth.tsx","../src/preact/theme.tsx"],"sourcesContent":["import type { ComponentChildren, JSX } from 'preact';\nimport { useEffect, useId, useMemo, useState } from 'preact/hooks';\n\nexport type Tone = 'error' | 'info' | 'muted' | 'ok' | 'warn';\nexport type ButtonVariant = 'danger' | 'primary' | 'quiet' | 'secondary';\n\nfunction classNames(...values: Array<string | false | null | undefined>): string {\n return values.filter(Boolean).join(' ');\n}\n\nexport interface ButtonProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: ButtonVariant;\n}\n\nexport function Button({ class: className, variant, type = 'button', ...props }: ButtonProps): JSX.Element {\n return <button {...props} type={type} class={classNames(className as string, variant && `bl-button-${variant}`)} />;\n}\n\nexport interface StatusTextProps extends JSX.HTMLAttributes<HTMLSpanElement> {\n children: ComponentChildren;\n tone?: Tone;\n}\n\nexport function StatusText({ children, class: className, tone = 'muted', ...props }: StatusTextProps): JSX.Element {\n return <span {...props} class={classNames(className as string, 'bl-status', `bl-status-${tone}`)}>{children}</span>;\n}\n\nexport function Toolbar({ children, class: className, ...props }: JSX.HTMLAttributes<HTMLElement>): JSX.Element {\n return <header {...props} class={classNames(className as string, 'bl-toolbar')}>{children}</header>;\n}\n\nexport function ToolbarRow({ children, class: className, ...props }: JSX.HTMLAttributes<HTMLDivElement>): JSX.Element {\n return <div {...props} class={classNames(className as string, 'toolbar-row')}>{children}</div>;\n}\n\nexport interface ToolbarBrandProps extends JSX.HTMLAttributes<HTMLSpanElement> {\n icon?: ComponentChildren;\n}\n\nexport function ToolbarBrand({ children, class: className, icon = '🐰', ...props }: ToolbarBrandProps): JSX.Element {\n return <span {...props} class={classNames(className as string, 'toolbar-brand')}>{icon}{children}</span>;\n}\n\nexport interface PaneProps extends Omit<JSX.HTMLAttributes<HTMLElement>, 'title'> {\n children: ComponentChildren;\n title?: ComponentChildren;\n tools?: ComponentChildren;\n}\n\nexport function Pane({ children, class: className, title, tools, ...props }: PaneProps): JSX.Element {\n return (\n <section {...props} class={classNames(className as string, 'pane')}>\n {(title || tools) && (\n <header class=\"pane-header\">\n {title && <span class=\"pane-title\">{title}</span>}\n {tools && <span class=\"pane-tools\">{tools}</span>}\n </header>\n )}\n {children}\n </section>\n );\n}\n\nexport interface FieldProps extends JSX.HTMLAttributes<HTMLLabelElement> {\n children: ComponentChildren;\n description?: ComponentChildren;\n label: ComponentChildren;\n wide?: boolean;\n}\n\nexport function Field({ children, class: className, description, label, wide, ...props }: FieldProps): JSX.Element {\n return (\n <label {...props} class={classNames(className as string, 'field', wide && 'wide')}>\n <span class=\"field-label\">{label}</span>\n {children}\n {description && <span class=\"hint\">{description}</span>}\n </label>\n );\n}\n\nexport function Pill({ children, class: className, ...props }: JSX.HTMLAttributes<HTMLSpanElement>): JSX.Element {\n return <span {...props} class={classNames(className as string, 'pill')}>{children}</span>;\n}\n\nexport function EmptyState({ children, class: className, ...props }: JSX.HTMLAttributes<HTMLDivElement>): JSX.Element {\n return <div {...props} class={classNames(className as string, 'empty')}>{children}</div>;\n}\n\nexport interface SearchOption {\n label: string;\n value: string;\n}\n\nexport interface SearchSelectProps {\n disabled?: boolean;\n emptyLabel?: string;\n id?: string;\n label?: string;\n onChange: (value: string, option: SearchOption | null) => void;\n options: SearchOption[];\n placeholder?: string;\n value?: string;\n}\n\nexport function SearchSelect({\n disabled = false,\n emptyLabel = 'No matches',\n id,\n label,\n onChange,\n options,\n placeholder = 'search...',\n value = '',\n}: SearchSelectProps): JSX.Element {\n const generatedId = useId();\n const inputId = id || generatedId;\n const listboxId = `${inputId}-listbox`;\n const selected = options.find(option => option.value === value) || null;\n const [query, setQuery] = useState(selected?.label || '');\n const [open, setOpen] = useState(false);\n const [active, setActive] = useState(0);\n const filtered = useMemo(() => {\n const needle = query.trim().toLowerCase();\n if (!needle) return options;\n return options.filter(option => option.label.toLowerCase().includes(needle) || option.value.toLowerCase().includes(needle));\n }, [options, query]);\n const selectedLabel = selected?.label || '';\n useEffect(() => setQuery(selectedLabel), [selectedLabel, value]);\n const choose = (option: SearchOption): void => {\n setQuery(option.label);\n setOpen(false);\n onChange(option.value, option);\n };\n\n return (\n <span class=\"search-dropdown\">\n {label && <label for={inputId}>{label}</label>}\n <input\n id={inputId}\n class=\"search-dropdown-input\"\n type=\"text\"\n value={query}\n placeholder={placeholder}\n disabled={disabled}\n autocomplete=\"off\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n aria-controls={listboxId}\n aria-expanded={open}\n onFocus={(): void => setOpen(true)}\n onBlur={(): void => setOpen(false)}\n onInput={(event): void => {\n setQuery(event.currentTarget.value);\n setActive(0);\n setOpen(true);\n if (!event.currentTarget.value) onChange('', null);\n }}\n onKeyDown={(event): void => {\n if (event.key === 'ArrowDown') {\n event.preventDefault();\n setActive(index => Math.min(index + 1, filtered.length - 1));\n } else if (event.key === 'ArrowUp') {\n event.preventDefault();\n setActive(index => Math.max(index - 1, 0));\n } else if (event.key === 'Enter' && filtered[active]) {\n event.preventDefault();\n choose(filtered[active]);\n } else if (event.key === 'Escape') {\n setOpen(false);\n }\n }}\n />\n {open && (\n <div id={listboxId} class=\"search-dropdown-menu\" role=\"listbox\">\n {filtered.length ? filtered.map((option, index) => (\n <button\n key={option.value}\n type=\"button\"\n class={classNames('search-dropdown-option', index === active && 'active')}\n role=\"option\"\n aria-selected={option.value === value}\n onMouseDown={(event): void => event.preventDefault()}\n onClick={(): void => choose(option)}\n >\n {option.label}\n </button>\n )) : <div class=\"search-dropdown-empty\">{emptyLabel}</div>}\n </div>\n )}\n </span>\n );\n}\n\nexport interface TagEditorProps {\n addLabel?: string;\n disabled?: boolean;\n emptyLabel?: string;\n onChange: (tags: string[]) => void;\n placeholder?: string;\n value: string[];\n}\n\nexport function TagEditor({\n addLabel = 'Add Tag',\n disabled = false,\n emptyLabel = 'No tags.',\n onChange,\n placeholder = 'add tag...',\n value,\n}: TagEditorProps): JSX.Element {\n const [draft, setDraft] = useState('');\n const add = (): void => {\n const tag = draft.trim();\n if (!tag) return;\n if (!value.includes(tag)) onChange([...value, tag]);\n setDraft('');\n };\n return (\n <div class=\"tag-editor\">\n <div class=\"tag-list\">\n {value.length ? value.map(tag => (\n <span class=\"tag-pill\" key={tag}>\n <span>{tag}</span>\n <Button\n disabled={disabled}\n aria-label={`Remove tag ${tag}`}\n onClick={(): void => onChange(value.filter(item => item !== tag))}\n >x</Button>\n </span>\n )) : <span class=\"tiny\">{emptyLabel}</span>}\n </div>\n <div class=\"tag-entry\">\n <input\n type=\"text\"\n value={draft}\n placeholder={placeholder}\n disabled={disabled}\n onInput={(event): void => setDraft(event.currentTarget.value)}\n onKeyDown={(event): void => {\n if (event.key !== 'Enter') return;\n event.preventDefault();\n add();\n }}\n />\n <Button disabled={disabled} onClick={add}>{addLabel}</Button>\n </div>\n </div>\n );\n}\n","import type { ComponentChildren, JSX } from 'preact';\nimport { createContext } from 'preact';\nimport { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'preact/hooks';\n\nimport {\n ApiError,\n authMe,\n login as createSession,\n logout as deleteSession,\n rotateAuth,\n setPlayerAuth,\n subscribePlayerAuth,\n} from '../api';\nimport { Button, EmptyState, StatusText } from './components';\n\nexport const AUTH_SCOPES = [\n 'character:profile',\n 'character:chat',\n 'world:play',\n 'world:admin',\n] as const;\n\nexport type AuthScope = typeof AUTH_SCOPES[number];\nexport type AuthStatus = 'anonymous' | 'authenticated' | 'checking' | 'error' | 'idle';\n\nexport interface AuthSession {\n expires_at: number;\n rotate_after: number | null;\n rotation_eligible: boolean;\n scopes: AuthScope[];\n subject: string;\n}\n\nexport interface AuthContextValue {\n closeLogin: () => void;\n error: string;\n hasScopes: (scopes: readonly AuthScope[]) => boolean;\n login: (username: string, password: string) => Promise<void>;\n logout: () => Promise<void>;\n openLogin: (scopes: readonly AuthScope[]) => void;\n refresh: () => Promise<void>;\n session: AuthSession | null;\n status: AuthStatus;\n}\n\ninterface AuthProviderProps {\n base: string;\n children: ComponentChildren;\n}\n\ninterface AuthGateProps {\n children: ComponentChildren;\n scopes: readonly AuthScope[];\n}\n\nconst SCOPE_LABELS: Record<AuthScope, string> = {\n 'character:profile': 'Character profile',\n 'character:chat': 'Character chat',\n 'world:play': 'World play',\n 'world:admin': 'World administration',\n};\n\nconst AuthContext = createContext<AuthContextValue | null>(null);\n\nfunction isAuthScope(value: unknown): value is AuthScope {\n return typeof value === 'string' && (AUTH_SCOPES as readonly string[]).includes(value);\n}\n\nfunction parseSession(value: unknown): AuthSession {\n if (!value || typeof value !== 'object') throw new Error('Invalid authentication response');\n const record = value as Record<string, unknown>;\n if (typeof record.subject !== 'string' || !Array.isArray(record.scopes)) {\n throw new Error('Invalid authentication response');\n }\n return {\n subject: record.subject,\n scopes: record.scopes.filter(isAuthScope),\n expires_at: Number(record.expires_at || 0),\n rotate_after: record.rotate_after == null ? null : Number(record.rotate_after),\n rotation_eligible: Boolean(record.rotation_eligible),\n };\n}\n\nexport function effectiveAuthScopes(scopes: readonly AuthScope[]): ReadonlySet<AuthScope> {\n const effective = new Set(scopes);\n if (effective.has('world:admin')) effective.add('world:play');\n if (effective.has('world:play')) {\n effective.add('character:profile');\n effective.add('character:chat');\n }\n return effective;\n}\n\nexport function hasAuthScopes(\n granted: readonly AuthScope[],\n required: readonly AuthScope[],\n): boolean {\n const effective = effectiveAuthScopes(granted);\n return required.every(scope => effective.has(scope));\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction LoginDialog({\n close,\n login,\n scopes,\n}: {\n close: () => void;\n login: (username: string, password: string) => Promise<void>;\n scopes: readonly AuthScope[];\n}): JSX.Element {\n const dialogRef = useRef<HTMLDialogElement>(null);\n const [username, setUsername] = useState('');\n const [password, setPassword] = useState('');\n const [error, setError] = useState('');\n const [submitting, setSubmitting] = useState(false);\n\n useEffect(() => {\n const dialog = dialogRef.current;\n if (!dialog) return;\n if (typeof dialog.showModal === 'function') dialog.showModal();\n else dialog.setAttribute('open', '');\n return () => {\n if (typeof dialog.close === 'function' && dialog.open) dialog.close();\n };\n }, []);\n\n const submit = async (event: JSX.TargetedSubmitEvent<HTMLFormElement>): Promise<void> => {\n event.preventDefault();\n if (!username.trim() || !password || submitting) return;\n setSubmitting(true);\n setError('');\n try {\n await login(username.trim(), password);\n close();\n } catch (loginError) {\n setError(errorMessage(loginError));\n } finally {\n setSubmitting(false);\n }\n };\n\n return (\n <dialog\n aria-labelledby=\"bl-auth-title\"\n class=\"bl-auth-dialog\"\n onCancel={(event): void => { event.preventDefault(); close(); }}\n ref={dialogRef}\n >\n <form class=\"bl-auth-form\" onSubmit={(event): void => { void submit(event); }}>\n <h2 id=\"bl-auth-title\">Sign in to Bunnyland</h2>\n <label>\n <span>Username</span>\n <input\n autocomplete=\"username\"\n autofocus\n disabled={submitting}\n onInput={(event): void => setUsername(event.currentTarget.value)}\n required\n value={username}\n />\n </label>\n <label>\n <span>Password</span>\n <input\n autocomplete=\"current-password\"\n disabled={submitting}\n onInput={(event): void => setPassword(event.currentTarget.value)}\n required\n type=\"password\"\n value={password}\n />\n </label>\n {error && <StatusText role=\"alert\" tone=\"error\">{error}</StatusText>}\n <p class=\"bl-auth-scopes\">\n Requested access: {scopes.map(scope => `${SCOPE_LABELS[scope]} (${scope})`).join(', ')}.\n Your account determines the access granted.\n </p>\n <div class=\"bl-auth-actions\">\n <Button disabled={submitting} onClick={close}>Cancel</Button>\n <Button disabled={submitting || !username.trim() || !password} type=\"submit\" variant=\"primary\">\n {submitting ? 'Signing in…' : 'Sign in'}\n </Button>\n </div>\n </form>\n </dialog>\n );\n}\n\nexport function AuthProvider({ base, children }: AuthProviderProps): JSX.Element {\n const [status, setStatus] = useState<AuthStatus>(base ? 'checking' : 'idle');\n const [session, setSession] = useState<AuthSession | null>(null);\n const [error, setError] = useState('');\n const [loginScopes, setLoginScopes] = useState<readonly AuthScope[] | null>(null);\n const requestGeneration = useRef(0);\n const rotationAttempt = useRef('');\n\n const refresh = useCallback(async (): Promise<void> => {\n const generation = ++requestGeneration.current;\n if (!base) {\n setSession(null);\n setError('');\n setStatus('idle');\n return;\n }\n setStatus('checking');\n setError('');\n try {\n const next = parseSession(await authMe(base));\n if (generation !== requestGeneration.current) return;\n setSession(next);\n setStatus('authenticated');\n } catch (authError) {\n if (generation !== requestGeneration.current) return;\n setSession(null);\n if (authError instanceof ApiError && authError.status === 401) {\n setStatus('anonymous');\n } else {\n setError(errorMessage(authError));\n setStatus('error');\n }\n }\n }, [base]);\n\n useEffect(() => {\n void refresh();\n return subscribePlayerAuth(() => { void refresh(); });\n }, [refresh]);\n\n const login = useCallback(async (username: string, password: string): Promise<void> => {\n const next = parseSession(await createSession(base, username, password));\n setPlayerAuth('');\n requestGeneration.current += 1;\n setSession(next);\n setError('');\n setStatus('authenticated');\n }, [base]);\n\n const logout = useCallback(async (): Promise<void> => {\n await deleteSession(base);\n setPlayerAuth('');\n requestGeneration.current += 1;\n setSession(null);\n setError('');\n setStatus('anonymous');\n }, [base]);\n\n useEffect(() => {\n if (status !== 'authenticated' || !session) return;\n const now = Date.now();\n const expiresDelay = Math.max(0, session.expires_at * 1000 - now);\n const rotateDelay = session.rotate_after == null\n ? Number.POSITIVE_INFINITY\n : Math.max(0, session.rotate_after * 1000 - now);\n const delay = Math.min(expiresDelay, rotateDelay, 2147483647);\n const timer = window.setTimeout(() => {\n if (rotateDelay <= expiresDelay && session.rotate_after != null) {\n const attempt = `${base}:${session.subject}:${session.rotate_after}`;\n if (rotationAttempt.current === attempt) return;\n rotationAttempt.current = attempt;\n void rotateAuth(base)\n .then(value => {\n const next = parseSession(value);\n setSession(next);\n setStatus('authenticated');\n })\n .catch(() => { void refresh(); });\n } else {\n void refresh();\n }\n }, delay);\n return () => window.clearTimeout(timer);\n }, [base, refresh, session, status]);\n\n const hasScopes = useCallback((scopes: readonly AuthScope[]): boolean => (\n Boolean(session && hasAuthScopes(session.scopes, scopes))\n ), [session]);\n const openLogin = useCallback((scopes: readonly AuthScope[]): void => setLoginScopes([...scopes]), []);\n const closeLogin = useCallback((): void => setLoginScopes(null), []);\n const value = useMemo<AuthContextValue>(() => ({\n closeLogin,\n error,\n hasScopes,\n login,\n logout,\n openLogin,\n refresh,\n session,\n status,\n }), [closeLogin, error, hasScopes, login, logout, openLogin, refresh, session, status]);\n\n return (\n <AuthContext.Provider value={value}>\n {children}\n {loginScopes && <LoginDialog close={closeLogin} login={login} scopes={loginScopes} />}\n </AuthContext.Provider>\n );\n}\n\nexport function useAuth(): AuthContextValue {\n const auth = useContext(AuthContext);\n if (!auth) throw new Error('useAuth must be used within an AuthProvider');\n return auth;\n}\n\nexport function AuthGate({ children, scopes }: AuthGateProps): JSX.Element {\n const auth = useAuth();\n if (auth.status === 'authenticated' && auth.hasScopes(scopes)) return <>{children}</>;\n if (auth.status === 'checking') return <EmptyState class=\"bl-auth-gate\">Checking access…</EmptyState>;\n if (auth.status === 'idle') return <EmptyState class=\"bl-auth-gate\">Choose a server to continue.</EmptyState>;\n if (auth.status === 'error') {\n return (\n <EmptyState class=\"bl-auth-gate\">\n <StatusText tone=\"error\">Authentication check failed: {auth.error}</StatusText>\n <Button onClick={(): void => { void auth.refresh(); }}>Retry</Button>\n </EmptyState>\n );\n }\n if (auth.status === 'authenticated') {\n const missing = scopes.filter(scope => !effectiveAuthScopes(auth.session?.scopes || []).has(scope));\n return (\n <EmptyState class=\"bl-auth-gate\">\n <StatusText tone=\"error\">Insufficient access for {auth.session?.subject || 'this account'}.</StatusText>\n <small>Missing: {missing.map(scope => `${SCOPE_LABELS[scope]} (${scope})`).join(', ')}.</small>\n <Button onClick={(): void => auth.openLogin(scopes)}>Sign in as another user</Button>\n </EmptyState>\n );\n }\n return (\n <EmptyState class=\"bl-auth-gate\">\n <span>Sign in to continue.</span>\n <Button onClick={(): void => auth.openLogin(scopes)} variant=\"primary\">Login</Button>\n </EmptyState>\n );\n}\n","import type { JSX } from 'preact';\nimport { useCallback, useEffect, useState } from 'preact/hooks';\n\nimport {\n DEFAULT_THEME,\n THEME_CHANGE_EVENT,\n initTheme,\n setTheme,\n themeOptions,\n type ThemeOption,\n} from '../theme';\n\nexport interface ThemeController {\n options: ThemeOption[];\n setTheme: (theme: string) => void;\n theme: string;\n}\n\nexport function useTheme(\n root: HTMLElement = document.documentElement,\n defaultTheme = DEFAULT_THEME,\n): ThemeController {\n const [theme, setCurrentTheme] = useState(() => initTheme(root, defaultTheme));\n\n useEffect(() => {\n const handleThemeChange = (event: Event): void => {\n const detail = (event as CustomEvent<{ theme?: string }>).detail;\n if (detail?.theme) setCurrentTheme(detail.theme);\n };\n root.addEventListener(THEME_CHANGE_EVENT, handleThemeChange);\n return () => root.removeEventListener(THEME_CHANGE_EVENT, handleThemeChange);\n }, [root]);\n\n const updateTheme = useCallback((value: string): void => {\n setCurrentTheme(setTheme(value, root));\n }, [root]);\n\n return { options: themeOptions(), setTheme: updateTheme, theme };\n}\n\nexport interface ThemeSelectProps extends Omit<JSX.HTMLAttributes<HTMLSelectElement>, 'onChange' | 'value'> {\n controller?: ThemeController;\n defaultTheme?: string;\n onChange?: (theme: string) => void;\n root?: HTMLElement;\n}\n\nexport function ThemeSelect({\n controller,\n defaultTheme = DEFAULT_THEME,\n onChange,\n root,\n ...props\n}: ThemeSelectProps): JSX.Element {\n const fallback = useTheme(root || document.documentElement, defaultTheme);\n const themes = controller || fallback;\n return (\n <select\n {...props}\n value={themes.theme}\n onChange={(event): void => {\n const value = event.currentTarget.value;\n themes.setTheme(value);\n onChange?.(value);\n }}\n >\n {themes.options.map(option => (\n <option key={option.value} value={option.value}>{option.label}</option>\n ))}\n </select>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ export declare const THEME_KEY = "bunnyland.theme";
2
+ export declare const THEME_CLASS_PREFIX = "bl-theme-";
3
+ export declare const COLOR_SCHEME_KEY = "bunnyland.color-scheme";
4
+ export declare const COLOR_SCHEME_CLASS_PREFIX = "bl-color-scheme-";
5
+ export declare const DEFAULT_THEME = "purple-blue";
6
+ export declare const THEME_CHANGE_EVENT = "bunnyland:themechange";
7
+ export interface ThemeOption {
8
+ value: string;
9
+ label: string;
10
+ }
11
+ export type ColorScheme = 'auto' | 'dark' | 'light';
12
+ export declare const DEFAULT_THEME_OPTIONS: ThemeOption[];
13
+ export declare const THEME_OPTIONS: ThemeOption[];
14
+ export declare function themeFromSearch(search?: string): string | null;
15
+ export declare function normalizeTheme(name: string | null | undefined): string;
16
+ export declare function currentTheme(root?: HTMLElement): string;
17
+ export declare function currentColorScheme(root?: HTMLElement): ColorScheme;
18
+ export declare function setColorScheme(name: string, root?: HTMLElement): ColorScheme;
19
+ export declare function setTheme(name: string, root?: HTMLElement): string;
20
+ export declare function initTheme(root?: HTMLElement, defaultTheme?: string, search?: string): string;
21
+ export declare function themeOptions(): ThemeOption[];
22
+ export declare function colorSchemeOptions(): ThemeOption[];
23
+ export declare function registerThemeOption(option: ThemeOption): ThemeOption | null;
24
+ export declare function registerThemeOptions(options: ThemeOption[] | null | undefined): ThemeOption[];
25
+ export declare function bindThemeSelect(select: HTMLSelectElement | null): {
26
+ setValue: (value: string) => void;
27
+ } | null;
28
+ export declare function bindColorSchemeSelect(select: HTMLSelectElement | null): {
29
+ setValue: (value: string) => void;
30
+ } | null;
31
+ //# sourceMappingURL=theme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,SAAS,oBAAoB,CAAC;AAC3C,eAAO,MAAM,kBAAkB,cAAc,CAAC;AAC9C,eAAO,MAAM,gBAAgB,2BAA2B,CAAC;AACzD,eAAO,MAAM,yBAAyB,qBAAqB,CAAC;AAC5D,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAC3C,eAAO,MAAM,kBAAkB,0BAA0B,CAAC;AAE1D,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAEpD,eAAO,MAAM,qBAAqB,EAAE,WAAW,EAO9C,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,WAAW,EAAyD,CAAC;AAsEjG,wBAAgB,eAAe,CAAC,MAAM,SAAoC,GAAG,MAAM,GAAG,IAAI,CAKzF;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAGtE;AAED,wBAAgB,YAAY,CAAC,IAAI,GAAE,WAAsC,GAAG,MAAM,CAEjF;AAED,wBAAgB,kBAAkB,CAAC,IAAI,GAAE,WAAsC,GAAG,WAAW,CAE5F;AAkCD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,WAAsC,GAAG,WAAW,CAEtG;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,WAAsC,GAAG,MAAM,CAK3F;AAED,wBAAgB,SAAS,CACvB,IAAI,GAAE,WAAsC,EAC5C,YAAY,SAAgB,EAC5B,MAAM,SAAoC,GACzC,MAAM,CAUR;AAED,wBAAgB,YAAY,IAAI,WAAW,EAAE,CAE5C;AAED,wBAAgB,kBAAkB,IAAI,WAAW,EAAE,CAElD;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,GAAG,IAAI,CAQ3E;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,WAAW,EAAE,CAK7F;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,GAAG;IAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GAAG,IAAI,CAY9G;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,GAAG;IAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GAAG,IAAI,CAWpH"}