@namphuongtechnologi/np-hub 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -23
- package/dist/{chunk-C5WUIBKX.js → chunk-BO2GWI5I.js} +62 -23
- package/dist/chunk-BO2GWI5I.js.map +1 -0
- package/dist/index.cjs +61 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/np-hub.min.global.js +5 -5
- package/dist/react.cjs +61 -22
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +5 -2
- package/dist/react.d.ts +5 -2
- package/dist/react.js +1 -1
- package/dist/react.js.map +1 -1
- package/dist/register.cjs +61 -22
- package/dist/register.cjs.map +1 -1
- package/dist/register.js +1 -1
- package/dist/{support-widget.element-BlSyAC7w.d.cts → support-widget.element-C6a1k-nt.d.cts} +14 -4
- package/dist/{support-widget.element-BlSyAC7w.d.ts → support-widget.element-C6a1k-nt.d.ts} +14 -4
- package/docs/DEVELOPER_GUIDE.md +9 -8
- package/docs/USAGE_STATIC_REACT.md +13 -2
- package/package.json +1 -1
- package/dist/chunk-C5WUIBKX.js.map +0 -1
package/README.md
CHANGED
|
@@ -121,14 +121,14 @@ export default function App() {
|
|
|
121
121
|
|
|
122
122
|
### `setConfig` / React props
|
|
123
123
|
|
|
124
|
-
| Name | Required | Default | Description
|
|
125
|
-
| --------------- | -------- | ------- |
|
|
126
|
-
| `projectId` | No | — | Project code forwarded to the API
|
|
127
|
-
| `isDev` | No | `false` | `true` → Development API; otherwise Production
|
|
128
|
-
| `priority` | No | `0` | Request priority
|
|
129
|
-
| `coordinators` | No | `[]` | Coordinator email list
|
|
130
|
-
| `emailContacts` | No | `[]` | Contact emails that receive the notification
|
|
131
|
-
| `toastDuration` | No | `4000` | Toast auto-close delay in
|
|
124
|
+
| Name | Required | Default | Description |
|
|
125
|
+
| --------------- | -------- | ------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
126
|
+
| `projectId` | No | — | Project code forwarded to the API |
|
|
127
|
+
| `isDev` | No | `false` | `true` → Development API; otherwise Production |
|
|
128
|
+
| `priority` | No | `0` | Request priority |
|
|
129
|
+
| `coordinators` | No | `[]` | Coordinator email list |
|
|
130
|
+
| `emailContacts` | No | `[]` | Contact emails that receive the notification |
|
|
131
|
+
| `toastDuration` | No | `4000` | Toast auto-close delay in ms. Can be a number or an object: `{ success?: number, error?: number }` |
|
|
132
132
|
|
|
133
133
|
### Prefill (optional)
|
|
134
134
|
|
|
@@ -192,23 +192,32 @@ widget.setAttribute("bottom", "24");
|
|
|
192
192
|
|
|
193
193
|
After a successful API submit or an API/exception failure, the widget shows a **centered toast** with the result message.
|
|
194
194
|
|
|
195
|
-
| Behavior | Detail
|
|
196
|
-
| ----------------- |
|
|
197
|
-
| When shown | Submit success, or API / runtime error
|
|
198
|
-
| When hidden | Missing required fields (only inline field highlighting)
|
|
199
|
-
| Auto-close | After `toastDuration` ms (default `4000`)
|
|
200
|
-
|
|
|
201
|
-
|
|
|
195
|
+
| Behavior | Detail |
|
|
196
|
+
| ----------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
197
|
+
| When shown | Submit success, or API / runtime error |
|
|
198
|
+
| When hidden | Missing required fields (only inline field highlighting) |
|
|
199
|
+
| Auto-close | After `toastDuration` ms (default `4000`). Pauses countdown on cursor hover. |
|
|
200
|
+
| Copy Actions | Provides quick-copy buttons for the support request code and lookup URL directly on the success toast |
|
|
201
|
+
| Manual close | User can dismiss via the `×` button |
|
|
202
|
+
| Success follow-up | Modal closes automatically after success toast auto-closes (controlled by `toastSuccessDuration`) |
|
|
203
|
+
|
|
204
|
+
`toastDuration` can be a single number (applies to both success/error) or structured with separate values:
|
|
202
205
|
|
|
203
206
|
```js
|
|
204
207
|
widget.setConfig({
|
|
205
208
|
projectId: "NPP",
|
|
206
|
-
toastDuration:
|
|
209
|
+
toastDuration: {
|
|
210
|
+
success: 3000, // closes success toast after 3s
|
|
211
|
+
error: 8000 // keeps error toast open for 8s
|
|
212
|
+
}
|
|
207
213
|
});
|
|
208
214
|
```
|
|
209
215
|
|
|
210
216
|
```tsx
|
|
211
|
-
<SupportWidget
|
|
217
|
+
<SupportWidget
|
|
218
|
+
projectId="NPP"
|
|
219
|
+
toastDuration={{ success: 3000, error: 8000 }}
|
|
220
|
+
/>
|
|
212
221
|
```
|
|
213
222
|
|
|
214
223
|
---
|
|
@@ -228,12 +237,13 @@ React equivalent props: `onOpen`, `onClose`, `onSubmitSuccess`, `onSubmitError`.
|
|
|
228
237
|
|
|
229
238
|
### Methods (Web Component)
|
|
230
239
|
|
|
231
|
-
| Method
|
|
232
|
-
|
|
|
233
|
-
| `setConfig(config)`
|
|
234
|
-
| `setUser(user)`
|
|
235
|
-
| `setFormPrefill(data)`
|
|
236
|
-
| `open()` / `close()`
|
|
240
|
+
| Method | Description |
|
|
241
|
+
| ----------------------------- | ------------------------------------------------------------------- |
|
|
242
|
+
| `setConfig(config)` | Apply project / API settings |
|
|
243
|
+
| `setUser(user)` | Prefill requester fields |
|
|
244
|
+
| `setFormPrefill(data)` | Prefill content / attachments |
|
|
245
|
+
| `open()` / `close()` | Open or close the modal |
|
|
246
|
+
| `showDemoToast(type)` | Show a dummy toast for testing/dev (accepts `"success"` or `"error"`) |
|
|
237
247
|
|
|
238
248
|
---
|
|
239
249
|
|
|
@@ -165,9 +165,9 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
165
165
|
this.prefilledAttachments = [];
|
|
166
166
|
this.previewUrls = [];
|
|
167
167
|
this.closeAfterSuccessTimer = null;
|
|
168
|
-
this.toastAutoCloseTimer = null;
|
|
169
168
|
this.toastCountdownInterval = null;
|
|
170
|
-
this.
|
|
169
|
+
this.toastSuccessDuration = 4e3;
|
|
170
|
+
this.toastErrorDuration = 4e3;
|
|
171
171
|
this.toastRemainingMs = 0;
|
|
172
172
|
this.toastTotalMs = 0;
|
|
173
173
|
this.isToastPaused = false;
|
|
@@ -195,7 +195,6 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
195
195
|
disconnectedCallback() {
|
|
196
196
|
window.removeEventListener("resize", this.handleResize);
|
|
197
197
|
this.clearCloseAfterSuccessTimer();
|
|
198
|
-
this.clearToastAutoCloseTimer();
|
|
199
198
|
this.clearToastCountdown();
|
|
200
199
|
}
|
|
201
200
|
attributeChangedCallback() {
|
|
@@ -208,6 +207,10 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
208
207
|
this.user = user;
|
|
209
208
|
this.prefillFromState();
|
|
210
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Updates the widget configuration.
|
|
212
|
+
* Supports custom toast auto-close durations via `toastDuration`.
|
|
213
|
+
*/
|
|
211
214
|
setConfig(config) {
|
|
212
215
|
this.config = {
|
|
213
216
|
...this.config,
|
|
@@ -233,7 +236,23 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
233
236
|
this.emailContacts = config.emailContacts;
|
|
234
237
|
}
|
|
235
238
|
if (config.toastDuration !== void 0) {
|
|
236
|
-
|
|
239
|
+
const duration = config.toastDuration;
|
|
240
|
+
if (typeof duration === "number") {
|
|
241
|
+
const val = this.normalizeToastDuration(duration);
|
|
242
|
+
this.toastSuccessDuration = val;
|
|
243
|
+
this.toastErrorDuration = val;
|
|
244
|
+
} else if (duration && typeof duration === "object") {
|
|
245
|
+
if (duration.success !== void 0) {
|
|
246
|
+
this.toastSuccessDuration = this.normalizeToastDuration(duration.success);
|
|
247
|
+
} else {
|
|
248
|
+
this.toastSuccessDuration = 4e3;
|
|
249
|
+
}
|
|
250
|
+
if (duration.error !== void 0) {
|
|
251
|
+
this.toastErrorDuration = this.normalizeToastDuration(duration.error);
|
|
252
|
+
} else {
|
|
253
|
+
this.toastErrorDuration = 4e3;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
237
256
|
}
|
|
238
257
|
}
|
|
239
258
|
setFormPrefill(data) {
|
|
@@ -251,9 +270,14 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
251
270
|
var _a, _b;
|
|
252
271
|
this.clearCloseAfterSuccessTimer();
|
|
253
272
|
this.resetFormData();
|
|
273
|
+
this.hideToast(false);
|
|
254
274
|
(_b = (_a = this.shadowRoot) == null ? void 0 : _a.getElementById("modal")) == null ? void 0 : _b.classList.remove("show");
|
|
255
275
|
this.dispatchEvent(new CustomEvent(WIDGET_EVENTS.CLOSE, { bubbles: true }));
|
|
256
276
|
}
|
|
277
|
+
/**
|
|
278
|
+
* Helper method to trigger a mock/demo toast popup for testing and development.
|
|
279
|
+
* @param type - Either "success" or "error"
|
|
280
|
+
*/
|
|
257
281
|
showDemoToast(type) {
|
|
258
282
|
if (type === "success") {
|
|
259
283
|
this.showToast({
|
|
@@ -280,9 +304,27 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
280
304
|
};
|
|
281
305
|
const toastDurationAttr = this.getAttribute("toast-duration");
|
|
282
306
|
if (toastDurationAttr !== null) {
|
|
283
|
-
const
|
|
284
|
-
if (
|
|
285
|
-
|
|
307
|
+
const trimmed = toastDurationAttr.trim();
|
|
308
|
+
if (trimmed.startsWith("{")) {
|
|
309
|
+
try {
|
|
310
|
+
const parsed = JSON.parse(trimmed);
|
|
311
|
+
if (parsed && typeof parsed === "object") {
|
|
312
|
+
if (parsed.success !== void 0) {
|
|
313
|
+
this.toastSuccessDuration = this.normalizeToastDuration(Number(parsed.success));
|
|
314
|
+
}
|
|
315
|
+
if (parsed.error !== void 0) {
|
|
316
|
+
this.toastErrorDuration = this.normalizeToastDuration(Number(parsed.error));
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
} catch (e) {
|
|
320
|
+
}
|
|
321
|
+
} else {
|
|
322
|
+
const parsed = parseInt(trimmed, 10);
|
|
323
|
+
if (!isNaN(parsed)) {
|
|
324
|
+
const val = this.normalizeToastDuration(parsed);
|
|
325
|
+
this.toastSuccessDuration = val;
|
|
326
|
+
this.toastErrorDuration = val;
|
|
327
|
+
}
|
|
286
328
|
}
|
|
287
329
|
}
|
|
288
330
|
}
|
|
@@ -723,7 +765,7 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
723
765
|
this.clearFieldError("email");
|
|
724
766
|
this.clearFieldError("content");
|
|
725
767
|
}
|
|
726
|
-
showToast(payload, autoCloseMs
|
|
768
|
+
showToast(payload, autoCloseMs) {
|
|
727
769
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
728
770
|
const toastEl = (_a = this.shadowRoot) == null ? void 0 : _a.getElementById("toast");
|
|
729
771
|
const titleEl = (_b = this.shadowRoot) == null ? void 0 : _b.getElementById("toast-title");
|
|
@@ -744,7 +786,6 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
744
786
|
if (!toastEl || !titleEl || !messageEl || !detailsEl || !requestCodeEl || !linkRowEl || !linkEl || !countdownBarEl) {
|
|
745
787
|
return;
|
|
746
788
|
}
|
|
747
|
-
this.clearToastAutoCloseTimer();
|
|
748
789
|
this.clearToastCountdown();
|
|
749
790
|
titleEl.textContent = payload.title;
|
|
750
791
|
messageEl.textContent = payload.message;
|
|
@@ -773,8 +814,9 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
773
814
|
linkRowEl.classList.add("is-hidden");
|
|
774
815
|
detailsEl.classList.add("is-hidden");
|
|
775
816
|
}
|
|
776
|
-
this.
|
|
777
|
-
this.
|
|
817
|
+
const duration = autoCloseMs !== void 0 ? autoCloseMs : payload.type === "success" ? this.toastSuccessDuration : this.toastErrorDuration;
|
|
818
|
+
this.toastRemainingMs = duration;
|
|
819
|
+
this.toastTotalMs = duration;
|
|
778
820
|
const toastCardEl = (_l = this.shadowRoot) == null ? void 0 : _l.querySelector(".toast-card");
|
|
779
821
|
this.isToastPaused = Boolean(toastCardEl == null ? void 0 : toastCardEl.matches(":hover"));
|
|
780
822
|
toastEl.classList.remove("success", "error");
|
|
@@ -785,7 +827,7 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
785
827
|
countdownBarEl
|
|
786
828
|
);
|
|
787
829
|
}
|
|
788
|
-
hideToast() {
|
|
830
|
+
hideToast(shouldCloseModal = true) {
|
|
789
831
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
790
832
|
const toastEl = (_a = this.shadowRoot) == null ? void 0 : _a.getElementById("toast");
|
|
791
833
|
const titleEl = (_b = this.shadowRoot) == null ? void 0 : _b.getElementById("toast-title");
|
|
@@ -806,7 +848,7 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
806
848
|
if (!toastEl || !titleEl || !messageEl || !detailsEl || !requestCodeEl || !linkRowEl || !linkEl || !countdownBarEl) {
|
|
807
849
|
return;
|
|
808
850
|
}
|
|
809
|
-
|
|
851
|
+
const isSuccess = toastEl.classList.contains("success");
|
|
810
852
|
this.clearToastCountdown();
|
|
811
853
|
this.toastRemainingMs = 0;
|
|
812
854
|
this.toastTotalMs = 0;
|
|
@@ -827,6 +869,9 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
827
869
|
countdownEl.classList.add("is-hidden");
|
|
828
870
|
}
|
|
829
871
|
toastEl.classList.remove("success", "error", "show");
|
|
872
|
+
if (isSuccess && shouldCloseModal) {
|
|
873
|
+
this.close();
|
|
874
|
+
}
|
|
830
875
|
}
|
|
831
876
|
startToastCountdown(countdownEl, secondsEl, barEl) {
|
|
832
877
|
const updateCountdown = () => {
|
|
@@ -869,12 +914,6 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
869
914
|
title: content.type === "success" ? "G\u1EEDi y\xEAu c\u1EA7u th\xE0nh c\xF4ng" : "G\u1EEDi y\xEAu c\u1EA7u th\u1EA5t b\u1EA1i"
|
|
870
915
|
};
|
|
871
916
|
}
|
|
872
|
-
clearToastAutoCloseTimer() {
|
|
873
|
-
if (this.toastAutoCloseTimer !== null) {
|
|
874
|
-
window.clearTimeout(this.toastAutoCloseTimer);
|
|
875
|
-
this.toastAutoCloseTimer = null;
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
917
|
normalizeToastDuration(value) {
|
|
879
918
|
if (!Number.isFinite(value) || value <= 0) {
|
|
880
919
|
return 4e3;
|
|
@@ -926,7 +965,7 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
926
965
|
async submit() {
|
|
927
966
|
var _a;
|
|
928
967
|
this.clearCloseAfterSuccessTimer();
|
|
929
|
-
this.hideToast();
|
|
968
|
+
this.hideToast(false);
|
|
930
969
|
if (!this.validateRequiredFields()) {
|
|
931
970
|
this.dispatchEvent(
|
|
932
971
|
new CustomEvent(WIDGET_EVENTS.SUBMIT_ERROR, {
|
|
@@ -1016,8 +1055,8 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
1016
1055
|
})
|
|
1017
1056
|
);
|
|
1018
1057
|
this.closeAfterSuccessTimer = window.setTimeout(() => {
|
|
1019
|
-
this.
|
|
1020
|
-
},
|
|
1058
|
+
this.hideToast();
|
|
1059
|
+
}, this.toastSuccessDuration);
|
|
1021
1060
|
} catch (error) {
|
|
1022
1061
|
const errorContent = mapHttpClientError(error);
|
|
1023
1062
|
this.showToast(this.toToastPayload(errorContent));
|
|
@@ -1135,4 +1174,4 @@ export {
|
|
|
1135
1174
|
SupportWidgetElement,
|
|
1136
1175
|
registerSupportWidget
|
|
1137
1176
|
};
|
|
1138
|
-
//# sourceMappingURL=chunk-
|
|
1177
|
+
//# sourceMappingURL=chunk-BO2GWI5I.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/config/endpoints.ts","../src/core/validation/request-validator.ts","../src/services/api/create-request-response.ts","../src/services/api/http-client.ts","../src/services/api/support-api.ts","../src/component/events/widget-events.ts","../src/component/support-widget.styles.css","../src/component/support-widget.template.html","../src/component/support-widget.element.ts","../src/register.ts"],"sourcesContent":["export const PROD_API_URL = \"https://namphuong-api.azurewebsites.net\";\nexport const DEV_API_URL = \"https://namphuong-api-dev.azurewebsites.net\";\n\nexport function resolveBaseUrl(input: { isDev?: boolean }): string {\n return input.isDev ? DEV_API_URL : PROD_API_URL;\n}\n","import type {\n SupportSubmissionInput,\n SupportUser,\n SupportWidgetConfig,\n} from \"../../types/public\";\n\nexport function validateWidgetConfig(config: SupportWidgetConfig): void {\n if (config.projectId !== undefined && config.projectId.trim().length === 0) {\n throw new Error(\"projectId cannot be empty.\");\n }\n}\n\nexport function validateUser(user: SupportUser): void {\n if (!user.name?.trim()) {\n throw new Error(\"user.name is required.\");\n }\n if (!user.email?.trim()) {\n throw new Error(\"user.email is required.\");\n }\n if (!user.phoneNumber?.trim()) {\n throw new Error(\"user.phoneNumber is required.\");\n }\n}\n\nexport function validateSubmission(input: SupportSubmissionInput): void {\n if (!input.content?.trim()) {\n throw new Error(\"Content is required.\");\n }\n}\n","export interface CreateRequestResponse {\n statusCode?: number;\n message?: string;\n totalRecord?: number;\n data?: {\n url?: string;\n requestCode?: string;\n } | null;\n}\n\nexport interface SubmitToastContent {\n type: \"success\" | \"error\";\n message: string;\n statusCode?: string;\n requestCode?: string;\n url?: string;\n}\n\nconst SUCCESS_FALLBACK_MESSAGE = \"Yêu cầu đã được tiếp nhận thành công.\";\nconst ERROR_FALLBACK_MESSAGE = \"Có lỗi xảy ra khi gửi yêu cầu.\";\n\nexport function isCreateRequestSuccess(\n result: CreateRequestResponse,\n): boolean {\n if (typeof result.statusCode === \"number\") {\n return result.statusCode >= 200 && result.statusCode < 300;\n }\n return true;\n}\n\nexport function mapCreateRequestSuccess(\n result: CreateRequestResponse,\n): SubmitToastContent {\n const statusCode =\n typeof result.statusCode === \"number\"\n ? String(result.statusCode)\n : undefined;\n const requestCode = result.data?.requestCode?.trim() ?? \"\";\n const url = result.data?.url?.trim() ?? \"\";\n\n return {\n type: \"success\",\n message: result.message?.trim() || SUCCESS_FALLBACK_MESSAGE,\n statusCode,\n requestCode: requestCode || undefined,\n url: url || undefined,\n };\n}\n\nexport function mapCreateRequestFailure(\n result: CreateRequestResponse,\n): SubmitToastContent {\n const statusCode =\n typeof result.statusCode === \"number\"\n ? String(result.statusCode)\n : undefined;\n\n return {\n type: \"error\",\n message: result.message?.trim() || ERROR_FALLBACK_MESSAGE,\n statusCode,\n };\n}\n\nexport function mapHttpClientError(error: unknown): SubmitToastContent {\n const fallbackMessage = ERROR_FALLBACK_MESSAGE;\n const rawMessage =\n error instanceof Error ? error.message : fallbackMessage;\n const matchedStatus = rawMessage.match(/Request failed \\((\\d+)\\):/);\n const httpStatus = matchedStatus ? matchedStatus[1] : \"\";\n const afterColon = rawMessage.match(/Request failed \\(\\d+\\):\\s*(.*)$/s);\n const payloadText = afterColon?.[1]?.trim() ?? \"\";\n\n let parsedMessage = \"\";\n let bodyStatus = \"\";\n if (payloadText.startsWith(\"{\")) {\n try {\n const parsed = JSON.parse(payloadText) as CreateRequestResponse;\n if (typeof parsed.statusCode === \"number\") {\n bodyStatus = String(parsed.statusCode);\n }\n if (typeof parsed.message === \"string\") {\n parsedMessage = parsed.message.trim();\n }\n } catch {\n // Ignore JSON parse error, fallback to message extraction below.\n }\n }\n\n if (!parsedMessage && payloadText) {\n parsedMessage = payloadText;\n }\n if (!parsedMessage) {\n parsedMessage = rawMessage || fallbackMessage;\n }\n\n return {\n type: \"error\",\n message: parsedMessage,\n statusCode: bodyStatus || httpStatus || undefined,\n };\n}\n","export async function postFormData<TResponse>(\n url: string,\n body: FormData,\n): Promise<TResponse> {\n const response = await fetch(url, {\n method: \"POST\",\n body,\n });\n\n if (!response.ok) {\n const message = await response.text();\n throw new Error(`Request failed (${response.status}): ${message}`);\n }\n\n return (await response.json()) as TResponse;\n}\n","import type { CreateRequestResponse } from \"./create-request-response\";\nimport { postFormData } from \"./http-client\";\n\nexport type { CreateRequestResponse };\n\nexport async function createSupportRequest(\n baseUrl: string,\n formData: FormData,\n): Promise<CreateRequestResponse> {\n const endpoint = `${baseUrl.replace(/\\/$/, \"\")}/api/supportcenter/create-request-anonymous`;\n return postFormData<CreateRequestResponse>(endpoint, formData);\n}\n\n","export const WIDGET_EVENTS = {\n OPEN: \"np-hub-open\",\n CLOSE: \"np-hub-close\",\n SUBMIT_SUCCESS: \"np-hub-submit-success\",\n SUBMIT_ERROR: \"np-hub-submit-error\",\n} as const;\n","@import url(\"https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap\");\n\n:host {\n --np-primary: #003c82;\n --np-primary-dark: #002a5c;\n --np-primary-soft: rgba(0, 60, 130, 0.08);\n --np-border: #e2e8f0;\n --np-text: #0f172a;\n --np-text-muted: #64748b;\n font-family: \"Outfit\", sans-serif;\n display: block;\n position: fixed;\n right: var(--np-hub-right, 20px);\n bottom: var(--np-hub-bottom, 20px);\n left: var(--np-hub-left, auto);\n top: var(--np-hub-top, auto);\n z-index: 10000;\n}\n\n.launcher {\n width: var(--np-hub-width, 65px);\n height: var(--np-hub-height, 65px);\n border: none;\n border-radius: 50%;\n cursor: grab;\n box-shadow: 0 8px 28px rgba(6, 6, 7, 0.2);\n background-color: #ffffff;\n background-image: var(--np-hub-icon);\n background-position: center;\n background-repeat: no-repeat;\n background-size: 75%;\n transition:\n transform 0.2s ease,\n box-shadow 0.2s ease;\n}\n\n.launcher:hover {\n transform: scale(1.05);\n box-shadow: 0 12px 32px rgba(0, 60, 130, 0.3);\n}\n\n.launcher:active {\n cursor: grabbing;\n}\n\n.modal {\n position: fixed;\n inset: 0;\n background: rgba(15, 23, 42, 0.55);\n backdrop-filter: blur(6px);\n display: none;\n align-items: center;\n justify-content: center;\n padding: 16px;\n opacity: 0;\n transition: opacity 0.25s ease-out;\n}\n\n.modal.show {\n display: flex;\n opacity: 1;\n}\n\n.dialog {\n width: min(540px, 100%);\n max-height: calc(100vh - 32px);\n overflow-y: auto;\n background: #ffffff;\n border-radius: 20px;\n box-shadow: 0 24px 60px rgba(15, 23, 42, 0.22);\n padding: 28px 28px 26px;\n box-sizing: border-box;\n position: relative;\n transform: translateY(24px) scale(0.98);\n transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n.modal.show .dialog {\n transform: translateY(0) scale(1);\n}\n\n.dialog::-webkit-scrollbar {\n width: 8px;\n}\n\n.dialog::-webkit-scrollbar-thumb {\n background: #cbd5e1;\n border-radius: 8px;\n}\n\n.header {\n display: flex;\n align-items: center;\n justify-content: center;\n padding-bottom: 12px;\n margin-bottom: 16px;\n border-bottom: 1px solid var(--np-border);\n position: relative;\n}\n\n.title {\n margin: 0;\n font-size: 20px;\n font-weight: 700;\n color: var(--np-primary);\n text-align: center;\n letter-spacing: -0.01em;\n}\n\n.close-btn {\n border: none;\n background: #f1f5f9;\n color: #64748b;\n font-size: 20px;\n line-height: 1;\n cursor: pointer;\n position: absolute;\n right: 0;\n top: 0;\n bottom: 18px;\n margin: auto 0;\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n transition:\n background 0.15s ease,\n color 0.15s ease,\n transform 0.15s ease;\n}\n\n.close-btn:hover {\n background: #e2e8f0;\n color: #0f172a;\n transform: rotate(90deg);\n}\n\n.form-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 20px;\n}\n\n.field {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.field-error {\n display: none;\n}\n\n.field.full {\n grid-column: 1 / -1;\n}\n\nlabel {\n font-size: 13px;\n font-weight: 600;\n color: #334155;\n}\n\n.required {\n color: #ef4444;\n margin-right: 3px;\n}\n\ninput,\ntextarea {\n width: 100%;\n box-sizing: border-box;\n border: 1px solid #d5dde7;\n border-radius: 10px;\n font-size: 14px;\n padding: 11px 13px;\n color: var(--np-text);\n outline: none;\n font-family: inherit;\n background: #fbfcfe;\n transition:\n border-color 0.15s ease,\n box-shadow 0.15s ease,\n background 0.15s ease;\n}\n\ntextarea {\n min-height: 100px;\n resize: vertical;\n line-height: 1.5;\n}\n\ninput::placeholder,\ntextarea::placeholder {\n color: #94a3b8;\n}\n\ninput:hover,\ntextarea:hover {\n border-color: #b6c2d1;\n}\n\ninput:focus,\ntextarea:focus {\n border-color: var(--np-primary);\n background: #ffffff;\n box-shadow: 0 0 0 4px var(--np-primary-soft);\n}\n\n.input-wrap.has-error input,\n.input-wrap.has-error textarea {\n border-color: #e45b5b;\n background: #ffffff;\n}\n\n.input-wrap.has-error input:focus,\n.input-wrap.has-error textarea:focus {\n border-color: #e45b5b;\n box-shadow: 0 0 0 3px rgba(228, 91, 91, 0.14);\n}\n\n.input-wrap {\n position: relative;\n}\n\n.input-wrap input,\n.input-wrap textarea {\n padding-right: 40px;\n}\n\n.btn-clear {\n position: absolute;\n right: 10px;\n top: 50%;\n transform: translateY(-50%);\n width: 24px;\n height: 24px;\n border: none;\n border-radius: 50%;\n background: #e2e8f0;\n color: #64748b;\n font-size: 16px;\n line-height: 1;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n opacity: 0;\n pointer-events: none;\n transition:\n opacity 0.15s ease,\n background 0.15s ease,\n color 0.15s ease;\n}\n\n.input-wrap.has-value:hover .btn-clear,\n.input-wrap.has-value:focus-within .btn-clear {\n opacity: 1;\n pointer-events: auto;\n}\n\n.btn-clear:hover {\n background: #cbd5e1;\n color: #0f172a;\n}\n\n.input-wrap--textarea .btn-clear {\n top: 12px;\n transform: none;\n}\n\n/* File upload styling */\n.upload-zone {\n display: flex;\n align-items: center;\n gap: 16px;\n background: #f8fafc;\n border: 1.5px dashed #cbd5e1;\n padding: 16px 16px;\n border-radius: 12px;\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n user-select: none;\n outline: none;\n}\n\n.upload-zone:hover {\n background: var(--np-primary-soft);\n border-color: var(--np-primary);\n}\n\n.upload-zone:focus-visible {\n border-color: var(--np-primary);\n box-shadow: 0 0 0 4px var(--np-primary-soft);\n}\n\n.upload-zone.dragover {\n background: rgba(0, 60, 130, 0.08);\n border-color: var(--np-primary);\n transform: scale(1.005);\n}\n\n.upload-icon-wrapper {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n border-radius: 10px;\n background: #ffffff;\n border: 1px solid #e2e8f0;\n color: #64748b;\n box-shadow: 0 2px 4px rgba(15, 23, 42, 0.04);\n transition: all 0.2s ease;\n flex-shrink: 0;\n}\n\n.upload-zone:hover .upload-icon-wrapper,\n.upload-zone.dragover .upload-icon-wrapper {\n color: var(--np-primary);\n border-color: rgba(0, 60, 130, 0.15);\n box-shadow: 0 4px 8px rgba(0, 60, 130, 0.08);\n}\n\n.upload-text-container {\n display: flex;\n flex-direction: column;\n gap: 3px;\n flex: 1;\n}\n\n.upload-main-text {\n font-size: 13px;\n font-weight: 600;\n color: #334155;\n transition: color 0.2s ease;\n}\n\n.upload-zone:hover .upload-main-text {\n color: var(--np-primary);\n}\n\n.upload-sub-text {\n font-size: 11px;\n color: #64748b;\n line-height: 1.4;\n}\n\n.file-list {\n margin-top: 8px;\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));\n gap: 12px;\n}\n\n.file-item {\n position: relative;\n display: flex;\n flex-direction: column;\n background: #ffffff;\n border: 1px solid #e2e8f0;\n border-radius: 12px;\n font-size: 11px;\n color: #475569;\n box-shadow: 0 2px 5px rgba(15, 23, 42, 0.03);\n animation: file-in 0.2s cubic-bezier(0.16, 1, 0.3, 1);\n transition: all 0.2s ease;\n overflow: hidden;\n}\n\n.file-item:hover {\n border-color: #cbd5e1;\n box-shadow: 0 4px 12px rgba(15, 23, 42, 0.08);\n transform: translateY(-2px);\n}\n\n.file-preview-trigger {\n border: none;\n background: none;\n padding: 0;\n margin: 0;\n cursor: pointer;\n display: flex;\n flex-direction: column;\n width: 100%;\n color: inherit;\n font: inherit;\n}\n\n.file-thumb {\n width: 100%;\n aspect-ratio: 1 / 1;\n display: flex;\n align-items: center;\n justify-content: center;\n background: #f8fafc;\n border-bottom: 1px solid #f1f5f9;\n position: relative;\n overflow: hidden;\n transition: background 0.2s ease;\n}\n\n.file-thumb img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n transition: transform 0.25s ease;\n}\n\n.file-item:hover .file-thumb img {\n transform: scale(1.06);\n}\n\n.file-thumb--icon {\n color: #64748b;\n background: #f1f5f9;\n}\n\n.file-item:hover .file-thumb--icon {\n background: #e2e8f0;\n color: var(--np-primary);\n}\n\n@keyframes file-in {\n from {\n opacity: 0;\n transform: translateY(6px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n.file-info-container {\n padding: 8px 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n box-sizing: border-box;\n}\n\n.file-item .file-name {\n width: 100%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n line-height: 1.2;\n color: #334155;\n font-size: 11px;\n font-weight: 500;\n text-align: center;\n}\n\n.file-badge {\n position: absolute;\n top: 6px;\n left: 6px;\n z-index: 2;\n padding: 3px 6px;\n border-radius: 6px;\n font-size: 9px;\n font-weight: 600;\n line-height: 1;\n color: #0369a1;\n background: rgba(224, 242, 254, 0.9);\n border: 1px solid rgba(186, 230, 253, 0.8);\n backdrop-filter: blur(4px);\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);\n}\n\n.file-item .btn-remove {\n position: absolute;\n top: 6px;\n right: 6px;\n z-index: 2;\n width: 20px;\n height: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(255, 255, 255, 0.9);\n border: 1px solid #e2e8f0;\n border-radius: 50%;\n color: #64748b;\n cursor: pointer;\n padding: 0;\n box-shadow: 0 2px 4px rgba(15, 23, 42, 0.06);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n backdrop-filter: blur(4px);\n}\n\n.file-item .btn-remove svg {\n width: 8px;\n height: 8px;\n stroke-width: 3px;\n transition: transform 0.2s ease;\n}\n\n.file-item .btn-remove:hover {\n color: #ffffff;\n background: #ef4444;\n border-color: #ef4444;\n transform: scale(1.1);\n box-shadow: 0 3px 8px rgba(239, 68, 68, 0.25);\n}\n\n.file-item .btn-remove:hover svg {\n transform: rotate(90deg);\n}\n\n/* File preview popup */\n.preview-overlay {\n position: fixed;\n inset: 0;\n z-index: 20;\n display: none;\n align-items: center;\n justify-content: center;\n padding: 24px;\n background: rgba(15, 23, 42, 0.72);\n backdrop-filter: blur(4px);\n opacity: 0;\n transition: opacity 0.2s ease-out;\n}\n\n.preview-overlay.show {\n display: flex;\n opacity: 1;\n}\n\n.preview-box {\n position: relative;\n max-width: min(720px, 100%);\n max-height: calc(100vh - 96px);\n background: #ffffff;\n border-radius: 16px;\n padding: 16px;\n box-shadow: 0 24px 60px rgba(15, 23, 42, 0.35);\n display: flex;\n flex-direction: column;\n}\n\n.preview-close {\n position: absolute;\n top: -14px;\n right: -14px;\n width: 34px;\n height: 34px;\n border-radius: 50%;\n border: none;\n background: #ffffff;\n color: #334155;\n font-size: 22px;\n line-height: 1;\n cursor: pointer;\n box-shadow: 0 4px 12px rgba(15, 23, 42, 0.25);\n transition:\n color 0.15s ease,\n transform 0.15s ease;\n}\n\n.preview-close:hover {\n color: #ef4444;\n transform: scale(1.05);\n}\n\n.preview-body {\n overflow: auto;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.preview-body img {\n max-width: 100%;\n max-height: calc(100vh - 140px);\n object-fit: contain;\n border-radius: 8px;\n display: block;\n}\n\n.preview-file {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 12px;\n padding: 32px 40px;\n text-align: center;\n color: var(--np-text);\n}\n\n.preview-file svg {\n color: var(--np-primary);\n}\n\n.preview-file .preview-file-name {\n font-size: 14px;\n font-weight: 600;\n word-break: break-all;\n}\n\n.preview-file a {\n color: var(--np-primary);\n font-size: 13px;\n font-weight: 600;\n text-decoration: none;\n}\n\n.preview-file a:hover {\n text-decoration: underline;\n}\n\n.actions {\n margin-top: 20px;\n display: flex;\n justify-content: flex-end;\n gap: 12px;\n}\n\n.toast {\n position: fixed;\n inset: 0;\n z-index: 10002;\n display: none;\n align-items: flex-start;\n justify-content: center;\n background: transparent;\n pointer-events: auto; /* disable interaction underneath */\n}\n\n.toast.show {\n display: flex;\n animation: toast-fade-in 0.35s cubic-bezier(0.16, 1, 0.3, 1) forwards;\n}\n\n.toast.success .toast-card {\n --toast-status-color: #10b981;\n --toast-status-bg: rgba(16, 185, 129, 0.12);\n --toast-badge-text: #047857;\n --toast-badge-bg: #ecfdf5;\n --toast-badge-border: #d1fae5;\n}\n\n.toast.error .toast-card {\n --toast-status-color: #ef4444;\n --toast-status-bg: rgba(239, 68, 68, 0.12);\n --toast-badge-text: #b91c1c;\n --toast-badge-bg: #fef2f2;\n --toast-badge-border: #fee2e2;\n}\n\n.toast-card {\n position: relative;\n width: min(90vw, 420px);\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 20px 24px 22px 24px;\n border-radius: 20px; /* matched to main dialog border-radius */\n background: #ffffff;\n border: 1px solid var(--np-border);\n box-shadow: 0 24px 60px rgba(15, 23, 42, 0.22); /* matched to main dialog shadow */\n pointer-events: auto;\n overflow: hidden;\n margin-top: 24px; /* spacing from top of viewport */\n animation: toast-in 0.35s cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n.toast-main {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n}\n\n.toast-icon {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n background: var(--toast-status-bg);\n margin-top: 2px;\n}\n\n.toast-icon svg {\n display: none;\n width: 18px;\n height: 18px;\n margin: 0;\n}\n\n.toast.success .toast-icon svg.icon-success {\n display: block;\n color: var(--toast-status-color);\n}\n\n.toast.error .toast-icon svg.icon-error {\n display: block;\n color: var(--toast-status-color);\n}\n\n.toast-body {\n display: flex;\n flex-direction: column;\n gap: 4px;\n flex: 1;\n min-width: 0;\n padding-top: 4px;\n}\n\n.toast-title {\n margin: 0;\n font-size: 16px;\n font-weight: 700;\n color: #111827; /* dark charcoal black */\n letter-spacing: -0.01em;\n line-height: 1.3;\n}\n\n.toast-message {\n margin: 0;\n font-size: 13.5px;\n line-height: 1.45;\n color: var(--np-text-muted);\n}\n\n.toast.success .toast-message {\n text-transform: uppercase;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.05em;\n color: var(--np-text-muted);\n margin-top: 2px;\n}\n\n.toast-close-wrapper {\n position: relative;\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n margin-left: auto;\n}\n\n.toast-countdown-ring {\n position: absolute;\n top: 0;\n left: 0;\n pointer-events: none;\n}\n\n.ring-track {\n stroke: #f1f5f9; /* soft gray track */\n}\n\n.ring-bar {\n stroke-linecap: round;\n transition: stroke-dashoffset 0.1s linear;\n}\n\n.toast-close-btn {\n position: relative;\n z-index: 1;\n border: none;\n background: transparent;\n color: #94a3b8; /* subtle gray close icon */\n width: 28px;\n height: 28px;\n border-radius: 50%;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition:\n background 0.15s ease,\n color 0.15s ease,\n transform 0.15s ease;\n}\n\n.toast-close-btn svg {\n width: 12px;\n height: 12px;\n display: block;\n}\n\n.toast-close-btn:hover {\n background: #f1f5f9;\n color: #0f172a;\n transform: rotate(90deg);\n}\n\n.toast-details {\n margin-left: 44px;\n margin-top: 4px;\n padding: 12px 0 0 0;\n border-top: 1px solid var(--np-border); /* separator line */\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.toast-detail-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n}\n\n.toast-detail-label {\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n font-weight: 600;\n color: var(--np-text-muted);\n}\n\n.toast-detail-value {\n min-width: 0;\n font-size: 13px;\n line-height: 1.4;\n color: var(--np-text);\n display: flex;\n justify-content: flex-end;\n}\n\n.toast-detail-code {\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;\n font-weight: 600;\n color: var(--np-primary);\n background: var(--np-primary-soft);\n border: 1px solid rgba(0, 60, 130, 0.15);\n padding: 2px 8px;\n border-radius: 8px;\n font-size: 12px;\n display: inline-block;\n text-align: right;\n}\n\n.toast-detail-link {\n color: var(--np-primary);\n text-decoration: none;\n font-weight: 600;\n display: inline-block;\n max-width: 220px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n border-bottom: 1px dashed rgba(0, 60, 130, 0.3);\n transition: all 0.15s ease;\n font-size: 12.5px;\n}\n\n.toast-detail-link:hover {\n color: var(--np-primary-dark);\n border-bottom-style: solid;\n border-bottom-color: var(--np-primary-dark);\n}\n\n.toast-detail-value-wrap {\n display: flex;\n align-items: center;\n gap: 6px;\n justify-content: flex-end;\n min-width: 0;\n}\n\n.toast-copy-btn {\n background: transparent;\n border: none;\n padding: 4px;\n margin: 0;\n cursor: pointer;\n color: var(--np-text-muted);\n border-radius: 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.15s ease;\n}\n\n.toast-copy-btn:hover {\n background: #f1f5f9;\n color: var(--np-primary);\n}\n\n.toast-copy-btn svg {\n width: 14px;\n height: 14px;\n display: block;\n}\n\n.toast-copy-btn .icon-copied {\n color: var(--np-primary);\n}\n\n.toast-copy-btn.copied {\n background: var(--np-primary-soft);\n}\n\n.toast-countdown {\n margin-left: 44px;\n margin-top: 14px;\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.toast-countdown::before {\n content: \"\";\n flex: 1;\n height: 1px;\n background: #f1f5f9; /* soft divider line */\n}\n\n.toast-countdown-row {\n font-size: 11px;\n color: var(--np-text-muted);\n font-weight: 500;\n white-space: nowrap;\n display: inline-block;\n}\n\n.toast.success .toast-countdown-row {\n text-transform: none;\n font-size: 12px;\n}\n\n.toast.error .toast-countdown-row {\n text-transform: uppercase;\n letter-spacing: 0.05em;\n}\n\n.toast .is-hidden {\n display: none !important;\n}\n\n@keyframes toast-in {\n from {\n opacity: 0;\n transform: translateY(-20px) scale(0.96);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n\n@keyframes toast-fade-in {\n from {\n background: rgba(15, 23, 42, 0);\n backdrop-filter: blur(0px);\n -webkit-backdrop-filter: blur(0px);\n }\n to {\n background: rgba(15, 23, 42, 0.35);\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n }\n}\n\n.btn {\n border-radius: 10px;\n padding: 10px 22px;\n font-size: 15px;\n font-weight: 600;\n font-family: inherit;\n cursor: pointer;\n transition: all 0.18s ease;\n min-width: 120px;\n}\n\n.btn:active {\n transform: translateY(1px);\n}\n\n.btn-secondary {\n background: #ffffff;\n border: 1px solid #d5dde7;\n color: #334155;\n}\n\n.btn-secondary:hover {\n background: #f1f5f9;\n border-color: #b6c2d1;\n}\n\n.btn-primary {\n background: linear-gradient(\n 135deg,\n var(--np-primary),\n var(--np-primary-dark)\n );\n border: 1px solid var(--np-primary-dark);\n color: #ffffff;\n box-shadow: 0 6px 16px rgba(0, 60, 130, 0.28);\n}\n\n.btn-primary:hover {\n box-shadow: 0 8px 22px rgba(0, 60, 130, 0.38);\n filter: brightness(1.06);\n}\n\n@media (max-width: 480px) {\n .dialog {\n padding: 24px 20px 24px;\n border-radius: 16px;\n }\n\n .form-grid {\n grid-template-columns: 1fr;\n }\n\n .actions {\n justify-content: stretch;\n }\n\n .actions .btn {\n flex: 1;\n }\n}\n","<button\n id=\"open-btn\"\n class=\"launcher\"\n type=\"button\"\n aria-label=\"Open support form\"\n></button>\n\n<div id=\"modal\" class=\"modal\">\n <div\n class=\"dialog\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Support request form\"\n >\n <div class=\"header\">\n <h3 class=\"title\">Tạo yêu cầu hỗ trợ</h3>\n <button id=\"close-btn\" class=\"close-btn\" type=\"button\" aria-label=\"Close\">\n ×\n </button>\n </div>\n\n <div class=\"form-grid\">\n <div class=\"field\">\n <label for=\"requester\"><span class=\"required\">*</span> Họ và tên</label>\n <div class=\"input-wrap\">\n <input id=\"requester\" type=\"text\" placeholder=\"Họ và tên\" />\n <button type=\"button\" class=\"btn-clear\" aria-label=\"Xóa\">×</button>\n </div>\n <p id=\"requester-error\" class=\"field-error\" aria-live=\"polite\"></p>\n </div>\n\n <div class=\"field\">\n <label for=\"phone-number\"\n ><span class=\"required\">*</span> Số điện thoại</label\n >\n <div class=\"input-wrap\">\n <input id=\"phone-number\" type=\"text\" placeholder=\"Số điện thoại\" />\n <button type=\"button\" class=\"btn-clear\" aria-label=\"Xóa\">×</button>\n </div>\n <p id=\"phone-number-error\" class=\"field-error\" aria-live=\"polite\"></p>\n </div>\n\n <div class=\"field full\">\n <label for=\"email\"><span class=\"required\">*</span> Email</label>\n <div class=\"input-wrap\">\n <input id=\"email\" type=\"email\" placeholder=\"Email\" />\n <button type=\"button\" class=\"btn-clear\" aria-label=\"Xóa\">×</button>\n </div>\n <p id=\"email-error\" class=\"field-error\" aria-live=\"polite\"></p>\n </div>\n\n <div class=\"field full\">\n <label for=\"content\"><span class=\"required\">*</span> Nội dung</label>\n <div class=\"input-wrap input-wrap--textarea\">\n <textarea\n id=\"content\"\n placeholder=\"Anh/Chị vui lòng nhập thông tin cần hỗ trợ\"\n ></textarea>\n <button type=\"button\" class=\"btn-clear\" aria-label=\"Xóa\">×</button>\n </div>\n <p id=\"content-error\" class=\"field-error\" aria-live=\"polite\"></p>\n </div>\n\n <div class=\"field full\">\n <label>File đính kèm</label>\n <div\n id=\"upload-trigger\"\n class=\"upload-zone\"\n role=\"button\"\n tabindex=\"0\"\n aria-label=\"Tải lên tệp\"\n >\n <input id=\"file-input\" type=\"file\" multiple style=\"display: none\" />\n <div class=\"upload-icon-wrapper\">\n <svg\n class=\"upload-icon\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2.2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"></path>\n <polyline points=\"17 8 12 3 7 8\"></polyline>\n <line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"15\"></line>\n </svg>\n </div>\n <div class=\"upload-text-container\">\n <span class=\"upload-main-text\"\n >Chọn tệp tải lên hoặc kéo thả vào đây</span\n >\n <span class=\"upload-sub-text\"\n >Hỗ trợ dán ảnh hoặc tệp (Ctrl+V / Cmd+V) để đính kèm nhanh</span\n >\n </div>\n </div>\n <div id=\"file-list\" class=\"file-list\"></div>\n </div>\n </div>\n\n <div id=\"preview-overlay\" class=\"preview-overlay\">\n <div\n class=\"preview-box\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Xem trước tệp\"\n >\n <button\n id=\"preview-close\"\n class=\"preview-close\"\n type=\"button\"\n aria-label=\"Đóng\"\n >\n ×\n </button>\n <div id=\"preview-body\" class=\"preview-body\"></div>\n </div>\n </div>\n\n <div class=\"actions\">\n <button id=\"cancel-btn\" class=\"btn btn-secondary\" type=\"button\">\n Hủy\n </button>\n <button id=\"submit-btn\" class=\"btn btn-primary\" type=\"button\">\n Tạo yêu cầu\n </button>\n </div>\n </div>\n</div>\n\n<div\n id=\"toast\"\n class=\"toast\"\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n>\n <div class=\"toast-card\">\n <div class=\"toast-main\">\n <div class=\"toast-icon\" aria-hidden=\"true\">\n <svg\n class=\"icon-success\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <polyline points=\"20 6 9 17 4 12\"></polyline>\n </svg>\n <svg\n class=\"icon-error\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"10\"></circle>\n <line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"12\"></line>\n <line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\"></line>\n </svg>\n </div>\n <div class=\"toast-body\">\n <h4 id=\"toast-title\" class=\"toast-title\"></h4>\n <p id=\"toast-message\" class=\"toast-message\"></p>\n </div>\n <div class=\"toast-close-wrapper\">\n <svg\n class=\"toast-countdown-ring\"\n width=\"36\"\n height=\"36\"\n viewBox=\"0 0 36 36\"\n >\n <circle\n class=\"ring-track\"\n cx=\"18\"\n cy=\"18\"\n r=\"15\"\n fill=\"none\"\n stroke=\"#e2e8f0\"\n stroke-width=\"2.5\"\n ></circle>\n <circle\n id=\"toast-countdown-bar\"\n class=\"ring-bar\"\n cx=\"18\"\n cy=\"18\"\n r=\"15\"\n fill=\"none\"\n stroke=\"var(--toast-status-color)\"\n stroke-width=\"2.5\"\n stroke-dasharray=\"94.25\"\n stroke-dashoffset=\"0\"\n transform=\"rotate(-90 18 18)\"\n ></circle>\n </svg>\n <button\n id=\"toast-close-btn\"\n class=\"toast-close-btn\"\n type=\"button\"\n aria-label=\"Đóng thông báo\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n </button>\n </div>\n </div>\n <div id=\"toast-details\" class=\"toast-details is-hidden\">\n <div class=\"toast-detail-row\">\n <span class=\"toast-detail-label\">Mã hỗ trợ</span>\n <div class=\"toast-detail-value-wrap\">\n <span\n id=\"toast-request-code\"\n class=\"toast-detail-value toast-detail-code\"\n ></span>\n <button\n id=\"toast-copy-code-btn\"\n class=\"toast-copy-btn\"\n type=\"button\"\n title=\"Sao chép mã hỗ trợ\"\n >\n <svg\n class=\"icon-copy\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"></rect>\n <path\n d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"\n ></path>\n </svg>\n <svg\n class=\"icon-copied is-hidden\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <polyline points=\"20 6 9 17 4 12\"></polyline>\n </svg>\n </button>\n </div>\n </div>\n <div id=\"toast-link-row\" class=\"toast-detail-row is-hidden\">\n <span class=\"toast-detail-label\">Tra cứu</span>\n <div class=\"toast-detail-value-wrap\">\n <a\n id=\"toast-link\"\n class=\"toast-detail-value toast-detail-link\"\n href=\"#\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n ></a>\n <button\n id=\"toast-copy-link-btn\"\n class=\"toast-copy-btn\"\n type=\"button\"\n title=\"Sao chép liên kết\"\n >\n <svg\n class=\"icon-copy\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"></rect>\n <path\n d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"\n ></path>\n </svg>\n <svg\n class=\"icon-copied is-hidden\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <polyline points=\"20 6 9 17 4 12\"></polyline>\n </svg>\n </button>\n </div>\n </div>\n </div>\n </div>\n</div>\n","import { resolveBaseUrl } from \"../core/config/endpoints\";\nimport {\n validateSubmission,\n validateUser,\n validateWidgetConfig,\n} from \"../core/validation/request-validator\";\nimport {\n isCreateRequestSuccess,\n mapCreateRequestFailure,\n mapCreateRequestSuccess,\n mapHttpClientError,\n type SubmitToastContent,\n} from \"../services/api/create-request-response\";\nimport { createSupportRequest } from \"../services/api/support-api\";\nimport type {\n SupportSubmissionInput,\n SupportUser,\n SupportUserPrefill,\n SupportWidgetConfig,\n} from \"../types/public\";\nimport { WIDGET_EVENTS } from \"./events/widget-events\";\nimport npSupportLogo from \"./assets/np-support-logo.png\";\nimport styles from \"./support-widget.styles.css\";\nimport markup from \"./support-widget.template.html\";\n\nlet sharedTemplate: HTMLTemplateElement | null = null;\n\nfunction getTemplate(): HTMLTemplateElement {\n if (!sharedTemplate) {\n sharedTemplate = document.createElement(\"template\");\n sharedTemplate.innerHTML = `<style>${styles}</style>${markup}`;\n }\n return sharedTemplate;\n}\n\nasync function urlToFile(url: string): Promise<File | null> {\n try {\n const response = await fetch(url);\n if (!response.ok) return null;\n const blob = await response.blob();\n const name = url.substring(url.lastIndexOf(\"/\") + 1) || \"attachment\";\n return new File([blob], name, { type: blob.type });\n } catch (e) {\n console.warn(\"Failed to fetch prefilled attachment\", url, e);\n return null;\n }\n}\n\ninterface FormPrefillInput {\n content?: string;\n attachments?: string[];\n}\n\ninterface ToastPayload extends SubmitToastContent {\n title: string;\n}\n\ntype RequiredFieldId = \"requester\" | \"phone-number\" | \"email\" | \"content\";\n\nexport class SupportWidgetElement extends HTMLElement {\n static observedAttributes = [\n \"project-id\",\n \"is-dev\",\n \"width\",\n \"height\",\n \"right\",\n \"bottom\",\n \"left\",\n \"top\",\n \"toast-duration\",\n ];\n\n private config: SupportWidgetConfig = {\n projectId: undefined,\n };\n\n private user?: SupportUserPrefill;\n private dragOccurred = false;\n\n // Internal states for fields mapped behind the scenes\n private selectedFiles: File[] = [];\n private prefilledAttachments: string[] = [];\n private previewUrls: string[] = [];\n private closeAfterSuccessTimer: number | null = null;\n private toastCountdownInterval: number | null = null;\n private toastSuccessDuration = 4000;\n private toastErrorDuration = 4000;\n private toastRemainingMs = 0;\n private toastTotalMs = 0;\n private isToastPaused = false;\n public priority: number | undefined;\n public coordinators: string[] = [];\n public emailContacts: string[] = [];\n\n constructor() {\n super();\n const root = this.attachShadow({ mode: \"open\" });\n root.appendChild(getTemplate().content.cloneNode(true));\n this.bindActions();\n }\n\n connectedCallback(): void {\n this.syncConfigFromAttributes();\n this.prefillFromState();\n this.applyLauncherStyle();\n this.applyPositionStyle();\n this.restorePosition();\n window.addEventListener(\"resize\", this.handleResize);\n }\n\n disconnectedCallback(): void {\n window.removeEventListener(\"resize\", this.handleResize);\n this.clearCloseAfterSuccessTimer();\n this.clearToastCountdown();\n }\n\n attributeChangedCallback(): void {\n this.syncConfigFromAttributes();\n this.prefillFromState();\n this.applyLauncherStyle();\n this.applyPositionStyle();\n }\n\n setUser(user: SupportUserPrefill): void {\n this.user = user;\n this.prefillFromState();\n }\n\n /**\n * Updates the widget configuration.\n * Supports custom toast auto-close durations via `toastDuration`.\n */\n setConfig(config: Partial<SupportWidgetConfig>): void {\n this.config = {\n ...this.config,\n ...config,\n };\n if (config.projectId !== undefined) {\n this.setAttribute(\"project-id\", config.projectId);\n }\n if (config.isDev !== undefined) {\n if (config.isDev) {\n this.setAttribute(\"is-dev\", \"\");\n } else {\n this.removeAttribute(\"is-dev\");\n }\n }\n if (config.priority !== undefined) {\n this.priority = config.priority;\n }\n if (config.coordinators !== undefined) {\n this.coordinators = config.coordinators;\n }\n if (config.emailContacts !== undefined) {\n this.emailContacts = config.emailContacts;\n }\n if (config.toastDuration !== undefined) {\n const duration = config.toastDuration;\n if (typeof duration === \"number\") {\n const val = this.normalizeToastDuration(duration);\n this.toastSuccessDuration = val;\n this.toastErrorDuration = val;\n } else if (duration && typeof duration === \"object\") {\n if (duration.success !== undefined) {\n this.toastSuccessDuration = this.normalizeToastDuration(duration.success);\n } else {\n this.toastSuccessDuration = 4000;\n }\n if (duration.error !== undefined) {\n this.toastErrorDuration = this.normalizeToastDuration(duration.error);\n } else {\n this.toastErrorDuration = 4000;\n }\n }\n }\n }\n\n setFormPrefill(data: FormPrefillInput): void {\n this.setInputValue(\"content\", data.content ?? \"\");\n this.prefilledAttachments = data.attachments ?? [];\n this.renderFileList();\n }\n\n open(): void {\n this.shadowRoot?.getElementById(\"modal\")?.classList.add(\"show\");\n this.dispatchEvent(new CustomEvent(WIDGET_EVENTS.OPEN, { bubbles: true }));\n }\n\n close(): void {\n this.clearCloseAfterSuccessTimer();\n this.resetFormData();\n this.hideToast(false);\n this.shadowRoot?.getElementById(\"modal\")?.classList.remove(\"show\");\n this.dispatchEvent(new CustomEvent(WIDGET_EVENTS.CLOSE, { bubbles: true }));\n }\n\n /**\n * Helper method to trigger a mock/demo toast popup for testing and development.\n * @param type - Either \"success\" or \"error\"\n */\n public showDemoToast(type: \"success\" | \"error\"): void {\n if (type === \"success\") {\n this.showToast({\n type: \"success\",\n title: \"Gửi yêu cầu thành công\",\n message: \"Successful.\",\n requestCode: \"2607-230-1322\",\n url: \"https://hotro.azurewebsites.net/yeu-cau/2607-230-1322\",\n });\n } else {\n this.showToast({\n type: \"error\",\n title: \"Gửi yêu cầu thất bại\",\n message: \"Phone number is invalid\",\n });\n }\n }\n\n private syncConfigFromAttributes(): void {\n this.config = {\n ...this.config,\n projectId: this.getAttribute(\"project-id\") ?? \"\",\n isDev: this.hasAttribute(\"is-dev\"),\n };\n\n const toastDurationAttr = this.getAttribute(\"toast-duration\");\n if (toastDurationAttr !== null) {\n const trimmed = toastDurationAttr.trim();\n if (trimmed.startsWith(\"{\")) {\n try {\n const parsed = JSON.parse(trimmed);\n if (parsed && typeof parsed === \"object\") {\n if (parsed.success !== undefined) {\n this.toastSuccessDuration = this.normalizeToastDuration(Number(parsed.success));\n }\n if (parsed.error !== undefined) {\n this.toastErrorDuration = this.normalizeToastDuration(Number(parsed.error));\n }\n }\n } catch {\n // ignore parsing error\n }\n } else {\n const parsed = parseInt(trimmed, 10);\n if (!isNaN(parsed)) {\n const val = this.normalizeToastDuration(parsed);\n this.toastSuccessDuration = val;\n this.toastErrorDuration = val;\n }\n }\n }\n }\n\n private bindActions(): void {\n const root = this.shadowRoot;\n if (!root) return;\n\n const launcher = root.getElementById(\"open-btn\");\n if (launcher) {\n launcher.addEventListener(\"click\", () => {\n if (this.dragOccurred) {\n this.dragOccurred = false;\n return;\n }\n this.open();\n });\n this.setupDrag(launcher);\n }\n\n root\n .getElementById(\"close-btn\")\n ?.addEventListener(\"click\", () => this.close());\n root\n .getElementById(\"cancel-btn\")\n ?.addEventListener(\"click\", () => this.close());\n root\n .getElementById(\"submit-btn\")\n ?.addEventListener(\"click\", () => void this.submit());\n root.getElementById(\"toast-close-btn\")?.addEventListener(\"click\", () => {\n this.hideToast();\n });\n\n const toastCardEl = root.querySelector(\".toast-card\");\n if (toastCardEl) {\n toastCardEl.addEventListener(\"mouseenter\", () => {\n this.isToastPaused = true;\n });\n toastCardEl.addEventListener(\"mouseleave\", () => {\n this.isToastPaused = false;\n });\n }\n\n this.setupClearableInputs();\n\n // File selection UI handlers\n const uploadTrigger = root.getElementById(\"upload-trigger\");\n const fileInput = root.getElementById(\n \"file-input\",\n ) as HTMLInputElement | null;\n\n if (uploadTrigger && fileInput) {\n uploadTrigger.addEventListener(\"click\", () => {\n fileInput.click();\n });\n\n uploadTrigger.addEventListener(\"keydown\", (e: KeyboardEvent) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n fileInput.click();\n }\n });\n\n uploadTrigger.addEventListener(\"dragover\", (e: DragEvent) => {\n e.preventDefault();\n if (e.dataTransfer) {\n e.dataTransfer.dropEffect = \"copy\";\n }\n uploadTrigger.classList.add(\"dragover\");\n });\n\n uploadTrigger.addEventListener(\"dragleave\", () => {\n uploadTrigger.classList.remove(\"dragover\");\n });\n\n uploadTrigger.addEventListener(\"drop\", (e: DragEvent) => {\n e.preventDefault();\n uploadTrigger.classList.remove(\"dragover\");\n if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {\n this.addSelectedFiles(Array.from(e.dataTransfer.files));\n }\n });\n\n fileInput.addEventListener(\"change\", () => {\n if (fileInput.files) {\n this.addSelectedFiles(Array.from(fileInput.files));\n // Reset file input value to allow selecting the same file again\n fileInput.value = \"\";\n }\n });\n }\n\n this.setupToastCopy();\n this.setupClipboardPaste();\n this.setupPreviewControls();\n }\n\n private setupPreviewControls(): void {\n const root = this.shadowRoot;\n if (!root) return;\n\n const overlay = root.getElementById(\"preview-overlay\");\n root\n .getElementById(\"preview-close\")\n ?.addEventListener(\"click\", () => this.closePreview());\n\n overlay?.addEventListener(\"click\", (event) => {\n if (event.target === overlay) this.closePreview();\n });\n\n root.addEventListener(\"keydown\", (event) => {\n if ((event as KeyboardEvent).key === \"Escape\") this.closePreview();\n });\n }\n\n private setupToastCopy(): void {\n const root = this.shadowRoot;\n if (!root) return;\n\n const copyCodeBtn = root.getElementById(\"toast-copy-code-btn\");\n const copyLinkBtn = root.getElementById(\"toast-copy-link-btn\");\n\n const copyText = async (text: string, btn: HTMLElement | null) => {\n if (!text || !btn) return;\n\n const doSuccessFeedback = () => {\n btn.classList.add(\"copied\");\n const copyIcon = btn.querySelector(\".icon-copy\");\n const copiedIcon = btn.querySelector(\".icon-copied\");\n if (copyIcon && copiedIcon) {\n copyIcon.classList.add(\"is-hidden\");\n copiedIcon.classList.remove(\"is-hidden\");\n }\n\n window.setTimeout(() => {\n btn.classList.remove(\"copied\");\n if (copyIcon && copiedIcon) {\n copyIcon.classList.remove(\"is-hidden\");\n copiedIcon.classList.add(\"is-hidden\");\n }\n }, 1500);\n };\n\n if (navigator.clipboard && window.isSecureContext) {\n try {\n await navigator.clipboard.writeText(text);\n doSuccessFeedback();\n return;\n } catch (err) {\n console.warn(\"navigator.clipboard.writeText failed, trying fallback: \", err);\n }\n }\n\n const textarea = document.createElement(\"textarea\");\n textarea.value = text;\n textarea.style.position = \"fixed\";\n textarea.style.opacity = \"0\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n document.execCommand(\"copy\");\n doSuccessFeedback();\n } catch (err) {\n console.error(\"Fallback copy failed: \", err);\n } finally {\n document.body.removeChild(textarea);\n }\n };\n\n copyCodeBtn?.addEventListener(\"click\", () => {\n const codeEl = root.getElementById(\"toast-request-code\");\n const text = codeEl?.textContent || \"\";\n void copyText(text, copyCodeBtn);\n });\n\n copyLinkBtn?.addEventListener(\"click\", () => {\n const linkEl = root.getElementById(\"toast-link\") as HTMLAnchorElement | null;\n const text = linkEl?.href || \"\";\n void copyText(text, copyLinkBtn);\n });\n }\n\n private setupClipboardPaste(): void {\n const dialog = this.shadowRoot?.querySelector(\".dialog\");\n if (!dialog) return;\n\n dialog.addEventListener(\"paste\", (event) => {\n const clipboard = (event as ClipboardEvent).clipboardData;\n if (!clipboard) return;\n\n const files = Array.from(clipboard.items)\n .filter((item: DataTransferItem) => item.kind === \"file\")\n .map((item: DataTransferItem) => item.getAsFile())\n .filter((file): file is File => file !== null);\n\n if (files.length === 0) return;\n\n event.preventDefault();\n this.addSelectedFiles(files);\n });\n }\n\n private addSelectedFiles(files: File[]): void {\n if (files.length === 0) return;\n this.selectedFiles.push(...files);\n this.renderFileList();\n }\n\n private renderFileList(): void {\n const fileListEl = this.shadowRoot?.getElementById(\"file-list\");\n if (!fileListEl) return;\n fileListEl.innerHTML = \"\";\n\n // Release object URLs created in the previous render to avoid leaks\n this.previewUrls.forEach((url) => URL.revokeObjectURL(url));\n this.previewUrls = [];\n\n // Prefilled URLs\n this.prefilledAttachments.forEach((url, index) => {\n const name = url.substring(url.lastIndexOf(\"/\") + 1) || url;\n const isImage = this.isImageName(name);\n const previewUrl = isImage ? url : null;\n fileListEl.appendChild(\n this.buildFileItem({\n name,\n title: url,\n previewUrl,\n type: \"prefilled\",\n index,\n isImage,\n openUrl: url,\n badge: \"Đã gán\",\n }),\n );\n });\n\n // Selected local files\n this.selectedFiles.forEach((file, index) => {\n let previewUrl: string | null = null;\n const isImage = file.type.startsWith(\"image/\");\n if (isImage) {\n previewUrl = URL.createObjectURL(file);\n this.previewUrls.push(previewUrl);\n }\n fileListEl.appendChild(\n this.buildFileItem({\n name: file.name,\n title: file.name,\n previewUrl,\n type: \"selected\",\n index,\n isImage,\n openUrl: null,\n badge: null,\n }),\n );\n });\n\n // Hook remove event\n fileListEl.querySelectorAll(\".btn-remove\").forEach((btn) => {\n btn.addEventListener(\"click\", (e) => {\n const target = e.currentTarget as HTMLElement;\n const type = target.getAttribute(\"data-type\");\n const index = parseInt(target.getAttribute(\"data-index\") || \"0\", 10);\n if (type === \"prefilled\") {\n this.prefilledAttachments.splice(index, 1);\n } else {\n this.selectedFiles.splice(index, 1);\n }\n this.renderFileList();\n });\n });\n }\n\n private buildFileItem(opts: {\n name: string;\n title: string;\n previewUrl: string | null;\n type: \"prefilled\" | \"selected\";\n index: number;\n isImage: boolean;\n openUrl: string | null;\n badge: string | null;\n }): HTMLDivElement {\n const item = document.createElement(\"div\");\n item.className = \"file-item\";\n\n const trigger = document.createElement(\"button\");\n trigger.type = \"button\";\n trigger.className = \"file-preview-trigger\";\n trigger.title = \"Nhấn để xem trước\";\n trigger.addEventListener(\"click\", () => {\n this.openPreview({\n name: opts.title,\n previewUrl: opts.previewUrl,\n isImage: opts.isImage,\n openUrl: opts.openUrl,\n });\n });\n\n const thumb = document.createElement(\"div\");\n thumb.className = \"file-thumb\";\n if (opts.previewUrl) {\n const img = document.createElement(\"img\");\n img.src = opts.previewUrl;\n img.alt = opts.name;\n // Fall back to a file icon if the image fails to load\n img.addEventListener(\"error\", () => this.fillWithFileIcon(thumb));\n thumb.appendChild(img);\n } else {\n this.fillWithFileIcon(thumb);\n }\n\n const infoContainer = document.createElement(\"div\");\n infoContainer.className = \"file-info-container\";\n\n const name = document.createElement(\"span\");\n name.className = \"file-name\";\n name.title = opts.title;\n name.textContent = opts.name;\n\n infoContainer.appendChild(name);\n trigger.append(thumb, infoContainer);\n\n const removeBtn = document.createElement(\"button\");\n removeBtn.type = \"button\";\n removeBtn.className = \"btn-remove\";\n removeBtn.setAttribute(\"aria-label\", \"Xóa tệp\");\n removeBtn.setAttribute(\"data-type\", opts.type);\n removeBtn.setAttribute(\"data-index\", String(opts.index));\n removeBtn.innerHTML = `\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n `;\n\n item.append(trigger, removeBtn);\n\n if (opts.badge) {\n const badge = document.createElement(\"span\");\n badge.className = \"file-badge\";\n badge.textContent = opts.badge;\n item.appendChild(badge);\n }\n\n return item;\n }\n\n private fillWithFileIcon(thumb: HTMLElement): void {\n thumb.innerHTML = \"\";\n thumb.classList.add(\"file-thumb--icon\");\n thumb.innerHTML = `\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"22\" height=\"22\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"></path>\n <polyline points=\"14 2 14 8 20 8\"></polyline>\n </svg>`;\n }\n\n private openPreview(opts: {\n name: string;\n previewUrl: string | null;\n isImage: boolean;\n openUrl: string | null;\n }): void {\n const overlay = this.shadowRoot?.getElementById(\"preview-overlay\");\n const body = this.shadowRoot?.getElementById(\"preview-body\");\n if (!overlay || !body) return;\n\n body.innerHTML = \"\";\n\n if (opts.isImage && opts.previewUrl) {\n const img = document.createElement(\"img\");\n img.src = opts.previewUrl;\n img.alt = opts.name;\n body.appendChild(img);\n } else {\n const info = document.createElement(\"div\");\n info.className = \"preview-file\";\n const openLink = opts.openUrl\n ? `<a href=\"${opts.openUrl}\" target=\"_blank\" rel=\"noopener noreferrer\">Mở trong tab mới</a>`\n : `<span>Không có bản xem trước cho tệp này</span>`;\n info.innerHTML = `\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"56\" height=\"56\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"></path>\n <polyline points=\"14 2 14 8 20 8\"></polyline>\n </svg>\n <span class=\"preview-file-name\"></span>\n ${openLink}`;\n const nameEl = info.querySelector(\".preview-file-name\");\n if (nameEl) nameEl.textContent = opts.name;\n body.appendChild(info);\n }\n\n overlay.classList.add(\"show\");\n }\n\n private closePreview(): void {\n const overlay = this.shadowRoot?.getElementById(\"preview-overlay\");\n if (!overlay) return;\n overlay.classList.remove(\"show\");\n const body = this.shadowRoot?.getElementById(\"preview-body\");\n if (body) body.innerHTML = \"\";\n }\n\n private isImageName(name: string): boolean {\n return /\\.(png|jpe?g|gif|webp|bmp|svg|avif)$/i.test(name);\n }\n\n private prefillFromState(): void {\n this.setInputValue(\"requester\", this.user?.name ?? \"\");\n this.setInputValue(\"email\", this.user?.email ?? \"\");\n this.setInputValue(\"phone-number\", this.user?.phoneNumber ?? \"\");\n }\n\n private applyLauncherStyle(): void {\n const launcher = this.shadowRoot?.getElementById(\"open-btn\");\n if (!launcher) return;\n\n const launcherStyles = [`--np-hub-icon: url(\"${npSupportLogo}\")`];\n const width = this.normalizeSize(this.getAttribute(\"width\"));\n const height = this.normalizeSize(this.getAttribute(\"height\"));\n if (width) launcherStyles.push(`--np-hub-width: ${width}`);\n if (height) launcherStyles.push(`--np-hub-height: ${height}`);\n\n launcher.setAttribute(\"style\", `${launcherStyles.join(\"; \")};`);\n }\n\n private applyPositionStyle(): void {\n // Skip when user already dragged the launcher (inline left/top set).\n if (this.style.left && this.style.top) return;\n\n const right = this.normalizeSize(this.getAttribute(\"right\"));\n const bottom = this.normalizeSize(this.getAttribute(\"bottom\"));\n const left = this.normalizeSize(this.getAttribute(\"left\"));\n const top = this.normalizeSize(this.getAttribute(\"top\"));\n\n this.setPositionVar(\"--np-hub-left\", left);\n this.setPositionVar(\"--np-hub-top\", top);\n this.setPositionVar(\"--np-hub-right\", right);\n this.setPositionVar(\"--np-hub-bottom\", bottom);\n\n // Avoid conflicting edges when only one side of a pair is given.\n if (left && !right) this.style.setProperty(\"--np-hub-right\", \"auto\");\n if (right && !left) this.style.setProperty(\"--np-hub-left\", \"auto\");\n if (top && !bottom) this.style.setProperty(\"--np-hub-bottom\", \"auto\");\n if (bottom && !top) this.style.setProperty(\"--np-hub-top\", \"auto\");\n }\n\n private setPositionVar(name: string, value: string | null): void {\n if (value) {\n this.style.setProperty(name, value);\n } else {\n this.style.removeProperty(name);\n }\n }\n\n private normalizeSize(value: string | null): string | null {\n const trimmed = value?.trim();\n if (!trimmed) return null;\n return /^\\d+(\\.\\d+)?$/.test(trimmed) ? `${trimmed}px` : trimmed;\n }\n\n private setupClearableInputs(): void {\n const root = this.shadowRoot;\n if (!root) return;\n\n root.querySelectorAll(\".input-wrap\").forEach((wrap) => {\n const input = wrap.querySelector(\"input, textarea\") as\n | HTMLInputElement\n | HTMLTextAreaElement\n | null;\n const clearBtn = wrap.querySelector(\n \".btn-clear\",\n ) as HTMLButtonElement | null;\n if (!input || !clearBtn) return;\n\n const updateVisibility = () => this.updateClearButton(input);\n\n input.addEventListener(\"input\", updateVisibility);\n input.addEventListener(\"input\", () => this.clearFieldError(input.id));\n clearBtn.addEventListener(\"click\", () => {\n input.value = \"\";\n input.focus();\n updateVisibility();\n this.clearFieldError(input.id);\n });\n\n updateVisibility();\n });\n }\n\n private updateClearButton(\n input: HTMLInputElement | HTMLTextAreaElement,\n ): void {\n const wrap = input.parentElement;\n if (wrap?.classList.contains(\"input-wrap\")) {\n wrap.classList.toggle(\"has-value\", input.value.length > 0);\n }\n }\n\n private setInputValue(id: string, value: string): void {\n const el = this.shadowRoot?.getElementById(id) as\n | HTMLInputElement\n | HTMLTextAreaElement\n | null;\n if (!el) return;\n el.value = value;\n this.updateClearButton(el);\n }\n\n private getInputValue(id: string): string {\n const el = this.shadowRoot?.getElementById(id) as\n | HTMLInputElement\n | HTMLTextAreaElement\n | null;\n return el?.value?.trim() ?? \"\";\n }\n\n private setFieldError(id: RequiredFieldId): void {\n const errorEl = this.shadowRoot?.getElementById(`${id}-error`);\n if (errorEl) {\n errorEl.textContent = \"\";\n }\n\n const input = this.shadowRoot?.getElementById(id) as\n | HTMLInputElement\n | HTMLTextAreaElement\n | null;\n const wrap = input?.parentElement;\n if (wrap?.classList.contains(\"input-wrap\")) {\n wrap.classList.add(\"has-error\");\n }\n }\n\n private clearFieldError(id: string): void {\n const errorEl = this.shadowRoot?.getElementById(`${id}-error`);\n if (errorEl) {\n errorEl.textContent = \"\";\n }\n\n const input = this.shadowRoot?.getElementById(id) as\n | HTMLInputElement\n | HTMLTextAreaElement\n | null;\n const wrap = input?.parentElement;\n if (wrap?.classList.contains(\"input-wrap\")) {\n wrap.classList.remove(\"has-error\");\n }\n }\n\n private clearAllFieldErrors(): void {\n this.clearFieldError(\"requester\");\n this.clearFieldError(\"phone-number\");\n this.clearFieldError(\"email\");\n this.clearFieldError(\"content\");\n }\n\n private showToast(\n payload: ToastPayload,\n autoCloseMs?: number,\n ): void {\n const toastEl = this.shadowRoot?.getElementById(\"toast\");\n const titleEl = this.shadowRoot?.getElementById(\"toast-title\");\n const messageEl = this.shadowRoot?.getElementById(\"toast-message\");\n const detailsEl = this.shadowRoot?.getElementById(\"toast-details\");\n const requestCodeEl = this.shadowRoot?.getElementById(\"toast-request-code\");\n const linkRowEl = this.shadowRoot?.getElementById(\"toast-link-row\");\n const linkEl = this.shadowRoot?.getElementById(\n \"toast-link\",\n ) as HTMLAnchorElement | null;\n const countdownEl = this.shadowRoot?.getElementById(\"toast-countdown\");\n const countdownSecondsEl = this.shadowRoot?.getElementById(\n \"toast-countdown-seconds\",\n );\n const countdownBarEl = this.shadowRoot?.getElementById(\n \"toast-countdown-bar\",\n );\n if (\n !toastEl ||\n !titleEl ||\n !messageEl ||\n !detailsEl ||\n !requestCodeEl ||\n !linkRowEl ||\n !linkEl ||\n !countdownBarEl\n ) {\n return;\n }\n\n this.clearToastCountdown();\n\n titleEl.textContent = payload.title;\n messageEl.textContent = payload.message;\n\n const hasRequestCode = Boolean(payload.requestCode);\n const hasUrl = Boolean(payload.url);\n const showDetails =\n payload.type === \"success\" && (hasRequestCode || hasUrl);\n\n if (showDetails) {\n requestCodeEl.textContent = payload.requestCode ?? \"—\";\n if (hasUrl && payload.url) {\n linkEl.href = payload.url;\n linkEl.textContent = payload.url;\n linkEl.title = payload.url;\n linkRowEl.classList.remove(\"is-hidden\");\n } else {\n linkEl.removeAttribute(\"href\");\n linkEl.textContent = \"\";\n linkEl.removeAttribute(\"title\");\n linkRowEl.classList.add(\"is-hidden\");\n }\n detailsEl.classList.remove(\"is-hidden\");\n } else {\n requestCodeEl.textContent = \"\";\n linkEl.removeAttribute(\"href\");\n linkEl.textContent = \"\";\n linkEl.removeAttribute(\"title\");\n linkRowEl.classList.add(\"is-hidden\");\n detailsEl.classList.add(\"is-hidden\");\n }\n\n const duration = autoCloseMs !== undefined\n ? autoCloseMs\n : (payload.type === \"success\" ? this.toastSuccessDuration : this.toastErrorDuration);\n\n this.toastRemainingMs = duration;\n this.toastTotalMs = duration;\n const toastCardEl = this.shadowRoot?.querySelector(\".toast-card\");\n this.isToastPaused = Boolean(toastCardEl?.matches(\":hover\"));\n\n toastEl.classList.remove(\"success\", \"error\");\n toastEl.classList.add(payload.type, \"show\");\n this.startToastCountdown(\n countdownEl ?? null,\n countdownSecondsEl ?? null,\n countdownBarEl,\n );\n }\n\n private hideToast(shouldCloseModal = true): void {\n const toastEl = this.shadowRoot?.getElementById(\"toast\");\n const titleEl = this.shadowRoot?.getElementById(\"toast-title\");\n const messageEl = this.shadowRoot?.getElementById(\"toast-message\");\n const detailsEl = this.shadowRoot?.getElementById(\"toast-details\");\n const requestCodeEl = this.shadowRoot?.getElementById(\"toast-request-code\");\n const linkRowEl = this.shadowRoot?.getElementById(\"toast-link-row\");\n const linkEl = this.shadowRoot?.getElementById(\n \"toast-link\",\n ) as HTMLAnchorElement | null;\n const countdownEl = this.shadowRoot?.getElementById(\"toast-countdown\");\n const countdownSecondsEl = this.shadowRoot?.getElementById(\n \"toast-countdown-seconds\",\n );\n const countdownBarEl = this.shadowRoot?.getElementById(\n \"toast-countdown-bar\",\n );\n if (\n !toastEl ||\n !titleEl ||\n !messageEl ||\n !detailsEl ||\n !requestCodeEl ||\n !linkRowEl ||\n !linkEl ||\n !countdownBarEl\n ) {\n return;\n }\n\n const isSuccess = toastEl.classList.contains(\"success\");\n\n this.clearToastCountdown();\n this.toastRemainingMs = 0;\n this.toastTotalMs = 0;\n this.isToastPaused = false;\n titleEl.textContent = \"\";\n messageEl.textContent = \"\";\n requestCodeEl.textContent = \"\";\n linkEl.removeAttribute(\"href\");\n linkEl.textContent = \"\";\n linkEl.removeAttribute(\"title\");\n linkRowEl.classList.add(\"is-hidden\");\n detailsEl.classList.add(\"is-hidden\");\n if (countdownSecondsEl) {\n countdownSecondsEl.textContent = \"\";\n }\n countdownBarEl.style.strokeDashoffset = \"0\";\n if (countdownEl) {\n countdownEl.classList.add(\"is-hidden\");\n }\n toastEl.classList.remove(\"success\", \"error\", \"show\");\n\n if (isSuccess && shouldCloseModal) {\n this.close();\n }\n }\n\n private startToastCountdown(\n countdownEl: HTMLElement | null,\n secondsEl: HTMLElement | null,\n barEl: HTMLElement,\n ): void {\n const updateCountdown = (): void => {\n const seconds = Math.max(1, Math.ceil(this.toastRemainingMs / 1000));\n if (secondsEl) {\n secondsEl.textContent = `${seconds}s`;\n }\n \n const percent = Math.max(0, Math.min(1, this.toastRemainingMs / this.toastTotalMs));\n const offset = 94.25 * (1 - percent);\n barEl.style.strokeDashoffset = `${offset}`;\n };\n\n updateCountdown();\n if (countdownEl) {\n countdownEl.classList.remove(\"is-hidden\");\n }\n\n this.toastCountdownInterval = window.setInterval(() => {\n if (this.isToastPaused) return;\n\n this.toastRemainingMs -= 100;\n if (this.toastRemainingMs <= 0) {\n this.toastRemainingMs = 0;\n barEl.style.strokeDashoffset = \"94.25\";\n if (secondsEl) {\n secondsEl.textContent = \"0s\";\n }\n this.hideToast();\n return;\n }\n updateCountdown();\n }, 100);\n }\n\n private clearToastCountdown(): void {\n if (this.toastCountdownInterval !== null) {\n window.clearInterval(this.toastCountdownInterval);\n this.toastCountdownInterval = null;\n }\n }\n\n private toToastPayload(content: SubmitToastContent): ToastPayload {\n return {\n ...content,\n title:\n content.type === \"success\"\n ? \"Gửi yêu cầu thành công\"\n : \"Gửi yêu cầu thất bại\",\n };\n }\n\n\n private normalizeToastDuration(value: number): number {\n if (!Number.isFinite(value) || value <= 0) {\n return 4000;\n }\n return Math.floor(value);\n }\n\n private clearCloseAfterSuccessTimer(): void {\n if (this.closeAfterSuccessTimer !== null) {\n window.clearTimeout(this.closeAfterSuccessTimer);\n this.closeAfterSuccessTimer = null;\n }\n }\n\n private validateRequiredFields(): boolean {\n this.clearAllFieldErrors();\n\n const requiredFields: RequiredFieldId[] = [\n \"requester\",\n \"phone-number\",\n \"email\",\n \"content\",\n ];\n\n let firstInvalidId: RequiredFieldId | null = null;\n\n requiredFields.forEach((id) => {\n if (!this.getInputValue(id)) {\n this.setFieldError(id);\n if (!firstInvalidId) {\n firstInvalidId = id;\n }\n }\n });\n\n if (firstInvalidId) {\n const firstInvalid = this.shadowRoot?.getElementById(firstInvalidId) as\n | HTMLInputElement\n | HTMLTextAreaElement\n | null;\n firstInvalid?.focus();\n return false;\n }\n\n return true;\n }\n\n private resetFormData(): void {\n this.setInputValue(\"requester\", \"\");\n this.setInputValue(\"email\", \"\");\n this.setInputValue(\"phone-number\", \"\");\n this.setInputValue(\"content\", \"\");\n this.clearAllFieldErrors();\n\n this.selectedFiles = [];\n this.prefilledAttachments = [];\n this.renderFileList();\n this.closePreview();\n }\n\n private async submit(): Promise<void> {\n this.clearCloseAfterSuccessTimer();\n this.hideToast(false);\n\n if (!this.validateRequiredFields()) {\n this.dispatchEvent(\n new CustomEvent(WIDGET_EVENTS.SUBMIT_ERROR, {\n bubbles: true,\n detail: { message: \"Please fill in all required fields.\" },\n }),\n );\n return;\n }\n\n const user: SupportUser = {\n name: this.getInputValue(\"requester\"),\n email: this.getInputValue(\"email\"),\n phoneNumber: this.getInputValue(\"phone-number\"),\n };\n\n this.user = user;\n\n try {\n validateWidgetConfig(this.config);\n validateUser(user);\n\n const content = this.getInputValue(\"content\");\n const submission: SupportSubmissionInput = {\n content,\n attachments: [], // dummy value for local validation of required content field\n };\n validateSubmission(submission);\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Invalid input.\";\n this.dispatchEvent(\n new CustomEvent(WIDGET_EVENTS.SUBMIT_ERROR, {\n bubbles: true,\n detail: { message },\n }),\n );\n return;\n }\n\n const content = this.getInputValue(\"content\");\n\n // Create FormData to send request as multipart/form-data\n const formData = new FormData();\n formData.append(\"Requester\", user.name);\n formData.append(\"PhoneNumber\", user.phoneNumber);\n formData.append(\"Email\", user.email);\n formData.append(\"Content\", content);\n if (this.config.projectId?.trim()) {\n formData.append(\"ProjectId\", this.config.projectId.trim());\n }\n if (typeof this.priority === \"number\") {\n formData.append(\"Priority\", String(this.priority));\n }\n\n // Fetch prefilled attachments and convert them to Files\n for (const url of this.prefilledAttachments) {\n const file = await urlToFile(url);\n if (file) {\n formData.append(\"Attachments\", file);\n }\n }\n\n // Add newly selected files\n for (const file of this.selectedFiles) {\n formData.append(\"Attachments\", file);\n }\n\n // Add coordinators and emailContacts\n if (this.coordinators.length > 0) {\n this.coordinators.forEach((coord) =>\n formData.append(\"Coordinators\", coord),\n );\n }\n if (this.emailContacts.length > 0) {\n this.emailContacts.forEach((contact) =>\n formData.append(\"EmailContacts\", contact),\n );\n }\n\n try {\n const baseUrl = resolveBaseUrl(this.config);\n const result = await createSupportRequest(baseUrl, formData);\n\n if (!isCreateRequestSuccess(result)) {\n const failureContent = mapCreateRequestFailure(result);\n this.showToast(this.toToastPayload(failureContent));\n this.dispatchEvent(\n new CustomEvent(WIDGET_EVENTS.SUBMIT_ERROR, {\n bubbles: true,\n detail: { message: failureContent.message, response: result },\n }),\n );\n return;\n }\n\n const successContent = mapCreateRequestSuccess(result);\n this.showToast(this.toToastPayload(successContent));\n this.dispatchEvent(\n new CustomEvent(WIDGET_EVENTS.SUBMIT_SUCCESS, {\n bubbles: true,\n detail: result,\n }),\n );\n this.closeAfterSuccessTimer = window.setTimeout(() => {\n this.hideToast();\n }, this.toastSuccessDuration);\n } catch (error) {\n const errorContent = mapHttpClientError(error);\n this.showToast(this.toToastPayload(errorContent));\n this.dispatchEvent(\n new CustomEvent(WIDGET_EVENTS.SUBMIT_ERROR, {\n bubbles: true,\n detail: { message: errorContent.message },\n }),\n );\n }\n }\n\n private setupDrag(launcher: HTMLElement): void {\n launcher.addEventListener(\"pointerdown\", (e: PointerEvent) => {\n if (e.button !== 0) return;\n\n this.dragOccurred = false;\n const rect = this.getBoundingClientRect();\n const startX = e.clientX;\n const startY = e.clientY;\n const initialLeft = rect.left;\n const initialTop = rect.top;\n\n try {\n launcher.setPointerCapture(e.pointerId);\n } catch {\n // ignore if pointer capture fails\n }\n\n const onPointerMove = (moveEvent: PointerEvent) => {\n const dx = moveEvent.clientX - startX;\n const dy = moveEvent.clientY - startY;\n\n if (!this.dragOccurred && (Math.abs(dx) > 4 || Math.abs(dy) > 4)) {\n this.dragOccurred = true;\n launcher.style.cursor = \"grabbing\";\n }\n\n if (this.dragOccurred) {\n const leftVal = initialLeft + dx;\n const topVal = initialTop + dy;\n this.adjustPosition(leftVal, topVal);\n }\n };\n\n const onPointerUp = (upEvent: PointerEvent) => {\n try {\n launcher.releasePointerCapture(upEvent.pointerId);\n } catch {\n // ignore\n }\n launcher.removeEventListener(\"pointermove\", onPointerMove);\n launcher.removeEventListener(\"pointerup\", onPointerUp);\n launcher.removeEventListener(\"pointercancel\", onPointerUp);\n launcher.style.cursor = \"\";\n\n if (this.dragOccurred) {\n localStorage.setItem(\n \"np-hub-widget-position\",\n JSON.stringify({\n left: this.style.left,\n top: this.style.top,\n }),\n );\n }\n };\n\n launcher.addEventListener(\"pointermove\", onPointerMove);\n launcher.addEventListener(\"pointerup\", onPointerUp);\n launcher.addEventListener(\"pointercancel\", onPointerUp);\n });\n }\n\n private adjustPosition(leftVal: number, topVal: number): void {\n const rect = this.getBoundingClientRect();\n const width = rect.width || 72;\n const height = rect.height || 72;\n\n const minLeft = 0;\n const maxLeft = window.innerWidth - width;\n const minTop = 0;\n const maxTop = window.innerHeight - height;\n\n const clampedLeft = Math.max(minLeft, Math.min(maxLeft, leftVal));\n const clampedTop = Math.max(minTop, Math.min(maxTop, topVal));\n\n this.style.right = \"auto\";\n this.style.bottom = \"auto\";\n this.style.left = `${clampedLeft}px`;\n this.style.top = `${clampedTop}px`;\n }\n\n private restorePosition(): void {\n const savedPosition = localStorage.getItem(\"np-hub-widget-position\");\n if (savedPosition) {\n try {\n const { left, top } = JSON.parse(savedPosition);\n if (left && top) {\n const leftVal = parseFloat(left);\n const topVal = parseFloat(top);\n this.adjustPosition(leftVal, topVal);\n }\n } catch {\n // Ignore parsing errors\n }\n }\n }\n\n private handleResize = (): void => {\n if (this.style.left && this.style.top) {\n const leftVal = parseFloat(this.style.left);\n const topVal = parseFloat(this.style.top);\n this.adjustPosition(leftVal, topVal);\n }\n };\n}\n","import { SupportWidgetElement } from \"./component/support-widget.element\";\n\nconst TAG_NAME = \"np-hub\";\n\nexport function registerSupportWidget(): void {\n if (!customElements.get(TAG_NAME)) {\n customElements.define(TAG_NAME, SupportWidgetElement);\n }\n}\n\nregisterSupportWidget();\n"],"mappings":";AAAO,IAAM,eAAe;AACrB,IAAM,cAAc;AAEpB,SAAS,eAAe,OAAoC;AACjE,SAAO,MAAM,QAAQ,cAAc;AACrC;;;ACCO,SAAS,qBAAqB,QAAmC;AACtE,MAAI,OAAO,cAAc,UAAa,OAAO,UAAU,KAAK,EAAE,WAAW,GAAG;AAC1E,UAAM,IAAI,MAAM,4BAA4B;AAAA,EAC9C;AACF;AAEO,SAAS,aAAa,MAAyB;AAZtD;AAaE,MAAI,GAAC,UAAK,SAAL,mBAAW,SAAQ;AACtB,UAAM,IAAI,MAAM,wBAAwB;AAAA,EAC1C;AACA,MAAI,GAAC,UAAK,UAAL,mBAAY,SAAQ;AACvB,UAAM,IAAI,MAAM,yBAAyB;AAAA,EAC3C;AACA,MAAI,GAAC,UAAK,gBAAL,mBAAkB,SAAQ;AAC7B,UAAM,IAAI,MAAM,+BAA+B;AAAA,EACjD;AACF;AAEO,SAAS,mBAAmB,OAAqC;AAxBxE;AAyBE,MAAI,GAAC,WAAM,YAAN,mBAAe,SAAQ;AAC1B,UAAM,IAAI,MAAM,sBAAsB;AAAA,EACxC;AACF;;;ACVA,IAAM,2BAA2B;AACjC,IAAM,yBAAyB;AAExB,SAAS,uBACd,QACS;AACT,MAAI,OAAO,OAAO,eAAe,UAAU;AACzC,WAAO,OAAO,cAAc,OAAO,OAAO,aAAa;AAAA,EACzD;AACA,SAAO;AACT;AAEO,SAAS,wBACd,QACoB;AAhCtB;AAiCE,QAAM,aACJ,OAAO,OAAO,eAAe,WACzB,OAAO,OAAO,UAAU,IACxB;AACN,QAAM,eAAc,wBAAO,SAAP,mBAAa,gBAAb,mBAA0B,WAA1B,YAAoC;AACxD,QAAM,OAAM,wBAAO,SAAP,mBAAa,QAAb,mBAAkB,WAAlB,YAA4B;AAExC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,WAAS,YAAO,YAAP,mBAAgB,WAAU;AAAA,IACnC;AAAA,IACA,aAAa,eAAe;AAAA,IAC5B,KAAK,OAAO;AAAA,EACd;AACF;AAEO,SAAS,wBACd,QACoB;AAnDtB;AAoDE,QAAM,aACJ,OAAO,OAAO,eAAe,WACzB,OAAO,OAAO,UAAU,IACxB;AAEN,SAAO;AAAA,IACL,MAAM;AAAA,IACN,WAAS,YAAO,YAAP,mBAAgB,WAAU;AAAA,IACnC;AAAA,EACF;AACF;AAEO,SAAS,mBAAmB,OAAoC;AAhEvE;AAiEE,QAAM,kBAAkB;AACxB,QAAM,aACJ,iBAAiB,QAAQ,MAAM,UAAU;AAC3C,QAAM,gBAAgB,WAAW,MAAM,2BAA2B;AAClE,QAAM,aAAa,gBAAgB,cAAc,CAAC,IAAI;AACtD,QAAM,aAAa,WAAW,MAAM,kCAAkC;AACtE,QAAM,eAAc,oDAAa,OAAb,mBAAiB,WAAjB,YAA2B;AAE/C,MAAI,gBAAgB;AACpB,MAAI,aAAa;AACjB,MAAI,YAAY,WAAW,GAAG,GAAG;AAC/B,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,WAAW;AACrC,UAAI,OAAO,OAAO,eAAe,UAAU;AACzC,qBAAa,OAAO,OAAO,UAAU;AAAA,MACvC;AACA,UAAI,OAAO,OAAO,YAAY,UAAU;AACtC,wBAAgB,OAAO,QAAQ,KAAK;AAAA,MACtC;AAAA,IACF,SAAQ;AAAA,IAER;AAAA,EACF;AAEA,MAAI,CAAC,iBAAiB,aAAa;AACjC,oBAAgB;AAAA,EAClB;AACA,MAAI,CAAC,eAAe;AAClB,oBAAgB,cAAc;AAAA,EAChC;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY,cAAc,cAAc;AAAA,EAC1C;AACF;;;ACrGA,eAAsB,aACpB,KACA,MACoB;AACpB,QAAM,WAAW,MAAM,MAAM,KAAK;AAAA,IAChC,QAAQ;AAAA,IACR;AAAA,EACF,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,UAAU,MAAM,SAAS,KAAK;AACpC,UAAM,IAAI,MAAM,mBAAmB,SAAS,MAAM,MAAM,OAAO,EAAE;AAAA,EACnE;AAEA,SAAQ,MAAM,SAAS,KAAK;AAC9B;;;ACVA,eAAsB,qBACpB,SACA,UACgC;AAChC,QAAM,WAAW,GAAG,QAAQ,QAAQ,OAAO,EAAE,CAAC;AAC9C,SAAO,aAAoC,UAAU,QAAQ;AAC/D;;;ACXO,IAAM,gBAAgB;AAAA,EAC3B,MAAM;AAAA,EACN,OAAO;AAAA,EACP,gBAAgB;AAAA,EAChB,cAAc;AAChB;A;;;;;ACLA;;;ACAA;;;ACyBA,IAAI,iBAA6C;AAEjD,SAAS,cAAmC;AAC1C,MAAI,CAAC,gBAAgB;AACnB,qBAAiB,SAAS,cAAc,UAAU;AAClD,mBAAe,YAAY,UAAU,6BAAM,WAAW,+BAAM;AAAA,EAC9D;AACA,SAAO;AACT;AAEA,eAAe,UAAU,KAAmC;AAC1D,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,GAAG;AAChC,QAAI,CAAC,SAAS,GAAI,QAAO;AACzB,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,UAAM,OAAO,IAAI,UAAU,IAAI,YAAY,GAAG,IAAI,CAAC,KAAK;AACxD,WAAO,IAAI,KAAK,CAAC,IAAI,GAAG,MAAM,EAAE,MAAM,KAAK,KAAK,CAAC;AAAA,EACnD,SAAS,GAAG;AACV,YAAQ,KAAK,wCAAwC,KAAK,CAAC;AAC3D,WAAO;AAAA,EACT;AACF;AAaO,IAAM,uBAAN,cAAmC,YAAY;AAAA,EAmCpD,cAAc;AACZ,UAAM;AAvBR,SAAQ,SAA8B;AAAA,MACpC,WAAW;AAAA,IACb;AAGA,SAAQ,eAAe;AAGvB;AAAA,SAAQ,gBAAwB,CAAC;AACjC,SAAQ,uBAAiC,CAAC;AAC1C,SAAQ,cAAwB,CAAC;AACjC,SAAQ,yBAAwC;AAChD,SAAQ,yBAAwC;AAChD,SAAQ,uBAAuB;AAC/B,SAAQ,qBAAqB;AAC7B,SAAQ,mBAAmB;AAC3B,SAAQ,eAAe;AACvB,SAAQ,gBAAgB;AAExB,SAAO,eAAyB,CAAC;AACjC,SAAO,gBAA0B,CAAC;AAuqClC,SAAQ,eAAe,MAAY;AACjC,UAAI,KAAK,MAAM,QAAQ,KAAK,MAAM,KAAK;AACrC,cAAM,UAAU,WAAW,KAAK,MAAM,IAAI;AAC1C,cAAM,SAAS,WAAW,KAAK,MAAM,GAAG;AACxC,aAAK,eAAe,SAAS,MAAM;AAAA,MACrC;AAAA,IACF;AAzqCE,UAAM,OAAO,KAAK,aAAa,EAAE,MAAM,OAAO,CAAC;AAC/C,SAAK,YAAY,YAAY,EAAE,QAAQ,UAAU,IAAI,CAAC;AACtD,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,oBAA0B;AACxB,SAAK,yBAAyB;AAC9B,SAAK,iBAAiB;AACtB,SAAK,mBAAmB;AACxB,SAAK,mBAAmB;AACxB,SAAK,gBAAgB;AACrB,WAAO,iBAAiB,UAAU,KAAK,YAAY;AAAA,EACrD;AAAA,EAEA,uBAA6B;AAC3B,WAAO,oBAAoB,UAAU,KAAK,YAAY;AACtD,SAAK,4BAA4B;AACjC,SAAK,oBAAoB;AAAA,EAC3B;AAAA,EAEA,2BAAiC;AAC/B,SAAK,yBAAyB;AAC9B,SAAK,iBAAiB;AACtB,SAAK,mBAAmB;AACxB,SAAK,mBAAmB;AAAA,EAC1B;AAAA,EAEA,QAAQ,MAAgC;AACtC,SAAK,OAAO;AACZ,SAAK,iBAAiB;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAU,QAA4C;AACpD,SAAK,SAAS;AAAA,MACZ,GAAG,KAAK;AAAA,MACR,GAAG;AAAA,IACL;AACA,QAAI,OAAO,cAAc,QAAW;AAClC,WAAK,aAAa,cAAc,OAAO,SAAS;AAAA,IAClD;AACA,QAAI,OAAO,UAAU,QAAW;AAC9B,UAAI,OAAO,OAAO;AAChB,aAAK,aAAa,UAAU,EAAE;AAAA,MAChC,OAAO;AACL,aAAK,gBAAgB,QAAQ;AAAA,MAC/B;AAAA,IACF;AACA,QAAI,OAAO,aAAa,QAAW;AACjC,WAAK,WAAW,OAAO;AAAA,IACzB;AACA,QAAI,OAAO,iBAAiB,QAAW;AACrC,WAAK,eAAe,OAAO;AAAA,IAC7B;AACA,QAAI,OAAO,kBAAkB,QAAW;AACtC,WAAK,gBAAgB,OAAO;AAAA,IAC9B;AACA,QAAI,OAAO,kBAAkB,QAAW;AACtC,YAAM,WAAW,OAAO;AACxB,UAAI,OAAO,aAAa,UAAU;AAChC,cAAM,MAAM,KAAK,uBAAuB,QAAQ;AAChD,aAAK,uBAAuB;AAC5B,aAAK,qBAAqB;AAAA,MAC5B,WAAW,YAAY,OAAO,aAAa,UAAU;AACnD,YAAI,SAAS,YAAY,QAAW;AAClC,eAAK,uBAAuB,KAAK,uBAAuB,SAAS,OAAO;AAAA,QAC1E,OAAO;AACL,eAAK,uBAAuB;AAAA,QAC9B;AACA,YAAI,SAAS,UAAU,QAAW;AAChC,eAAK,qBAAqB,KAAK,uBAAuB,SAAS,KAAK;AAAA,QACtE,OAAO;AACL,eAAK,qBAAqB;AAAA,QAC5B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,eAAe,MAA8B;AAjL/C;AAkLI,SAAK,cAAc,YAAW,UAAK,YAAL,YAAgB,EAAE;AAChD,SAAK,wBAAuB,UAAK,gBAAL,YAAoB,CAAC;AACjD,SAAK,eAAe;AAAA,EACtB;AAAA,EAEA,OAAa;AAvLf;AAwLI,qBAAK,eAAL,mBAAiB,eAAe,aAAhC,mBAA0C,UAAU,IAAI;AACxD,SAAK,cAAc,IAAI,YAAY,cAAc,MAAM,EAAE,SAAS,KAAK,CAAC,CAAC;AAAA,EAC3E;AAAA,EAEA,QAAc;AA5LhB;AA6LI,SAAK,4BAA4B;AACjC,SAAK,cAAc;AACnB,SAAK,UAAU,KAAK;AACpB,qBAAK,eAAL,mBAAiB,eAAe,aAAhC,mBAA0C,UAAU,OAAO;AAC3D,SAAK,cAAc,IAAI,YAAY,cAAc,OAAO,EAAE,SAAS,KAAK,CAAC,CAAC;AAAA,EAC5E;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,cAAc,MAAiC;AACpD,QAAI,SAAS,WAAW;AACtB,WAAK,UAAU;AAAA,QACb,MAAM;AAAA,QACN,OAAO;AAAA,QACP,SAAS;AAAA,QACT,aAAa;AAAA,QACb,KAAK;AAAA,MACP,CAAC;AAAA,IACH,OAAO;AACL,WAAK,UAAU;AAAA,QACb,MAAM;AAAA,QACN,OAAO;AAAA,QACP,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEQ,2BAAiC;AA1N3C;AA2NI,SAAK,SAAS;AAAA,MACZ,GAAG,KAAK;AAAA,MACR,YAAW,UAAK,aAAa,YAAY,MAA9B,YAAmC;AAAA,MAC9C,OAAO,KAAK,aAAa,QAAQ;AAAA,IACnC;AAEA,UAAM,oBAAoB,KAAK,aAAa,gBAAgB;AAC5D,QAAI,sBAAsB,MAAM;AAC9B,YAAM,UAAU,kBAAkB,KAAK;AACvC,UAAI,QAAQ,WAAW,GAAG,GAAG;AAC3B,YAAI;AACF,gBAAM,SAAS,KAAK,MAAM,OAAO;AACjC,cAAI,UAAU,OAAO,WAAW,UAAU;AACxC,gBAAI,OAAO,YAAY,QAAW;AAChC,mBAAK,uBAAuB,KAAK,uBAAuB,OAAO,OAAO,OAAO,CAAC;AAAA,YAChF;AACA,gBAAI,OAAO,UAAU,QAAW;AAC9B,mBAAK,qBAAqB,KAAK,uBAAuB,OAAO,OAAO,KAAK,CAAC;AAAA,YAC5E;AAAA,UACF;AAAA,QACF,SAAQ;AAAA,QAER;AAAA,MACF,OAAO;AACL,cAAM,SAAS,SAAS,SAAS,EAAE;AACnC,YAAI,CAAC,MAAM,MAAM,GAAG;AAClB,gBAAM,MAAM,KAAK,uBAAuB,MAAM;AAC9C,eAAK,uBAAuB;AAC5B,eAAK,qBAAqB;AAAA,QAC5B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,cAAoB;AA7P9B;AA8PI,UAAM,OAAO,KAAK;AAClB,QAAI,CAAC,KAAM;AAEX,UAAM,WAAW,KAAK,eAAe,UAAU;AAC/C,QAAI,UAAU;AACZ,eAAS,iBAAiB,SAAS,MAAM;AACvC,YAAI,KAAK,cAAc;AACrB,eAAK,eAAe;AACpB;AAAA,QACF;AACA,aAAK,KAAK;AAAA,MACZ,CAAC;AACD,WAAK,UAAU,QAAQ;AAAA,IACzB;AAEA,eACG,eAAe,WAAW,MAD7B,mBAEI,iBAAiB,SAAS,MAAM,KAAK,MAAM;AAC/C,eACG,eAAe,YAAY,MAD9B,mBAEI,iBAAiB,SAAS,MAAM,KAAK,MAAM;AAC/C,eACG,eAAe,YAAY,MAD9B,mBAEI,iBAAiB,SAAS,MAAM,KAAK,KAAK,OAAO;AACrD,eAAK,eAAe,iBAAiB,MAArC,mBAAwC,iBAAiB,SAAS,MAAM;AACtE,WAAK,UAAU;AAAA,IACjB;AAEA,UAAM,cAAc,KAAK,cAAc,aAAa;AACpD,QAAI,aAAa;AACf,kBAAY,iBAAiB,cAAc,MAAM;AAC/C,aAAK,gBAAgB;AAAA,MACvB,CAAC;AACD,kBAAY,iBAAiB,cAAc,MAAM;AAC/C,aAAK,gBAAgB;AAAA,MACvB,CAAC;AAAA,IACH;AAEA,SAAK,qBAAqB;AAG1B,UAAM,gBAAgB,KAAK,eAAe,gBAAgB;AAC1D,UAAM,YAAY,KAAK;AAAA,MACrB;AAAA,IACF;AAEA,QAAI,iBAAiB,WAAW;AAC9B,oBAAc,iBAAiB,SAAS,MAAM;AAC5C,kBAAU,MAAM;AAAA,MAClB,CAAC;AAED,oBAAc,iBAAiB,WAAW,CAAC,MAAqB;AAC9D,YAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;AACtC,YAAE,eAAe;AACjB,oBAAU,MAAM;AAAA,QAClB;AAAA,MACF,CAAC;AAED,oBAAc,iBAAiB,YAAY,CAAC,MAAiB;AAC3D,UAAE,eAAe;AACjB,YAAI,EAAE,cAAc;AAClB,YAAE,aAAa,aAAa;AAAA,QAC9B;AACA,sBAAc,UAAU,IAAI,UAAU;AAAA,MACxC,CAAC;AAED,oBAAc,iBAAiB,aAAa,MAAM;AAChD,sBAAc,UAAU,OAAO,UAAU;AAAA,MAC3C,CAAC;AAED,oBAAc,iBAAiB,QAAQ,CAAC,MAAiB;AApU/D,YAAAA;AAqUQ,UAAE,eAAe;AACjB,sBAAc,UAAU,OAAO,UAAU;AACzC,cAAIA,MAAA,EAAE,iBAAF,gBAAAA,IAAgB,UAAS,EAAE,aAAa,MAAM,SAAS,GAAG;AAC5D,eAAK,iBAAiB,MAAM,KAAK,EAAE,aAAa,KAAK,CAAC;AAAA,QACxD;AAAA,MACF,CAAC;AAED,gBAAU,iBAAiB,UAAU,MAAM;AACzC,YAAI,UAAU,OAAO;AACnB,eAAK,iBAAiB,MAAM,KAAK,UAAU,KAAK,CAAC;AAEjD,oBAAU,QAAQ;AAAA,QACpB;AAAA,MACF,CAAC;AAAA,IACH;AAEA,SAAK,eAAe;AACpB,SAAK,oBAAoB;AACzB,SAAK,qBAAqB;AAAA,EAC5B;AAAA,EAEQ,uBAA6B;AA1VvC;AA2VI,UAAM,OAAO,KAAK;AAClB,QAAI,CAAC,KAAM;AAEX,UAAM,UAAU,KAAK,eAAe,iBAAiB;AACrD,eACG,eAAe,eAAe,MADjC,mBAEI,iBAAiB,SAAS,MAAM,KAAK,aAAa;AAEtD,uCAAS,iBAAiB,SAAS,CAAC,UAAU;AAC5C,UAAI,MAAM,WAAW,QAAS,MAAK,aAAa;AAAA,IAClD;AAEA,SAAK,iBAAiB,WAAW,CAAC,UAAU;AAC1C,UAAK,MAAwB,QAAQ,SAAU,MAAK,aAAa;AAAA,IACnE,CAAC;AAAA,EACH;AAAA,EAEQ,iBAAuB;AAC7B,UAAM,OAAO,KAAK;AAClB,QAAI,CAAC,KAAM;AAEX,UAAM,cAAc,KAAK,eAAe,qBAAqB;AAC7D,UAAM,cAAc,KAAK,eAAe,qBAAqB;AAE7D,UAAM,WAAW,OAAO,MAAc,QAA4B;AAChE,UAAI,CAAC,QAAQ,CAAC,IAAK;AAEnB,YAAM,oBAAoB,MAAM;AAC9B,YAAI,UAAU,IAAI,QAAQ;AAC1B,cAAM,WAAW,IAAI,cAAc,YAAY;AAC/C,cAAM,aAAa,IAAI,cAAc,cAAc;AACnD,YAAI,YAAY,YAAY;AAC1B,mBAAS,UAAU,IAAI,WAAW;AAClC,qBAAW,UAAU,OAAO,WAAW;AAAA,QACzC;AAEA,eAAO,WAAW,MAAM;AACtB,cAAI,UAAU,OAAO,QAAQ;AAC7B,cAAI,YAAY,YAAY;AAC1B,qBAAS,UAAU,OAAO,WAAW;AACrC,uBAAW,UAAU,IAAI,WAAW;AAAA,UACtC;AAAA,QACF,GAAG,IAAI;AAAA,MACT;AAEA,UAAI,UAAU,aAAa,OAAO,iBAAiB;AACjD,YAAI;AACF,gBAAM,UAAU,UAAU,UAAU,IAAI;AACxC,4BAAkB;AAClB;AAAA,QACF,SAAS,KAAK;AACZ,kBAAQ,KAAK,2DAA2D,GAAG;AAAA,QAC7E;AAAA,MACF;AAEA,YAAM,WAAW,SAAS,cAAc,UAAU;AAClD,eAAS,QAAQ;AACjB,eAAS,MAAM,WAAW;AAC1B,eAAS,MAAM,UAAU;AACzB,eAAS,KAAK,YAAY,QAAQ;AAClC,eAAS,OAAO;AAChB,UAAI;AACF,iBAAS,YAAY,MAAM;AAC3B,0BAAkB;AAAA,MACpB,SAAS,KAAK;AACZ,gBAAQ,MAAM,0BAA0B,GAAG;AAAA,MAC7C,UAAE;AACA,iBAAS,KAAK,YAAY,QAAQ;AAAA,MACpC;AAAA,IACF;AAEA,+CAAa,iBAAiB,SAAS,MAAM;AAC3C,YAAM,SAAS,KAAK,eAAe,oBAAoB;AACvD,YAAM,QAAO,iCAAQ,gBAAe;AACpC,WAAK,SAAS,MAAM,WAAW;AAAA,IACjC;AAEA,+CAAa,iBAAiB,SAAS,MAAM;AAC3C,YAAM,SAAS,KAAK,eAAe,YAAY;AAC/C,YAAM,QAAO,iCAAQ,SAAQ;AAC7B,WAAK,SAAS,MAAM,WAAW;AAAA,IACjC;AAAA,EACF;AAAA,EAEQ,sBAA4B;AA/atC;AAgbI,UAAM,UAAS,UAAK,eAAL,mBAAiB,cAAc;AAC9C,QAAI,CAAC,OAAQ;AAEb,WAAO,iBAAiB,SAAS,CAAC,UAAU;AAC1C,YAAM,YAAa,MAAyB;AAC5C,UAAI,CAAC,UAAW;AAEhB,YAAM,QAAQ,MAAM,KAAK,UAAU,KAAK,EACrC,OAAO,CAAC,SAA2B,KAAK,SAAS,MAAM,EACvD,IAAI,CAAC,SAA2B,KAAK,UAAU,CAAC,EAChD,OAAO,CAAC,SAAuB,SAAS,IAAI;AAE/C,UAAI,MAAM,WAAW,EAAG;AAExB,YAAM,eAAe;AACrB,WAAK,iBAAiB,KAAK;AAAA,IAC7B,CAAC;AAAA,EACH;AAAA,EAEQ,iBAAiB,OAAqB;AAC5C,QAAI,MAAM,WAAW,EAAG;AACxB,SAAK,cAAc,KAAK,GAAG,KAAK;AAChC,SAAK,eAAe;AAAA,EACtB;AAAA,EAEQ,iBAAuB;AAzcjC;AA0cI,UAAM,cAAa,UAAK,eAAL,mBAAiB,eAAe;AACnD,QAAI,CAAC,WAAY;AACjB,eAAW,YAAY;AAGvB,SAAK,YAAY,QAAQ,CAAC,QAAQ,IAAI,gBAAgB,GAAG,CAAC;AAC1D,SAAK,cAAc,CAAC;AAGpB,SAAK,qBAAqB,QAAQ,CAAC,KAAK,UAAU;AAChD,YAAM,OAAO,IAAI,UAAU,IAAI,YAAY,GAAG,IAAI,CAAC,KAAK;AACxD,YAAM,UAAU,KAAK,YAAY,IAAI;AACrC,YAAM,aAAa,UAAU,MAAM;AACnC,iBAAW;AAAA,QACT,KAAK,cAAc;AAAA,UACjB;AAAA,UACA,OAAO;AAAA,UACP;AAAA,UACA,MAAM;AAAA,UACN;AAAA,UACA;AAAA,UACA,SAAS;AAAA,UACT,OAAO;AAAA,QACT,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAGD,SAAK,cAAc,QAAQ,CAAC,MAAM,UAAU;AAC1C,UAAI,aAA4B;AAChC,YAAM,UAAU,KAAK,KAAK,WAAW,QAAQ;AAC7C,UAAI,SAAS;AACX,qBAAa,IAAI,gBAAgB,IAAI;AACrC,aAAK,YAAY,KAAK,UAAU;AAAA,MAClC;AACA,iBAAW;AAAA,QACT,KAAK,cAAc;AAAA,UACjB,MAAM,KAAK;AAAA,UACX,OAAO,KAAK;AAAA,UACZ;AAAA,UACA,MAAM;AAAA,UACN;AAAA,UACA;AAAA,UACA,SAAS;AAAA,UACT,OAAO;AAAA,QACT,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAGD,eAAW,iBAAiB,aAAa,EAAE,QAAQ,CAAC,QAAQ;AAC1D,UAAI,iBAAiB,SAAS,CAAC,MAAM;AACnC,cAAM,SAAS,EAAE;AACjB,cAAM,OAAO,OAAO,aAAa,WAAW;AAC5C,cAAM,QAAQ,SAAS,OAAO,aAAa,YAAY,KAAK,KAAK,EAAE;AACnE,YAAI,SAAS,aAAa;AACxB,eAAK,qBAAqB,OAAO,OAAO,CAAC;AAAA,QAC3C,OAAO;AACL,eAAK,cAAc,OAAO,OAAO,CAAC;AAAA,QACpC;AACA,aAAK,eAAe;AAAA,MACtB,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEQ,cAAc,MASH;AACjB,UAAM,OAAO,SAAS,cAAc,KAAK;AACzC,SAAK,YAAY;AAEjB,UAAM,UAAU,SAAS,cAAc,QAAQ;AAC/C,YAAQ,OAAO;AACf,YAAQ,YAAY;AACpB,YAAQ,QAAQ;AAChB,YAAQ,iBAAiB,SAAS,MAAM;AACtC,WAAK,YAAY;AAAA,QACf,MAAM,KAAK;AAAA,QACX,YAAY,KAAK;AAAA,QACjB,SAAS,KAAK;AAAA,QACd,SAAS,KAAK;AAAA,MAChB,CAAC;AAAA,IACH,CAAC;AAED,UAAM,QAAQ,SAAS,cAAc,KAAK;AAC1C,UAAM,YAAY;AAClB,QAAI,KAAK,YAAY;AACnB,YAAM,MAAM,SAAS,cAAc,KAAK;AACxC,UAAI,MAAM,KAAK;AACf,UAAI,MAAM,KAAK;AAEf,UAAI,iBAAiB,SAAS,MAAM,KAAK,iBAAiB,KAAK,CAAC;AAChE,YAAM,YAAY,GAAG;AAAA,IACvB,OAAO;AACL,WAAK,iBAAiB,KAAK;AAAA,IAC7B;AAEA,UAAM,gBAAgB,SAAS,cAAc,KAAK;AAClD,kBAAc,YAAY;AAE1B,UAAM,OAAO,SAAS,cAAc,MAAM;AAC1C,SAAK,YAAY;AACjB,SAAK,QAAQ,KAAK;AAClB,SAAK,cAAc,KAAK;AAExB,kBAAc,YAAY,IAAI;AAC9B,YAAQ,OAAO,OAAO,aAAa;AAEnC,UAAM,YAAY,SAAS,cAAc,QAAQ;AACjD,cAAU,OAAO;AACjB,cAAU,YAAY;AACtB,cAAU,aAAa,cAAc,iBAAS;AAC9C,cAAU,aAAa,aAAa,KAAK,IAAI;AAC7C,cAAU,aAAa,cAAc,OAAO,KAAK,KAAK,CAAC;AACvD,cAAU,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAOtB,SAAK,OAAO,SAAS,SAAS;AAE9B,QAAI,KAAK,OAAO;AACd,YAAM,QAAQ,SAAS,cAAc,MAAM;AAC3C,YAAM,YAAY;AAClB,YAAM,cAAc,KAAK;AACzB,WAAK,YAAY,KAAK;AAAA,IACxB;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,iBAAiB,OAA0B;AACjD,UAAM,YAAY;AAClB,UAAM,UAAU,IAAI,kBAAkB;AACtC,UAAM,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA,EAKpB;AAAA,EAEQ,YAAY,MAKX;AArmBX;AAsmBI,UAAM,WAAU,UAAK,eAAL,mBAAiB,eAAe;AAChD,UAAM,QAAO,UAAK,eAAL,mBAAiB,eAAe;AAC7C,QAAI,CAAC,WAAW,CAAC,KAAM;AAEvB,SAAK,YAAY;AAEjB,QAAI,KAAK,WAAW,KAAK,YAAY;AACnC,YAAM,MAAM,SAAS,cAAc,KAAK;AACxC,UAAI,MAAM,KAAK;AACf,UAAI,MAAM,KAAK;AACf,WAAK,YAAY,GAAG;AAAA,IACtB,OAAO;AACL,YAAM,OAAO,SAAS,cAAc,KAAK;AACzC,WAAK,YAAY;AACjB,YAAM,WAAW,KAAK,UAClB,YAAY,KAAK,OAAO,+EACxB;AACJ,WAAK,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAMb,QAAQ;AACZ,YAAM,SAAS,KAAK,cAAc,oBAAoB;AACtD,UAAI,OAAQ,QAAO,cAAc,KAAK;AACtC,WAAK,YAAY,IAAI;AAAA,IACvB;AAEA,YAAQ,UAAU,IAAI,MAAM;AAAA,EAC9B;AAAA,EAEQ,eAAqB;AAtoB/B;AAuoBI,UAAM,WAAU,UAAK,eAAL,mBAAiB,eAAe;AAChD,QAAI,CAAC,QAAS;AACd,YAAQ,UAAU,OAAO,MAAM;AAC/B,UAAM,QAAO,UAAK,eAAL,mBAAiB,eAAe;AAC7C,QAAI,KAAM,MAAK,YAAY;AAAA,EAC7B;AAAA,EAEQ,YAAY,MAAuB;AACzC,WAAO,wCAAwC,KAAK,IAAI;AAAA,EAC1D;AAAA,EAEQ,mBAAyB;AAlpBnC;AAmpBI,SAAK,cAAc,cAAa,gBAAK,SAAL,mBAAW,SAAX,YAAmB,EAAE;AACrD,SAAK,cAAc,UAAS,gBAAK,SAAL,mBAAW,UAAX,YAAoB,EAAE;AAClD,SAAK,cAAc,iBAAgB,gBAAK,SAAL,mBAAW,gBAAX,YAA0B,EAAE;AAAA,EACjE;AAAA,EAEQ,qBAA2B;AAxpBrC;AAypBI,UAAM,YAAW,UAAK,eAAL,mBAAiB,eAAe;AACjD,QAAI,CAAC,SAAU;AAEf,UAAM,iBAAiB,CAAC,uBAAuB,uBAAa,IAAI;AAChE,UAAM,QAAQ,KAAK,cAAc,KAAK,aAAa,OAAO,CAAC;AAC3D,UAAM,SAAS,KAAK,cAAc,KAAK,aAAa,QAAQ,CAAC;AAC7D,QAAI,MAAO,gBAAe,KAAK,mBAAmB,KAAK,EAAE;AACzD,QAAI,OAAQ,gBAAe,KAAK,oBAAoB,MAAM,EAAE;AAE5D,aAAS,aAAa,SAAS,GAAG,eAAe,KAAK,IAAI,CAAC,GAAG;AAAA,EAChE;AAAA,EAEQ,qBAA2B;AAEjC,QAAI,KAAK,MAAM,QAAQ,KAAK,MAAM,IAAK;AAEvC,UAAM,QAAQ,KAAK,cAAc,KAAK,aAAa,OAAO,CAAC;AAC3D,UAAM,SAAS,KAAK,cAAc,KAAK,aAAa,QAAQ,CAAC;AAC7D,UAAM,OAAO,KAAK,cAAc,KAAK,aAAa,MAAM,CAAC;AACzD,UAAM,MAAM,KAAK,cAAc,KAAK,aAAa,KAAK,CAAC;AAEvD,SAAK,eAAe,iBAAiB,IAAI;AACzC,SAAK,eAAe,gBAAgB,GAAG;AACvC,SAAK,eAAe,kBAAkB,KAAK;AAC3C,SAAK,eAAe,mBAAmB,MAAM;AAG7C,QAAI,QAAQ,CAAC,MAAO,MAAK,MAAM,YAAY,kBAAkB,MAAM;AACnE,QAAI,SAAS,CAAC,KAAM,MAAK,MAAM,YAAY,iBAAiB,MAAM;AAClE,QAAI,OAAO,CAAC,OAAQ,MAAK,MAAM,YAAY,mBAAmB,MAAM;AACpE,QAAI,UAAU,CAAC,IAAK,MAAK,MAAM,YAAY,gBAAgB,MAAM;AAAA,EACnE;AAAA,EAEQ,eAAe,MAAc,OAA4B;AAC/D,QAAI,OAAO;AACT,WAAK,MAAM,YAAY,MAAM,KAAK;AAAA,IACpC,OAAO;AACL,WAAK,MAAM,eAAe,IAAI;AAAA,IAChC;AAAA,EACF;AAAA,EAEQ,cAAc,OAAqC;AACzD,UAAM,UAAU,+BAAO;AACvB,QAAI,CAAC,QAAS,QAAO;AACrB,WAAO,gBAAgB,KAAK,OAAO,IAAI,GAAG,OAAO,OAAO;AAAA,EAC1D;AAAA,EAEQ,uBAA6B;AACnC,UAAM,OAAO,KAAK;AAClB,QAAI,CAAC,KAAM;AAEX,SAAK,iBAAiB,aAAa,EAAE,QAAQ,CAAC,SAAS;AACrD,YAAM,QAAQ,KAAK,cAAc,iBAAiB;AAIlD,YAAM,WAAW,KAAK;AAAA,QACpB;AAAA,MACF;AACA,UAAI,CAAC,SAAS,CAAC,SAAU;AAEzB,YAAM,mBAAmB,MAAM,KAAK,kBAAkB,KAAK;AAE3D,YAAM,iBAAiB,SAAS,gBAAgB;AAChD,YAAM,iBAAiB,SAAS,MAAM,KAAK,gBAAgB,MAAM,EAAE,CAAC;AACpE,eAAS,iBAAiB,SAAS,MAAM;AACvC,cAAM,QAAQ;AACd,cAAM,MAAM;AACZ,yBAAiB;AACjB,aAAK,gBAAgB,MAAM,EAAE;AAAA,MAC/B,CAAC;AAED,uBAAiB;AAAA,IACnB,CAAC;AAAA,EACH;AAAA,EAEQ,kBACN,OACM;AACN,UAAM,OAAO,MAAM;AACnB,QAAI,6BAAM,UAAU,SAAS,eAAe;AAC1C,WAAK,UAAU,OAAO,aAAa,MAAM,MAAM,SAAS,CAAC;AAAA,IAC3D;AAAA,EACF;AAAA,EAEQ,cAAc,IAAY,OAAqB;AA9uBzD;AA+uBI,UAAM,MAAK,UAAK,eAAL,mBAAiB,eAAe;AAI3C,QAAI,CAAC,GAAI;AACT,OAAG,QAAQ;AACX,SAAK,kBAAkB,EAAE;AAAA,EAC3B;AAAA,EAEQ,cAAc,IAAoB;AAxvB5C;AAyvBI,UAAM,MAAK,UAAK,eAAL,mBAAiB,eAAe;AAI3C,YAAO,oCAAI,UAAJ,mBAAW,WAAX,YAAqB;AAAA,EAC9B;AAAA,EAEQ,cAAc,IAA2B;AAhwBnD;AAiwBI,UAAM,WAAU,UAAK,eAAL,mBAAiB,eAAe,GAAG,EAAE;AACrD,QAAI,SAAS;AACX,cAAQ,cAAc;AAAA,IACxB;AAEA,UAAM,SAAQ,UAAK,eAAL,mBAAiB,eAAe;AAI9C,UAAM,OAAO,+BAAO;AACpB,QAAI,6BAAM,UAAU,SAAS,eAAe;AAC1C,WAAK,UAAU,IAAI,WAAW;AAAA,IAChC;AAAA,EACF;AAAA,EAEQ,gBAAgB,IAAkB;AAhxB5C;AAixBI,UAAM,WAAU,UAAK,eAAL,mBAAiB,eAAe,GAAG,EAAE;AACrD,QAAI,SAAS;AACX,cAAQ,cAAc;AAAA,IACxB;AAEA,UAAM,SAAQ,UAAK,eAAL,mBAAiB,eAAe;AAI9C,UAAM,OAAO,+BAAO;AACpB,QAAI,6BAAM,UAAU,SAAS,eAAe;AAC1C,WAAK,UAAU,OAAO,WAAW;AAAA,IACnC;AAAA,EACF;AAAA,EAEQ,sBAA4B;AAClC,SAAK,gBAAgB,WAAW;AAChC,SAAK,gBAAgB,cAAc;AACnC,SAAK,gBAAgB,OAAO;AAC5B,SAAK,gBAAgB,SAAS;AAAA,EAChC;AAAA,EAEQ,UACN,SACA,aACM;AA1yBV;AA2yBI,UAAM,WAAU,UAAK,eAAL,mBAAiB,eAAe;AAChD,UAAM,WAAU,UAAK,eAAL,mBAAiB,eAAe;AAChD,UAAM,aAAY,UAAK,eAAL,mBAAiB,eAAe;AAClD,UAAM,aAAY,UAAK,eAAL,mBAAiB,eAAe;AAClD,UAAM,iBAAgB,UAAK,eAAL,mBAAiB,eAAe;AACtD,UAAM,aAAY,UAAK,eAAL,mBAAiB,eAAe;AAClD,UAAM,UAAS,UAAK,eAAL,mBAAiB;AAAA,MAC9B;AAAA;AAEF,UAAM,eAAc,UAAK,eAAL,mBAAiB,eAAe;AACpD,UAAM,sBAAqB,UAAK,eAAL,mBAAiB;AAAA,MAC1C;AAAA;AAEF,UAAM,kBAAiB,UAAK,eAAL,mBAAiB;AAAA,MACtC;AAAA;AAEF,QACE,CAAC,WACD,CAAC,WACD,CAAC,aACD,CAAC,aACD,CAAC,iBACD,CAAC,aACD,CAAC,UACD,CAAC,gBACD;AACA;AAAA,IACF;AAEA,SAAK,oBAAoB;AAEzB,YAAQ,cAAc,QAAQ;AAC9B,cAAU,cAAc,QAAQ;AAEhC,UAAM,iBAAiB,QAAQ,QAAQ,WAAW;AAClD,UAAM,SAAS,QAAQ,QAAQ,GAAG;AAClC,UAAM,cACJ,QAAQ,SAAS,cAAc,kBAAkB;AAEnD,QAAI,aAAa;AACf,oBAAc,eAAc,aAAQ,gBAAR,YAAuB;AACnD,UAAI,UAAU,QAAQ,KAAK;AACzB,eAAO,OAAO,QAAQ;AACtB,eAAO,cAAc,QAAQ;AAC7B,eAAO,QAAQ,QAAQ;AACvB,kBAAU,UAAU,OAAO,WAAW;AAAA,MACxC,OAAO;AACL,eAAO,gBAAgB,MAAM;AAC7B,eAAO,cAAc;AACrB,eAAO,gBAAgB,OAAO;AAC9B,kBAAU,UAAU,IAAI,WAAW;AAAA,MACrC;AACA,gBAAU,UAAU,OAAO,WAAW;AAAA,IACxC,OAAO;AACL,oBAAc,cAAc;AAC5B,aAAO,gBAAgB,MAAM;AAC7B,aAAO,cAAc;AACrB,aAAO,gBAAgB,OAAO;AAC9B,gBAAU,UAAU,IAAI,WAAW;AACnC,gBAAU,UAAU,IAAI,WAAW;AAAA,IACrC;AAEA,UAAM,WAAW,gBAAgB,SAC7B,cACC,QAAQ,SAAS,YAAY,KAAK,uBAAuB,KAAK;AAEnE,SAAK,mBAAmB;AACxB,SAAK,eAAe;AACpB,UAAM,eAAc,UAAK,eAAL,mBAAiB,cAAc;AACnD,SAAK,gBAAgB,QAAQ,2CAAa,QAAQ,SAAS;AAE3D,YAAQ,UAAU,OAAO,WAAW,OAAO;AAC3C,YAAQ,UAAU,IAAI,QAAQ,MAAM,MAAM;AAC1C,SAAK;AAAA,MACH,oCAAe;AAAA,MACf,kDAAsB;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,UAAU,mBAAmB,MAAY;AA33BnD;AA43BI,UAAM,WAAU,UAAK,eAAL,mBAAiB,eAAe;AAChD,UAAM,WAAU,UAAK,eAAL,mBAAiB,eAAe;AAChD,UAAM,aAAY,UAAK,eAAL,mBAAiB,eAAe;AAClD,UAAM,aAAY,UAAK,eAAL,mBAAiB,eAAe;AAClD,UAAM,iBAAgB,UAAK,eAAL,mBAAiB,eAAe;AACtD,UAAM,aAAY,UAAK,eAAL,mBAAiB,eAAe;AAClD,UAAM,UAAS,UAAK,eAAL,mBAAiB;AAAA,MAC9B;AAAA;AAEF,UAAM,eAAc,UAAK,eAAL,mBAAiB,eAAe;AACpD,UAAM,sBAAqB,UAAK,eAAL,mBAAiB;AAAA,MAC1C;AAAA;AAEF,UAAM,kBAAiB,UAAK,eAAL,mBAAiB;AAAA,MACtC;AAAA;AAEF,QACE,CAAC,WACD,CAAC,WACD,CAAC,aACD,CAAC,aACD,CAAC,iBACD,CAAC,aACD,CAAC,UACD,CAAC,gBACD;AACA;AAAA,IACF;AAEA,UAAM,YAAY,QAAQ,UAAU,SAAS,SAAS;AAEtD,SAAK,oBAAoB;AACzB,SAAK,mBAAmB;AACxB,SAAK,eAAe;AACpB,SAAK,gBAAgB;AACrB,YAAQ,cAAc;AACtB,cAAU,cAAc;AACxB,kBAAc,cAAc;AAC5B,WAAO,gBAAgB,MAAM;AAC7B,WAAO,cAAc;AACrB,WAAO,gBAAgB,OAAO;AAC9B,cAAU,UAAU,IAAI,WAAW;AACnC,cAAU,UAAU,IAAI,WAAW;AACnC,QAAI,oBAAoB;AACtB,yBAAmB,cAAc;AAAA,IACnC;AACA,mBAAe,MAAM,mBAAmB;AACxC,QAAI,aAAa;AACf,kBAAY,UAAU,IAAI,WAAW;AAAA,IACvC;AACA,YAAQ,UAAU,OAAO,WAAW,SAAS,MAAM;AAEnD,QAAI,aAAa,kBAAkB;AACjC,WAAK,MAAM;AAAA,IACb;AAAA,EACF;AAAA,EAEQ,oBACN,aACA,WACA,OACM;AACN,UAAM,kBAAkB,MAAY;AAClC,YAAM,UAAU,KAAK,IAAI,GAAG,KAAK,KAAK,KAAK,mBAAmB,GAAI,CAAC;AACnE,UAAI,WAAW;AACb,kBAAU,cAAc,GAAG,OAAO;AAAA,MACpC;AAEA,YAAM,UAAU,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,mBAAmB,KAAK,YAAY,CAAC;AAClF,YAAM,SAAS,SAAS,IAAI;AAC5B,YAAM,MAAM,mBAAmB,GAAG,MAAM;AAAA,IAC1C;AAEA,oBAAgB;AAChB,QAAI,aAAa;AACf,kBAAY,UAAU,OAAO,WAAW;AAAA,IAC1C;AAEA,SAAK,yBAAyB,OAAO,YAAY,MAAM;AACrD,UAAI,KAAK,cAAe;AAExB,WAAK,oBAAoB;AACzB,UAAI,KAAK,oBAAoB,GAAG;AAC9B,aAAK,mBAAmB;AACxB,cAAM,MAAM,mBAAmB;AAC/B,YAAI,WAAW;AACb,oBAAU,cAAc;AAAA,QAC1B;AACA,aAAK,UAAU;AACf;AAAA,MACF;AACA,sBAAgB;AAAA,IAClB,GAAG,GAAG;AAAA,EACR;AAAA,EAEQ,sBAA4B;AAClC,QAAI,KAAK,2BAA2B,MAAM;AACxC,aAAO,cAAc,KAAK,sBAAsB;AAChD,WAAK,yBAAyB;AAAA,IAChC;AAAA,EACF;AAAA,EAEQ,eAAe,SAA2C;AAChE,WAAO;AAAA,MACL,GAAG;AAAA,MACH,OACE,QAAQ,SAAS,YACb,8CACA;AAAA,IACR;AAAA,EACF;AAAA,EAGQ,uBAAuB,OAAuB;AACpD,QAAI,CAAC,OAAO,SAAS,KAAK,KAAK,SAAS,GAAG;AACzC,aAAO;AAAA,IACT;AACA,WAAO,KAAK,MAAM,KAAK;AAAA,EACzB;AAAA,EAEQ,8BAAoC;AAC1C,QAAI,KAAK,2BAA2B,MAAM;AACxC,aAAO,aAAa,KAAK,sBAAsB;AAC/C,WAAK,yBAAyB;AAAA,IAChC;AAAA,EACF;AAAA,EAEQ,yBAAkC;AA3/B5C;AA4/BI,SAAK,oBAAoB;AAEzB,UAAM,iBAAoC;AAAA,MACxC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,QAAI,iBAAyC;AAE7C,mBAAe,QAAQ,CAAC,OAAO;AAC7B,UAAI,CAAC,KAAK,cAAc,EAAE,GAAG;AAC3B,aAAK,cAAc,EAAE;AACrB,YAAI,CAAC,gBAAgB;AACnB,2BAAiB;AAAA,QACnB;AAAA,MACF;AAAA,IACF,CAAC;AAED,QAAI,gBAAgB;AAClB,YAAM,gBAAe,UAAK,eAAL,mBAAiB,eAAe;AAIrD,mDAAc;AACd,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,gBAAsB;AAC5B,SAAK,cAAc,aAAa,EAAE;AAClC,SAAK,cAAc,SAAS,EAAE;AAC9B,SAAK,cAAc,gBAAgB,EAAE;AACrC,SAAK,cAAc,WAAW,EAAE;AAChC,SAAK,oBAAoB;AAEzB,SAAK,gBAAgB,CAAC;AACtB,SAAK,uBAAuB,CAAC;AAC7B,SAAK,eAAe;AACpB,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,MAAc,SAAwB;AAziCxC;AA0iCI,SAAK,4BAA4B;AACjC,SAAK,UAAU,KAAK;AAEpB,QAAI,CAAC,KAAK,uBAAuB,GAAG;AAClC,WAAK;AAAA,QACH,IAAI,YAAY,cAAc,cAAc;AAAA,UAC1C,SAAS;AAAA,UACT,QAAQ,EAAE,SAAS,sCAAsC;AAAA,QAC3D,CAAC;AAAA,MACH;AACA;AAAA,IACF;AAEA,UAAM,OAAoB;AAAA,MACxB,MAAM,KAAK,cAAc,WAAW;AAAA,MACpC,OAAO,KAAK,cAAc,OAAO;AAAA,MACjC,aAAa,KAAK,cAAc,cAAc;AAAA,IAChD;AAEA,SAAK,OAAO;AAEZ,QAAI;AACF,2BAAqB,KAAK,MAAM;AAChC,mBAAa,IAAI;AAEjB,YAAMC,WAAU,KAAK,cAAc,SAAS;AAC5C,YAAM,aAAqC;AAAA,QACzC,SAAAA;AAAA,QACA,aAAa,CAAC;AAAA;AAAA,MAChB;AACA,yBAAmB,UAAU;AAAA,IAC/B,SAAS,OAAO;AACd,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,WAAK;AAAA,QACH,IAAI,YAAY,cAAc,cAAc;AAAA,UAC1C,SAAS;AAAA,UACT,QAAQ,EAAE,QAAQ;AAAA,QACpB,CAAC;AAAA,MACH;AACA;AAAA,IACF;AAEA,UAAM,UAAU,KAAK,cAAc,SAAS;AAG5C,UAAM,WAAW,IAAI,SAAS;AAC9B,aAAS,OAAO,aAAa,KAAK,IAAI;AACtC,aAAS,OAAO,eAAe,KAAK,WAAW;AAC/C,aAAS,OAAO,SAAS,KAAK,KAAK;AACnC,aAAS,OAAO,WAAW,OAAO;AAClC,SAAI,UAAK,OAAO,cAAZ,mBAAuB,QAAQ;AACjC,eAAS,OAAO,aAAa,KAAK,OAAO,UAAU,KAAK,CAAC;AAAA,IAC3D;AACA,QAAI,OAAO,KAAK,aAAa,UAAU;AACrC,eAAS,OAAO,YAAY,OAAO,KAAK,QAAQ,CAAC;AAAA,IACnD;AAGA,eAAW,OAAO,KAAK,sBAAsB;AAC3C,YAAM,OAAO,MAAM,UAAU,GAAG;AAChC,UAAI,MAAM;AACR,iBAAS,OAAO,eAAe,IAAI;AAAA,MACrC;AAAA,IACF;AAGA,eAAW,QAAQ,KAAK,eAAe;AACrC,eAAS,OAAO,eAAe,IAAI;AAAA,IACrC;AAGA,QAAI,KAAK,aAAa,SAAS,GAAG;AAChC,WAAK,aAAa;AAAA,QAAQ,CAAC,UACzB,SAAS,OAAO,gBAAgB,KAAK;AAAA,MACvC;AAAA,IACF;AACA,QAAI,KAAK,cAAc,SAAS,GAAG;AACjC,WAAK,cAAc;AAAA,QAAQ,CAAC,YAC1B,SAAS,OAAO,iBAAiB,OAAO;AAAA,MAC1C;AAAA,IACF;AAEA,QAAI;AACF,YAAM,UAAU,eAAe,KAAK,MAAM;AAC1C,YAAM,SAAS,MAAM,qBAAqB,SAAS,QAAQ;AAE3D,UAAI,CAAC,uBAAuB,MAAM,GAAG;AACnC,cAAM,iBAAiB,wBAAwB,MAAM;AACrD,aAAK,UAAU,KAAK,eAAe,cAAc,CAAC;AAClD,aAAK;AAAA,UACH,IAAI,YAAY,cAAc,cAAc;AAAA,YAC1C,SAAS;AAAA,YACT,QAAQ,EAAE,SAAS,eAAe,SAAS,UAAU,OAAO;AAAA,UAC9D,CAAC;AAAA,QACH;AACA;AAAA,MACF;AAEA,YAAM,iBAAiB,wBAAwB,MAAM;AACrD,WAAK,UAAU,KAAK,eAAe,cAAc,CAAC;AAClD,WAAK;AAAA,QACH,IAAI,YAAY,cAAc,gBAAgB;AAAA,UAC5C,SAAS;AAAA,UACT,QAAQ;AAAA,QACV,CAAC;AAAA,MACH;AACA,WAAK,yBAAyB,OAAO,WAAW,MAAM;AACpD,aAAK,UAAU;AAAA,MACjB,GAAG,KAAK,oBAAoB;AAAA,IAC9B,SAAS,OAAO;AACd,YAAM,eAAe,mBAAmB,KAAK;AAC7C,WAAK,UAAU,KAAK,eAAe,YAAY,CAAC;AAChD,WAAK;AAAA,QACH,IAAI,YAAY,cAAc,cAAc;AAAA,UAC1C,SAAS;AAAA,UACT,QAAQ,EAAE,SAAS,aAAa,QAAQ;AAAA,QAC1C,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,UAAU,UAA6B;AAC7C,aAAS,iBAAiB,eAAe,CAAC,MAAoB;AAC5D,UAAI,EAAE,WAAW,EAAG;AAEpB,WAAK,eAAe;AACpB,YAAM,OAAO,KAAK,sBAAsB;AACxC,YAAM,SAAS,EAAE;AACjB,YAAM,SAAS,EAAE;AACjB,YAAM,cAAc,KAAK;AACzB,YAAM,aAAa,KAAK;AAExB,UAAI;AACF,iBAAS,kBAAkB,EAAE,SAAS;AAAA,MACxC,SAAQC,IAAA;AAAA,MAER;AAEA,YAAM,gBAAgB,CAAC,cAA4B;AACjD,cAAM,KAAK,UAAU,UAAU;AAC/B,cAAM,KAAK,UAAU,UAAU;AAE/B,YAAI,CAAC,KAAK,iBAAiB,KAAK,IAAI,EAAE,IAAI,KAAK,KAAK,IAAI,EAAE,IAAI,IAAI;AAChE,eAAK,eAAe;AACpB,mBAAS,MAAM,SAAS;AAAA,QAC1B;AAEA,YAAI,KAAK,cAAc;AACrB,gBAAM,UAAU,cAAc;AAC9B,gBAAM,SAAS,aAAa;AAC5B,eAAK,eAAe,SAAS,MAAM;AAAA,QACrC;AAAA,MACF;AAEA,YAAM,cAAc,CAAC,YAA0B;AAC7C,YAAI;AACF,mBAAS,sBAAsB,QAAQ,SAAS;AAAA,QAClD,SAAQA,IAAA;AAAA,QAER;AACA,iBAAS,oBAAoB,eAAe,aAAa;AACzD,iBAAS,oBAAoB,aAAa,WAAW;AACrD,iBAAS,oBAAoB,iBAAiB,WAAW;AACzD,iBAAS,MAAM,SAAS;AAExB,YAAI,KAAK,cAAc;AACrB,uBAAa;AAAA,YACX;AAAA,YACA,KAAK,UAAU;AAAA,cACb,MAAM,KAAK,MAAM;AAAA,cACjB,KAAK,KAAK,MAAM;AAAA,YAClB,CAAC;AAAA,UACH;AAAA,QACF;AAAA,MACF;AAEA,eAAS,iBAAiB,eAAe,aAAa;AACtD,eAAS,iBAAiB,aAAa,WAAW;AAClD,eAAS,iBAAiB,iBAAiB,WAAW;AAAA,IACxD,CAAC;AAAA,EACH;AAAA,EAEQ,eAAe,SAAiB,QAAsB;AAC5D,UAAM,OAAO,KAAK,sBAAsB;AACxC,UAAM,QAAQ,KAAK,SAAS;AAC5B,UAAM,SAAS,KAAK,UAAU;AAE9B,UAAM,UAAU;AAChB,UAAM,UAAU,OAAO,aAAa;AACpC,UAAM,SAAS;AACf,UAAM,SAAS,OAAO,cAAc;AAEpC,UAAM,cAAc,KAAK,IAAI,SAAS,KAAK,IAAI,SAAS,OAAO,CAAC;AAChE,UAAM,aAAa,KAAK,IAAI,QAAQ,KAAK,IAAI,QAAQ,MAAM,CAAC;AAE5D,SAAK,MAAM,QAAQ;AACnB,SAAK,MAAM,SAAS;AACpB,SAAK,MAAM,OAAO,GAAG,WAAW;AAChC,SAAK,MAAM,MAAM,GAAG,UAAU;AAAA,EAChC;AAAA,EAEQ,kBAAwB;AAC9B,UAAM,gBAAgB,aAAa,QAAQ,wBAAwB;AACnE,QAAI,eAAe;AACjB,UAAI;AACF,cAAM,EAAE,MAAM,IAAI,IAAI,KAAK,MAAM,aAAa;AAC9C,YAAI,QAAQ,KAAK;AACf,gBAAM,UAAU,WAAW,IAAI;AAC/B,gBAAM,SAAS,WAAW,GAAG;AAC7B,eAAK,eAAe,SAAS,MAAM;AAAA,QACrC;AAAA,MACF,SAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AASF;AA/sCa,qBACJ,qBAAqB;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;;;ACpEF,IAAM,WAAW;AAEV,SAAS,wBAA8B;AAC5C,MAAI,CAAC,eAAe,IAAI,QAAQ,GAAG;AACjC,mBAAe,OAAO,UAAU,oBAAoB;AAAA,EACtD;AACF;AAEA,sBAAsB;","names":["_a","content","e"]}
|