hexo-theme-gnix 12.0.0 → 13.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.
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Friends list custom elements.
3
+ *
4
+ * Usage:
5
+ * <friends-list id="friends">
6
+ * <friend-card
7
+ * name="Example"
8
+ * href="https://example.com"
9
+ * display-url="example.com"
10
+ * avatar="https://example.com/avatar.png"
11
+ * description="Personal site"
12
+ * feed="https://example.com/atom.xml"
13
+ * open-label="Open"
14
+ * ></friend-card>
15
+ * </friends-list>
16
+ */
17
+
18
+ let friendsListStyleSheetInjected = false;
19
+
20
+ function escapeHtml(value) {
21
+ const span = document.createElement("span");
22
+ span.textContent = value == null ? "" : String(value);
23
+ return span.innerHTML;
24
+ }
25
+
26
+ function escapeAttribute(value) {
27
+ return escapeHtml(value).replace(/"/g, "&quot;");
28
+ }
29
+
30
+ function displayUrlFromHref(href) {
31
+ try {
32
+ const url = new URL(href);
33
+ return url.hostname.replace(/^www\./, "www.");
34
+ } catch {
35
+ return href.replace(/^https?:\/\//, "").replace(/\/$/, "");
36
+ }
37
+ }
38
+
39
+ function injectFriendsListStyles() {
40
+ if (friendsListStyleSheetInjected) return;
41
+
42
+ const style = `
43
+ friends-list {
44
+ display: grid;
45
+ grid-template-columns: repeat(6, 1fr);
46
+ padding: 0;
47
+ }
48
+
49
+ friend-card {
50
+ position: relative;
51
+ grid-column: span 2;
52
+ padding: 1.5rem;
53
+ background: var(--base);
54
+ border: .5px solid var(--surface0);
55
+ cursor: pointer;
56
+ overflow: hidden;
57
+ transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
58
+ min-height: 5.25rem;
59
+ display: flex;
60
+ flex-direction: column;
61
+ justify-content: center;
62
+ }
63
+
64
+ friend-card:focus-within,
65
+ friend-card:hover {
66
+ border-color: var(--lavender);
67
+ box-shadow: 0 0.75rem 2.5rem -0.75rem hsl(from var(--text) h s l / 0.1);
68
+ }
69
+
70
+ friend-card:nth-last-child(1):nth-child(3n+1) {
71
+ grid-column: span 6;
72
+ }
73
+
74
+ friend-card:nth-last-child(1):nth-child(3n+2),
75
+ friend-card:nth-last-child(2):nth-child(3n+1) {
76
+ grid-column: span 3;
77
+ }
78
+
79
+ friend-card::before {
80
+ content: '';
81
+ position: absolute;
82
+ inset: 0;
83
+ background:
84
+ radial-gradient(circle at 100% 100%, hsl(from var(--lavender) h s l / 0.14), transparent 55%);
85
+ opacity: 0;
86
+ transition: opacity 0.3s ease;
87
+ z-index: 0;
88
+ }
89
+
90
+ friend-card:focus-within::before,
91
+ friend-card:hover::before {
92
+ opacity: 1;
93
+ }
94
+
95
+ .friend-hit {
96
+ position: absolute;
97
+ inset: 0;
98
+ z-index: 2;
99
+ border-radius: inherit;
100
+ }
101
+
102
+ .friend-avatar {
103
+ position: absolute;
104
+ bottom: -0.5rem;
105
+ right: -0.5rem;
106
+ width: 6rem;
107
+ height: 6rem;
108
+ border-radius: 50%;
109
+ background-color: var(--surface0);
110
+ object-fit: cover;
111
+ opacity: 0.08;
112
+ filter: grayscale(0.5);
113
+ transform: rotate(-8deg);
114
+ transition: all 0.3s ease;
115
+ z-index: 0;
116
+ pointer-events: none;
117
+ border: none;
118
+ }
119
+
120
+ friend-card:focus-within .friend-avatar,
121
+ friend-card:hover .friend-avatar {
122
+ opacity: 0.15;
123
+ filter: grayscale(0);
124
+ transform: scale(1.1) rotate(-8deg);
125
+ }
126
+
127
+ .friend-detail {
128
+ position: relative;
129
+ z-index: 1;
130
+ display: flex;
131
+ flex-direction: column;
132
+ gap: 0.25rem;
133
+ margin: 0;
134
+ }
135
+
136
+ h3.friend-name {
137
+ font-family: var(--font-serif);
138
+ font-style: italic;
139
+ font-synthesis: none;
140
+ font-size: 0.92rem;
141
+ font-weight: bolder;
142
+ color: var(--text);
143
+ display: flex;
144
+ align-items: center;
145
+ gap: 0.5rem;
146
+ line-height: 1.3;
147
+ margin: 0;
148
+ }
149
+
150
+ .friend-name .rss-link {
151
+ display: inline-flex;
152
+ align-items: center;
153
+ justify-content: center;
154
+ width: 1.25rem;
155
+ height: 1.25rem;
156
+ color: var(--subtext0);
157
+ text-decoration: none;
158
+ transition: all 0.2s ease;
159
+ border: 1px solid var(--surface0);
160
+ border-radius: 50%;
161
+ font-size: 0.75rem;
162
+ position: relative;
163
+ z-index: 3;
164
+ flex: 0 0 auto;
165
+ }
166
+
167
+ .friend-name .rss-link:hover {
168
+ color: var(--lavender);
169
+ border-color: var(--lavender);
170
+ }
171
+
172
+ .friend-url {
173
+ font-family: var(--font-mono);
174
+ font-size: 0.75rem;
175
+ font-weight: 400;
176
+ color: var(--subtext0);
177
+ opacity: 0.7;
178
+ transition: all 0.2s ease;
179
+ white-space: nowrap;
180
+ overflow: hidden;
181
+ text-overflow: ellipsis;
182
+ margin: 0;
183
+ }
184
+
185
+ friend-card:hover .friend-url {
186
+ opacity: 1;
187
+ color: var(--lavender);
188
+ }
189
+
190
+ .friend-desc {
191
+ font-family: var(--font-sans-serif);
192
+ font-size: 0.8rem;
193
+ color: var(--subtext1);
194
+ margin: 0.5rem 0 0;
195
+ padding: 0;
196
+ overflow: hidden;
197
+ }
198
+
199
+ @media (max-width: 768px) {
200
+ friends-list {
201
+ grid-template-columns: 1fr;
202
+ }
203
+
204
+ friend-card,
205
+ friend-card:nth-last-child(1):nth-child(3n+1),
206
+ friend-card:nth-last-child(1):nth-child(3n+2),
207
+ friend-card:nth-last-child(2):nth-child(3n+1) {
208
+ grid-column: span 1;
209
+ }
210
+
211
+ friend-card:nth-child(1) {
212
+ min-height: 6.25rem;
213
+ }
214
+ }
215
+
216
+ @media (max-width: 480px) {
217
+ friend-card {
218
+ padding: 1.1rem;
219
+ }
220
+ }
221
+ `;
222
+
223
+ const styleEl = document.createElement("style");
224
+ styleEl.textContent = style;
225
+ document.head.appendChild(styleEl);
226
+ friendsListStyleSheetInjected = true;
227
+ }
228
+
229
+ class FriendsList extends HTMLElement {
230
+ connectedCallback() {
231
+ injectFriendsListStyles();
232
+ if (!this.hasAttribute("role")) this.setAttribute("role", "list");
233
+ }
234
+ }
235
+
236
+ class FriendCard extends HTMLElement {
237
+ connectedCallback() {
238
+ injectFriendsListStyles();
239
+ this.render();
240
+ }
241
+
242
+ render() {
243
+ const name = this.getAttribute("name") || "";
244
+ const href = this.getAttribute("href") || "";
245
+ const displayUrl = this.getAttribute("display-url") || displayUrlFromHref(href);
246
+ const avatar = this.getAttribute("avatar") || "";
247
+ const description = this.getAttribute("description") || "";
248
+ const feed = this.getAttribute("feed") || "";
249
+ const openLabel = this.getAttribute("open-label") || "Open";
250
+ const ariaLabel = href && name ? `${openLabel} ${name}` : "";
251
+
252
+ this.setAttribute("role", "listitem");
253
+ this.innerHTML = `
254
+ ${href ? `<a class="friend-hit" href="${escapeAttribute(href)}" target="_blank" rel="noopener noreferrer" aria-label="${escapeAttribute(ariaLabel)}"></a>` : ""}
255
+ ${avatar ? `<img class="friend-avatar" src="${escapeAttribute(avatar)}" alt="" width="96" height="96" loading="lazy" decoding="async">` : ""}
256
+ <article class="friend-detail">
257
+ <h3 class="friend-name">
258
+ ${escapeHtml(name)}
259
+ ${feed ? `<a class="rss-link" target="_blank" rel="noopener noreferrer" href="${escapeAttribute(feed)}" title="RSS Feed">◎</a>` : ""}
260
+ </h3>
261
+ ${displayUrl ? `<div class="friend-url">${escapeHtml(displayUrl)}</div>` : ""}
262
+ ${description ? `<div class="friend-desc">${escapeHtml(description)}</div>` : ""}
263
+ </article>
264
+ `;
265
+ }
266
+ }
267
+
268
+ if (!customElements.get("friends-list")) customElements.define("friends-list", FriendsList);
269
+ if (!customElements.get("friend-card")) customElements.define("friend-card", FriendCard);
270
+
271
+ export { FriendCard, FriendsList };
@@ -0,0 +1,297 @@
1
+ /**
2
+ * Info card custom element.
3
+ *
4
+ * Usage:
5
+ * <x-info-card
6
+ * name="GnixAij"
7
+ * description="Life Tracking & Tech Sharing"
8
+ * avatar="https://assets.vluv.space/avatar.webp"
9
+ * website="https://vluv.space"
10
+ * feed="/atom.xml"
11
+ * links='{"Github":{"icon":"mdi:github","url":"https://github.com/Efterklang"}}'
12
+ * quicklinks='{"Now":"now"}'
13
+ * ></x-info-card>
14
+ */
15
+
16
+ let infoCardStyleSheetInjected = false;
17
+
18
+ function escapeHtml(value) {
19
+ const span = document.createElement("span");
20
+ span.textContent = value == null ? "" : String(value);
21
+ return span.innerHTML;
22
+ }
23
+
24
+ function escapeAttribute(value) {
25
+ return escapeHtml(value).replace(/"/g, "&quot;");
26
+ }
27
+
28
+ function parseJsonAttribute(element, name) {
29
+ const value = element.getAttribute(name);
30
+ if (!value) return {};
31
+
32
+ try {
33
+ const parsed = JSON.parse(value);
34
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
35
+ } catch {
36
+ return {};
37
+ }
38
+ }
39
+
40
+ function normalizeLinkEntry(entry) {
41
+ if (typeof entry === "string") return { url: entry };
42
+ if (entry && typeof entry === "object" && typeof entry.url === "string") return entry;
43
+ return null;
44
+ }
45
+
46
+ class InfoCard extends HTMLElement {
47
+ connectedCallback() {
48
+ this.injectStyles();
49
+ this.render();
50
+ }
51
+
52
+ injectStyles() {
53
+ if (infoCardStyleSheetInjected) return;
54
+
55
+ const style = `
56
+ x-info-card {
57
+ display: block;
58
+ margin: 2rem 0;
59
+ }
60
+
61
+ .x-info-card {
62
+ background: var(--base);
63
+ border: 1px solid var(--surface0);
64
+ border-radius: var(--radius);
65
+ padding: clamp(1.25rem, 4vw, 2rem);
66
+ position: relative;
67
+ overflow: hidden;
68
+ }
69
+
70
+ .x-info-card::before {
71
+ content: "";
72
+ position: absolute;
73
+ inset: auto -20% -45% 35%;
74
+ height: 70%;
75
+ pointer-events: none;
76
+ }
77
+
78
+ .x-info-header {
79
+ display: flex;
80
+ align-items: center;
81
+ gap: 1rem;
82
+ margin-bottom: 1.25rem;
83
+ position: relative;
84
+ z-index: 1;
85
+ }
86
+
87
+ .x-info-avatar {
88
+ width: 3.5rem;
89
+ height: 3.5rem;
90
+ border-radius: 50%;
91
+ object-fit: cover;
92
+ border: 2px solid var(--surface0);
93
+ flex: 0 0 auto;
94
+ }
95
+
96
+ .x-info-name {
97
+ font-family: var(--font-serif);
98
+ font-style: italic;
99
+ font-size: 1.35rem;
100
+ font-weight: 700;
101
+ color: var(--rosewater);
102
+ margin: 0;
103
+ line-height: 1.2;
104
+ }
105
+
106
+ .x-info-desc {
107
+ font-family: var(--font-sans-serif);
108
+ font-size: 0.8rem;
109
+ font-weight: 300;
110
+ color: var(--subtext0);
111
+ margin: 0.2rem 0 0;
112
+ }
113
+
114
+ .x-info-details {
115
+ display: grid;
116
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
117
+ gap: 0.75rem;
118
+ padding-top: 1.25rem;
119
+ border-top: 1px solid var(--surface0);
120
+ position: relative;
121
+ z-index: 1;
122
+ }
123
+
124
+ .x-info-item {
125
+ display: flex;
126
+ flex-direction: column;
127
+ gap: 0.15rem;
128
+ min-width: 0;
129
+ }
130
+
131
+ .x-info-label {
132
+ font-family: var(--font-mono);
133
+ font-size: 0.65rem;
134
+ font-weight: 500;
135
+ color: var(--subtext0);
136
+ text-transform: uppercase;
137
+ letter-spacing: 0.1em;
138
+ }
139
+
140
+ .x-info-value {
141
+ font-family: var(--font-mono);
142
+ font-size: 0.8125rem;
143
+ color: var(--text);
144
+ text-decoration: none;
145
+ word-break: break-word;
146
+ transition: color 0.2s ease;
147
+ }
148
+
149
+ .x-info-value:hover {
150
+ color: var(--lavender);
151
+ }
152
+
153
+ .x-info-links {
154
+ display: flex;
155
+ flex-wrap: wrap;
156
+ align-items: center;
157
+ gap: 0.6rem 0.9rem;
158
+ padding-top: 1.25rem;
159
+ margin-top: 0.25rem;
160
+ border-top: 1px solid var(--surface0);
161
+ position: relative;
162
+ z-index: 1;
163
+ }
164
+
165
+ .x-info-link {
166
+ font-family: var(--font-mono);
167
+ font-size: 0.8rem;
168
+ color: var(--subtext0);
169
+ text-decoration: none;
170
+ transition: color 0.2s ease;
171
+ }
172
+
173
+ .x-info-link:hover {
174
+ color: var(--lavender);
175
+ }
176
+
177
+ .x-info-link--icon {
178
+ display: inline-flex;
179
+ align-items: center;
180
+ justify-content: center;
181
+ width: 1.6rem;
182
+ height: 1.6rem;
183
+ border-radius: 50%;
184
+ transition: color 0.2s ease, background 0.2s ease;
185
+ }
186
+
187
+ .x-info-link--icon:hover {
188
+ color: var(--lavender);
189
+ }
190
+
191
+ .x-info-link--icon svg {
192
+ width: 1.1rem;
193
+ height: 1.1rem;
194
+ }
195
+
196
+ .x-info-sep {
197
+ width: 1px;
198
+ height: 1rem;
199
+ background: var(--surface0);
200
+ margin: 0 0.15rem;
201
+ }
202
+
203
+ @media (max-width: 520px) {
204
+ .x-info-header {
205
+ align-items: flex-start;
206
+ }
207
+
208
+ .x-info-details {
209
+ grid-template-columns: 1fr;
210
+ }
211
+ }
212
+ `;
213
+
214
+ const styleEl = document.createElement("style");
215
+ styleEl.textContent = style;
216
+ document.head.appendChild(styleEl);
217
+ infoCardStyleSheetInjected = true;
218
+ }
219
+
220
+ brandIconSVG(label) {
221
+ const svg = (d) => `<svg aria-hidden="true" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="${d}"/></svg>`;
222
+
223
+ const ICONS = {
224
+ Bluesky: "M12 11.388c-.906-1.761-3.372-5.044-5.665-6.662c-2.197-1.55-3.034-1.283-3.583-1.033C2.116 3.978 2 4.955 2 5.528c0 .575.315 4.709.52 5.4c.68 2.28 3.094 3.05 5.32 2.803c-3.26.483-6.157 1.67-2.36 5.898c4.178 4.325 5.726-.927 6.52-3.59c.794 2.663 1.708 7.726 6.444 3.59c3.556-3.59.977-5.415-2.283-5.898c2.225.247 4.64-.523 5.319-2.803c.205-.69.52-4.825.52-5.399c0-.575-.116-1.55-.752-1.838c-.549-.248-1.386-.517-3.583 1.033c-2.293 1.621-4.76 4.904-5.665 6.664",
225
+ Github: "M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5c.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34c-.46-1.16-1.11-1.47-1.11-1.47c-.91-.62.07-.6.07-.6c1 .07 1.53 1.03 1.53 1.03c.87 1.52 2.34 1.07 2.91.83c.09-.65.35-1.09.63-1.34c-2.22-.25-4.55-1.11-4.55-4.92c0-1.11.38-2 1.03-2.71c-.1-.25-.45-1.29.1-2.64c0 0 .84-.27 2.75 1.02c.79-.22 1.65-.33 2.5-.33s1.71.11 2.5.33c1.91-1.29 2.75-1.02 2.75-1.02c.55 1.35.2 2.39.1 2.64c.65.71 1.03 1.6 1.03 2.71c0 3.82-2.34 4.66-4.57 4.91c.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2",
226
+ Youtube: "M12 4c.855 0 1.732.022 2.582.058l1.004.048l.961.057l.9.061l.822.064a3.8 3.8 0 0 1 3.494 3.423l.04.425l.075.91c.07.943.122 1.971.122 2.954s-.052 2.011-.122 2.954l-.075.91l-.04.425a3.8 3.8 0 0 1-3.495 3.423l-.82.063l-.9.062l-.962.057l-1.004.048A62 62 0 0 1 12 20a62 62 0 0 1-2.582-.058l-1.004-.048l-.961-.057l-.9-.062l-.822-.063a3.8 3.8 0 0 1-3.494-3.423l-.04-.425l-.075-.91A41 41 0 0 1 2 12c0-.983.052-2.011.122-2.954l.075-.91l.04-.425A3.8 3.8 0 0 1 5.73 4.288l.821-.064l.9-.061l.962-.057l1.004-.048A62 62 0 0 1 12 4m-2 5.575v4.85c0 .462.5.75.9.52l4.2-2.425a.6.6 0 0 0 0-1.04l-4.2-2.424a.6.6 0 0 0-.9.52Z",
227
+ Bilibili: "M17.555 3.168a1 1 0 0 1 .277 1.387L16.87 6H18a4 4 0 0 1 4 4v7a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4v-7a4 4 0 0 1 4-4h1.131l-.963-1.445a1 1 0 0 1 1.664-1.11L9.535 6h4.93l1.703-2.555a1 1 0 0 1 1.387-.277M9 11a1 1 0 0 0-.993.883L8 12v2a1 1 0 0 0 1.993.117L10 14v-2a1 1 0 0 0-1-1m6 0a1 1 0 0 0-1 1v2a1 1 0 1 0 2 0v-2a1 1 0 0 0-1-1",
228
+ Threads: "M5.086 4.28c1.65-1.76 4.031-2.78 6.93-2.78c4.792 0 8.017 2.784 9.033 6.65a1.5 1.5 0 0 1-2.902.762C17.48 6.37 15.45 4.5 12.017 4.5c-2.164 0-3.72.742-4.742 1.832C6.239 7.438 5.64 9.02 5.64 10.875v2.25c0 1.855.598 3.437 1.634 4.543c1.022 1.09 2.578 1.832 4.741 1.832c1.576 0 2.795-.365 3.714-.93c.92-.568 1.42-1.285 1.56-2.068c.173-.972-.044-1.579-.364-2.001a2 2 0 0 0-.116-.14a5.3 5.3 0 0 1-.623 1.382c-1.514 2.3-4.369 2.46-6.203 1.728c-1.091-.435-1.972-1.583-2.247-2.788a3.5 3.5 0 0 1 .168-2.147c.312-.75.884-1.37 1.662-1.828c.8-.472 1.927-.665 2.979-.694a11 11 0 0 1 1.254.04c-.09-.2-.187-.343-.274-.425c-.384-.357-1.06-.632-1.746-.628c-.647.005-1.126.247-1.41.7a1.5 1.5 0 1 1-2.544-1.59c.948-1.515 2.507-2.1 3.933-2.11c1.388-.01 2.821.512 3.81 1.432c.954.888 1.373 2.254 1.513 3.485c.836.403 1.63.974 2.234 1.77c.874 1.15 1.233 2.624.927 4.34c-.32 1.793-1.45 3.178-2.94 4.096c-1.457.898-3.239 1.376-5.287 1.376c-2.899 0-5.28-1.02-6.93-2.78c-1.636-1.746-2.445-4.1-2.445-6.595v-2.25c0-2.494.81-4.85 2.445-6.594Zm8.947 8.823a8 8 0 0 0-1.405-.09c-.86.024-1.384.188-1.537.279c-.305.18-.39.333-.417.398a.53.53 0 0 0-.011.327c.036.16.121.333.238.48a.8.8 0 0 0 .194.186q.008.006 0 .002c.985.393 2.105.14 2.586-.592c.137-.207.265-.553.352-.99",
229
+ X: "M19.753 4.659a1 1 0 0 0-1.506-1.317l-5.11 5.84L8.8 3.4A1 1 0 0 0 8 3H4a1 1 0 0 0-.8 1.6l6.437 8.582l-5.39 6.16a1 1 0 0 0 1.506 1.317l5.11-5.841L15.2 20.6a1 1 0 0 0 .8.4h4a1 1 0 0 0 .8-1.6l-6.437-8.582l5.39-6.16ZM16.5 19L6 5h1.5L18 19",
230
+ Steam: "M12 2a10 10 0 0 1 10 10a10 10 0 0 1-10 10c-4.6 0-8.45-3.08-9.64-7.27l3.83 1.58a2.84 2.84 0 0 0 2.78 2.27c1.56 0 2.83-1.27 2.83-2.83v-.13l3.4-2.43h.08c2.08 0 3.77-1.69 3.77-3.77s-1.69-3.77-3.77-3.77s-3.78 1.69-3.78 3.77v.05l-2.37 3.46l-.16-.01c-.59 0-1.14.18-1.59.49L2 11.2C2.43 6.05 6.73 2 12 2M8.28 17.17c.8.33 1.72-.04 2.05-.84s-.05-1.71-.83-2.04l-1.28-.53c.49-.18 1.04-.19 1.56.03c.53.21.94.62 1.15 1.15c.22.52.22 1.1 0 1.62c-.43 1.08-1.7 1.6-2.78 1.15c-.5-.21-.88-.59-1.09-1.04zm9.52-7.75c0 1.39-1.13 2.52-2.52 2.52a2.52 2.52 0 0 1-2.51-2.52a2.5 2.5 0 0 1 2.51-2.51a2.52 2.52 0 0 1 2.52 2.51m-4.4 0c0 1.04.84 1.89 1.89 1.89c1.04 0 1.88-.85 1.88-1.89s-.84-1.89-1.88-1.89c-1.05 0-1.89.85-1.89 1.89",
231
+ RSS: "M5 17a2 2 0 1 1 0 4a2 2 0 0 1 0-4M5 3c8.837 0 16 7.163 16 16q0 .277-.01.55a1.5 1.5 0 1 1-2.997-.1A13 13 0 0 0 18 19c0-7.18-5.82-13-13-13q-.225 0-.45.008a1.5 1.5 0 0 1-.1-2.999Q4.722 3 5 3m0 7a9 9 0 0 1 8.98 9.599a1.5 1.5 0 1 1-2.993-.198a6 6 0 0 0-6.388-6.388a1.5 1.5 0 0 1-.197-2.993Q4.699 10 5 10",
232
+ };
233
+
234
+ return ICONS[label] ? svg(ICONS[label]) : svg("M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14l11-11");
235
+ }
236
+
237
+ render() {
238
+ const name = this.getAttribute("name") || "";
239
+ const description = this.getAttribute("description") || "";
240
+ const avatar = this.getAttribute("avatar") || "";
241
+ const website = this.getAttribute("website") || "";
242
+ const feed = this.getAttribute("feed") || "";
243
+ const avatarLink = this.getAttribute("avatar-link") || avatar;
244
+ const links = parseJsonAttribute(this, "links");
245
+ const quicklinks = parseJsonAttribute(this, "quicklinks");
246
+
247
+ const details = [
248
+ website
249
+ ? `<div class="x-info-item"><span class="x-info-label">Website</span><a href="${escapeAttribute(website)}" class="x-info-value" target="_blank" rel="noopener noreferrer">${escapeHtml(website.replace(/^https?:\/\//, ""))}</a></div>`
250
+ : "",
251
+ feed
252
+ ? `<div class="x-info-item"><span class="x-info-label">Feed</span><a href="${escapeAttribute(feed)}" class="x-info-value" target="_blank" rel="noopener noreferrer">${escapeHtml(feed.replace(/^https?:\/\//, ""))}</a></div>`
253
+ : "",
254
+ avatarLink
255
+ ? `<div class="x-info-item"><span class="x-info-label">Avatar</span><a href="${escapeAttribute(avatarLink)}" class="x-info-value" target="_blank" rel="noopener noreferrer">${escapeHtml(avatarLink.replace(/^https?:\/\//, ""))}</a></div>`
256
+ : "",
257
+ ]
258
+ .filter(Boolean)
259
+ .join("");
260
+
261
+ const quicklinkItems = Object.keys(quicklinks)
262
+ .map((label) => {
263
+ const link = normalizeLinkEntry(quicklinks[label]);
264
+ if (!link) return "";
265
+ return `<a class="x-info-link" href="${escapeAttribute(link.url)}" target="_self" rel="noopener noreferrer">${escapeHtml(label)}</a>`;
266
+ })
267
+ .filter(Boolean);
268
+
269
+ const socialItems = Object.keys(links)
270
+ .map((label) => {
271
+ const link = normalizeLinkEntry(links[label]);
272
+ if (!link) return "";
273
+ const iconSvg = this.brandIconSVG(label);
274
+ return `<a class="x-info-link x-info-link--icon" href="${escapeAttribute(link.url)}" target="_blank" rel="noopener noreferrer" title="${escapeAttribute(label)}">${iconSvg}</a>`;
275
+ })
276
+ .filter(Boolean);
277
+
278
+ const hasDivider = quicklinkItems.length > 0 && socialItems.length > 0;
279
+ const linksHtml = [...quicklinkItems, hasDivider ? `<span class="x-info-sep" aria-hidden="true"></span>` : "", ...socialItems].filter(Boolean).join("");
280
+
281
+ this.innerHTML = `
282
+ <div class="x-info-card">
283
+ <div class="x-info-header">
284
+ ${avatar ? `<img src="${escapeAttribute(avatar)}" alt="${escapeAttribute(name)}" class="x-info-avatar" width="56" height="56" loading="lazy" decoding="async">` : ""}
285
+ <div>
286
+ ${name ? `<h3 class="x-info-name">${escapeHtml(name)}</h3>` : ""}
287
+ ${description ? `<p class="x-info-desc">${escapeHtml(description)}</p>` : ""}
288
+ </div>
289
+ </div>
290
+ ${details ? `<div class="x-info-details">${details}</div>` : ""}
291
+ ${linksHtml ? `<div class="x-info-links">${linksHtml}</div>` : ""}
292
+ </div>
293
+ `;
294
+ }
295
+ }
296
+
297
+ if (!customElements.get("x-info-card")) customElements.define("x-info-card", InfoCard);
package/source/js/main.js CHANGED
@@ -1,3 +1,27 @@
1
+ function showSiteToast(message) {
2
+ if (!message) return;
3
+
4
+ let toast = document.getElementById("site-toast");
5
+ if (!toast) {
6
+ toast = document.createElement("div");
7
+ toast.id = "site-toast";
8
+ toast.setAttribute("role", "status");
9
+ toast.setAttribute("aria-live", "polite");
10
+ toast.className = "site-toast";
11
+ document.body.appendChild(toast);
12
+ }
13
+
14
+ toast.textContent = message;
15
+ toast.classList.add("is-visible");
16
+
17
+ clearTimeout(toast._hideTimer);
18
+ toast._hideTimer = setTimeout(() => {
19
+ toast.classList.remove("is-visible");
20
+ }, 1800);
21
+ }
22
+
23
+ window.showSiteToast = showSiteToast;
24
+
1
25
  function twikoo_handler() {
2
26
  const el = document.getElementById("tko");
3
27
  if (!el) return;
@@ -573,26 +597,6 @@ function initArticleCommentPopover() {
573
597
  });
574
598
  }
575
599
 
576
- function getComparablePath(url) {
577
- const path = new URL(url, window.location.href).pathname.replace(/\/index\.html$/, "/").replace(/\/+$/, "");
578
- return path || "/";
579
- }
580
-
581
- function updateNavbarCurrentPage() {
582
- const currentPath = getComparablePath(window.location.href);
583
- document.querySelectorAll(".navbar-start .navbar-item").forEach((item) => {
584
- const itemPath = getComparablePath(item.href);
585
- const active = itemPath === currentPath || (itemPath !== "/" && currentPath.startsWith(`${itemPath}/`));
586
-
587
- item.classList.toggle("is-active", active);
588
- if (active) {
589
- item.setAttribute("aria-current", "page");
590
- } else {
591
- item.removeAttribute("aria-current");
592
- }
593
- });
594
- }
595
-
596
600
  function refreshNavbarIcons() {
597
601
  document.querySelectorAll(".navbar-end iconify-icon").forEach((el) => {
598
602
  if (!el.getAttribute("icon")) return;
@@ -614,7 +618,6 @@ function initPage() {
614
618
  document.querySelectorAll(".content img").forEach((img) => zoomImgs.add(img));
615
619
  mediumZoom([...zoomImgs], zoomOpts);
616
620
  initArticleCommentPopover();
617
- updateNavbarCurrentPage();
618
621
  refreshNavbarIcons();
619
622
  }
620
623
 
@@ -60,6 +60,7 @@
60
60
  this.isDragging = false;
61
61
  this.startX = 0;
62
62
  this.startY = 0;
63
+ this.wrapper = container.querySelector(".mermaid-wrapper");
63
64
  this.initEvents();
64
65
  }
65
66
 
@@ -70,6 +71,15 @@
70
71
  }
71
72
 
72
73
  initEvents() {
74
+ this.container.addEventListener("pointerdown", () => {
75
+ this.wrapper?.classList.add("is-controls-visible");
76
+ });
77
+
78
+ document.addEventListener("pointerdown", (e) => {
79
+ if (this.container.contains(e.target)) return;
80
+ this.wrapper?.classList.remove("is-controls-visible");
81
+ });
82
+
73
83
  // Toolbar & Grid Panel
74
84
  this.container.addEventListener("click", (e) => {
75
85
  const btn = e.target.closest("button");