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.
- package/README.md +6 -2
- package/include/hexo/encrypt.js +42 -0
- package/include/hexo/feed.js +330 -0
- package/include/util/common.js +7 -16
- package/languages/en.yml +5 -2
- package/languages/zh-CN.yml +5 -2
- package/layout/archive.jsx +8 -204
- package/layout/comment/twikoo.jsx +2 -11
- package/layout/common/article.jsx +45 -32
- package/layout/common/article_cover.jsx +11 -1
- package/layout/common/article_media.jsx +2 -4
- package/layout/common/footer.jsx +10 -14
- package/layout/common/head.jsx +7 -15
- package/layout/common/navbar.jsx +3 -18
- package/layout/common/scripts.jsx +6 -5
- package/layout/common/theme_selector.jsx +5 -6
- package/layout/common/toc.jsx +8 -14
- package/layout/index.jsx +2 -4
- package/layout/misc/open_graph.jsx +4 -4
- package/layout/misc/paginator.jsx +10 -4
- package/layout/misc/structured_data.jsx +3 -4
- package/layout/plugin/busuanzi.jsx +1 -1
- package/layout/plugin/cookie_consent.jsx +40 -31
- package/layout/plugin/swup.jsx +2 -22
- package/layout/search/insight.jsx +16 -3
- package/package.json +12 -8
- package/scripts/hot-reload.js +92 -0
- package/scripts/index.js +2 -0
- package/source/css/archive.css +251 -0
- package/source/css/default.css +300 -309
- package/source/css/encrypt.css +55 -0
- package/source/css/responsive/desktop.css +0 -119
- package/source/css/responsive/mobile.css +2 -22
- package/source/css/responsive/touch.css +9 -103
- package/source/css/twikoo.css +265 -249
- package/source/img/og_image.webp +0 -0
- package/source/js/archive-breadcrumb.js +1 -5
- package/source/js/busuanzi.js +1 -12
- package/source/js/components/chat.js +239 -0
- package/source/js/components/image-carousel.js +410 -0
- package/source/js/components/text-image-section.js +180 -0
- package/source/js/components/theme-stacked.js +165 -246
- package/source/js/components/tree.js +437 -0
- package/source/js/decrypt.js +112 -0
- package/source/js/insight.js +75 -65
- package/source/js/main.js +48 -31
- package/source/js/mdit/mermaid.js +12 -4
- package/source/js/swup.bundle.js +1 -0
- package/source/js/theme-selector.js +94 -113
- package/source/img/og_image.png +0 -0
- package/source/js/host/swup/Swup.umd.min.js +0 -1
- package/source/js/host/swup/head-plugin.umd.min.js +0 -1
- package/source/js/host/swup/scripts-plugin.umd.min.js +0 -2
|
@@ -3,91 +3,95 @@
|
|
|
3
3
|
* Displays themes as a stack of interactive cards with circular navigation
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
const PREVIEW_COLORS = [
|
|
7
|
+
"rosewater",
|
|
8
|
+
"flamingo",
|
|
9
|
+
"pink",
|
|
10
|
+
"mauve",
|
|
11
|
+
"red",
|
|
12
|
+
"maroon",
|
|
13
|
+
"peach",
|
|
14
|
+
"yellow",
|
|
15
|
+
"green",
|
|
16
|
+
"teal",
|
|
17
|
+
"sky",
|
|
18
|
+
"sapphire",
|
|
19
|
+
"blue",
|
|
20
|
+
"lavender",
|
|
21
|
+
"text",
|
|
22
|
+
"subtext1",
|
|
23
|
+
"subtext0",
|
|
24
|
+
"overlay2",
|
|
25
|
+
"overlay1",
|
|
26
|
+
"overlay0",
|
|
27
|
+
"surface2",
|
|
28
|
+
"surface1",
|
|
29
|
+
"surface0",
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const THEMES = [
|
|
33
|
+
{ id: "latte", name: "Catppuccin Latte" },
|
|
34
|
+
{ id: "nord", name: "Nord Light" },
|
|
35
|
+
{ id: "nord_night", name: "Nord Night" },
|
|
36
|
+
{ id: "rose_pine", name: "Rosé Pine" },
|
|
37
|
+
{ id: "mocha", name: "Catppuccin Mocha" },
|
|
38
|
+
{ id: "tokyo_night", name: "Tokyo Night" },
|
|
39
|
+
];
|
|
40
|
+
|
|
6
41
|
class ThemeStackedElement extends HTMLElement {
|
|
7
42
|
constructor() {
|
|
8
43
|
super();
|
|
9
44
|
this._currentIndex = 0;
|
|
10
|
-
this._isDragging = false;
|
|
11
|
-
this._startX = 0;
|
|
12
|
-
this._currentX = 0;
|
|
13
45
|
this._cards = [];
|
|
14
|
-
|
|
15
|
-
this.attachShadow({ mode: "open" });
|
|
16
|
-
|
|
17
|
-
// All theme color variables to display
|
|
18
|
-
this.previewColors = [
|
|
19
|
-
"rosewater",
|
|
20
|
-
"flamingo",
|
|
21
|
-
"pink",
|
|
22
|
-
"mauve",
|
|
23
|
-
"red",
|
|
24
|
-
"maroon",
|
|
25
|
-
"peach",
|
|
26
|
-
"yellow",
|
|
27
|
-
"green",
|
|
28
|
-
"teal",
|
|
29
|
-
"sky",
|
|
30
|
-
"sapphire",
|
|
31
|
-
"blue",
|
|
32
|
-
"lavender",
|
|
33
|
-
"text",
|
|
34
|
-
"subtext1",
|
|
35
|
-
"subtext0",
|
|
36
|
-
"overlay2",
|
|
37
|
-
"overlay1",
|
|
38
|
-
"overlay0",
|
|
39
|
-
"surface2",
|
|
40
|
-
"surface1",
|
|
41
|
-
"surface0",
|
|
42
|
-
];
|
|
43
|
-
|
|
44
|
-
this.themes = [
|
|
45
|
-
{ id: "latte", name: "Catppuccin Latte" },
|
|
46
|
-
{ id: "nord", name: "Nord Light" },
|
|
47
|
-
{ id: "nord_night", name: "Nord Night" },
|
|
48
|
-
{ id: "rose_pine", name: "Rosé Pine" },
|
|
49
|
-
{ id: "mocha", name: "Catppuccin Mocha" },
|
|
50
|
-
{ id: "tokyo_night", name: "Tokyo Night" },
|
|
51
|
-
];
|
|
52
|
-
|
|
46
|
+
this._isVisible = false;
|
|
53
47
|
this._themeData = {};
|
|
48
|
+
this.attachShadow({ mode: "open" });
|
|
54
49
|
}
|
|
55
50
|
|
|
56
|
-
connectedCallback() {
|
|
57
|
-
this.
|
|
58
|
-
this.
|
|
59
|
-
this.init();
|
|
51
|
+
async connectedCallback() {
|
|
52
|
+
this._observer = new IntersectionObserver((e) => {
|
|
53
|
+
this._isVisible = e[0].isIntersecting;
|
|
60
54
|
});
|
|
55
|
+
this._observer.observe(this);
|
|
56
|
+
await this.loadThemeData();
|
|
57
|
+
this.render();
|
|
58
|
+
this.init();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
disconnectedCallback() {
|
|
62
|
+
this._observer?.disconnect();
|
|
63
|
+
document.removeEventListener("keydown", this._keyHandler);
|
|
64
|
+
document.removeEventListener("mouseup", this._mouseUpHandler);
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
async loadThemeData() {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
themeEl.style.cssText = "position:absolute;";
|
|
74
|
-
tempContainer.appendChild(themeEl);
|
|
75
|
-
|
|
76
|
-
// Extract computed colors for this theme
|
|
77
|
-
const colors = {};
|
|
78
|
-
const computedStyle = window.getComputedStyle(themeEl);
|
|
79
|
-
|
|
80
|
-
// Get each color variable
|
|
81
|
-
for (const colorVar of this.previewColors) {
|
|
82
|
-
const varValue = computedStyle.getPropertyValue(`--${colorVar}`).trim();
|
|
83
|
-
if (varValue) colors[colorVar] = varValue;
|
|
68
|
+
if (window.__cachedThemeData) {
|
|
69
|
+
this._themeData = window.__cachedThemeData;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const cached = localStorage.getItem("themeDataCache");
|
|
74
|
+
if (cached) {
|
|
75
|
+
this._themeData = window.__cachedThemeData = JSON.parse(cached);
|
|
76
|
+
return;
|
|
84
77
|
}
|
|
85
|
-
|
|
78
|
+
} catch (_e) {}
|
|
79
|
+
|
|
80
|
+
const temp = document.createElement("div");
|
|
81
|
+
temp.style.cssText = "position:absolute;left:-9999px;width:0;height:0;overflow:hidden;";
|
|
82
|
+
document.body.appendChild(temp);
|
|
86
83
|
|
|
87
|
-
|
|
84
|
+
for (const theme of THEMES) {
|
|
85
|
+
temp.setAttribute("data-theme", theme.id);
|
|
86
|
+
const computed = window.getComputedStyle(temp);
|
|
87
|
+
this._themeData[theme.id] = Object.fromEntries(PREVIEW_COLORS.map((c) => [c, computed.getPropertyValue(`--${c}`).trim()]).filter(([, v]) => v));
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
temp.remove();
|
|
91
|
+
window.__cachedThemeData = this._themeData;
|
|
92
|
+
try {
|
|
93
|
+
localStorage.setItem("themeDataCache", JSON.stringify(this._themeData));
|
|
94
|
+
} catch (_e) {}
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
render() {
|
|
@@ -128,17 +132,18 @@ class ThemeStackedElement extends HTMLElement {
|
|
|
128
132
|
position: relative;
|
|
129
133
|
width: min(550px, 90%);
|
|
130
134
|
background: var(--base);
|
|
131
|
-
border: 2px solid var(--
|
|
135
|
+
border: 2px solid color-mix(in oklch, var(--base) 80%, var(--text));
|
|
132
136
|
border-radius: 16px;
|
|
133
137
|
padding: 1.5rem;
|
|
134
138
|
cursor: grab;
|
|
135
|
-
transition:
|
|
139
|
+
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
|
136
140
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
|
137
141
|
user-select: none;
|
|
138
142
|
touch-action: pan-y;
|
|
139
143
|
opacity: 0;
|
|
140
144
|
transform: scale(0.8) translateX(200px);
|
|
141
145
|
pointer-events: none;
|
|
146
|
+
contain: layout style;
|
|
142
147
|
|
|
143
148
|
&.active {
|
|
144
149
|
opacity: 1;
|
|
@@ -193,7 +198,7 @@ class ThemeStackedElement extends HTMLElement {
|
|
|
193
198
|
|
|
194
199
|
.color-grid {
|
|
195
200
|
display: grid;
|
|
196
|
-
grid-template-columns: repeat(
|
|
201
|
+
grid-template-columns: repeat(9, 1fr);
|
|
197
202
|
gap: 6px;
|
|
198
203
|
margin-bottom: 1rem;
|
|
199
204
|
|
|
@@ -302,8 +307,6 @@ class ThemeStackedElement extends HTMLElement {
|
|
|
302
307
|
&:active {
|
|
303
308
|
transform: scale(0.95);
|
|
304
309
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
310
|
}
|
|
308
311
|
|
|
309
312
|
.dots {
|
|
@@ -337,9 +340,7 @@ class ThemeStackedElement extends HTMLElement {
|
|
|
337
340
|
</style>
|
|
338
341
|
|
|
339
342
|
<div class="stacked-container">
|
|
340
|
-
<div class="card-stack" id="card-stack">
|
|
341
|
-
<!-- Cards will be generated here -->
|
|
342
|
-
</div>
|
|
343
|
+
<div class="card-stack" id="card-stack"></div>
|
|
343
344
|
|
|
344
345
|
<div class="controls">
|
|
345
346
|
<button class="nav-btn" id="prev-btn" aria-label="Previous theme">
|
|
@@ -368,137 +369,66 @@ class ThemeStackedElement extends HTMLElement {
|
|
|
368
369
|
|
|
369
370
|
this.renderCards();
|
|
370
371
|
this.renderDots();
|
|
371
|
-
this.updateStack();
|
|
372
372
|
this.attachEvents();
|
|
373
373
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
const initialIndex = this.themes.findIndex((t) => t.id === currentTheme);
|
|
377
|
-
if (initialIndex !== -1) {
|
|
378
|
-
this.goTo(initialIndex, false);
|
|
379
|
-
}
|
|
374
|
+
const idx = THEMES.findIndex((t) => t.id === this.getCurrentTheme());
|
|
375
|
+
this.goTo(idx !== -1 ? idx : 0, false);
|
|
380
376
|
}
|
|
381
377
|
|
|
382
378
|
renderCards() {
|
|
383
|
-
|
|
384
|
-
this.
|
|
385
|
-
|
|
386
|
-
this.themes.forEach((theme, index) => {
|
|
387
|
-
const card = document.createElement("div");
|
|
388
|
-
card.className = "theme-card";
|
|
389
|
-
card.dataset.index = index;
|
|
390
|
-
card.dataset.theme = theme.id;
|
|
391
|
-
|
|
379
|
+
const current = this.getCurrentTheme();
|
|
380
|
+
this._cardStack.innerHTML = THEMES.map((theme, i) => {
|
|
392
381
|
const colors = this._themeData[theme.id] || {};
|
|
393
|
-
const
|
|
394
|
-
.map((
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
card.innerHTML = `
|
|
382
|
+
const themeVars = Object.entries(colors)
|
|
383
|
+
.map(([k, v]) => `--${k}: ${v}`)
|
|
384
|
+
.join(";");
|
|
385
|
+
const swatches = PREVIEW_COLORS.map((c) => {
|
|
386
|
+
const v = colors[c] || "transparent";
|
|
387
|
+
return `<div class="color-swatch" data-color="${c}" data-value="${v}" style="--color:${v}"></div>`;
|
|
388
|
+
}).join("");
|
|
389
|
+
const active = current === theme.id;
|
|
390
|
+
return `<div class="theme-card" data-index="${i}" data-theme="${theme.id}" style="${themeVars}">
|
|
403
391
|
<h4 class="card-title">${theme.name}</h4>
|
|
404
|
-
<div class="color-grid">
|
|
405
|
-
${colorSwatches}
|
|
406
|
-
</div>
|
|
392
|
+
<div class="color-grid">${swatches}</div>
|
|
407
393
|
<div class="card-footer">
|
|
408
|
-
<button class="apply-btn
|
|
409
|
-
${
|
|
394
|
+
<button class="apply-btn${active ? " applied" : ""}" data-theme="${theme.id}">
|
|
395
|
+
${active ? "Applied ✓" : "Apply"}
|
|
410
396
|
</button>
|
|
411
397
|
</div>
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
const applyBtn = card.querySelector(".apply-btn");
|
|
416
|
-
applyBtn.addEventListener("click", (e) => {
|
|
417
|
-
e.stopPropagation();
|
|
418
|
-
this.applyTheme(theme.id);
|
|
419
|
-
applyBtn.textContent = "Applied";
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
// Card click to navigate
|
|
423
|
-
card.addEventListener("click", () => {
|
|
424
|
-
if (!card.classList.contains("active")) {
|
|
425
|
-
this.goTo(index);
|
|
426
|
-
}
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
this._cardStack.appendChild(card);
|
|
430
|
-
this._cards.push(card);
|
|
431
|
-
});
|
|
398
|
+
</div>`;
|
|
399
|
+
}).join("");
|
|
400
|
+
this._cards = [...this._cardStack.querySelectorAll(".theme-card")];
|
|
432
401
|
}
|
|
433
402
|
|
|
434
403
|
renderDots() {
|
|
435
|
-
this._dotsContainer.innerHTML = "";
|
|
436
|
-
this.
|
|
437
|
-
const dot = document.createElement("button");
|
|
438
|
-
dot.className = "dot";
|
|
439
|
-
dot.dataset.index = index;
|
|
440
|
-
dot.setAttribute("aria-label", `Go to theme ${index + 1}`);
|
|
441
|
-
dot.addEventListener("click", () => this.goTo(index));
|
|
442
|
-
this._dotsContainer.appendChild(dot);
|
|
443
|
-
});
|
|
404
|
+
this._dotsContainer.innerHTML = THEMES.map((_, i) => `<button class="dot" data-index="${i}" aria-label="Go to theme ${i + 1}"></button>`).join("");
|
|
405
|
+
this._dotsContainer.querySelectorAll(".dot").forEach((dot, i) => dot.addEventListener("click", () => this.goTo(i)));
|
|
444
406
|
}
|
|
445
407
|
|
|
446
408
|
updateStack() {
|
|
447
409
|
const total = this._cards.length;
|
|
448
|
-
|
|
449
|
-
this._cards.forEach((card,
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
const dist = this.getDistance(index, this._currentIndex, total);
|
|
453
|
-
|
|
454
|
-
if (dist === 0) {
|
|
455
|
-
card.classList.add("active");
|
|
456
|
-
} else if (dist === -1 || (this._currentIndex === 0 && index === total - 1)) {
|
|
457
|
-
// Previous card (circular)
|
|
458
|
-
card.classList.add("prev");
|
|
459
|
-
} else if (dist === 1 || (this._currentIndex === total - 1 && index === 0)) {
|
|
460
|
-
// Next card (circular)
|
|
461
|
-
card.classList.add("next");
|
|
462
|
-
} else {
|
|
463
|
-
card.classList.add("hidden");
|
|
464
|
-
}
|
|
465
|
-
});
|
|
466
|
-
|
|
467
|
-
// Update dots
|
|
468
|
-
this._dotsContainer.querySelectorAll(".dot").forEach((dot, index) => {
|
|
469
|
-
dot.classList.toggle("active", index === this._currentIndex);
|
|
410
|
+
const distClass = { 0: "active", "-1": "prev", 1: "next" };
|
|
411
|
+
this._cards.forEach((card, i) => {
|
|
412
|
+
const cls = distClass[this.getDistance(i, this._currentIndex, total)] ?? "hidden";
|
|
413
|
+
card.className = `theme-card ${cls}`;
|
|
470
414
|
});
|
|
415
|
+
this._dotsContainer.querySelectorAll(".dot").forEach((dot, i) => dot.classList.toggle("active", i === this._currentIndex));
|
|
471
416
|
}
|
|
472
417
|
|
|
473
418
|
getDistance(index, current, total) {
|
|
474
|
-
const
|
|
475
|
-
|
|
476
|
-
if (current === 0 && index === total - 1) return -1;
|
|
477
|
-
if (current === total - 1 && index === 0) return 1;
|
|
478
|
-
return 2;
|
|
419
|
+
const d = (index - current + total) % total;
|
|
420
|
+
return d === 0 ? 0 : d === 1 ? 1 : d === total - 1 ? -1 : 2;
|
|
479
421
|
}
|
|
480
422
|
|
|
481
423
|
goTo(index, animate = true) {
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
// Handle circular wrapping for goTo as well
|
|
485
|
-
if (index < 0) {
|
|
486
|
-
index = total - 1;
|
|
487
|
-
} else if (index >= total) {
|
|
488
|
-
index = 0;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
this._currentIndex = index;
|
|
424
|
+
this._currentIndex = ((index % THEMES.length) + THEMES.length) % THEMES.length;
|
|
492
425
|
this.updateStack();
|
|
493
|
-
|
|
494
|
-
// Trigger animation
|
|
495
|
-
if (animate) {
|
|
426
|
+
if (animate)
|
|
496
427
|
this.dispatchEvent(
|
|
497
428
|
new CustomEvent("themeChange", {
|
|
498
|
-
detail: { index, theme: this.
|
|
429
|
+
detail: { index: this._currentIndex, theme: THEMES[this._currentIndex] },
|
|
499
430
|
}),
|
|
500
431
|
);
|
|
501
|
-
}
|
|
502
432
|
}
|
|
503
433
|
|
|
504
434
|
next() {
|
|
@@ -509,99 +439,88 @@ class ThemeStackedElement extends HTMLElement {
|
|
|
509
439
|
}
|
|
510
440
|
|
|
511
441
|
attachEvents() {
|
|
512
|
-
// Navigation buttons
|
|
513
442
|
this._prevBtn.addEventListener("click", () => this.prev());
|
|
514
443
|
this._nextBtn.addEventListener("click", () => this.next());
|
|
515
444
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
if (
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
break;
|
|
530
|
-
case "Enter":
|
|
531
|
-
if (this._cards[this._currentIndex].classList.contains("active")) {
|
|
532
|
-
this.applyTheme(this.themes[this._currentIndex].id);
|
|
533
|
-
}
|
|
534
|
-
break;
|
|
445
|
+
this._cardStack.addEventListener("click", (e) => {
|
|
446
|
+
const swatch = e.target.closest(".color-swatch");
|
|
447
|
+
if (swatch) {
|
|
448
|
+
e.stopPropagation();
|
|
449
|
+
const hex = (swatch.dataset.value || "").replace("#", "");
|
|
450
|
+
if (hex && hex !== "transparent") window.open(`https://www.colorhexa.com/${hex}`, "_blank");
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
const applyBtn = e.target.closest(".apply-btn");
|
|
454
|
+
if (applyBtn) {
|
|
455
|
+
e.stopPropagation();
|
|
456
|
+
this.applyTheme(applyBtn.dataset.theme);
|
|
457
|
+
return;
|
|
535
458
|
}
|
|
459
|
+
const card = e.target.closest(".theme-card");
|
|
460
|
+
if (card && !card.classList.contains("active")) this.goTo(Number(card.dataset.index));
|
|
536
461
|
});
|
|
537
462
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
463
|
+
this._keyHandler = (e) => {
|
|
464
|
+
if (!this._isVisible) return;
|
|
465
|
+
if (e.key === "ArrowLeft") {
|
|
466
|
+
e.preventDefault();
|
|
467
|
+
this.prev();
|
|
468
|
+
} else if (e.key === "ArrowRight" || e.key === " ") {
|
|
469
|
+
e.preventDefault();
|
|
470
|
+
this.next();
|
|
471
|
+
} else if (e.key === "Enter") this.applyTheme(THEMES[this._currentIndex].id);
|
|
472
|
+
};
|
|
473
|
+
document.addEventListener("keydown", this._keyHandler);
|
|
541
474
|
|
|
542
|
-
|
|
475
|
+
let startX = 0,
|
|
476
|
+
dragging = false;
|
|
477
|
+
const onStart = (x) => {
|
|
478
|
+
if (!this._isVisible) return;
|
|
543
479
|
startX = x;
|
|
544
|
-
|
|
480
|
+
dragging = true;
|
|
545
481
|
};
|
|
546
|
-
const
|
|
547
|
-
if (!
|
|
548
|
-
|
|
482
|
+
const onEnd = (x) => {
|
|
483
|
+
if (!dragging) return;
|
|
484
|
+
dragging = false;
|
|
549
485
|
const diff = startX - x;
|
|
550
486
|
if (Math.abs(diff) > 50) diff > 0 ? this.next() : this.prev();
|
|
551
487
|
};
|
|
552
488
|
|
|
553
|
-
this._cardStack.addEventListener("touchstart", (e) =>
|
|
554
|
-
this._cardStack.addEventListener("touchend", (e) =>
|
|
555
|
-
|
|
489
|
+
this._cardStack.addEventListener("touchstart", (e) => onStart(e.touches[0].clientX), { passive: true });
|
|
490
|
+
this._cardStack.addEventListener("touchend", (e) => onEnd(e.changedTouches[0].clientX));
|
|
556
491
|
this._cardStack.addEventListener("mousedown", (e) => {
|
|
557
|
-
|
|
492
|
+
onStart(e.clientX);
|
|
558
493
|
this._cardStack.style.cursor = "grabbing";
|
|
559
494
|
});
|
|
560
|
-
|
|
561
|
-
|
|
495
|
+
this._mouseUpHandler = (e) => {
|
|
496
|
+
if (!this._isVisible) return;
|
|
497
|
+
onEnd(e.clientX);
|
|
562
498
|
this._cardStack.style.cursor = "";
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
isVisible() {
|
|
567
|
-
const rect = this.getBoundingClientRect();
|
|
568
|
-
return rect.top < window.innerHeight && rect.bottom > 0;
|
|
499
|
+
};
|
|
500
|
+
document.addEventListener("mouseup", this._mouseUpHandler, { passive: true });
|
|
569
501
|
}
|
|
570
502
|
|
|
571
503
|
getCurrentTheme() {
|
|
572
|
-
|
|
573
|
-
const stored = localStorage.getItem("themePreference");
|
|
574
|
-
if (stored) return stored;
|
|
575
|
-
return document.documentElement.getAttribute("data-theme") || "mocha";
|
|
576
|
-
}
|
|
577
|
-
return "mocha";
|
|
504
|
+
return localStorage.getItem("themePreference") || document.documentElement.getAttribute("data-theme") || "mocha";
|
|
578
505
|
}
|
|
579
506
|
|
|
580
507
|
applyTheme(themeId) {
|
|
581
|
-
if (
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
const activeCard = this._cards[this._currentIndex];
|
|
595
|
-
const activeBtn = activeCard.querySelector(".apply-btn");
|
|
596
|
-
activeBtn.style.transform = "scale(0.95)";
|
|
597
|
-
setTimeout(() => {
|
|
598
|
-
activeBtn.style.transform = "";
|
|
599
|
-
}, 150);
|
|
600
|
-
}
|
|
508
|
+
if (!window.applyTheme) return;
|
|
509
|
+
window.applyTheme(themeId, true);
|
|
510
|
+
this._cards.forEach((card, i) => {
|
|
511
|
+
const btn = card.querySelector(".apply-btn");
|
|
512
|
+
const match = THEMES[i].id === themeId;
|
|
513
|
+
btn.classList.toggle("applied", match);
|
|
514
|
+
btn.textContent = match ? "Applied ✓" : "Apply Theme";
|
|
515
|
+
});
|
|
516
|
+
const btn = this._cards[this._currentIndex].querySelector(".apply-btn");
|
|
517
|
+
btn.style.transform = "scale(0.95)";
|
|
518
|
+
setTimeout(() => {
|
|
519
|
+
btn.style.transform = "";
|
|
520
|
+
}, 150);
|
|
601
521
|
}
|
|
602
522
|
}
|
|
603
523
|
|
|
604
|
-
// Register the custom element
|
|
605
524
|
if (!customElements.get("theme-stacked")) {
|
|
606
525
|
customElements.define("theme-stacked", ThemeStackedElement);
|
|
607
526
|
}
|