@rxdrag/website-lib 0.0.102 → 0.0.103

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.
@@ -0,0 +1,374 @@
1
+ ---
2
+ interface Props {
3
+ dialogId: string;
4
+ dialogClass?: string | string[];
5
+ backdropClass?: string | string[];
6
+ closeSelector?: string;
7
+ closeAnimMs?: number;
8
+ forceFallback?: boolean;
9
+ preset?: "center" | "top";
10
+ openAnimMs?: number;
11
+ translateY?: number;
12
+ backdropOpacity?: number;
13
+ backdropBlurPx?: number;
14
+ topOffsetPx?: number;
15
+ baseTransform?: string;
16
+ left?: string;
17
+ right?: string;
18
+ top?: string;
19
+ bottom?: string;
20
+ openTransform?: string;
21
+ enterTransform?: string;
22
+ exitTransform?: string;
23
+ }
24
+
25
+ const {
26
+ dialogId,
27
+ dialogClass = "",
28
+ backdropClass = "",
29
+ closeSelector = "[data-dialog-close]",
30
+ closeAnimMs = 240,
31
+ forceFallback = false,
32
+ preset = "center",
33
+ openAnimMs = 320,
34
+ translateY = 64,
35
+ backdropOpacity = 0.35,
36
+ backdropBlurPx = 0,
37
+ topOffsetPx = 0,
38
+ baseTransform,
39
+ left,
40
+ right,
41
+ top,
42
+ bottom,
43
+ openTransform,
44
+ enterTransform,
45
+ exitTransform,
46
+ } = Astro.props;
47
+
48
+ const backdropClassString = Array.isArray(backdropClass)
49
+ ? backdropClass.join(" ")
50
+ : backdropClass;
51
+
52
+ const resolvedBaseTransform =
53
+ baseTransform ||
54
+ (preset === "center" ? "translate(-50%,-50%)" : "translateX(-50%)");
55
+
56
+ const resolvedLeft = left ?? (preset === "center" || preset === "top" ? "50%" : "auto");
57
+ const resolvedRight = right ?? "auto";
58
+ const resolvedTop = top ?? (preset === "center" ? "50%" : "0");
59
+ const resolvedBottom = bottom ?? "auto";
60
+
61
+ const resolvedOpenTransform = openTransform || resolvedBaseTransform;
62
+ const resolvedEnterTransform =
63
+ enterTransform || `${resolvedOpenTransform} translateY(${translateY}px)`;
64
+ const resolvedExitTransform =
65
+ exitTransform || `${resolvedOpenTransform} translateY(${translateY}px)`;
66
+ ---
67
+
68
+ <style is:global>
69
+ dialog[data-generic-dialog] {
70
+ margin: 0;
71
+ outline: none;
72
+ position: fixed;
73
+ left: var(--dialog-left);
74
+ right: var(--dialog-right);
75
+ top: var(--dialog-top);
76
+ bottom: var(--dialog-bottom);
77
+ transform: var(--dialog-open-transform);
78
+ }
79
+
80
+ @keyframes genericDialogIn {
81
+ from {
82
+ opacity: 0;
83
+ transform: var(--dialog-enter-transform);
84
+ }
85
+ to {
86
+ opacity: 1;
87
+ transform: var(--dialog-open-transform);
88
+ }
89
+ }
90
+
91
+ @keyframes genericDialogOut {
92
+ from {
93
+ opacity: 1;
94
+ transform: var(--dialog-open-transform);
95
+ }
96
+ to {
97
+ opacity: 0;
98
+ transform: var(--dialog-exit-transform);
99
+ }
100
+ }
101
+
102
+ @keyframes genericBackdropIn {
103
+ from {
104
+ opacity: 0;
105
+ }
106
+ to {
107
+ opacity: 1;
108
+ }
109
+ }
110
+
111
+ @keyframes genericBackdropOut {
112
+ from {
113
+ opacity: 1;
114
+ }
115
+ to {
116
+ opacity: 0;
117
+ }
118
+ }
119
+
120
+ dialog[data-generic-dialog][open] {
121
+ animation: genericDialogIn var(--dialog-open-ms) ease-out;
122
+ transform-origin: top center;
123
+ }
124
+
125
+ dialog[data-generic-dialog][data-dialog-state="closing"] {
126
+ animation: genericDialogOut var(--dialog-close-ms) ease-in;
127
+ animation-fill-mode: forwards;
128
+ transform-origin: top center;
129
+ }
130
+
131
+ dialog[data-generic-dialog]::backdrop {
132
+ background: rgba(0, 0, 0, var(--dialog-backdrop-opacity));
133
+ backdrop-filter: blur(var(--dialog-backdrop-blur));
134
+ animation: genericBackdropIn var(--dialog-open-ms) ease-out;
135
+ }
136
+
137
+ dialog[data-generic-dialog][data-dialog-state="closing"]::backdrop {
138
+ animation: genericBackdropOut var(--dialog-close-ms) ease-in;
139
+ }
140
+
141
+ dialog[data-generic-dialog] + .backdrop {
142
+ animation: genericBackdropIn var(--dialog-open-ms) ease-out;
143
+ }
144
+
145
+ dialog[data-generic-dialog] + .backdrop[data-dialog-state="closing"] {
146
+ animation: genericBackdropOut var(--dialog-close-ms) ease-in;
147
+ }
148
+ </style>
149
+
150
+ <dialog
151
+ id={dialogId}
152
+ data-generic-dialog
153
+ data-openable-key={dialogId}
154
+ data-dialog-preset={preset}
155
+ tabindex="-1"
156
+ class:list={[dialogClass]}
157
+ style={preset === "center"
158
+ ? `--dialog-left:${resolvedLeft};--dialog-right:${resolvedRight};--dialog-top:${resolvedTop};--dialog-bottom:${resolvedBottom};--dialog-open-transform:${resolvedOpenTransform};--dialog-enter-transform:${resolvedEnterTransform};--dialog-exit-transform:${resolvedExitTransform};--dialog-open-ms:${openAnimMs}ms;--dialog-close-ms:${closeAnimMs}ms;--dialog-ty:${translateY}px;--dialog-backdrop-opacity:${backdropOpacity};--dialog-backdrop-blur:${backdropBlurPx}px;`
159
+ : `--dialog-left:${resolvedLeft};--dialog-right:${resolvedRight};--dialog-top:${resolvedTop};--dialog-bottom:${resolvedBottom};--dialog-open-transform:${resolvedOpenTransform};--dialog-enter-transform:${resolvedEnterTransform};--dialog-exit-transform:${resolvedExitTransform};--dialog-open-ms:${openAnimMs}ms;--dialog-close-ms:${closeAnimMs}ms;--dialog-ty:${translateY}px;--dialog-backdrop-opacity:${backdropOpacity};--dialog-backdrop-blur:${backdropBlurPx}px;margin-top:${topOffsetPx}px;`}
160
+ >
161
+ <slot />
162
+ </dialog>
163
+
164
+ <script
165
+ is:inline
166
+ define:vars={{
167
+ openableKey: dialogId,
168
+ closeSelector,
169
+ closeAnimMs,
170
+ forceFallback,
171
+ backdropClassString,
172
+ openAnimMs,
173
+ backdropOpacity,
174
+ backdropBlurPx,
175
+ }}
176
+ >
177
+ (() => {
178
+ const globalKey = "__generic_dialog_open_bound__";
179
+ const w = window;
180
+ if (!w[globalKey]) w[globalKey] = new Set();
181
+
182
+ const logPrefix = `[Dialog:${openableKey}]`;
183
+
184
+ const backdropClass = backdropClassString || "";
185
+
186
+ const isDialogEl = (el) => {
187
+ if (!(el instanceof HTMLElement)) return false;
188
+ return el.tagName.toLowerCase() === "dialog";
189
+ };
190
+
191
+ const getDialog = () => {
192
+ const el = document.getElementById(openableKey);
193
+ if (!isDialogEl(el)) {
194
+ console.log(logPrefix, "getDialog: dialog not found", openableKey);
195
+ return null;
196
+ }
197
+ return el;
198
+ };
199
+
200
+ const isOpen = (dialog) => {
201
+ return dialog.open === true || dialog.hasAttribute("open");
202
+ };
203
+
204
+ const ensureBackdrop = (dialog) => {
205
+ if (dialog.nextElementSibling?.classList?.contains("backdrop")) return;
206
+ const backdrop = document.createElement("div");
207
+ backdrop.className = backdropClass
208
+ ? `backdrop ${backdropClass}`
209
+ : "backdrop";
210
+ backdrop.removeAttribute("data-dialog-state");
211
+ backdrop.style.background = `rgba(0, 0, 0, ${backdropOpacity})`;
212
+ backdrop.style.backdropFilter = backdropBlurPx
213
+ ? `blur(${backdropBlurPx}px)`
214
+ : "";
215
+ backdrop.style.animation = `genericBackdropIn ${openAnimMs}ms ease-out`;
216
+ console.log(logPrefix, "ensureBackdrop: create", {
217
+ backdropClass,
218
+ backdropOpacity,
219
+ backdropBlurPx,
220
+ openAnimMs,
221
+ });
222
+ backdrop.addEventListener("click", () =>
223
+ requestClose(dialog, "backdrop")
224
+ );
225
+ dialog.insertAdjacentElement("afterend", backdrop);
226
+ };
227
+
228
+ const getBackdrop = (dialog) => {
229
+ const next = dialog.nextElementSibling;
230
+ if (next && next.classList?.contains("backdrop")) return next;
231
+ return null;
232
+ };
233
+
234
+ const removeBackdrop = (dialog) => {
235
+ const next = dialog.nextElementSibling;
236
+ if (next && next.classList?.contains("backdrop")) {
237
+ console.log(logPrefix, "removeBackdrop");
238
+ next.remove();
239
+ }
240
+ };
241
+
242
+ const closeNow = (dialog) => {
243
+ console.log(logPrefix, "closeNow");
244
+ if (typeof dialog.close === "function") {
245
+ dialog.close();
246
+ } else {
247
+ dialog.removeAttribute("open");
248
+ }
249
+ removeBackdrop(dialog);
250
+ document.removeEventListener("keydown", dialog.__genericDialogEscHandler);
251
+ dialog.__genericDialogEscHandler = undefined;
252
+ };
253
+
254
+ const requestClose = (dialog, reason = "unknown") => {
255
+ console.log(logPrefix, "requestClose", {
256
+ reason,
257
+ open: isOpen(dialog),
258
+ state: dialog.getAttribute("data-dialog-state"),
259
+ });
260
+ if (!isOpen(dialog)) return;
261
+ if (dialog.getAttribute("data-dialog-state") === "closing") return;
262
+ dialog.setAttribute("data-dialog-state", "closing");
263
+ const backdrop = getBackdrop(dialog);
264
+ backdrop?.setAttribute?.("data-dialog-state", "closing");
265
+ if (backdrop) {
266
+ backdrop.style.animation = `genericBackdropOut ${closeAnimMs}ms ease-in`;
267
+ }
268
+ window.setTimeout(() => {
269
+ dialog.removeAttribute("data-dialog-state");
270
+ closeNow(dialog);
271
+ }, closeAnimMs);
272
+ };
273
+
274
+ const ensureDialogBound = (dialog) => {
275
+ if (dialog.dataset.genericDialogBound) return;
276
+ dialog.dataset.genericDialogBound = "1";
277
+
278
+ console.log(logPrefix, "ensureDialogBound", {
279
+ closeSelector,
280
+ });
281
+
282
+ dialog.addEventListener("click", (e) => {
283
+ if (e.target === dialog) requestClose(dialog, "dialog_blank_area");
284
+ });
285
+
286
+ if (typeof dialog.addEventListener === "function") {
287
+ dialog.addEventListener("cancel", (e) => {
288
+ e.preventDefault();
289
+ requestClose(dialog, "cancel");
290
+ });
291
+ }
292
+
293
+ dialog
294
+ .querySelector(closeSelector)
295
+ ?.addEventListener("click", () =>
296
+ requestClose(dialog, "close_selector")
297
+ );
298
+ };
299
+
300
+ const open = () => {
301
+ const dialog = getDialog();
302
+ if (!dialog) return;
303
+ ensureDialogBound(dialog);
304
+ if (isOpen(dialog)) return;
305
+
306
+ try {
307
+ console.log(logPrefix, "open: start", {
308
+ forceFallback,
309
+ __force_dialog_fallback__: window.__force_dialog_fallback__,
310
+ hasShowModal: typeof dialog.showModal === "function",
311
+ });
312
+ const { scrollX, scrollY } = window;
313
+ dialog.removeAttribute("data-dialog-state");
314
+
315
+ const effectiveForceFallback =
316
+ forceFallback === true || window.__force_dialog_fallback__ === true;
317
+
318
+ if (!effectiveForceFallback && typeof dialog.showModal === "function") {
319
+ console.log(logPrefix, "open: native showModal");
320
+ dialog.showModal();
321
+ } else {
322
+ console.log(logPrefix, "open: fallback open attr + backdrop + esc");
323
+ ensureBackdrop(dialog);
324
+ dialog.setAttribute("open", "");
325
+ dialog.__genericDialogEscHandler = (e) => {
326
+ if (e.key === "Escape") requestClose(dialog, "esc");
327
+ };
328
+ document.addEventListener(
329
+ "keydown",
330
+ dialog.__genericDialogEscHandler
331
+ );
332
+ }
333
+
334
+ requestAnimationFrame(() => {
335
+ window.scrollTo(scrollX, scrollY);
336
+ try {
337
+ dialog.focus({ preventScroll: true });
338
+ } catch {
339
+ dialog.focus();
340
+ }
341
+ console.log(logPrefix, "open: focus done");
342
+ });
343
+ } catch (err) {
344
+ console.log(logPrefix, "open failed", err);
345
+ }
346
+ };
347
+
348
+ const init = () => {
349
+ console.log(logPrefix, "init", {
350
+ alreadyBound: w[globalKey].has(openableKey),
351
+ });
352
+ if (!w[globalKey].has(openableKey)) {
353
+ w[globalKey].add(openableKey);
354
+ document.addEventListener("click", (e) => {
355
+ const target = e.target;
356
+ if (!(target instanceof Element)) return;
357
+ const opener = target.closest(`[data-modal-open="${openableKey}"]`);
358
+ if (!opener) return;
359
+ const cta = opener.getAttribute("data-call-to-action") || "";
360
+ window.lastCta = cta;
361
+ console.log(logPrefix, "opener click -> open", {
362
+ cta,
363
+ openerTag: opener.tagName,
364
+ });
365
+ open();
366
+ });
367
+ }
368
+ };
369
+
370
+ init();
371
+ document.addEventListener("astro:page-load", init);
372
+ document.addEventListener("astro:after-swap", init);
373
+ })();
374
+ </script>
@@ -0,0 +1,26 @@
1
+ ---
2
+ interface Props {
3
+ dialogId: string;
4
+ callToAction?: string;
5
+ class?: string;
6
+ className?: string;
7
+ }
8
+
9
+ const {
10
+ dialogId,
11
+ callToAction,
12
+ class: _class,
13
+ className,
14
+ ...rest
15
+ } = Astro.props;
16
+ ---
17
+
18
+ <button
19
+ type="button"
20
+ data-modal-open={dialogId}
21
+ data-call-to-action={callToAction}
22
+ class:list={[className, _class]}
23
+ {...rest}
24
+ >
25
+ <slot />
26
+ </button>
@@ -1,32 +1,23 @@
1
1
  ---
2
2
  import {
3
3
  toHref,
4
- type IMotionProps,
5
4
  type LinkType,
6
5
  } from "@rxdrag/website-lib-core";
7
- import Motion from "./Motion.astro";
8
-
9
- // 定义a标签属性的接口
10
- interface AnchorAttributes {
11
- target?: "_blank" | "_self" | "_parent" | "_top";
12
- rel?: string;
13
- download?: boolean | string;
14
- ping?: string;
15
- referrerpolicy?: string;
16
- hreflang?: string;
17
- options?: {
18
- productsSlug?: string;
19
- postsSlug?: string;
20
- };
21
- }
22
6
 
23
- interface Props extends IMotionProps, AnchorAttributes {
7
+ import type { HTMLAttributes } from "astro/types";
8
+
9
+ interface Props extends HTMLAttributes<"a"> {
24
10
  type?: LinkType | string;
25
11
  to?: string | undefined;
26
12
  class?: string;
27
13
  className?: string;
28
14
  //用于active类名的传递
29
15
  activeWhen?: string | true;
16
+
17
+ options?: {
18
+ productsSlug?: string;
19
+ postsSlug?: string;
20
+ };
30
21
  }
31
22
 
32
23
  const {
@@ -42,20 +33,39 @@ const href = toHref(type || "", to || "", options);
42
33
  const hrefObj = href ? { href } : {};
43
34
  ---
44
35
 
45
- <Motion
46
- as={"a"}
36
+ <a
47
37
  data-actived-path={activeWhen}
48
38
  {...hrefObj}
49
39
  class:list={[cls, className]}
50
40
  {...props}
51
41
  >
52
42
  <slot />
53
- </Motion>
43
+ </a>
44
+
45
+ <script is:inline>
46
+ import { initLinks } from "@rxdrag/website-lib-core";
47
+
48
+ (() => {
49
+ const bindAstroLifecycle = (key, fn) => {
50
+ const w = window;
51
+ const bound = (w.__astro_lifecycle_bound__ ||= Object.create(null));
52
+ if (bound[key]) return;
53
+ bound[key] = true;
54
+
55
+ const boot = () => fn();
56
+
57
+ if (document.readyState === "loading") {
58
+ document.addEventListener("DOMContentLoaded", boot, { once: true });
59
+ } else {
60
+ boot();
61
+ }
54
62
 
55
- <script>
56
- import { initLinks, pageLoader } from "@rxdrag/website-lib-core";
63
+ document.addEventListener("astro:page-load", boot);
64
+ document.addEventListener("astro:after-swap", boot);
65
+ };
57
66
 
58
- pageLoader.onLoaded(() => {
59
- initLinks();
60
- });
67
+ bindAstroLifecycle("website-lib:init-links", () => {
68
+ initLinks();
69
+ });
70
+ })();
61
71
  </script>
@@ -1,20 +1,187 @@
1
1
  ---
2
- import {
3
- getPopoverDataAttrs,
4
- type IMotionProps,
5
- } from "@rxdrag/website-lib-core";
6
-
7
- import Motion from "./Motion.astro";
2
+ interface ClassNames {
3
+ root?: string | string[];
4
+ trigger?: string | string[];
5
+ panel?: string | string[];
6
+ }
8
7
 
9
- interface Props extends IMotionProps {
10
- openableKey?: string;
8
+ interface Props {
9
+ id: string;
10
+ classNames?: ClassNames;
11
11
  }
12
12
 
13
- const { openableKey, ...rest } = Astro.props;
13
+ const { id, classNames } = Astro.props;
14
+
15
+ const normalize = (v?: string | string[]) => {
16
+ if (!v) return [];
17
+ return Array.isArray(v) ? v : [v];
18
+ };
14
19
 
15
- const dataAttrs = getPopoverDataAttrs(openableKey);
20
+ const rootClassList = normalize(classNames?.root);
21
+ const triggerClassList = normalize(classNames?.trigger);
22
+ const panelClassList = normalize(classNames?.panel);
16
23
  ---
17
24
 
18
- <Motion {...dataAttrs} {...rest}>
19
- <slot />
20
- </Motion>
25
+ <div class:list={rootClassList} data-popover={id} data-open="0">
26
+ <style>
27
+ [data-popover-panel] {
28
+ height: 0;
29
+ overflow: hidden;
30
+ will-change: height;
31
+ }
32
+
33
+ [data-popover-arrow] {
34
+ transition: transform 200ms ease;
35
+ }
36
+
37
+ [data-popover][data-open="1"] [data-popover-arrow] {
38
+ transform: rotate(180deg);
39
+ }
40
+ </style>
41
+
42
+ <button
43
+ type="button"
44
+ class:list={triggerClassList}
45
+ data-popover-trigger
46
+ aria-expanded="false"
47
+ aria-controls={`${id}-panel`}
48
+ >
49
+ <slot name="trigger" />
50
+ </button>
51
+
52
+ <div
53
+ id={`${id}-panel`}
54
+ data-popover-panel
55
+ class:list={panelClassList}
56
+ style="height:0px;overflow:hidden;"
57
+ >
58
+ <slot />
59
+ </div>
60
+ </div>
61
+
62
+ <script is:inline>
63
+ (() => {
64
+ const globalKey = "__popover_doc_bound__";
65
+ const w = window;
66
+ if (!w[globalKey]) w[globalKey] = { docBound: false };
67
+
68
+ const init = () => {
69
+ const roots = document.querySelectorAll("[data-popover]");
70
+ roots.forEach((root) => {
71
+ if (!(root instanceof HTMLElement)) return;
72
+ if (root.dataset.bound === "1") return;
73
+ root.dataset.bound = "1";
74
+
75
+ const panel = root.querySelector("[data-popover-panel]");
76
+ const trigger = root.querySelector("[data-popover-trigger]");
77
+ if (!(panel instanceof HTMLElement) || !(trigger instanceof HTMLElement)) {
78
+ console.log("Popover: trigger/panel not found");
79
+ return;
80
+ }
81
+
82
+ let open = false;
83
+ let hoverTimer;
84
+
85
+ const setExpanded = (v) => {
86
+ trigger.setAttribute("aria-expanded", v ? "true" : "false");
87
+ root.setAttribute("data-open", v ? "1" : "0");
88
+ };
89
+
90
+ const openPanel = () => {
91
+ if (open) return;
92
+ open = true;
93
+ setExpanded(true);
94
+ panel.style.transition = "height 600ms ease";
95
+ panel.style.height = "0px";
96
+ const h = panel.scrollHeight;
97
+ requestAnimationFrame(() => {
98
+ panel.style.height = h + "px";
99
+ });
100
+ const onEnd = () => {
101
+ if (open) {
102
+ panel.style.height = "auto";
103
+ }
104
+ panel.removeEventListener("transitionend", onEnd);
105
+ };
106
+ panel.addEventListener("transitionend", onEnd);
107
+ };
108
+
109
+ const closePanel = () => {
110
+ if (!open) return;
111
+ open = false;
112
+ setExpanded(false);
113
+ const h = panel.scrollHeight;
114
+ panel.style.transition = "height 250ms ease";
115
+ panel.style.height = h + "px";
116
+ requestAnimationFrame(() => {
117
+ panel.style.height = "0px";
118
+ });
119
+ };
120
+
121
+ root.__popoverClose = closePanel;
122
+
123
+ const toggle = () => {
124
+ if (open) closePanel();
125
+ else openPanel();
126
+ };
127
+
128
+ trigger.addEventListener("click", (e) => {
129
+ e.preventDefault();
130
+ toggle();
131
+ });
132
+
133
+ root.addEventListener("mouseenter", () => {
134
+ clearTimeout(hoverTimer);
135
+ hoverTimer = setTimeout(() => openPanel(), 60);
136
+ });
137
+
138
+ root.addEventListener("mouseleave", () => {
139
+ clearTimeout(hoverTimer);
140
+ hoverTimer = setTimeout(() => closePanel(), 120);
141
+ });
142
+ });
143
+
144
+ if (!w[globalKey].docBound) {
145
+ w[globalKey].docBound = true;
146
+
147
+ const closeAll = () => {
148
+ const opened = document.querySelectorAll('[data-popover][data-open="1"]');
149
+ opened.forEach((r) => {
150
+ if (!(r instanceof HTMLElement)) return;
151
+ if (typeof r.__popoverClose === "function") {
152
+ r.__popoverClose();
153
+ return;
154
+ }
155
+ const p = r.querySelector("[data-popover-panel]");
156
+ const trig = r.querySelector("[data-popover-trigger]");
157
+ if (!(p instanceof HTMLElement) || !(trig instanceof HTMLElement)) return;
158
+ trig.setAttribute("aria-expanded", "false");
159
+ r.setAttribute("data-open", "0");
160
+ const h = p.scrollHeight;
161
+ p.style.transition = "height 250ms ease";
162
+ p.style.height = h + "px";
163
+ requestAnimationFrame(() => {
164
+ p.style.height = "0px";
165
+ });
166
+ });
167
+ };
168
+
169
+ document.addEventListener("click", (e) => {
170
+ const t = e.target;
171
+ if (!(t instanceof Element)) return;
172
+ if (t.closest('[data-popover][data-open="1"]')) return;
173
+ closeAll();
174
+ });
175
+
176
+ document.addEventListener("keydown", (e) => {
177
+ if (e.key !== "Escape") return;
178
+ closeAll();
179
+ });
180
+ }
181
+ };
182
+
183
+ init();
184
+ document.addEventListener("astro:page-load", init);
185
+ document.addEventListener("astro:after-swap", init);
186
+ })();
187
+ </script>