PageSourceSearch

https://www.hamienet.com/assets/index-w5lXls15.js

js hamienet.com collected 2026-10-02 04:59:06 UTC 89,879 bytes, 2,467 lines download raw bytes

1import { r as reactExports, j as jsxRuntimeExports } from "./vendor-react-Bay9Y7Lb.js";
2import { i as instance, B as Backend, a as Browser, b as initReactI18next } from "./vendor-i18n-BC7ZMXWQ.js";
3(function polyfill() {
4  const relList = document.createElement("link").relList;
5  if (relList && relList.supports && relList.supports("modulepreload")) {
6    return;
7  }
8  for (const link of document.querySelectorAll('link[rel="modulepreload"]')) {
9    processPreload(link);
10  }
11  new MutationObserver((mutations) => {
12    for (const mutation of mutations) {
13      if (mutation.type !== "childList") {
14        continue;
15      }
16      for (const node of mutation.addedNodes) {
17        if (node.tagName === "LINK" && node.rel === "modulepreload")
18          processPreload(node);
19      }
20    }
21  }).observe(document, { childList: true, subtree: true });
22  function getFetchOpts(link) {
23    const fetchOpts = {};
24    if (link.integrity) fetchOpts.integrity = link.integrity;
25    if (link.referrerPolicy) fetchOpts.referrerPolicy = link.referrerPolicy;
26    if (link.crossOrigin === "use-credentials")
27      fetchOpts.credentials = "include";
28    else if (link.crossOrigin === "anonymous") fetchOpts.credentials = "omit";
29    else fetchOpts.credentials = "same-origin";
30    return fetchOpts;
31  }
32  function processPreload(link) {
33    if (link.ep)
34      return;
35    link.ep = true;
36    const fetchOpts = getFetchOpts(link);
37    fetch(link.href, fetchOpts);
38  }
39})();
40class MemoryStorage {
41  constructor() {
42    this.data = {};
43  }
44  get length() {
45    return Object.keys(this.data).length;
46  }
47  clear() {
48    this.data = {};
49  }
50  getItem(key) {
51    return Object.prototype.hasOwnProperty.call(this.data, key) ? this.data[key] : null;
52  }
53  key(index) {
54    const keys = Object.keys(this.data);
55    return keys[index] || null;
56  }
57  removeItem(key) {
58    delete this.data[key];
59  }
60  setItem(key, value) {
61    this.data[key] = String(value);
62  }
63}
64const isStorageSupported = (type) => {
65  try {
66    const storage = window[type];
67    if (!storage) return false;
68    const testKey = "__storage_test__";
69    storage.setItem(testKey, testKey);
70    storage.removeItem(testKey);
71    return true;
72  } catch (e) {
73    return false;
74  }
75};
76if (!isStorageSupported("localStorage")) {
77  try {
78    const mockLocalStorage = new MemoryStorage();
79    Object.defineProperty(window, "localStorage", {
80      value: mockLocalStorage,
81      configurable: true,
82      writable: true,
83      enumerable: true
84    });
85    console.warn("localStorage is blocked by browser settings; using in-memory fallback.");
86  } catch (e) {
87    console.error("Failed to shim localStorage:", e);
88  }
89}
90if (!isStorageSupported("sessionStorage")) {
91  try {
92    const mockSessionStorage = new MemoryStorage();
93    Object.defineProperty(window, "sessionStorage", {
94      value: mockSessionStorage,
95      configurable: true,
96      writable: true,
97      enumerable: true
98    });
99    console.warn("sessionStorage is blocked by browser settings; using in-memory fallback.");
100  } catch (e) {
101    console.error("Failed to shim sessionStorage:", e);
102  }
103}
104class ApiClient {
105  constructor(apiBase2) {
106    console.log("[API] Initializing LIVE mode...", apiBase2);
107  }
108  getDataSource() {
109    return "LIVE";
110  }
111  async geolocate() {
112    return this.request("/system/geolocate");
113  }
114  async heartbeat() {
115    return this.request("/system/heartbeat");
116  }
117  async getAppVersionDate() {
118    return this.request("/system/version");
119  }
120  setDataSource(dataSource) {
121    console.warn(`[API] DataSource switching to ${dataSource} is disabled. System is locked to LIVE mode.`);
122  }
123  async get(endpoint, options = {}) {
124    return this.request(endpoint, {
125      method: "GET",
126      ...options
127    });
128  }
129  async post(endpoint, data = {}) {
130    return this.request(endpoint, {
131      method: "POST",
132      body: JSON.stringify(data)
133    });
134  }
135  async put(endpoint, data = {}) {
136    return this.request(endpoint, {
137      method: "PUT",
138      body: JSON.stringify(data)
139    });
140  }
141  async delete(endpoint, data) {
142    return this.request(endpoint, {
143      method: "DELETE",
144      ...data ? { body: JSON.stringify(data) } : {}
145    });
146  }
147  async request(endpoint, options = {}) {
148    const cleanEndpoint = endpoint.startsWith("/api") ? endpoint.slice(4) : endpoint;
149    const normalizedEndpoint = cleanEndpoint.startsWith("/") ? cleanEndpoint : `/${cleanEndpoint}`;
150    const start = performance.now();
151    const res = await fetch(`/api${normalizedEndpoint}`, {
152      ...options,
153      credentials: "include",
154      headers: {
155        "Content-Type": "application/json",
156        ...options.headers
157      }
158    });
159    const duration = performance.now() - start;
160    if (typeof window !== "undefined") {
161      try {
162        const queryTimeHeader = res.headers.get("X-Query-Time");
163        if (queryTimeHeader) {
164          const queryTime = parseFloat(queryTimeHeader);
165          if (!isNaN(queryTime)) {
166            const currentPath = window.location.pathname;
167            const globalCtx = window;
168            if (globalCtx.__lastPathname !== currentPath) {
169              globalCtx.__lastPathname = currentPath;
170              globalCtx.__pageQueryTime = 0;
171              globalCtx.__pageQueryCount = 0;
172              globalCtx.__pageApiTime = 0;
173            }
174            globalCtx.__pageQueryTime = (globalCtx.__pageQueryTime || 0) + queryTime;
175            globalCtx.__pageQueryCount = (globalCtx.__pageQueryCount || 0) + 1;
176            globalCtx.__pageApiTime = (globalCtx.__pageApiTime || 0) + duration;
177            window.dispatchEvent(new CustomEvent("page-query-timing", {
178              detail: {
179                queryTime: globalCtx.__pageQueryTime,
180                queryCount: globalCtx.__pageQueryCount,
181                apiTime: globalCtx.__pageApiTime
182              }
183            }));
184          }
185        }
186      } catch (err) {
187      }
188    }
189    if (!res.ok) {
190      const err = await res.json().catch(() => ({ error: res.statusText }));
191      throw new Error(err.error || `API Error: ${res.status} ${res.statusText}`);
192    }
193    return res.json();
194  }
195  async resetDatabase(target) {
196    await this.post("/admin/database/reset", { target });
197  }
198  // --- Internal Helpers ---
199  async purgeServerCache(trackId) {
200    await this.post("/internal/purge-cache", { trackId }).catch(() => {
201    });
202  }
203  // --- Database Inspection ---
204  async getDatabaseSchema() {
205    return this.request("/admin/database/schema").catch(() => []);
206  }
207  async getTableData(table, params) {
208    var _a;
209    const query = new URLSearchParams({
210      page: String(params.page || 1),
211      pageSize: String(params.pageSize || 10),
212      ...params.search && { search: params.search },
213      ...params.sortBy && { sortBy: params.sortBy },
214      ...params.sortDir && { sortDir: params.sortDir },
215      ...((_a = params.columnFilters) == null ? void 0 : _a.length) && { columnFilters: JSON.stringify(params.columnFilters) }
216    });
217    return this.request(`/admin/database/table/${table}?${query.toString()}`).catch(() => ({ data: [], total: 0 }));
218  }
219  // --- Staff Mode & Inline Moderation ---
220  async toggleStaffMode() {
221    return this.post("/auth/staff-mode/toggle");
222  }
223  async getStaffModeStatus() {
224    return this.request("/auth/staff-mode/status");
225  }
226  async toggleTrackVisibility(trackId) {
227    return this.post(`/admin/tracks/${trackId}/toggle-visibility`);
228  }
229  async toggleCommentVisibility(commentId) {
230    return this.post(`/admin/comments/${commentId}/toggle-visibility`);
231  }
232  async getActiveStaffStats() {
233    return this.request("/admin/reports/active");
234  }
235  async resolveReportV2(reportId, action = "dismissed") {
236    await this.post(`/admin/reports/${reportId}/resolve`, { action });
237  }
238  async governUser(userId, data) {
239    await this.post(`/admin/users/${userId}/govern`, data);
240  }
241  async updateDatabaseRecord(table, id, data) {
242    await this.put(`/admin/database/table/${table}/${id}`, data);
243  }
244  async deleteDatabaseRecord(table, id) {
245    await this.delete(`/admin/database/table/${table}/${id}`);
246  }
247  // --- User & Auth ---
248  async checkUserExists(email) {
249    return this.post("/auth/check", { email: email.toLowerCase().trim() }).then((data) => data.exists).catch(() => false);
250  }
251  async login(email, pass) {
252    return this.post("/auth/login", { email: email.toLowerCase().trim(), password: pass });
253  }
254  async register(name, email, pass, avatarChoice, language) {
255    return this.post("/auth/register", { name, email, password: pass, avatarChoice, language });
256  }
257  async reloadOAuthStrategies() {
258    await this.request("/auth/reload", { method: "POST" });
259  }
260  async getMe() {
261    try {
262      const res = await this.request("/auth/me");
263      return (res == null ? void 0 : res.user) || null;
264    } catch {
265      return null;
266    }
267  }
268  async verifyEmail(token) {
269    return this.request(`/auth/verify-email?token=${token}`).then(() =>
269 true).catch(() => false);
270  }
271  async resendVerification(email, language) {
272    await this.post("/auth/resend-verification", { email, language });
273  }
274  async forgotPassword(email, language) {
275    return this.post("/auth/forgot-password", { email, language });
276  }
277  async resetPassword(token, password) {
278    return this.post("/auth/reset-password", { token, password });
279  }
280  async lookupHandle(query) {
281    return this.post("/auth/lookup-handle", { query });
282  }
283  async updateEmail(email) {
284    return this.post("/auth/update-email", { email });
285  }
286  async socialLogin(provider) {
287    const { url } = await this.request(`/auth/${provider}/url`);
288    const authWindow = window.open(url, "oauth_popup", "width=600,height=700");
289    if (!authWindow) throw new Error("Please allow popups to connect your account.");
290    return new Promise((resolve, reject) => {
291      const handleMessage = (event) => {
292        var _a;
293        const origin = event.origin;
294        if (!origin.endsWith(".run.app") && !origin.includes("localhost") && !origin.includes("hamienet.com")) {
295          return;
296        }
297        if (((_a = event.data) == null ? void 0 : _a.type) === "OAUTH_AUTH_SUCCESS") {
298          window.removeEventListener("message", handleMessage);
299          this.request("/auth/status").then((data) => {
300            if (data.authenticated) {
301              resolve(data.user);
302            } else {
303              reject(new Error("Social login failed."));
304            }
305          }).catch(reject);
306        }
307      };
308      window.addEventListener("message", handleMessage);
309      const timer = setInterval(() => {
310        if (authWindow.closed) {
311          clearInterval(timer);
312          window.removeEventListener("message", handleMessage);
313          reject(new Error("Login window closed."));
314        }
315      }, 1e3);
316    });
317  }
318  async getAuthProviders() {
319    try {
320      return await this.request("/auth/providers");
321    } catch (e) {
322      console.warn("Failed to fetch auth providers:", e);
323      return { google: false, facebook: false, apple: false };
324    }
325  }
326  async getNotifications(userId) {
327    return this.request(`/notifications?userId=${userId}`).catch(() => []);
328  }
329  async getNotificationPage(cursor) {
330    const url = cursor ? `/notifications/page?cursor=${cursor}` : `/notifications/page`;
331    return this.request(url).catch(() => ({ items: [], hasMore: false, nextCursor: null }));
332  }
333  async markNotificationRead(id) {
334    await this.post(`/notifications/${id}/read`);
335  }
336  async markAllNotificationsRead() {
337    await this.post(`/notifications/read-all`);
338  }
339  async getSubscriptions(userId) {
340    return this.request(`/users/${userId}/subscriptions`).catch(() => []);
341  }
342  async toggleSubscription(userId, artistId) {
343    await this.post(`/users/${userId}/subscribe`, { artistId });
344  }
345  async getChannelProfile(username) {
346    return this.request(`/users/profile/${encodeURIComponent(username)}`);
347  }
348  async getSubscribedTracks(userId) {
349    return this.request(`/tracks/subscriptions?userId=${userId}`);
350  }
351  async getSubscriptionTracks(userId) {
352    return this.request(`/tracks/subscriptions?userId=${userId}`).catch(() => []);
353  }
354  async updateUser(id, updates) {
355    return this.put(`/users/${id}`, updates);
356  }
357  async getUserLimits(userId) {
358    return this.request(`/users/${userId}/limits`);
359  }
360  // --- Content ---
361  async getContentStats() {
362    return this.request("/content/stats").catch(() => ({ totalTracks: 87e3, totalUsers: 103e3, totalArtists: 24e3 }));
363  }
364  async getContributors() {
365    const result = await this.request("/content/contributors").catch(() => []);
366    return Array.isArray(result) ? result : [];
367  }
368  async getTracks(query, category, sort, limit, skip, mine, username) {
369    const params = new URLSearchParams({
370      q: query || "",
371      cat: category || "all",
372      ...sort && { sort },
373      ...limit && { limit: limit.toString() },
374      ...skip && { skip: skip.toString() },
375      ...mine && { mine: "true" },
376      ...username && { username }
377    });
378    return this.request(`/tracks?${params.toString()}`).catch(() => []);
379  }
380  async advancedSearch(params) {
381    const queryParams = new URLSearchParams();
382    for (const [key, val] of Object.entries(params)) {
383      if (val === void 0 || val === null || val === "") continue;
384      if (Array.isArray(val)) {
385        val.forEach((v) => queryParams.append(key, v));
386      } else {
387        queryParams.append(key, String(val));
388      }
389    }
390    return this.request(`/tracks/advanced-search?${queryParams.toString()}`);
391  }
392  async getSearchSuggestions(q) {
393    return this.request(`/search/suggestions?q=${encodeURIComponent(q)}`).catch(() => []);
394  }
395  async getRecommendations(userId) {
396    const url = userId ? `/tracks/recommendations?userId=${userId}` : "/tracks/recommendations";
397    return this.request(url).catch(() => []);
398  }
399  async getRelatedTracks(trackId) {
400    return this.request(`/tracks/${trackId}/related`).catch(() => []);
401  }
402  async getVariations(trackId) {
403    return this.request(`/tracks/${trackId}/variations`).catch(() => []);
404  }
405  async getCategories(skip, limit) {
406    const params = new URLSearchParams();
407    if (skip !== void 0) params.append("skip", skip.toString());
408    if (limit !== void 0) params.append("limit", limit.toString());
409    const query = params.toString() ? `?${params.toString()}` : "";
410    return this.request(`/categories${query}`);
411  }
412  async getTrack(id) {
413    return this.request(`/tracks/${id}`);
414  }
415  async getAdminTrack(id) {
416    return this.getTrack(String(id));
417  }
418  async deleteTrackAsUser(id, userId) {
419    return this.request(`/tracks/${id}`, {
420      method: "DELETE",
421      body: JSON.stringify({ userId })
422    });
423  }
424  async recordPlay(id) {
425    await this.post(`/tracks/${id}/play`).catch(() => {
426    });
427  }
428  async uploadTrack(data) {
429    const res = await fetch("/api/upload", {
430      method: "POST",
431      body: data,
432      credentials: "include"
433    });
434    if (!res.ok) {
435      const err = await res.json().catch(() => ({ error: res.statusText }));
436      const errorObj = new Error(err.error || err.message || "Upload failed");
437      errorObj.duplicate = err.duplicate;
438      errorObj.existingTracks = err.existingTracks;
439      throw errorObj;
440    }
441    return await res.json();
442  }
443  async reuploadMidi(trackId, file) {
444    const formData = new FormData();
445    formData.append("midiFile", file);
446    const res = await fetch(`/api/tracks/${trackId}/reupload-midi`, {
447      method: "POST",
448      body: formData,
449      credentials: "include"
450    });
451    if (!res.ok) {
452      const err = await res.json().catch(() => ({ error: "Re-upload failed" }));
453      throw new Error(err.error || "Re-upload failed");
454    }
455    return await res.json();
456  }
457  async processOMR(files, preprocess = false) {
458    const formData = new FormData();
459    files.forEach((f) => formData.append("pages", f));
460    formData.append("preprocess", preprocess ? "true" : "false");
461    const headers = {};
462    if (typeof window !== "undefined") {
463      const testKey = localStorage.getItem("digitizer_test_key");
464      if (testKey) {
465        headers["x-digitizer-test-key"] = testKey;
466      }
467    }
468    const res = await fetch("/api/omr/process", {
469      method: "POST",
470      body: formData,
471      headers,
472      credentials: "include"
473    });
474    if (!res.ok) {
475      const err = await res.json().catch(() => ({ error: "OMR processing failed" }));
476      throw new Error(err.error || "OMR processing failed");
477    }
478    return await res.json();
479  }
480  async getOMRStatus(jobId) {
481    const res = await fetch(`/api/omr/status/${jobId}`, {
482      method: "GET",
483      credentials: "include"
484    });
485    if (!res.ok) {
486      const err = await res.json().catch(() => ({ error: "Failed to fetch OMR status" }));
487      throw new Error(err.error || "Failed to fetch OMR status");
488    }
489    return await res.json();
490  }
491  async getAdminUser(id) {
492    return this.request(`/admin/users/${id}`);
493  }
494  async editTrackWiki(id, data) {
495    return this.put(`/tracks/${id}/wiki-edit`, data);
496  }
497  async validateContent(fields) {
498    return this.post("/content/validate", { fields });
499  }
500  async getComments(trackId) {
501    return this.request(`/tracks/${trackId}/comments`).catch(() => []);
502  }
503  async postComment(trackId, userId, text, parentId) {
504    return this.post(`/tracks/${trackId}/comments`, { userId, text, parentId });
505  }
506  async voteComment(commentId, userId, value) {
507    return this.post(`/comments/${commentId}/vote`, { userId, value });
508  }
509  async searchMelody(notes, mode = "easy") {
510    return this.post("/tracks/search-melody", { notes, mode }).catch(() => []);
511  }
512  async getTags(params = {}) {
513    const query = params.type ? `?type=${params.type}` : "";
514    return this.request(`/tags${query}`).catch(() => []);
515  }
516  async getSuggestedTags(q) {
517    return this.request(`/tags/suggest?q=${encodeURIComponent(q)}`).catch(() => []);
518  }
519  async suggestArtistsByTitle(title) {
520    return this.request(`/tags/suggest-artists-by-title?title=${encodeURIComponent(title)}`).catch(() => []);
521  }
522  async getCanonicalWorksByComposer(composer) {
523    return this.request(`/collections/works?composer=${encodeURIComponent(composer)}`).catch(() => []);
524  }
525  async autofillTags(artist, title) {
526    const params = new URLSearchParams();
527    if (artist) params.append("artist", artist);
528    if (title) params.append("title", title);
529    if (!params.toString()) return [];
530    return this.request(`/tags/autofill?${params.toString()}`).catch(() => []);
531  }
532  async getTrendingTags() {
533    return this.request("/tags/trending").catch(() => []);
534  }
535  async proposeTag(trackId, tagName, userId) {
536    return this.post(`/tracks/${trackId}/tags/propose`, { tagName, userId });
537  }
538  async voteTag(trackId, tagName, value, userId) {
539    await this.post(`/tracks/${trackId}/tags/vote`, { tagName, value, userId });
540  }
541  async getNavSections() {
542    return this.request("/navigation").catch(() => []);
543  }
544  // Alias for compatibility
545  async getNavigation() {
546    return this.getNavSections();
547  }
548  async createNavSection(data) {
549    return this.post("/admin/navigation/section", data);
550  }
551  async updateNavSection(id, data) {
552    return this.put(`/admin/navigation/section/${id}`, data);
553  }
554  async deleteNavSection(id) {
555    await this.delete(`/admin/navigation/section/${id}`);
556  }
557  async createNavLink(data) {
558    return this.post("/admin/navigation/link", data);
559  }
560  async updateNavLink(id, data) {
561    return this.put(`/admin/navigation/link/${id}`, data);
562  }
563  async deleteNavLink(id) {
564    await this.delete(`/admin/navigation/link/${id}`);
565  }
566  async reorderNavSections(items) {
567    await this.put("/admin/navigation/reorder/sections", { items });
568  }
569  async reorderNavLinks(items) {
570    await this.put("/admin/navigation/reorder/links", { items });
571  }
572  async seedNavigation() {
573    await this.post("/admin/navigation/seed");
574  }
575  // --- Admin ---
576  async getSystemInfo() {
577    return this.request("/system/info");
578  }
579  async getSystemStorage(refresh) {
580    return this.request(`/system/storage${refresh ? "?refresh=true" : ""}`);
581  }
582  async getAdminUsers({ page = 1, pageSize = 10, search = "", filters = {} }) {
583    const query = new URLSearchParams({ page: page.toString(), pageSize: pageSize.toString(), search, role: filters.role || "" });
584    return this.request(`/admin/users?${query}`);
585  }
586  async getAdminTracks({ page = 1, pageSize = 10, search = "", filters = {}, sortBy = "", sortDir = "asc" }) {
587    const query = new URLSearchParams({
588      page: page.toString(),
589      pageSize: pageSize.toString(),
590      search,
591      status: filters.status || "",
592      id: filters.id || "",
593      userId: filters.userId || "",
594      sortBy: sortBy || "",
595      sortDir: sortDir || "asc"
596    });
597    return this.request(`/admin/tracks?${query}`);
598  }
599  async getTickets({ page = 1, pageSize = 10, search = "", filters = {}, sortBy = "", sortDir = "desc" } = {}) {
600    const query = new URLSearchParams({
601      page: page.toString(),
602      pageSize: pageSize.toString(),
603      search: search || "",
604      category: filters.category || "",
605      status: filters.status || "",
606      ...sortBy && { sortBy },
607      ...sortDir && { sortDir }
608    });
609    return this.request(`/admin/tickets?${query.toString()}`);
610  }
611  async getActivityLogs({ page = 1, pageSize = 10, category, search, startDate, endDate, userId, actions }) {
612    const params = new URLSearchParams({ page: String(page), pageSize: String(pageSize) });
613    if (category) params.append("category", category);
614    if (search) params.append("search", search);
615    if (startDate) params.append("startDate", startDate);
616    if (endDate) params.append("endDate", endDate);
617    if (userId) params.append("userId", String(userId));
618    if (actions) params.append("actions", actions.join(","));
619    return this.request(`/admin/logs?${params.toString()}`);
620  }
621  async clearActivityLogs({ category, beforeDate, source, userId, clearLegacy }) {
622    return this.delete("/admin/logs", { category, beforeDate, source, userId, clearLegacy });
623  }
624  async deleteActivityLog(id) {
625    return this.delete(`/admin/logs/${id}`);
626  }
627  async getAdminTags({ page = 1, pageSize = 10, search = "", sortBy = "", sortDir = "asc" }) {
628    const query = new URLSearchParams({
629      page: page.toString(),
630      pageSize: pageSize.toString(),
631      search,
632      sortBy: sortBy || "",
633      sortDir: sortDir || "asc"
634    });
635    return this.request(`/admin/tags?${query}`);
636  }
637  async getAdminRegions(params) {
638    const queryString = new URLSearchParams(params).toString();
639    return this.request(`/admin/regions?${queryString}`);
640  }
641  async getAdminRoles() {
642    return this.request("/admin/roles");
643  }
644  // --- Pinned Modules ---
645  async getAdminPins() {
646    return this.request("/admin/pins");
647  }
648  async saveAdminPins(pinnedModules) {
649    await this.post("/admin/pins", { pinnedModules });
650  }
651  // --- Dynamic Modules ---
652  async getAdminModules() {
653    return this.request("/admin/modules");
654  }
655  async getSitePages() {
656    return this.request("/pages").catch(() => []);
657  }
658  // --- Admin Mutations ---
659  async updateAdminModule(id, content, changeLog, author) {
660    await this.put(`/admin/modules/${id}
660`, { content, changeLog, author });
661  }
662  async createAdminModule(name, path, description, content, author) {
663    await this.post("/admin/modules", { name, path, description, content, author });
664  }
665  async getModuleHistory(id) {
666    return this.request(`/admin/modules/${id}/history`);
667  }
668  async revertModuleVersion(id, versionId) {
669    await this.post(`/admin/modules/${id}/revert`, { versionId });
670  }
671  async createSitePage(name, path, description, content, author, visibility) {
672    await this.post("/admin/pages", { name, path, description, content, author, visibility });
673  }
674  async updateSitePage(id, updates, changeLog, author) {
675    await this.put(`/admin/pages/${id}`, { ...updates, changeLog, author });
676  }
677  async getSitePageHistory(id) {
678    return this.request(`/admin/pages/${id}/history`);
679  }
680  async revertSitePageVersion(id, versionId) {
681    await this.post(`/admin/pages/${id}/revert`, { versionId });
682  }
683  // --- System Settings ---
684  async getSystemSettings() {
685    return this.request("/settings").catch(() => []);
686  }
687  async getGscStatus() {
688    return this.request("/admin/gsc/status");
689  }
690  async generateGscSitemap() {
691    return this.post("/admin/gsc/generate-sitemap");
692  }
693  async getSystemSetting(key) {
694    return this.request(`/settings/${key}`).catch(() => null);
695  }
696  async getAdminSystemSetting(key) {
697    return this.request(`/admin/settings/${key}`).catch(() => null);
698  }
699  async saveSystemSetting(key, value) {
700    await this.post(`/admin/settings/${key}`, { value });
701    if (typeof window !== "undefined") window.dispatchEvent(new CustomEvent("settings-update", { detail: { key, value } }));
702  }
703  // --- UX & Performance Monitoring ---
704  async getPerformanceMonitoringStatus() {
705    return this.request("/system/monitoring-status").catch(() => ({ active: false }));
706  }
707  async startPerformanceMonitoring(durationMinutes) {
708    return this.post("/admin/performance/monitoring-session/start", { durationMinutes });
709  }
710  async stopPerformanceMonitoring() {
711    return this.post("/admin/performance/monitoring-session/stop", {});
712  }
713  async getPerformanceSessions() {
714    return this.request("/admin/performance/sessions").catch(() => []);
715  }
716  async getPerformanceSessionDetails(id) {
717    return this.request(`/admin/performance/sessions/${id}`);
718  }
719  async analyzePerformanceSession(id) {
720    return this.post(`/admin/performance/sessions/${id}/analyze`, {});
721  }
722  async deletePerformanceSession(id) {
723    return this.delete(`/admin/performance/sessions/${id}`);
724  }
725  async getLabConfig() {
726    return this.request("/settings/lab_config").then((r) => (r == null ? void 0 : r.value) || null).catch(() => null);
727  }
728  async toggleLabTool(toolId, enabled) {
729    return this.put(`/settings/lab_config/${toolId}`, { enabled });
730  }
731  // --- Localization ---
732  async getLocales() {
733    return this.request("/locales");
734  }
735  async updateUserLanguage(language) {
736    return this.put("/user/language", { language });
737  }
738  async getTranslation(lang, ns) {
739    return this.request(`/locales/${lang}/${ns}.json`).catch(() => ({}));
740  }
741  async createLocale(locale) {
742    await this.post("/admin/locales", locale);
743  }
744  async deleteLocale(code) {
745    await this.delete(`/admin/locales/${code}`);
746  }
747  async updateLocale(code, updates) {
748    await this.put(`/admin/locales/${code}`, updates);
749  }
750  async getRegions() {
751    return this.request("/regions");
752  }
753  // --- Help Center ---
754  async getHelpCategories() {
755    return this.request("/help/categories").catch(() => []);
756  }
757  async getHelpArticles() {
758    return this.request("/help/articles").catch(() => []);
759  }
760  async saveHelpCategory(category) {
761    await this.post("/admin/help/categories", category);
762  }
763  async saveHelpArticle(article) {
764    await this.post("/admin/help/articles", article);
765  }
766  async deleteHelpCategory(id) {
767    await this.delete(`/admin/help/categories/${id}`);
768  }
769  async deleteHelpArticle(id) {
770    await this.delete(`/admin/help/articles/${id}`);
771  }
772  // --- Live Monitor ---
773  async getLiveMonitorSnapshot() {
774    return this.request("/admin/live-monitor/snapshot");
775  }
776  async getLiveMonitorHistory(range) {
777    return this.request(`/admin/live-monitor/history?range=${range}`);
778  }
779  async createTicket(userId, subject, category, priority, description, honeypot) {
780    await this.post("/tickets", { userId, subject, category, priority, description, _hp: honeypot });
781  }
782  async getMyTickets() {
783    return this.request("/my-tickets").catch(() => []);
784  }
785  async getTicketDetails(id) {
786    return this.request(`/tickets/${id}`);
787  }
788  async replyToTicket(ticketId, text, sendEmail = false) {
789    await this.post(`/tickets/${ticketId}/reply`, { text, sendEmail });
790  }
791  async updateTicketStatus(ticketId, status) {
792    await this.post(`/tickets/${ticketId}/status`, { status });
793  }
794  async resolveTicket(ticketId) {
795    await this.post(`/tickets/${ticketId}/resolve`);
796  }
797  async deleteTicketMessage(ticketId, msgId) {
798    await this.post(`/tickets/${ticketId}/messages/${msgId}/delete`);
799  }
800  async getFileSystem() {
801    return this.request("/admin/fs").catch(() => []);
802  }
803  async readDevAgentFile(paths) {
804    return this.post("/dev-agent/read", { paths });
805  }
806  async commitDevAgentFile(changes, message) {
807    return this.post("/dev-agent/commit", { changes, message });
808  }
809  async createRegion(data) {
810    await this.post("/admin/regions", data);
811  }
812  async updateRegion(code, data) {
813    await this.put(`/admin/regions/${code}`, data);
814  }
815  async deleteRegion(code) {
816    await this.delete(`/admin/regions/${code}`);
817  }
818  async createTag(name, slug, translations, type = "TAG") {
819    await this.post("/admin/tags", { name, slug, translations, type });
820  }
821  async updateTag(id, name, slug, translations, type) {
822    await this.put(`/admin/tags/${id}`, { name, slug, translations, type });
823  }
824  async deleteTag(id) {
825    await this.delete(`/admin/tags/${id}`);
826  }
827  async mergeTags(sourceId, targetId) {
828    await this.post("/admin/tags/merge", { sourceId, targetId });
829  }
830  async previewTagCleanup() {
831    return this.post("/admin/tags/cleanup-preview");
832  }
833  async executeTagCleanup(options) {
834    return this.post("/admin/tags/cleanup", options);
835  }
836  async getScanTagReferences() {
837    return this.request("/admin/scan-tag/references");
838  }
839  async createScanTagReference(data) {
840    return this.post("/admin/scan-tag/references", data);
841  }
842  async updateScanTagReference(id, data) {
843    return this.put(`/admin/scan-tag/references/${id}`, data);
844  }
845  async deleteScanTagReference(id) {
846    return this.delete(`/admin/scan-tag/references/${id}`);
847  }
848  async getScanTagStats() {
849    return this.request("/admin/scan-tag/stats");
850  }
851  async getScanTagHealth() {
852    return this.request("/admin/scan-tag/health");
853  }
854  async runMelodyScan(options) {
855    return this.post("/admin/scan-tag/scan", options || {});
856  }
857  async applyScanTagMatches(matches) {
858    return this.post("/admin/scan-tag/apply", { matches });
859  }
860  async getMusicBrainzStatus() {
861    return this.request("/admin/musicbrainz/status");
862  }
863  async musicbrainzTestLookup(artist, title) {
864    return this.post("/admin/musicbrainz/test-lookup", { artist, title });
865  }
866  async triggerMusicBrainzSync() {
867    return this.post("/admin/musicbrainz/trigger-sync");
868  }
869  async getMusicBrainzSyncLogs() {
870    return this.request("/admin/musicbrainz/sync-logs");
871  }
872  async getTrustConfig() {
873    return this.request("/admin/trust/config").catch(() => ({ baseScore: 50, weights: { verifiedEmail: 10, hasAvatar: 5, hasBio: 5, accountAgeDays: 0.1, uploads: 5, comments: 1, likesReceived: 1, votesGiven: 0.5, reportsReceived: -10, dislikesReceived: -1 }, caps: { maxScore: 100, minScore: 0 }, thresholds: { trustedUser: 80, autoSuspend: 20 } }));
874  }
875  async saveTrustConfig(config) {
876    await this.post("/admin/trust/config", config);
877  }
878  async recalculateAllTrustScores() {
879    await this.post("/admin/trust/recalculate");
880  }
881  calculateUserScoreDetails(user, config) {
882    var _a, _b;
883    const { weights, baseScore, caps } = config;
884    const breakdown = {
885      base: baseScore,
886      identity: {
887        email: user.emailVerified ? weights.verifiedEmail : 0,
888        avatar: user.avatar && !user.avatar.includes("default") ? weights.hasAvatar : 0,
889        bio: user.description && user.description.length > 5 ? weights.hasBio : 0
890      },
891      age: { value: 0, points: 0 },
892      uploads: { value: ((_a = user._count) == null ? void 0 : _a.midis) || user.legacyMidiCount || 0, points: 0 },
893      comments: { value: ((_b = user._count) == null ? void 0 : _b.comments) || user.legacyPostCount || 0, points: 0 },
894      likes: { value: user.likesReceivedCount || 0, points: 0 },
895      votes: { value: user.votesGivenCount || 0, points: 0 },
896      reports: { value: user.reportsReceivedCount || 0, points: 0 },
897      dislikes: { value: 0, points: 0 }
898    };
899    if (user.createdAt) {
900      const days = Math.floor((Date.now() - new Date(user.createdAt).getTime()) / (1e3 * 60 * 60 * 24));
901      breakdown.age.value = days;
902      breakdown.age.points = Math.min(20, days * weights.accountAgeDays);
903    }
904    breakdown.uploads.points = breakdown.uploads.value * weights.uploads;
905    breakdown.comments.points = breakdown.comments.value * weights.comments;
906    breakdown.likes.points = breakdown.likes.value * weights.likesReceived;
907    breakdown.votes.points = breakdown.votes.value * weights.votesGiven;
908    breakdown.reports.points = breakdown.reports.value * weights.reportsReceived;
909    breakdown.dislikes.points = breakdown.dislikes.value * weights.dislikesReceived;
910    const totalIdentity = (breakdown.identity.email || 0) + (breakdown.identity.avatar || 0) + (breakdown.identity.bio || 0);
911    const totalActivity = breakdown.age.points + breakdown.uploads.points + breakdown.comments.points + breakdown.likes.points + breakdown.votes.points;
912    const totalNegative = breakdown.reports.points + breakdown.dislikes.points;
913    let total = baseScore + totalIdentity + totalActivity + totalNegative;
914    total = Math.max(caps.minScore, Math.min(caps.maxScore, total));
915    return {
916      total: Math.round(total * 10) / 10,
917      breakdown
918    };
919  }
920  async getTrustSafetySummary() {
921    return this.request("/admin/trust/summary").catch(() => ({ atRiskUsers: 0, flaggedTracks: 0, pendingItems: 0 }));
922  }
923  async getPendingContent() {
924    return this.request("/admin/trust/queue").catch(() => ({ tracks: [], comments: [] }));
925  }
926  async getFlaggedContent() {
927    return this.request("/admin/trust/flagged").catch(() => ({ users: [], tracks: [] }));
928  }
929  async getReports(status) {
930    return this.request(`/admin/trust/reports?status=${status}`).catch(() => []);
931  }
932  async reportContent(targetId, targetType, reason, details, reporterId) {
933    await this.post("/reports", { targetId, targetType, reason, details, reporterId });
934  }
935  async resolveTrustItem(type, id, action) {
936    await this.post("/admin/moderation/resolve", { type, id, action });
937  }
938  async resolveReport(reportId, action) {
939    await this.post("/admin/moderation/resolve-report", { reportId, action });
940  }
941  async getAnalyticsDashboard() {
942    return this.request("/admin/analytics").catch(() => ({ cohorts: [], searchInsights: [], forecast: [], hotspots: [] }));
943  }
944  async getAnalyticsOverview(range, start, end) {
945    const params = new URLSearchParams({ range });
946    if (start) params.set("start", start);
947    if (end) params.set("end", end);
948    return this.request(`/admin/analytics/overview?${params}`);
949  }
950  async getAnalyticsTraffic(range, start, end) {
951    const params = new URLSearchParams({ range });
952    if (start) params.set("start", start);
953    if (end) params.set("end", end);
954    return this.request(`/admin/analytics/traffic?${params}`);
955  }
956  async getAnalyticsContent(range, start, end) {
957    const params = new URLSearchParams({ range });
958    if (start) params.set("start", start);
959    if (end) params.set("end", end);
960    return this.request(`/admin/analytics/content?${params}`);
961  }
962  async getAnalyticsGames(range, start, end) {
963    const params = new URLSearchParams({ range });
964    if (start) params.set("start", start);
965    if (end) params.set("end", end);
966    return this.request(`/admin/analytics/games?${params}`);
967  }
968  async getAnalyticsUsers(range, start, end) {
969    const params = new URLSearchParams({ range });
970    if (start) params.set("start", start);
971    if (end) params.set("end", end);
972    return this.request(`/admin/analytics/users?${params}`);
973  }
974  async getAnalyticsBots(range, start, end) {
975    const params = new URLSearchParams({ range });
976    if (start) params.set("start", start);
977    if (end) params.set("end", end);
978    return this.request(`/admin/analytics/bots?${params}`);
979  }
980  async getAnalyticsReferrers(range, start, end) {
981    const params = new URLSearchParams({ range });
982    if (start) params.set("start", start);
983    if (end) params.set("end", end);
984    return this.request(`/admin/analytics/referrers?${params}`);
985  }
986  async getAnalyticsRealtime() {
987    return this.request("/admin/analytics/realtime");
988  }
989  // Stripe & Supporter Payments API
990  async getPaymentStatus() {
991    return this.request("/payments/status");
992  }
993  async createCheckoutSession(data) {
994    return this.post("/payments/checkout-session", data);
995  }
996  async createPortalSession() {
997    return this.post("/payments/portal-session", {});
998  }
999  // Admin Subscriber Analytics & Override API
1000  async getAdminSubscribers(params) {
1001    const query = new URLSearchParams({
1002      page: String(params.page),
1003      pageSize: String(params.pageSize),
1004      search: params.search || ""
1005    });
1006    return this.request(`/payments/admin/subscribers?${query.toString()}`);
1007  }
1008  async adminOverrideSupporter(data) {
1009    return this.post("/payments/admin/override", data);
1010  }
1011  async saveEmailConfig(config) {
1012    await this.post("/admin/email/config", config);
1013  }
1014  async testEmailConfig(config, targetEmail) {
1015    return this.post("/admin/email/test", { config, targetEmail });
1016  }
1017  async getEmailTemplates() {
1018    return this.request("/admin/email/templates");
1019  }
1020  async saveEmailTemplateOverride(templateKey, language, subject, bodyHtml) {
1021    await this.post(`/admin/email/templates/${templateKey}`, { language, subject, bodyHtml });
1022  }
1023  async resetEmailTemplateOverride(templateKey, language) {
1024    await this.post(`/admin/email/templates/${templateKey}/reset`, { language });
1025  }
1026  async getEmailTemplateConfig() {
1027    return this.request("/admin/email/template-config");
1028  }
1029  async saveEmailTemplateConfig(config) {
1030    await this.post("/admin/email/template-config", config);
1031  }
1032  async adminCreateUser(data) {
1033    return this.post("/admin/users/create", data);
1034  }
1035  async deleteAdminRole(id) {
1036    await this.delete(`/admin/roles/${id}`);
1037  }
1038  async updateAdminRole(id, data) {
1039    await this.put(`/admin/roles/${id}`, data);
1040  }
1041  async createAdminRole(data) {
1042    await this.post("/admin/roles", data);
1043  }
1044  async seedData(type, data) {
1045    await this.post("/admin/seed", { type, data });
1046  }
1047  async toggleBan(userId, isBanned) {
1048    await this.post(`/admin/users/${userId}/ban`, { isBanned });
1049  }
1050  async addModNote(userId, note) {
1051    await this.post(`/admin/users/${userId}/note`, { note });
1052  }
1053  async assignRoleToUser(userId, roleId) {
1054    await this.post(`/admin/users/${userId}/role`, { roleId });
1055  }
1056  // --- Avatar Management ---
1057  async getDefaultAvatars() {
1058    return this.request("/avatars/defaults").catch(() => []);
1059  }
1060  async uploadAvatar(userId, file) {
1061    const formData = new FormData();
1062    formData.append("avatar", file);
1063    const res = await fetch(`/api/users/${userId}/avatar`, { method: "POST", body: formData, credentials: "include" });
1064    if (!res.ok) throw new Error("Avatar upload failed");
1065    return await res.json();
1066  }
1067  async uploadDefaultAvatar(file, name) {
1068    const formData = new FormData();
1069    formData.append("avatar", file);
1070    if (name) formData.append("name", name);
1071    const res = await fetch("/api/admin/avatars/defaults", { method: "POST", body: formData, credentials: "include" });
1072    if (!res.ok) throw new Error("Default avatar upload failed");
1073    return await res.json();
1074  }
1075  async deleteDefaultAvatar(filename) {
1076    await this.delete(`/admin/avatars/defaults/${filename}`);
1077  }
1078  async bulkUpdateTrackMetadata(ids, updates) {
1079    await this.post("/admin/tracks/bulk", { ids, updates });
1080  }
1081  async updateTrackMetadata(id, updates) {
1082    await this.put(`/admin/tracks/${id}`, updates);
1083  }
1084  async deleteTrack(id) {
1085    await this.delete(`/admin/tracks/${id}`);
1086  }
1087  async searchUsersForMessaging(query) {
1088    return this.request(`/admin/users?search=${encodeURIComponent(query)}`).then((r) => r.data || []).catch(() => []);
1089  }
1090  async getUnreadMessageCount(userId) {
1091    const res = await this.request(`/messages/unread-count?userId=${userId}`).catch(() => ({ count: 0 }));
1092    return res.count || 0;
1093  }
1094  async getConversations(userId) {
1095    return this.request(`/conversations?userId=${userId}`).catch(() => []);
1096  }
1097  async getMessages(userId, otherId) {
1098    return this.request(`/messages/${otherId}?userId=${userId}`).catch(() => []);
1099  }
1100  async sendMessage(senderId, receiverId, content) {
1101    return this.post("/messages", { senderId, receiverId, content });
1102  }
1103  async markMessagesRead(userId, otherUserId) {
1104    await this.post("/messages/read", { userId, otherUserId });
1105  }
1106  async getPlaylists(userId) {
1107    return this.request(`/users/${userId}/playlists`).catch(() => []);
1108  }
1109  async createPlaylist(userId, title, isPublic = false) {
1110    return this.post("/playlists", { userId, title, isPublic });
1111  }
1112  async addTrackToPlaylist(playlistId, trackId) {
1113    await this.post(`/playlists/${playlistId}/tracks`, { trackId });
1114  }
1115  async softDeleteTrack(trackId, userId) {
1116    await this.delete(`/tracks/${trackId}`, { userId });
1117  }
1118  // --- Library Features ---
1119  async toggleLikeTrack(trackId, userId) {
1120    return this.post(`/tracks/${trackId}/like`, { userId });
1121  }
1122  async getUserLikes(userId) {
1123    return this.request(`/users/${userId}/likes`).catch(() => []);
1124  }
1125  async getLikedTracks(userId) {
1126    return this.request(`/users/${userId}/liked-tracks`).catch(() => []);
1127  }
1128  async toggleWatchLaterTrack(trackId, userId) {
1129    return this.post(`/tracks/${trackId}/watch-later`, { userId });
1130  }
1131  async getUserWatchLater(userId) {
1132    return this.request(`/users/${userId}/watch-later`).catch(() => []);
1133  }
1134  async getWatchLaterTracks(userId) {
1135    return this.request(`/users/${userId}/watch-later-tracks`).catch(() => []);
1136  }
1137  async recordHistory(trackId, userId) {
1138    await this.post(`/tracks/${trackId}/history`, { userId }).catch(() => {
1139    });
1140  }
1141  async getUserHistory(userId) {
1142    return this.request(`/users/${userId}/history`).catch(() => []);
1143  }
1144  async verifyAdminPin(pin, remember = false) {
1145    try {
1146      return await this.post("/auth/admin-pin", { pin, remember });
1147    } catch (err) {
1148      console.error("PIN Verification API Error:", err);
1149      throw err;
1150    }
1151  }
1152  async reverifyAdmin(token) {
1153    return this.post("/auth/admin-reverify", { token });
1154  }
1155  async getAdminStatus() {
1156    return this.request("/auth/admin-status");
1157  }
1158  // --- Soundfonts ---
1159  async getSoundfonts(options) {
1160    const query = (options == null ? void 0 : options.scan) ? "?scan=true" : "";
1161    const res = await fetch(`/api/soundfonts${query}`);
1162    if (!res.ok) throw new Error("Failed to fetch soundfonts");
1163    return res.json();
1164  }
1165  async setActiveSoundfont(filename) {
1166    await this.post("/admin/soundfonts/active", { filename });
1167  }
1168  async getSoundfontJobs() {
1169    return this.request("/admin/soundfonts/jobs");
1170  }
1171  async startSoundfontJob(name, inputPath, jobType = "CONVERT") {
1172    return this.post("/admin/soundfonts/jobs", { name, inputPath, jobType });
1173  }
1174  async deleteSoundfontJob(id) {
1175    await this.delete(`/admin/soundfonts/jobs/${id}`);
1176  }
1177  async deleteSoundfont(filename) {
1178    await this.delete(`/admin/soundfonts?filename=${encodeURIComponent(filename)}`);
1179  }
1180  async startSoundfontTest(sf2Path, params) {
1181    return this.post("/admin/soundfonts/test", { sf2Path, params });
1182  }
1183  async autoLevelSoundfont(sf2Path, program, targetDb = -18) {
1184    return this.post("/admin/soundfonts/auto-level", { sf2Path, program, targetDb });
1185  }
1186  async fetchSourceData(dbConfig, tableName, limit = 1e3, targetModel) {
1187    return this.post("/admin/migration/fetch-source", { dbConfig, tableName, limit, targetModel });
1188  }
1189  async countCatchUp(dbConfig, sourceTable, targetModel, mapping, limit = 0, startId = 0) {
1190    return this.post("/admin/migration/count-catchup", { dbConfig, sourceTable, targetModel, mapping, limit, startId });
1191  }
1192  async executeMigration(dbConfig, sourceTable, targetModel, mapping, limit = 0, startId = 0, overwrite = false, verbose = false) {
1193    return this.post("/admin/migration/execute", { dbConfig, sourceTable, targetModel, mapping, limit, startId, overwrite, verbose });
1194  }
1195  async executeAvatarMigration() {
1196    return this.post("/admin/migration/avatars");
1197  }
1198  async getMidiExtractionStatus(dbConfig) {
1199    return this.post("/admin/migration/midi-extraction-status", { dbConfig });
1200  }
1201  async extractMidiFiles(dbConfig, startId, limit) {
1202    return this.post("/admin/migration/extract-midi-files", { dbConfig, startId, limit });
1203  }
1204  async generateUserHandles() {
1205    return this.post("/admin/migration/generate-handles", {});
1206  }
1207  async runSmartMigration(dbConfig) {
1208    return this.post("/admin/migration/smart-run", { dbConfig });
1209  }
1210  async getSoundFonts() {
1211    return this.request("/soundfonts");
1212  }
1213  async saveGeneratedAsset(data, name) {
1214    return this.post("/admin/assets/save-generated", { data, name });
1215  }
1216  async getAvailableAssets() {
1217    return this.request("/admin/assets");
1218  }
1219  async uploadSoundFont(file) {
1220    const formData = new FormData();
1221    formData.append("soundfont", file);
1222    const res = await fetch("/api/admin/soundfonts", { method: "POST", body: formData, credentials: "include" });
1223    if (!res.ok) throw new Error("SoundFont upload failed");
1224  }
1225  async setActiveSoundFont(filename) {
1226    await this.post("/admin/soundfonts/active", { filename });
1227  }
1228  async reprocessTracks(params) {
1229    await this.post("/admin/tracks/reprocess", params);
1230  }
1231  async analyzeTrackStructure(id) {
1232    await this.post(`/admin/tracks/${id}/analyze-structure`);
1233  }
1234  async getWorkerStatus() {
1235    return this.request("/admin/worker/status");
1236  }
1237  async flushCache() {
1238    await this.post("/admin/system/flush-cache");
1239  }
1240  async logError(error, context = "client") {
1241    try {
1242      const message = error instanceof Error ? error.message : String(error);
1243      const stack = error instanceof Error ? error.stack : "";
1244      await this.post("/log-client-error", { message, stack, url: window.location.href, source: context });
1245    } catch (err) {
1246      console.error("Failed to log error to server:", err);
1247    }
1248  }
1249  async logEvent(event, details = {}, userId) {
1250    try {
1251      await this.post("/analytics/event", { event, details, userId });
1252    } catch (err) {
1253      console.error("Failed to log event to server:", err);
1254    }
1255  }
1256  // --- High Scores ---
1257  async getHighscores(trackId, gameKey = "piano-roll", userId) {
1258    let url = `/tracks/${trackId}/highscores?gameKey=${gameKey}`;
1259    if (userId) url += `&userId=${userId}`;
1260    return this.request(url).catch(() => []);
1261  }
1262  async postHighscore(trackId, data) {
1263    await this.post(`/tracks/${trackId}/highscores`, data);
1264  }
1265  async hideComment(commentId, adminId) {
1266    await this.request(`/comments/${commentId}`, {
1267      method: "DELETE",
1268      body: JSON.stringify({ userId: adminId })
1269    });
1270  }
1271  async resetStuckTasks() {
1272    return this.post("/admin/worker/reset_stuck");
1273  }
1274  async clearQueueTasks() {
1275    return this.post("/admin/worker/clear_queue");
1276  }
1277  async stopAllTasks() {
1278    return this.post("/admin/worker/stop_all");
1279  }
1280  async clearFailureTasks() {
1281    return this.post("/admin/worker/clear_failures");
1282  }
1283  async getWorkerLogs() {
1284    return this.request("/admin/worker/logs");
1285  }
1286  async clearWorkerLogs() {
1287    return this.delete("/admin/worker/logs");
1288  }
1289  async getWorkerPipelineSteps() {
1290    return this.request("/admin/worker/steps");
1291  }
1292  async getWorkerInstances() {
1293    return this.request("/admin/workers/instances");
1294  }
1295  async spawnWorkerInstance(count, scale) {
1296    return this.post("/admin/workers/instances/spawn", { count, scale });
1297  }
1298  async closeIdleWorkerInstances() {
1299    return this.post("/admin/workers/instances/close-idle");
1300  }
1301  async terminateWorkerInstance(pid) {
1302    return this.post(`/admin/workers/instances/${pid}/terminate`);
1303  }
1304  async runMIDIIntegrityCheck(startId, endId) {
1305    const params = {};
1306    if (startId !== void 0 && startId !== null) params.startId = startId.toString();
1307    if (endId !== void 0 && endId !== null) params.endId = endId.toString();
1308    const queryStr = new URLSearchParams(params).toString();
1309    return this.request(queryStr ? `/admin/workers/integrity-check?${queryStr}` : "/admin/workers/integrity-check");
1310  }
1311  async getMIDIMap(startId, endId) {
1312    const params = {};
1313    if (startId !== void 0 && startId !== null) params.startId = startId.toString();
1314    if (endId !== void 0 && endId !== null) params.endId = endId.toString();
1315    const queryStr = new URLSearchParams(params).toString();
1316    return this.request(`/admin/workers/midi-map?${queryStr}`);
1317  }
1318  async reprocessUnhealthyMIDIs(filters, options) {
1319    return this.post("/admin/workers/reprocess-unhealthy", { filters, options });
1320  }
1321  async requeueMissingFound() {
1322    return this.post("/admin/workers/requeue-missing-found");
1323  }
1324  async getRequeueMissingStatus() {
1325    return this.request("/admin/workers/requeue-missing-status");
1326  }
1327  async getQueueAllUnhealthyStatus() {
1328    return this.request("/admin/workers/queue-all-unhealthy-status");
1329  }
1330  async reprocessMIDIRange(startId, endId, reprocessSteps, skipCompleted) {
1331    return this.post("/admin/workers/reprocess-range", { startId, endId, reprocessSteps, skipCompleted });
1332  }
1333  async prioritizeMidi(midiId) {
1334    return this.post("/admin/workers/prioritize", { midiId });
1335  }
1336  async replaceMidi(trackId, file) {
1337    const formData = new FormData();
1338    formData.append("midi", file);
1339    const res = await fetch(`/api/admin/tracks/${trackId}/replace-midi`, {
1340      method: "POST",
1341      body: formData,
1342      credentials: "include"
1343    });
1344    if (!res.ok) {
1345      const err = await res.json().catch(() => ({}));
1346      throw new Error(err.error || "MIDI replacement upload failed");
1347    }
1348    return await res.json();
1349  }
1350  async logActivity(data) {
1351    try {
1352      await this.post("/admin/logs", data);
1353    } catch (e) {
1354      console.error("Failed to report activity", e);
1355    }
1356  }
1357  // --- Hamie Tribunal ---
1358  async getTribunalCases(status) {
1359    const query = status ? `?status=${status}` : "";
1360    return this.request(`/tribunal/cases${query}`).catch(() => []);
1361  }
1362  async getTribunalCase(caseId) {
1363    return this.request(`/tribunal/cases/${caseId}`);
1364  }
1365  async voteTribunalCase(caseId, verdict, sentenceId, reason) {
1366    return this.post(`/tribunal/cases/${caseId}/vote`, { verdict, sentenceId, reason });
1367  }
1368  async getMyTribunalVotes() {
1369    return this.request("/tribunal/my-votes").catch(() => []);
1370  }
1371  async getJailCases(status) {
1372    const query = status ? `?status=${status}` : "";
1373    return this.request(`/admin/jail/cases${query}`).catch(() => []);
1374  }
1375  async resolveJailCase(caseId, action, sentenceId) {
1376    await this.post(`/admin/jail/cases/${caseId}/resolve`, { action, sentenceId });
1377  }
1378  async getSpamConfig() {
1379    return this.request("/admin/spam/config");
1380  }
1381  async saveSpamConfig(config) {
1382    await this.post("/admin/spam/config", config);
1383  }
1384  async uploadArchiveBatch(data) {
1385    const res = await fetch("/api/admin/archive-import/upload-batch", {
1386      method: "POST",
1387      body: data,
1388      credentials: "include"
1389    });
1390    if (!res.ok) {
1391      const err = await res.json().catch(() => ({ error: res.statusText }));
1392      throw new Error(err.error || `Batch Upload failed: ${res.status}`);
1393    }
1394    return await res.json();
1395  }
1396  async getStagingStats() {
1397    const res = await fetch("/api/admin/archive-import/staging/stats", { credentials: "include" });
1398    if (!res.ok) throw new Error(`Failed to fetch staging stats: ${res.statusText}`);
1399    return await res.json();
1400  }
1401  async clearStaging() {
1402    const res = await fetch("/api/admin/archive-import/staging/clear", {
1403      method: "POST",
1404      credentials: "include"
1405    });
1406    if (!res.ok) throw new Error(`Failed to clear staging folder: ${res.statusText}`);
1407    return await res.json();
1408  }
1409  async uploadToStaging(data) {
1410    const res = await fetch("/api/admin/archive-import/staging/upload", {
1411      method: "POST",
1412      body: data,
1413      credentials: "include"
1414    });
1415    if (!res.ok) {
1416      const err = await res.json().catch(() => ({ error: res.statusText }));
1417      throw new Error(err.error || `Staged Upload failed: ${res.status}`);
1418    }
1419    return await res.json();
1420  }
1421  async getStagingFiles(page = 1, limit = 50, search = "") {
1422    const url = `/api/admin/archive-import/staging/list?page=${page}&limit=${limit}&search=${encodeURIComponent(search)}`;
1423    const res = await fetch(url, { credentials: "include" });
1424    if (!res.ok) throw new Error(`Failed to fetch staging list: ${res.statusText}`);
1425    return await res.json();
1426  }
1427  async importStagingBatch(items, overwrite) {
1428    const res = await fetch("/api/admin/archive-import/staging/import", {
1429      method: "POST",
1430      headers: { "Content-Type": "application/json" },
1431      body: JSON.stringify({ items, overwrite }),
1432      credentials: "include"
1433    });
1434    if (!res.ok) {
1435      const err = await res.json().catch(() => ({ error: res.statusText }));
1436      throw new Error(err.error || `Staged Import failed: ${res.status}`);
1437    }
1438    return await res.json();
1439  }
1440  async getLakhStatus() {
1441    const res = await fetch("/api/admin/archive-import/lakh/status", { credentials: "include" });
1442    if (!res.ok) throw new Error(`Failed to fetch Lakh status: ${res.statusText}`);
1443    return await res.json();
1444  }
1445  async getLakhList(page = 1, limit = 10, search = "") {
1446    const url = `/api/admin/archive-import/lakh/list?page=${page}&limit=${limit}&search=${encodeURIComponent(search)}`;
1447    const res = await fetch(url, { credentials: "include" });
1448    if (!res.ok) throw new Error(`Failed to fetch Lakh list: ${res.statusText}`);
1449    return await res.json();
1450  }
1451  async setupLakhDemo() {
1452    const res = await fetch("/api/admin/archive-import/lakh/setup-demo", {
1453      method: "POST",
1454      credentials: "include"
1455    });
1456    if (!res.ok) throw new Error(`Failed to setup Lakh demo seeds: ${res.statusText}`);
1457    return await res.json();
1458  }
1459  async importLakhTracks(hashes, items, overwrite) {
1460    const res = await fetch("/api/admin/archive-import/lakh/import", {
1461      method: "POST",
1462      headers: { "Content-Type": "application/json" },
1463      body: JSON.stringify({ hashes, items, overwrite }),
1464      credentials: "include"
1465    });
1466    if (!res.ok) {
1467      const err = await res.json().catch(() => ({ error: res.statusText }));
1468      throw new Error(err.error || `Lakh Import failed: ${res.status}`);
1469    }
1470    return await res.json();
1471  }
1472  async startLakhImportAll(overwrite) {
1473    const res = await fetch("/api/admin/archive-import/lakh/import-all", {
1474      method: "POST",
1475      headers: { "Content-Type": "application/json" },
1476      body: JSON.stringify({ overwrite }),
1477      credentials: "include"
1478    });
1479    if (!res.ok) {
1480      const err = await res.json().catch(() => ({ error: res.statusText }));
1481      throw new Error(err.error || `Lakh Import All failed: ${res.status}`);
1482    }
1483    return await res.json();
1484  }
1485  async getLakhImportAllStatus() {
1486    const res = await fetch("/api/admin/archive-import/lakh/import-all-status", {
1487      method: "GET",
1488      credentials: "include"
1489    });
1490    if (!res.ok) throw new Error(`Failed to get Lakh import-all status: ${res.statusText}`);
1491    return await res.json();
1492  }
1493  async stopLakhImportAll() {
1494    const res = await fetch("/api/admin/archive-import/lakh/import-all-stop", {
1495      method: "POST",
1496      credentials: "include"
1497    });
1498    if (!res.ok) throw new Error(`Failed to stop Lakh import-all: ${res.statusText}`);
1499    return await res.json();
1500  }
1501  async getAriaStatus() {
1502    const res = await fetch("/api/admin/archive-import/aria/status", { credentials: "include" });
1503    if (!res.ok) throw new Error(`Failed to fetch Aria status: ${res.statusText}`);
1504    return await res.json();
1505  }
1506  async getAriaList(page = 1, limit = 10, search = "") {
1507    const url = `/api/admin/archive-import/aria/list?page=${page}&limit=${limit}&search=${encodeURIComponent(search)}`;
1508    const res = await fetch(url, { credentials: "include" });
1509    if (!res.ok) throw new Error(`Failed to fetch Aria list: ${res.statusText}`);
1510    return await res.json();
1511  }
1512  async importAriaTracks(hashes, items, overwrite) {
1513    const res = await fetch("/api/admin/archive-import/aria/import", {
1514      method: "POST",
1515      headers: { "Content-Type": "application/json" },
1516      body: JSON.stringify({ hashes, items, overwrite }),
1517      credentials: "include"
1518    });
1519    if (!res.ok) {
1520      const err = await res.json().catch(() => ({ error: res.statusText }));
1521      throw new Error(err.error || `Aria Import failed: ${res.status}`);
1522    }
1523    return await res.json();
1524  }
1525  async startAriaImportAll(overwrite) {
1526    const res = await fetch("/api/admin/archive-import/aria/import-all", {
1527      method: "POST",
1528      headers: { "Content-Type": "application/json" },
1529      body: JSON.stringify({ overwrite }),
1530      credentials: "include"
1531    });
1532    if (!res.ok) {
1533      const err = await res.json().catch(() => ({ error: res.statusText }));
1534      throw new Error(err.error || `Aria Import All failed: ${res.status}`);
1535    }
1536    return await res.json();
1537  }
1538  async getAriaImportAllStatus() {
1539    const res = await fetch("/api/admin/archive-import/aria/import-all-status", {
1540      method: "GET",
1541      credentials: "include"
1542    });
1543    if (!res.ok) throw new Error(`Failed to get Aria import-all status: ${res.statusText}`);
1544    return await res.json();
1545  }
1546  async stopAriaImportAll() {
1547    const res = await fetch("/api/admin/archive-import/aria/import-all-stop", {
1548      method: "POST",
1549      credentials: "include"
1550    });
1551    if (!res.ok) throw new Error(`Failed to stop Aria import-all: ${res.statusText}`);
1552    return await res.json();
1553  }
1554  async getPianocoreStatus() {
1555    const res = await fetch("/api/admin/archive-import/pianocore/status", { credentials: "include" });
1556    if (!res.ok) throw new Error(`Failed to fetch Pianocore status: ${res.statusText}`);
1557    return await res.json();
1558  }
1559  async getPianocoreList(page = 1, limit = 10, search = "", refresh = false) {
1560    const url = `/api/admin/archive-import/pianocore/list?page=${page}&limit=${limit}&search=${encodeURIComponent(search)}${refresh ? "&refresh=true" : ""}`;
1561    const res = await fetch(url, { credentials: "include" });
1562    if (!res.ok) throw new Error(`Failed to fetch Pianocore list: ${res.statusText}`);
1563    return await res.json();
1564  }
1565  async importPianocoreTracks(hashes, items, overwrite) {
1566    const res = await fetch("/api/admin/archive-import/pianocore/import", {
1567      method: "POST",
1568      headers: { "Content-Type": "application/json" },
1569      body: JSON.stringify({ hashes, items, overwrite }),
1570      credentials: "include"
1571    });
1572    if (!res.ok) {
1573      const err = await res.json().catch(() => ({ error: res.statusText }));
1574      throw new Error(err.error || `Pianocore Import failed: ${res.status}`);
1575    }
1576    return await res.json();
1577  }
1578  async startPianocoreImportAll(overwrite) {
1579    const res = await fetch("/api/admin/archive-import/pianocore/import-all", {
1580      method: "POST",
1581      headers: { "Content-Type": "application/json" },
1582      body: JSON.stringify({ overwrite }),
1583      credentials: "include"
1584    });
1585    if (!res.ok) {
1586      const err = await res.json().catch(() => ({ error: res.statusText }));
1587      throw new Error(err.error || `Pianocore Import All failed: ${res.status}`);
1588    }
1589    return await res.json();
1590  }
1591  async getPianocoreImportAllStatus() {
1592    const res = await fetch("/api/admin/archive-import/pianocore/import-all-status", {
1593      method: "GET",
1594      credentials: "include"
1595    });
1596    if (!res.ok) throw new Error(`Failed to get Pianocore import-all status: ${res.statusText}`);
1597    return await res.json();
1598  }
1599  async stopPianocoreImportAll() {
1600    const res = await fetch("/api/admin/archive-import/pianocore/import-all-stop", {
1601      method: "POST",
1602      credentials: "include"
1603    });
1604    if (!res.ok) throw new Error(`Failed to stop Pianocore import-all: ${res.statusText}`);
1605    return await res.json();
1606  }
1607  async getClusterCredentials() {
1608    return this.request("/admin/cluster/load");
1609  }
1610  async saveClusterCredentials(credentials) {
1611    return this.post("/admin/cluster/save", { credentials });
1612  }
1613  async getClusterRsyncStatus() {
1614    return this.request("/admin/cluster/rsync/status");
1615  }
1616  async controlClusterRsync(action, node, mode = "fast") {
1617    return this.post("/admin/cluster/rsync/control", { action, node, mode });
1618  }
1619  // --- Cluster DB Status Response Contract ---
1620  // If the backend /api/admin/cluster/db/status response shape ever changes,
1621  // update this interface AND the parseRows calls in web/routes/admin/cluster.js.
1622  async getClusterDbStatus() {
1623    return this.request("/admin/cluster/db/status");
1624  }
1625  async testOpenRouterAPI(prompt, model) {
1626    return this.post("/admin/test-openrouter", { prompt, model });
1627  }
1628  // --- Keyboard Emulator Presets ---
1629  async getKeyboardEmulatorPresets() {
1630    return this.request("/v1/keyboard-emulator/presets");
1631  }
1632  async createKeyboardEmulatorPreset(name, state) {
1633    return this.post("/v1/keyboard-emulator/presets", { name, state });
1634  }
1635  async updateKeyboardEmulatorPreset(id, name, state) {
1636    return this.put(`/v1/keyboard-emulator/presets/${id}`, { name, state });
1637  }
1638  async deleteKeyboardEmulatorPreset(id) {
1639    return this.delete(`/v1/keyboard-emulator/presets/${id}`);
1640  }
1641  async resetClusterDbSubscription(node) {
1642    return this.post("/admin/cluster/db/resync", { node });
1643  }
1644  async controlClusterDbSubscription(action, node = "both") {
1645    return this.post("/admin/cluster/db/control", { action, node });
1646  }
1647  async compareClusterDb(table = "Midi") {
1648    return this.request(`/admin/cluster/db/compare?table=${encodeURIComponent(table)}`);
1649  }
1650  async syncClusterDbRows(node, table, ids) {
1651    return this.post("/admin/cluster/db/sync-rows", { node, table, ids });
1652  }
1653  async repairClusterPublication() {
1654    return this.post("/admin/cluster/db/repair-publication", {});
1655  }
1656  async getClusterDbLogs() {
1657    return this.request("/admin/cluster/db/logs");
1658  }
1659  async unblockClusterReplication() {
1660    return this.post("/admin/cluster/db/unblock", {});
1661  }
1662  async getProjects() {
1663    return this.request("/projects").catch(() => []);
1664  }
1665  async deleteProject(id) {
1666    return this.delete(`/projects/${id}`);
1667  }
1668  // --- Lyrics Endpoints ---
1669  async getLyricsById(id) {
1670    return this.request(`/lyrics/${id}`);
1671  }
1672  async searchLyrics(q, page = 1, limit = 20) {
1673    const controller = typeof AbortController !== "undefined" ? new AbortController() : null;
1674    const timeoutId = controller ? setTimeout(() => controller.abort(), 8e3) : null;
1675    try {
1676      const res = await this.request(`/lyrics/search?q=${encodeURIComponent(q)}&page=${page}&limit=${limit}`, {
1677        signal: controller ? controller.signal : void 0
1678      });
1679      if (timeoutId) clearTimeout(timeoutId);
1680      return res || { results: [], total: 0 };
1681    } catch (e) {
1682      if (timeoutId) clearTimeout(timeoutId);
1683      return { results: [], total: 0 };
1684    }
1685  }
1686  async submitLyrics(data) {
1687    return this.post("/lyrics", data);
1688  }
1689  async updateLyrics(id, data) {
1690    return this.request(`/lyrics/${id}`, {
1691      method: "PATCH",
1692      body: JSON.stringify(data)
1693    });
1694  }
1695  async deleteLyrics(id) {
1696    return this.delete(`/lyrics/${id}`);
1697  }
1698  async getDigitizerProjects() {
1699    return this.request("/digitizer/projects").catch(() => []);
1700  }
1701  async getDigitizerProject(id) {
1702    return this.request(`/digitizer/projects/${id}`);
1703  }
1704  async deleteDigitizerProject(id) {
1705    return this.delete(`/digitizer/projects/${id}`);
1706  }
1707  async saveDigitizerProject(formData) {
1708    const res = await fetch(`/api/digitizer/projects/save`, {
1709      method: "POST",
1710      body: formData,
1711      credentials: "include"
1712    });
1713    if (!res.ok) {
1714      const err = await res.json().catch(() => ({ error: "Failed to save digitizer project" }));
1715      throw new Error(err.error || "Failed to save digitizer project");
1716    }
1717    return res.json();
1718  }
1719  async processAudio(formData) {
1720    const res = await fetch("/api/audio-digitizer/process", {
1721      method: "POST",
1722      body: formData,
1723      credentials: "include"
1724    });
1725    if (!res.ok) {
1726      const err = await res.json().catch(() => ({ error: "Audio transcription job failed" }));
1727      throw new Error(err.error || "Audio transcription job failed");
1728    }
1729    return await res.json();
1730  }
1731  async getAudioStatus(jobId) {
1732    return this.request(`/audio-digitizer/status/${jobId}`);
1733  }
1734  // --- Collections Endpoints ---
1735  async getCollections() {
1736    return this.request("/collections");
1737  }
1738  async searchCollectionSections(query, limit = 24) {
1739    if (!query || query.trim().length < 2) return [];
1740    const res = await this.request(`/collections/search?q=${encodeURIComponent(query.trim())}&limit=${limit}`).catch(() => ({ sections: [] }));
1741    return (res == null ? void 0 : res.sections) || [];
1742  }
1743  async getTrackCollections(trackId) {
1744    return this.request(`/collections/by-track/${trackId}`).catch(() => []);
1745  }
1746  async getRandomCollectionSection() {
1747    return this.request("/collections/random-section");
1748  }
1749  async getRandomBannerRow() {
1750    return this.request("/collections/random-banner-row");
1751  }
1752  async getRandomBannerRows(count = 4) {
1753    const res = await this.request(`/collections/random-banner-rows?count=${count}`);
1754    return (res == null ? void 0 : res.rows) || [];
1755  }
1756  async getCollection(slug) {
1757    return this.request(`/collections/${slug}`);
1758  }
1759  async getSectionTracks(slug, sectionId, sort = "popular", limit = 20, skip = 0) {
1760    return this.request(`/collections/${slug}/sections/${sectionId}/tracks?sort=${sort}&limit=${limit}&skip=${skip}`);
1761  }
1762  async getSectionSongs(slug, sectionId) {
1763    return this.request(`/collections/${slug}/sections/${sectionId}/songs`);
1764  }
1765  async getSectionCounts(slug, sectionIds) {
1766    return this.post(`/collections/${slug}/section-counts`, { sectionIds });
1767  }
1768  async scanSectionSongs(slug, sectionId) {
1769    return this.post(`/collections/${slug}/sections/${sectionId}/songs/scan`);
1770  }
1771  // --- Collections Admin Endpoints ---
1772  async getAdminCollections() {
1773    return this.request("/admin/collections");
1774  }
1775  async getAdminCollection(id) {
1776    return this.request(`/admin/collections/${id}`);
1777  }
1778  async createAdminCollection(data) {
1779    return this.post("/admin/collections", data);
1780  }
1781  async updateAdminCollection(id, data) {
1782    return this.put(`/admin/collections/${id}`, data);
1783  }
1784  async deleteAdminCollection(id) {
1785    return this.delete(`/admin/collections/${id}`);
1786  }
1787  async createAdminSection(collectionId, data) {
1788    return this.post(`/admin/collections/${collectionId}/sections`, data);
1789  }
1790  async updateAdminSection(collectionId, sectionId, data) {
1791    return this.put(`/admin/collections/${collectionId}/sections/${sectionId}`, data);
1792  }
1793  async deleteAdminSection(collectionId, sectionId) {
1794    return this.delete(`/admin/collections/${collectionId}/sections/${sectionId}`);
1795  }
1796  async curateAdminSection(collectionId, sectionId, trackIds) {
1797    return this.post(`/admin/collections/${collectionId}/sections/${sectionId}/curate`, { trackIds });
1798  }
1799  async reorderAdminSections(collectionId, sectionIds) {
1800    return this.post(`/admin/collections/${collectionId}/reorder`, { sectionIds });
1801  }
1802  // Curated Works (Songs) Management
1803  async createSectionSong(collectionId, sectionId, data) {
1804    return this.post(`/admin/collections/${collectionId}/sections/${sectionId}/songs`, data);
1805  }
1806  async updateSectionSong(collectionId, sectionId, songId, data) {
1807    return this.put(`/admin/collections/${collectionId}/sections/${sectionId}/songs/${songId}`, data);
1808  }
1809  async deleteSectionSong(collectionId, sectionId, songId) {
1810    return this.delete(`/admin/collections/${collectionId}/sections/${sectionId}/songs/${songId}`);
1811  }
1812  async reorderSectionSongs(collectionId, sectionId, songIds) {
1813    return this.post(`/admin/collections/${collectionId}/sections/${sectionId}/songs/reorder`, { songIds });
1814  }
1815  // --- Soundfonts API ---
1816  async getPublicSoundfonts(params) {
1817    const queryParams = new URLSearchParams();
1818    if (params.page) queryParams.append("page", params.page.toString());
1819    if (params.limit) queryParams.append("limit", params.limit.toString());
1820    if (params.sort) queryParams.append("sort", params.sort);
1821    if (params.search) queryParams.append("search", params.search);
1822    if (params.tag) queryParams.append("tag", params.tag);
1823    return this.get(`/soundfonts?${queryParams.toString()}`);
1824  }
1825  async getSoundfont(slug) {
1826    return this.get(`/soundfonts/${slug}`);
1827  }
1828  async uploadSoundfont(formData) {
1829    return this.post("/soundfonts/upload", formData, {
1830      headers: { "Content-Type": "multipart/form-data" }
1831    });
1832  }
1833  async rateSoundfont(id, rating, reviewText) {
1834    return this.post(`/soundfonts/${id}/rate`, { rating, reviewText });
1835  }
1836}
1837const apiBase = "/api";
1838const api = new ApiClient(apiBase);
1839class UxTracker {
1840  constructor() {
1841    this.eventQueue = [];
1842    this.flushInterval = null;
1843    this.initialized = false;
1844    this.activeSessionId = null;
1845    this.expiresAt = null;
1846    this.lastResourceTimestamp = 0;
1847  }
1848  init() {
1849    if (this.initialized || typeof window === "undefined") return;
1850    this.initialized = true;
1851    console.log("[UX Tracker] Initializing UX telemetry logger...");
1852    this.checkMonitoringStatus();
1853    this.setupFetchInterceptor();
1854    this.setupClickInterceptor();
1855    this.setupResourceTimingMonitor();
1856    this.flushInterval = setInterval(() => this.flush(), 5e3);
1857    window.addEventListener("beforeunload", () => this.flushSync());
1858    window.addEventListener("visibilitychange", () => {
1859      if (document.visibilityState === "hidden") {
1860        this.flushSync();
1861      }
1862    });
1863  }
1864  async checkMonitoringStatus() {
1865    try {
1866      const res = await window.fetch("/api/system/monitoring-status", { credentials: "include" });
1867      if (res.ok) {
1868        const data = await res.json();
1869        this.updateSessionState(data.active ? data.sessionId : null, data.expiresAt || null);
1870      }
1871    } catch (e) {
1872      console.error("[UX Tracker] Status check failed:", e);
1873    }
1874  }
1875  updateSessionState(sessionId, expiresAt) {
1876    this.activeSessionId = sessionId;
1877    this.expiresAt = expiresAt;
1878    window.__activeMonitoringSessionId = sessionId;
1879    if (sessionId) {
1880      console.log(`[UX Tracker] Interaction Logging ACTIVE for session: ${sessionId}`);
1881    } else {
1882      if (window.__activeMonitoringSessionId) {
1883        console.log("[UX Tracker] Interaction Logging INACTIVE.");
1884      }
1885    }
1886  }
1887  logEvent(eventType, data) {
1888    if (typeof window === "undefined") return;
1889    const sessionId = this.activeSessionId || window.__activeMonitoringSessionId;
1890    if (!sessionId) return;
1891    if (this.expiresAt && Date.now() > this.expiresAt) {
1892      this.updateSessionState(null, null);
1893      return;
1894    }
1895    const logEntry = {
1896      url: data.url || window.location.pathname + window.location.search,
1897      eventType,
1898      elementId: data.elementId || null,
1899      path: data.path || null,
1900      duration: data.duration !== void 0 ? Math.round(data.duration) : null,
1901      metadata: data.metadata || {}
1902    };
1903    this.eventQueue.push(logEntry);
1904    if (this.eventQueue.length >= 15) {
1905      this.flush();
1906    }
1907  }
1908  setupFetchInterceptor() {
1909    const originalFetch = window.fetch;
1910    const self = this;
1911    window.fetch = async function(input, init) {
1912      const start = performance.now();
1913      let url = "";
1914      if (typeof input === "string") {
1915        url = input;
1916      } else if (input instanceof Request) {
1917        url = input.url;
1918      }
1919      const isTelemetry = url.includes("/api/performance/logs") || url.includes("/api/system/monitoring-status");
1920      try {
1921        const response = await originalFetch(input, init);
1922        if (!isTelemetry) {
1923          const monitoringHeader = response.headers.get("X-Monitoring-Active");
1924          const currentSession = self.activeSessionId;
1925          if (monitoringHeader && monitoringHeader !== currentSession) {
1926            self.updateSessionState(monitoringHeader, Date.now() + 30 * 60 * 1e3);
1927          } else if (monitoringHeader === "" && currentSession) {
1928            self.updateSessionState(null, null);
1929          }
1930        }
1931        const activeSession = self.activeSessionId || window.__activeMonitoringSessionId;
1932        if (activeSession && !isTelemetry) {
1933          const duration = performance.now() - start;
1934          self.logEvent("api_call", {
1935            url,
1936            duration,
1937            metadata: {
1938              method: (init == null ? void 0 : init.method) || "GET",
1939              status: response.status
1940            }
1941          });
1942          const proxiedResponse = response.clone();
1943          const wrapBodyMethod = (methodName) => {
1944            const originalMethod = response[methodName];
1945            response[methodName] = async function() {
1946              const bodyStart = performance.now();
1947              try {
1948                const result = await originalMethod.call(response);
1949                const bodyDuration = performance.now() - bodyStart;
1950                self.logEvent("download", {
1951                  url,
1952                  duration: duration + bodyDuration,
1953                  // Headers roundtrip + download streaming time
1954                  metadata: {
1955                    method: (init == null ? void 0 : init.method) || "GET",
1956                    status: response.status,
1957                    type: methodName,
1958                    size: result instanceof Blob ? result.size : void 0
1959                  }
1960                });
1961                return result;
1962              } catch (err) {
1963                throw err;
1964              }
1965            };
1966          };
1967          if (url.includes("/secure-download") || url.includes("/storage/tracks") || url.endsWith(".opus") || url.endsWith(".mp3")) {
1968            wrapBodyMethod("blob");
1969          }
1970        }
1971        return response;
1972      } catch (err) {
1973        const activeSession = self.activeSessionId || window.__activeMonitoringSessionId;
1974        if (activeSession && !isTelemetry) {
1975          const duration = performance.now() - start;
1976          self.logEvent("error", {
1977            url,
1978            duration,
1979            metadata: {
1980              method: (init == null ? void 0 : init.method) || "GET",
1981              error: (err == null ? void 0 : err.message) || String(err)
1982            }
1983          });
1984        }
1985        throw err;
1986      }
1987    };
1988  }
1989  setupClickInterceptor() {
1990    document.addEventListener("click", (e) => {
1991      var _a;
1992      const activeSession = this.activeSessionId || window.__activeMonitoringSessionId;
1993      if (!activeSession) return;
1994      const target = e.target;
1995      const interactive = target.closest('button, a, input, select, [role="button"], .btn');
1996      if (interactive) {
1997        const tag = interactive.tagName.toLowerCase();
1998        const text = ((_a = interactive.innerText) == null ? void 0 : _a.substring(0, 30).trim()) || interactive.value || "";
1999        const id = interactive.id || null;
2000        const className = interactive.className || "";
2001        this.logEvent("click", {
2002          url: window.location.pathname + window.location.search,
2003          elementId: id,
2004          path: tag,
2005          metadata: {
2006            target: tag,
2007            text,
2008            className: className.substring(0, 50)
2009          }
2010        });
2011      }
2012    }, true);
2013  }
2014  setupResourceTimingMonitor() {
2015    setInterval(() => {
2016      const activeSession = this.activeSessionId || window.__activeMonitoringSessionId;
2017      if (!activeSession) return;
2018      const entries = performance.getEntriesByType("resource");
2019      const newEntries = entries.filter((entry) => entry.startTime > this.lastResourceTimestamp);
2020      for (const entry of newEntries) {
2021        const isSlow = entry.duration > 800;
2022        const isTargetAsset = entry.name.includes(".opus") || entry.name.includes(".mp3") || entry.name.includes("-opus.js") || entry.name.includes(".mid");
2023        if (isSlow && isTargetAsset) {
2024          this.logEvent("resource", {
2025            url: entry.name,
2026            duration: entry.duration,
2027            metadata: {
2028              initiatorType: entry.initiatorType,
2029              transferSize: entry.transferSize,
2030              decodedBodySize: entry.decodedBodySize,
2031              encodedBodySize: entry.encodedBodySize
2032            }
2033          });
2034        }
2035      }
2036      if (entries.length > 0) {
2037        this.lastResourceTimestamp = Math.max(...entries.map((e) => e.startTime));
2038      }
2039    }, 1e4);
2040  }
2041  async flush() {
2042    if (this.eventQueue.length === 0) return;
2043    const sessionId = this.activeSessionId || window.__activeMonitoringSessionId;
2044    if (!sessionId) {
2045      this.eventQueue = [];
2046      return;
2047    }
2048    const logsToSend = [...this.eventQueue];
2049    this.eventQueue = [];
2050    try {
2051      const res = await window.fetch("/api/performance/logs", {
2052        method: "POST",
2053        headers: { "Content-Type": "application/json" },
2054        body: JSON.stringify({ sessionId, logs: logsToSend }),
2055        credentials: "include"
2056      });
2057      if (!res.ok) {
2058        this.eventQueue = [...logsToSend, ...this.eventQueue].slice(0, 100);
2059      }
2060    } catch (e) {
2061      this.eventQueue = [...logsToSend, ...this.eventQueue].slice(0, 100);
2062    }
2063  }
2064  flushSync() {
2065    const sessionId = this.activeSessionId || window.__activeMonitoringSessionId;
2066    if (!sessionId || this.eventQueue.length === 0) return;
2067    const payload = JSON.stringify({ sessionId, logs: this.eventQueue });
2068    this.eventQueue = [];
2069    if (navigator.sendBeacon) {
2070      navigator.sendBeacon("/api/performance/logs", new Blob([payload], { type: "application/json" }));
2071    } else {
2072      try {
2073        const xhr = new XMLHttpRequest();
2074        xhr.open("POST", "/api/performance/logs", false);
2075        xhr.setRequestHeader("Content-Type", "application/json");
2076        xhr.withCredentials = true;
2077        xhr.send(payload);
2078      } catch (e) {
2079      }
2080    }
2081  }
2082}
2083const uxTracker = new UxTracker();
2084instance.use(Backend).use(Browser).use(initReactI18next).init({
2085  fallbackLng: {
2086    "zh-Hant": ["zh-TW", "en"],
2087    "zh-Hans": ["zh-CN", "en"],
2088    "zh": ["zh-CN", "en"],
2089    "default": ["en"]
2090  },
2091  load: "currentOnly",
2092  // Changed from 'languageOnly' to allow variants like zh-CN
2093  debug: false,
2094  // Backend Configuration
2095  backend: {
2096    loadPath: "/locales/{{lng}}/{{ns}}.json"
2097  },
2098  react: {
2099    useSuspense: true
2100    // Enable suspense so the UI waits for translations
2101  },
2102  interpolation: {
2103    escapeValue: false
2104  },
2105  detection: {
2106    order: ["cookie", "localStorage", "navigator"],
2107    caches: ["cookie", "localStorage"],
2108    lookupCookie: "i18nextLng",
2109    lookupLocalStorage: "i18nextLng",
2110    cookieMinutes: 525600,
2111    // 1 year
2112    cookieOptions: { path: "/", sameSite: "lax" }
2113  }
2114});
2115const reloadI18nResources = () => {
2116  instance.reloadResources();
2117};
2118const NATIVE_LANGUAGE_NAMES = {
2119  "en": "English",
2120  "zh-CN": "简体中文",
2121  "zh-TW": "繁體中文",
2122  "es": "Español",
2123  "fr": "Français",
2124  "de": "Deutsch",
2125  "ja": "日本語",
2126  "pt": "Português",
2127  "ru": "Русский",
2128  "ko": "한국어",
2129  "it": "Italiano",
2130  "id": "Bahasa Indonesia",
2131  "ar": "العربية",
2132  "hi": "हिन्दी",
2133  "bn": "বাংলা",
2134  "tr": "Türkçe",
2135  "vi": "Tiếng Việt",
2136  "pl": "Polski",
2137  "uk": "Українська",
2138  "nl": "Nederlands",
2139  "th": "ภาษาไทย",
2140  "el": "Ελληνικά",
2141  "cs": "Čeština",
2142  "ro": "Română",
2143  "sv": "Svenska",
2144  "hu": "Magyar",
2145  "da": "Dansk",
2146  "fi": "Suomi",
2147  "no": "Norsk",
2148  "sk": "Slovenčina",
2149  "bg": "Български",
2150  "hr": "Hrvatski",
2151  "sr": "Српски",
2152  "ms": "Bahasa Melayu",
2153  "he": "עברית",
2154  "fa": "فارسی",
2155  "ta": "தமிழ்",
2156  "te": "తెలుగు",
2157  "mr": "मराठी",
2158  "ur": "اردو",
2159  "gu": "ગુજરાતી",
2160  "kn": "ಕನ್ನಡ",
2161  "ml": "മലയാളം",
2162  "pa": "ਪੰਜਾਬੀ",
2163  "fil": "Filipino",
2164  "sw": "Kiswahili",
2165  "jv": "Basa Jawa",
2166  "uz": "Oʻzbekcha",
2167  "am": "አማርኛ",
2168  "ne": "नेपाली",
2169  "si": "සිංහල",
2170  "km": "ភាសាខ្មែរ",
2171  "my": "မြန်မာစာ",
2172  "tk": "Türkmen",
2173  "ug": "ئۇيغۇرچە",
2174  "lo": "ລາວ",
2175  "ku": "Kurdî",
2176  "ps": "پښتو",
2177  "cy": "Cymraeg",
2178  "ga": "Gaeilge",
2179  "mt": "Malti",
2180  "bs": "Bosanski",
2181  "mk": "Македонски",
2182  "sq": "Shqip",
2183  "ka": "ქართული",
2184  "hy": "Հայերեն",
2185  "az": "Azərbaycan",
2186  "et": "Eesti",
2187  "lv": "Latviešu",
2188  "lt": "Lietuvių",
2189  "sl": "Slovenščina",
2190  "is": "Íslenska",
2191  "af": "Afrikaans",
2192  "gl": "Galego",
2193  "eu": "Euskara",
2194  "ca": "Català",
2195  "mn": "Монгол",
2196  "la": "Latina",
2197  "eo": "Esperanto",
2198  "yi": "ייִדיש"
2199};
2200const LANGUAGE_REGIONS = {
2201  "Popular": ["en", "zh-CN", "zh-TW", "es", "fr", "de", "ja", "pt", "ru", "ko"],
2202  "Asia": ["zh-CN", "zh-TW", "ja", "ko", "hi", "bn", "vi", "th", "ms", "id", "ta", "te", "mr", "ur", "gu", "kn", "ml", "pa", "fil", "jv", "uz", "ne", "si", "km", "my", "tk", "ug", "lo", "ps", "mn"],
2203  "Europe": ["en", "es", "fr", "de", "pt", "ru", "it", "tr", "pl", "uk", "nl", "el", "cs", "ro", "sv", "hu", "da", "fi", "no", "sk", "bg", "hr", "sr", "cy", "ga", "mt", "bs", "mk", "sq", "ka", "hy", "az", "et", "lv", "lt", "sl", "is", "gl", "eu", "ca", "la", "eo", "yi"],
2204  "Middle East & Africa": ["ar", "he", "fa", "sw", "am", "ku", "af"]
2205};
2206const COUNTRY_TO_LANGUAGE = {
2207  "US": "en",
2208  "GB": "en",
2209  "CA": "en",
2210  "AU": "en",
2211  "NZ": "en",
2212  "CN": "zh-CN",
2213  "TW": "zh-TW",
2214  "HK": "zh-TW",
2215  "MO": "zh-TW",
2216  "ES": "es",
2217  "MX": "es",
2218  "AR": "es",
2219  "CO": "es",
2220  "PE": "es",
2221  "VE": "es",
2222  "CL": "es",
2223  "FR": "fr",
2224  "BE": "fr",
2225  "CH": "de",
2226  "DE": "de",
2227  "AT": "de",
2228  "JP": "ja",
2229  "KR": "ko",
2230  "BR": "pt",
2231  "PT": "pt",
2232  "RU": "ru",
2233  "BY": "ru",
2234  "KZ": "ru",
2235  "IT": "it",
2236  "ID": "id",
2237  "IN": "hi",
2238  "BD": "bn",
2239  "TR": "tr",
2240  "VN": "vi",
2241  "PL": "pl",
2242  "UA": "uk",
2243  "NL": "nl",
2244  "TH": "th",
2245  "GR": "el",
2246  "CZ": "cs",
2247  "RO": "ro",
2248  "SE": "sv",
2249  "HU": "hu",
2250  "DK": "da",
2251  "FI": "fi",
2252  "NO": "no",
2253  "SK": "sk",
2254  "BG": "bg",
2255  "HR": "hr",
2256  "RS": "sr",
2257  "MY": "ms",
2258  "IL": "he",
2259  "IR": "fa",
2260  "PK": "ur",
2261  "PH": "fil",
2262  "KE": "sw",
2263  "TZ": "sw",
2264  "ZA": "af",
2265  "EG": "ar",
2266  "SA": "ar",
2267  "AE": "ar",
2268  "IQ": "ar",
2269  "MA": "ar",
2270  "DZ": "ar"
2271};
2272const i18n = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
2273  __proto__: null,
2274  COUNTRY_TO_LANGUAGE,
2275  LANGUAGE_REGIONS,
2276  NATIVE_LANGUAGE_NAMES,
2277  default: instance,
2278  reloadI18nResources
2279}, Symbol.toStringTag, { value: "Module" }));
2280const DEFAULT_GRAPHICS = {
2281  mainLogo: "",
2282  favicon: "",
2283  defaultThumbnail: "",
2284  seasonalLogos: [],
2285  icons: {},
2286  iconSets: []
2287};
2288const DEFAULT_UI_CONFIG = {
2289  enableLanguageSelector: true,
2290  enableCurrencySelector: true,
2291  enableAppearanceSelector: true
2292};
2293const DEFAULT_AD_SETTINGS = {
2294  isEnabled: false,
2295  publisherId: "",
2296  enableAutoAds: false,
2297  enableVignettes: false,
2298  enableAnchors: false,
2299  enableNonIntrusiveOnly: false,
2300  adUnits: {
2301    homeTop: { enabled: false, slotId: "" },
2302    homeBottom: { enabled: false, slotId: "" },
2303    watchSidebar: { enabled: false, slotId: "" },
2304    watchBottom: { enabled: false, slotId: "" },
2305    exploreSidebar: { enabled: false, slotId: "" },
2306    exploreList: { enabled: false, slotId: "" },
2307    converterBottom: { enabled: false, slotId: "" }
2308  },
2309  excludeRoles: ["ADMIN"]
2310};
2311const SettingsContext = reactExports.createContext(void 0);
2312const SettingsProvider = ({ children }) => {
2313  const getSavedSettings = () => {
2314    const saved = localStorage.getItem("hamie_settings");
2315    if (saved) {
2316      try {
2317        return JSON.parse(saved);
2318      } catch (e) {
2319        console.error("Failed to parse settings", e);
2320      }
2321    }
2322    return {};
2323  };
2324  const initialSettings = getSavedSettings();
2325  const [language, setLanguageState] = reactExports.useState(initialSettings.language || instance.language || "en");
2326  const [currency, setCurrencyState] = reactExports.useState(initialSettings.currency || "USD");
2327  const [country, setCountryState] = reactExports.useState(initialSettings.country || "US");
2328  const [autoplay, setAutoplayState] = reactExports.useState(initialSettings.autoplay ?? false);
2329  const [audioQuality, setAudioQualityState] = reactExports.useState(initialSettings.audioQuality || "low");
2330  const [soundfontVolume, setSoundfontVolumeState] = reactExports.useState(() => {
2331    const val = initialSettings.soundfontVolume;
2332    if (val === void 0 || val === 4) {
2333      return 6;
2334    }
2335    return val;
2336  });
2337  const [availableLanguages, setAvailableLanguages] = reactExports.useState([]);
2338  const [graphics, setGraphics] = reactExports.useState(DEFAULT_GRAPHICS);
2339  const [uiConfig, setUiConfig] = reactExports.useState(DEFAULT_UI_CONFIG);
2340  const [adSettings, setAdSettings] = reactExports.useState(DEFAULT_AD_SETTINGS);
2341  reactExports.useEffect(() => {
2342    if (initialSettings.language) {
2343      instance.changeLanguage(initialSettings.language);
2344    }
2345    const handleLanguageChanged = (lng) => {
2346      setLanguageState(lng);
2347    };
2348    instance.on("languageChanged", handleLanguageChanged);
2349    api.getLocales().then((locales) => {
2350      setAvailableLanguages(locales.filter((l) => l.isActive).map((l) => ({ code: l.code, name: NATIVE_LANGUAGE_NAMES[l.code] || l.name })));
2351    }).catch(console.error);
2352    const loadSystemSettings = async () => {
2353      try {
2354        const gfx = await api.getSystemSetting("site_graphics");
2355        if (gfx && gfx.value) setGraphics(gfx.value);
2356        const ui = await api.getSystemSetting("ui_config");
2357        if (ui && ui.value) setUiConfig(ui.value);
2358        const ads = await api.getSystemSetting("ad_settings");
2359        if (ads && ads.value) setAdSettings(ads.value);
2360      } catch (error) {
2361        console.error("Failed to load system settings", error);
2362      }
2363    };
2364    loadSystemSettings();
2365    const handleSettingsUpdate = (event) => {
2366      const customEvent = event;
2367      const { key, value } = customEvent.detail;
2368      if (key === "site_graphics") {
2369        setGraphics((prev) => ({ ...prev, ...value }));
2370      } else if (key === "ui_config") {
2371        setUiConfig((prev) => ({ ...prev, ...value }));
2372      } else if (key === "ad_settings") {
2373        setAdSettings((prev) => ({ ...prev, ...value }));
2374      }
2375    };
2376    window.addEventListener("settings-update", handleSettingsUpdate);
2377    return () => {
2378      window.removeEventListener("settings-update", handleSettingsUpdate);
2379      instance.off("languageChanged", handleLanguageChanged);
2380    };
2381  }, []);
2382  reactExports.useEffect(() => {
2383    const rtlLanguages = ["ar", "he", "fa", "ur", "ku", "ps", "yi", "sd"];
2384    const isRtl = rtlLanguages.includes(language);
2385    document.documentElement.dir = isRtl ? "rtl" : "ltr";
2386    document.documentElement.lang = language;
2387  }, [language]);
2388  const setLanguage = (lang) => {
2389    setLanguageState(lang);
2390    instance.changeLanguage(lang);
2391    const current = JSON.parse(localStorage.getItem("hamie_settings") || "{}");
2392    localStorage.setItem("hamie_settings", JSON.stringify({ ...current, language: lang }));
2393    api.updateUserLanguage(lang).catch((err) => {
2394      if ((err == null ? void 0 : err.status) !== 401) {
2395        console.error("Failed to save language to profile", err);
2396      }
2397    });
2398  };
2399  const setCurrency = (curr) => {
2400    setCurrencyState(curr);
2401    const current = JSON.parse(localStorage.getItem("hamie_settings") || "{}");
2402    localStorage.setItem("hamie_settings", JSON.stringify({ ...current, currency: curr }));
2403  };
2404  const setCountry = (c) => {
2405    setCountryState(c);
2406    const current = JSON.parse(localStorage.getItem("hamie_settings") || "{}");
2407    localStorage.setItem("hamie_settings", JSON.stringify({ ...current, country: c }));
2408  };
2409  const setAutoplay = (enabled) => {
2410    setAutoplayState(enabled);
2411    const current = JSON.parse(localStorage.getItem("hamie_settings") || "{}");
2412    localStorage.setItem("hamie_settings", JSON.stringify({ ...current, autoplay: enabled }));
2413  };
2414  const setAudioQuality = (q) => {
2415    setAudioQualityState(q);
2416    const current = JSON.parse(localStorage.getItem("hamie_settings") || "{}");
2417    localStorage.setItem("hamie_settings", JSON.stringify({ ...current, audioQuality: q }));
2418  };
2419  const setSoundfontVolume = (vol) => {
2420    setSoundfontVolumeState(vol);
2421    const current = JSON.parse(localStorage.getItem("hamie_settings") || "{}");
2422    localStorage.setItem("hamie_settings", JSON.stringify({ ...current, soundfontVolume: vol }));
2423  };
2424  const formatNumber = (num) => {
2425    return new Intl.NumberFormat(language, { notation: "compact", compactDisplay: "short" }).format(num);
2426  };
2427  const formatCurrency = (amount) => {
2428    return new Intl.NumberFormat(language, { style: "currency", currency }).format(amount);
2429  };
2430  return /* @__PURE__ */ jsxRuntimeExports.jsx(SettingsContext.Provider, { value: {
2431    language,
2432    setLanguage,
2433    currency,
2434    setCurrency,
2435    country,
2436    setCountry,
2437    autoplay,
2438    setAutoplay,
2439    audioQuality,
2440    setAudioQuality,
2441    soundfontVolume,
2442    setSoundfontVolume,
2443    formatNumber,
2444    formatCurrency,
2445    availableLanguages,
2446    graphics,
2447    uiConfig,
2448    adSettings
2449  }, children });
2450};
2451const useSettings = () => {
2452  const context = reactExports.useContext(SettingsContext);
2453  if (context === void 0) {
2454    throw new Error("useSettings must be used within a SettingsProvider");
2455  }
2456  return context;
2457};
2458export {
2459  LANGUAGE_REGIONS as L,
2460  NATIVE_LANGUAGE_NAMES as N,
2461  SettingsProvider as S,
2462  api as a,
2463  uxTracker as b,
2464  i18n as i,
2465  reloadI18nResources as r,
2466  useSettings as u
2467};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.