hexo-theme-gnix 7.0.0 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +6 -2
  2. package/include/hexo/encrypt.js +42 -0
  3. package/include/hexo/feed.js +330 -0
  4. package/include/util/common.js +7 -16
  5. package/languages/en.yml +5 -2
  6. package/languages/zh-CN.yml +5 -2
  7. package/layout/archive.jsx +8 -204
  8. package/layout/comment/twikoo.jsx +2 -11
  9. package/layout/common/article.jsx +45 -32
  10. package/layout/common/article_cover.jsx +11 -1
  11. package/layout/common/article_media.jsx +2 -4
  12. package/layout/common/footer.jsx +10 -14
  13. package/layout/common/head.jsx +7 -15
  14. package/layout/common/navbar.jsx +3 -18
  15. package/layout/common/scripts.jsx +6 -5
  16. package/layout/common/theme_selector.jsx +5 -6
  17. package/layout/common/toc.jsx +8 -14
  18. package/layout/index.jsx +2 -4
  19. package/layout/misc/open_graph.jsx +4 -4
  20. package/layout/misc/paginator.jsx +10 -4
  21. package/layout/misc/structured_data.jsx +3 -4
  22. package/layout/plugin/busuanzi.jsx +1 -1
  23. package/layout/plugin/cookie_consent.jsx +40 -31
  24. package/layout/plugin/swup.jsx +2 -22
  25. package/layout/search/insight.jsx +16 -3
  26. package/package.json +12 -8
  27. package/scripts/hot-reload.js +92 -0
  28. package/scripts/index.js +2 -0
  29. package/source/css/archive.css +251 -0
  30. package/source/css/default.css +300 -309
  31. package/source/css/encrypt.css +55 -0
  32. package/source/css/responsive/desktop.css +0 -119
  33. package/source/css/responsive/mobile.css +2 -22
  34. package/source/css/responsive/touch.css +9 -103
  35. package/source/css/twikoo.css +265 -249
  36. package/source/img/og_image.webp +0 -0
  37. package/source/js/archive-breadcrumb.js +1 -5
  38. package/source/js/busuanzi.js +1 -12
  39. package/source/js/components/chat.js +239 -0
  40. package/source/js/components/image-carousel.js +410 -0
  41. package/source/js/components/text-image-section.js +180 -0
  42. package/source/js/components/theme-stacked.js +165 -246
  43. package/source/js/components/tree.js +437 -0
  44. package/source/js/decrypt.js +112 -0
  45. package/source/js/insight.js +75 -65
  46. package/source/js/main.js +48 -31
  47. package/source/js/mdit/mermaid.js +12 -4
  48. package/source/js/swup.bundle.js +1 -0
  49. package/source/js/theme-selector.js +94 -113
  50. package/source/img/og_image.png +0 -0
  51. package/source/js/host/swup/Swup.umd.min.js +0 -1
  52. package/source/js/host/swup/head-plugin.umd.min.js +0 -1
  53. package/source/js/host/swup/scripts-plugin.umd.min.js +0 -2
Binary file
@@ -20,7 +20,7 @@
20
20
 
21
21
  function getMenuForTrigger(trigger) {
22
22
  const menu = trigger?.nextElementSibling;
23
- if (!menu || !menu.classList.contains("archive-breadcrumb__menu")) return null;
23
+ if (!menu?.classList.contains("archive-breadcrumb__menu")) return null;
24
24
  return menu;
25
25
  }
26
26
 
@@ -129,8 +129,4 @@
129
129
  }
130
130
 
131
131
  initAll();
132
-
133
- if (typeof swup !== "undefined") {
134
- swup.hooks.on("page:view", initAll);
135
- }
136
132
  })();
@@ -1,13 +1,11 @@
1
1
  !(() => {
2
2
  const TYPES = ["site_pv", "site_uv", "page_pv", "page_uv"];
3
- const script = document.currentScript;
4
- const api = script.getAttribute("data-api") || "https://bsz.dusays.com:9001/api";
5
3
  const STORAGE_KEY = "bsz-id";
6
4
  const BASE = { site_pv: 12801, site_uv: 2450 };
7
5
 
8
6
  const update = () => {
9
7
  const xhr = new XMLHttpRequest();
10
- xhr.open("POST", api, true);
8
+ xhr.open("POST", "https://bsz.dusays.com:9001/api", true);
11
9
 
12
10
  const token = localStorage.getItem(STORAGE_KEY);
13
11
  token && xhr.setRequestHeader("Authorization", `Bearer ${token}`);
@@ -33,13 +31,4 @@
33
31
  };
34
32
 
35
33
  update();
36
-
37
- if (script.hasAttribute("pjax")) {
38
- const pushState = history.pushState;
39
- history.pushState = function (...args) {
40
- pushState.apply(this, ...args);
41
- update();
42
- };
43
- addEventListener("popstate", update);
44
- }
45
34
  })();
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Chat Component Custom Element
3
+ * Displays group chat conversations with avatars, names, timestamps, and messages
4
+ *
5
+ * Usage:
6
+ * <x-chat>
7
+ * <chat-message
8
+ * name="User Name"
9
+ * avatar="/path/to/avatar.png"
10
+ * timestamp="2024-01-15 10:30"
11
+ * is-me
12
+ * >
13
+ * Message content here...
14
+ * </chat-message>
15
+ * <chat-message name="Other User" avatar="/path/to/avatar.png" timestamp="2024-01-15 10:32">
16
+ * Another message...
17
+ * </chat-message>
18
+ * </x-chat>
19
+ *
20
+ * Or with data attribute:
21
+ * <x-chat messages='[{"name": "User", "content": "Hello", "timestamp": "10:30"}]'></x-chat>
22
+ */
23
+
24
+ const CHAT_STYLES = `
25
+ :host {
26
+ display: block;
27
+ font-family: var(--font-sans, sans-serif);
28
+ }
29
+
30
+ .chat-container {
31
+ display: flex;
32
+ flex-direction: column;
33
+ gap: 16px;
34
+ padding: 16px;
35
+ background: var(--base, #1e1e2e);
36
+ border-radius: 12px;
37
+ max-height: 500px;
38
+ overflow-y: auto;
39
+ scrollbar-width: none;
40
+ -ms-overflow-style: none;
41
+ }
42
+
43
+ .chat-container::-webkit-scrollbar {
44
+ display: none;
45
+ }
46
+
47
+ .chat-message {
48
+ display: flex;
49
+ gap: 12px;
50
+ align-items: flex-start;
51
+ }
52
+
53
+ .chat-message.is-me {
54
+ flex-direction: row-reverse;
55
+ }
56
+
57
+ .avatar {
58
+ width: 40px;
59
+ height: 40px;
60
+ border-radius: 50%;
61
+ object-fit: cover;
62
+ flex-shrink: 0;
63
+ }
64
+
65
+ .avatar-placeholder {
66
+ width: 40px;
67
+ height: 40px;
68
+ border-radius: 50%;
69
+ display: flex;
70
+ align-items: center;
71
+ justify-content: center;
72
+ font-size: 18px;
73
+ font-weight: 600;
74
+ color: var(--text, #cdd6f4);
75
+ flex-shrink: 0;
76
+ background: var(--surface1, #45475a);
77
+ }
78
+
79
+ .message-content {
80
+ display: flex;
81
+ flex-direction: column;
82
+ gap: 4px;
83
+ max-width: 70%;
84
+ }
85
+
86
+ .chat-message.is-me .message-content {
87
+ align-items: flex-end;
88
+ }
89
+
90
+ .message-header {
91
+ display: flex;
92
+ align-items: center;
93
+ gap: 8px;
94
+ font-size: 12px;
95
+ }
96
+
97
+ .chat-message.is-me .message-header {
98
+ flex-direction: row-reverse;
99
+ }
100
+
101
+ .sender-name {
102
+ font-weight: 600;
103
+ color: var(--subtext1, #bac2de);
104
+ }
105
+
106
+ .timestamp {
107
+ color: var(--subtext0, #a6adc8);
108
+ font-size: 11px;
109
+ }
110
+
111
+ .message-bubble {
112
+ padding: 10px 14px;
113
+ border-radius: 16px;
114
+ background: var(--surface1, #45475a);
115
+ color: var(--text, #cdd6f4);
116
+ line-height: 1.5;
117
+ font-size: 14px;
118
+ word-wrap: break-word;
119
+ }
120
+
121
+ .chat-message.is-me .message-bubble {
122
+ background: var(--blue, #89b4fa);
123
+ color: var(--base, #1e1e2e);
124
+ }
125
+
126
+ .message-bubble a {
127
+ color: var(--blue, #89b4fa);
128
+ text-decoration: underline;
129
+ }
130
+
131
+ .message-bubble code {
132
+ font-family: var(--font-mono, 'Maple Mono', 'Fira Code', monospace);
133
+ font-size: 13px;
134
+ background: var(--mantle, #181825);
135
+ padding: 2px 6px;
136
+ border-radius: 4px;
137
+ }
138
+
139
+ .empty-state {
140
+ text-align: center;
141
+ padding: 40px 20px;
142
+ color: var(--subtext0, #a6adc8);
143
+ }
144
+ `;
145
+
146
+ class Chat extends HTMLElement {
147
+ constructor() {
148
+ super();
149
+ this.attachShadow({ mode: "open" });
150
+ }
151
+
152
+ connectedCallback() {
153
+ this.render();
154
+ }
155
+
156
+ getMessagesFromSlots() {
157
+ const messages = this.querySelectorAll("chat-message");
158
+ return Array.from(messages).map((msg) => ({
159
+ name: msg.getAttribute("name") || "Anonymous",
160
+ avatar: msg.getAttribute("avatar") || "",
161
+ timestamp: msg.getAttribute("timestamp") || "",
162
+ isMe: msg.hasAttribute("is-me"),
163
+ content: msg.innerHTML.trim(),
164
+ }));
165
+ }
166
+
167
+ getMessagesFromAttribute() {
168
+ const data = this.getAttribute("messages");
169
+ if (!data) return [];
170
+ try {
171
+ return JSON.parse(data);
172
+ } catch (e) {
173
+ console.warn("Invalid messages JSON:", e);
174
+ return [];
175
+ }
176
+ }
177
+
178
+ render() {
179
+ const messages = this.getMessagesFromSlots();
180
+
181
+ const messagesHTML = messages.map((msg) => this.renderMessage(msg)).join("");
182
+
183
+ this.shadowRoot.innerHTML = `
184
+ <style>${CHAT_STYLES}</style>
185
+ <div class="chat-container">
186
+ ${messagesHTML}
187
+ </div>
188
+ `;
189
+ }
190
+
191
+ renderMessage(msg) {
192
+ const initial = msg.name.charAt(0).toUpperCase();
193
+ const avatarHTML = msg.avatar ? `<img src="${msg.avatar}" alt="${msg.name}" class="avatar" loading="lazy"/>` : `<div class="avatar-placeholder">${initial}</div>`;
194
+
195
+ return `
196
+ <div class="chat-message ${msg.isMe ? "is-me" : ""}">
197
+ ${avatarHTML}
198
+ <div class="message-content">
199
+ <div class="message-header">
200
+ <span class="sender-name">${msg.name}</span>
201
+ <span class="timestamp">${msg.timestamp}</span>
202
+ </div>
203
+ <div class="message-bubble">${msg.content}</div>
204
+ </div>
205
+ </div>
206
+ `;
207
+ }
208
+
209
+ static get observedAttributes() {
210
+ return ["messages"];
211
+ }
212
+
213
+ attributeChangedCallback(name, oldValue, newValue) {
214
+ if (name === "messages" && oldValue !== newValue) {
215
+ this.render();
216
+ }
217
+ }
218
+
219
+ // Public API
220
+ addMessage(msg) {
221
+ const messages = this.getMessagesFromAttribute();
222
+ messages.push(msg);
223
+ this.setAttribute("messages", JSON.stringify(messages));
224
+ }
225
+
226
+ clear() {
227
+ this.innerHTML = "";
228
+ this.render();
229
+ }
230
+ }
231
+
232
+ // Placeholder for slot content
233
+ class ChatMessage extends HTMLElement {}
234
+
235
+ // Register custom elements
236
+ customElements.define("x-chat", Chat);
237
+ customElements.define("chat-message", ChatMessage);
238
+
239
+ export { Chat, ChatMessage };
@@ -0,0 +1,410 @@
1
+ /**
2
+ * Image Carousel Custom Element
3
+ * A responsive image carousel with fade transitions and autoplay
4
+ *
5
+ * Usage:
6
+ * <image-carousel autoplay interval="4000">
7
+ * ![alt text](image-url)
8
+ * ![alt text](image-url)
9
+ * </image-carousel>
10
+ *
11
+ * Attributes:
12
+ * - autoplay: Enable automatic slide advancement
13
+ * - interval: Autoplay interval in ms (default: 3000)
14
+ * - ratio: Aspect ratio as CSS value (default: 3/2)
15
+ */
16
+
17
+ // Shared stylesheet — parsed once, reused across all carousel instances
18
+ let _sheet;
19
+
20
+ const STYLES = `
21
+ :host {
22
+ display: block;
23
+ margin: 1.5em 0;
24
+ }
25
+
26
+ .carousel {
27
+ outline: none;
28
+ }
29
+
30
+ .carousel:focus-visible {
31
+ outline: 2px solid var(--blue, #89b4fa);
32
+ outline-offset: 2px;
33
+ }
34
+
35
+ .stage {
36
+ position: relative;
37
+ border-radius: var(--radius, 12px);
38
+ overflow: hidden;
39
+ background: var(--crust, #11111b);
40
+ contain: content;
41
+ }
42
+
43
+ .slides {
44
+ position: relative;
45
+ width: 100%;
46
+ aspect-ratio: var(--carousel-ratio, 3/2);
47
+ }
48
+
49
+ .slide {
50
+ position: absolute;
51
+ inset: 0;
52
+ opacity: 0;
53
+ transition: opacity 0.7s cubic-bezier(0.25, 1, 0.5, 1);
54
+ pointer-events: none;
55
+ }
56
+
57
+ .slide.active {
58
+ opacity: 1;
59
+ pointer-events: auto;
60
+ }
61
+
62
+ .slide figure {
63
+ margin: 0;
64
+ width: 100%;
65
+ height: 100%;
66
+ }
67
+
68
+ .slide img {
69
+ width: 100%;
70
+ height: 100%;
71
+ object-fit: cover;
72
+ display: block;
73
+ }
74
+
75
+ .slide figcaption {
76
+ position: absolute;
77
+ bottom: 0;
78
+ left: 0;
79
+ right: 0;
80
+ padding: 2rem 1.25rem 0.875rem;
81
+ background: linear-gradient(transparent, rgba(0, 0, 0, 0.55));
82
+ color: #fff;
83
+ font-size: 0.875rem;
84
+ font-style: italic;
85
+ font-family: var(--font-serif, serif);
86
+ text-align: center;
87
+ pointer-events: none;
88
+ }
89
+
90
+ .nav {
91
+ position: absolute;
92
+ top: 50%;
93
+ transform: translateY(-50%);
94
+ background: rgba(0, 0, 0, 0.28);
95
+ backdrop-filter: blur(4px);
96
+ -webkit-backdrop-filter: blur(4px);
97
+ color: white;
98
+ border: none;
99
+ width: 2.5rem;
100
+ height: 2.5rem;
101
+ border-radius: 50%;
102
+ cursor: pointer;
103
+ display: flex;
104
+ align-items: center;
105
+ justify-content: center;
106
+ opacity: 0;
107
+ transition: background 0.2s ease-out, opacity 0.2s ease-out;
108
+ z-index: 2;
109
+ padding: 0;
110
+ }
111
+
112
+ .nav svg {
113
+ width: 1.5rem;
114
+ height: 1.5rem;
115
+ }
116
+
117
+ .carousel:hover .nav,
118
+ .carousel:focus-within .nav {
119
+ opacity: 1;
120
+ }
121
+
122
+ .nav:hover {
123
+ background: rgba(0, 0, 0, 0.55);
124
+ }
125
+
126
+ .nav:focus-visible {
127
+ opacity: 1;
128
+ outline: 2px solid #fff;
129
+ outline-offset: 2px;
130
+ }
131
+
132
+ .nav:active {
133
+ transform: translateY(-50%) scale(0.92);
134
+ }
135
+
136
+ .prev { left: 0.75rem; }
137
+ .next { right: 0.75rem; }
138
+
139
+ .dots {
140
+ display: flex;
141
+ justify-content: center;
142
+ gap: 6px;
143
+ padding: 0.5rem 0 0;
144
+ }
145
+
146
+ .dot {
147
+ width: 8px;
148
+ height: 8px;
149
+ border-radius: 50%;
150
+ border: none;
151
+ background: var(--overlay0, rgba(127, 127, 127, 0.45));
152
+ cursor: pointer;
153
+ padding: 0;
154
+ transition: background 0.2s ease-out, transform 0.2s ease-out;
155
+ }
156
+
157
+ .dot:hover {
158
+ background: var(--overlay1);
159
+ }
160
+
161
+ .dot:focus-visible {
162
+ outline: 2px solid var(--blue, #89b4fa);
163
+ outline-offset: 2px;
164
+ }
165
+
166
+ .dot.active {
167
+ background: var(--text);
168
+ transform: scale(1.25);
169
+ }
170
+
171
+ @media (prefers-reduced-motion: reduce) {
172
+ .slide { transition: none; }
173
+ .nav, .dot { transition: none; }
174
+ }
175
+ `;
176
+
177
+ class ImageCarousel extends HTMLElement {
178
+ constructor() {
179
+ super();
180
+ this.attachShadow({ mode: "open" });
181
+ this._currentIndex = 0;
182
+ this._timer = null;
183
+ this._images = [];
184
+ this._slides = [];
185
+ this._dots = [];
186
+ this._touchStartX = 0;
187
+ this._observer = null;
188
+ this._isVisible = true;
189
+ this._handleVisibility = () => {
190
+ if (document.hidden) this._stopAutoplay();
191
+ else if (this._isVisible && this.hasAttribute("autoplay")) this._startAutoplay();
192
+ };
193
+ }
194
+
195
+ connectedCallback() {
196
+ this._images = this._collectImages();
197
+ this.render();
198
+ if (this._images.length > 1) {
199
+ this._setupListeners();
200
+ this._observeVisibility();
201
+ }
202
+ if (this.hasAttribute("autoplay") && this._images.length > 1) {
203
+ this._startAutoplay();
204
+ }
205
+ }
206
+
207
+ disconnectedCallback() {
208
+ this._stopAutoplay();
209
+ this._observer?.disconnect();
210
+ document.removeEventListener("visibilitychange", this._handleVisibility);
211
+ }
212
+
213
+ _collectImages() {
214
+ return Array.from(this.querySelectorAll("img")).map((img) => ({
215
+ src: img.src || img.getAttribute("src"),
216
+ alt: img.alt || "",
217
+ srcset: img.getAttribute("srcset") || "",
218
+ }));
219
+ }
220
+
221
+ _getInterval() {
222
+ return parseInt(this.getAttribute("interval") || "3000", 10);
223
+ }
224
+
225
+ _startAutoplay() {
226
+ this._stopAutoplay();
227
+ this._timer = setInterval(() => {
228
+ this._goTo((this._currentIndex + 1) % this._images.length);
229
+ }, this._getInterval());
230
+ }
231
+
232
+ _stopAutoplay() {
233
+ if (this._timer) {
234
+ clearInterval(this._timer);
235
+ this._timer = null;
236
+ }
237
+ }
238
+
239
+ _goTo(index) {
240
+ const prev = this._currentIndex;
241
+ this._currentIndex = index;
242
+ this._toggleSlide(prev, false);
243
+ this._toggleSlide(index, true);
244
+ }
245
+
246
+ _toggleSlide(i, active) {
247
+ this._slides[i]?.classList.toggle("active", active);
248
+ const dot = this._dots[i];
249
+ if (dot) {
250
+ dot.classList.toggle("active", active);
251
+ dot.setAttribute("aria-selected", String(active));
252
+ }
253
+ }
254
+
255
+ render() {
256
+ const images = this._images;
257
+ const ratio = this.getAttribute("ratio") || "3/2";
258
+
259
+ if (images.length === 0) {
260
+ this.shadowRoot.innerHTML = "";
261
+ return;
262
+ }
263
+
264
+ if (!_sheet) {
265
+ _sheet = new CSSStyleSheet();
266
+ _sheet.replaceSync(STYLES);
267
+ }
268
+ this.shadowRoot.adoptedStyleSheets = [_sheet];
269
+ this.style.setProperty("--carousel-ratio", ratio);
270
+
271
+ const slidesHTML = images
272
+ .map(
273
+ (img, i) => `
274
+ <div class="slide${i === 0 ? " active" : ""}" role="tabpanel" aria-label="${img.alt || `Slide ${i + 1}`}">
275
+ <figure>
276
+ <img src="${img.src}"${img.srcset ? ` srcset="${img.srcset}"` : ""} alt="${img.alt}" loading="${i === 0 ? "eager" : "lazy"}">
277
+ ${img.alt ? `<figcaption>${img.alt}</figcaption>` : ""}
278
+ </figure>
279
+ </div>
280
+ `,
281
+ )
282
+ .join("");
283
+
284
+ const navHTML =
285
+ images.length > 1
286
+ ? `<button class="nav prev" aria-label="Previous slide"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 18 9 12 15 6"/></svg></button>
287
+ <button class="nav next" aria-label="Next slide"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="9 18 15 12 9 6"/></svg></button>`
288
+ : "";
289
+
290
+ const dotsHTML =
291
+ images.length > 1
292
+ ? `<div class="dots" role="tablist" aria-label="Slide navigation">
293
+ ${images.map((_img, i) => `<button class="dot${i === 0 ? " active" : ""}" data-index="${i}" role="tab" aria-label="Slide ${i + 1}" aria-selected="${i === 0}"></button>`).join("")}
294
+ </div>`
295
+ : "";
296
+
297
+ this.shadowRoot.innerHTML = `
298
+ <div class="carousel" role="region" aria-label="Image carousel" tabindex="0">
299
+ <div class="stage">
300
+ <div class="slides">${slidesHTML}</div>
301
+ ${navHTML}
302
+ </div>
303
+ ${dotsHTML}
304
+ </div>
305
+ <slot style="display:none"></slot>
306
+ `;
307
+
308
+ this._slides = Array.from(this.shadowRoot.querySelectorAll(".slide"));
309
+ this._dots = Array.from(this.shadowRoot.querySelectorAll(".dot"));
310
+ }
311
+
312
+ _setupListeners() {
313
+ const root = this.shadowRoot;
314
+ const carousel = root.querySelector(".carousel");
315
+ const n = this._images.length;
316
+
317
+ root.querySelector(".prev")?.addEventListener("click", () => {
318
+ this._resetAutoplay();
319
+ this._goTo((this._currentIndex - 1 + n) % n);
320
+ });
321
+
322
+ root.querySelector(".next")?.addEventListener("click", () => {
323
+ this._resetAutoplay();
324
+ this._goTo((this._currentIndex + 1) % n);
325
+ });
326
+
327
+ root.querySelector(".dots")?.addEventListener("click", (e) => {
328
+ const dot = e.target.closest(".dot");
329
+ if (!dot) return;
330
+ this._resetAutoplay();
331
+ this._goTo(parseInt(dot.dataset.index, 10));
332
+ });
333
+
334
+ // Pause autoplay on hover
335
+ carousel.addEventListener("mouseenter", () => this._stopAutoplay());
336
+ carousel.addEventListener("mouseleave", () => {
337
+ if (this.hasAttribute("autoplay")) this._startAutoplay();
338
+ });
339
+
340
+ // Keyboard navigation
341
+ carousel.addEventListener("keydown", (e) => {
342
+ if (e.key === "ArrowLeft") {
343
+ this._resetAutoplay();
344
+ this._goTo((this._currentIndex - 1 + n) % n);
345
+ } else if (e.key === "ArrowRight") {
346
+ this._resetAutoplay();
347
+ this._goTo((this._currentIndex + 1) % n);
348
+ }
349
+ });
350
+
351
+ // Touch/swipe support
352
+ carousel.addEventListener(
353
+ "touchstart",
354
+ (e) => {
355
+ this._touchStartX = e.touches[0].clientX;
356
+ },
357
+ { passive: true },
358
+ );
359
+
360
+ carousel.addEventListener(
361
+ "touchend",
362
+ (e) => {
363
+ const dx = e.changedTouches[0].clientX - this._touchStartX;
364
+ if (Math.abs(dx) > 40) {
365
+ this._resetAutoplay();
366
+ this._goTo(dx < 0 ? (this._currentIndex + 1) % n : (this._currentIndex - 1 + n) % n);
367
+ }
368
+ },
369
+ { passive: true },
370
+ );
371
+ }
372
+
373
+ _observeVisibility() {
374
+ this._observer = new IntersectionObserver(([entry]) => {
375
+ this._isVisible = entry.isIntersecting;
376
+ if (!entry.isIntersecting) {
377
+ this._stopAutoplay();
378
+ } else if (this.hasAttribute("autoplay")) {
379
+ this._startAutoplay();
380
+ }
381
+ });
382
+ this._observer.observe(this);
383
+ document.addEventListener("visibilitychange", this._handleVisibility);
384
+ }
385
+
386
+ _resetAutoplay() {
387
+ if (this.hasAttribute("autoplay")) {
388
+ this._stopAutoplay();
389
+ this._startAutoplay();
390
+ }
391
+ }
392
+
393
+ static get observedAttributes() {
394
+ return ["autoplay", "interval"];
395
+ }
396
+
397
+ attributeChangedCallback(name, oldValue, newValue) {
398
+ if (oldValue === newValue || !this._images.length) return;
399
+ if (name === "autoplay") {
400
+ newValue !== null ? this._startAutoplay() : this._stopAutoplay();
401
+ }
402
+ if (name === "interval" && this._timer) {
403
+ this._startAutoplay();
404
+ }
405
+ }
406
+ }
407
+
408
+ customElements.define("image-carousel", ImageCarousel);
409
+
410
+ export { ImageCarousel };