@anglefeint/astro-theme 0.1.8 → 0.1.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anglefeint/astro-theme",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "type": "module",
5
5
  "description": "Anglefeint core theme package for Astro",
6
6
  "license": "MIT",
@@ -9,6 +9,12 @@
9
9
  runtimeConfig = {};
10
10
  }
11
11
  }
12
+ var prefersReducedMotion = false;
13
+ try {
14
+ prefersReducedMotion = !!(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches);
15
+ } catch (_e) {
16
+ prefersReducedMotion = false;
17
+ }
12
18
  // ── Terminal background: dir + 可输入 (点击背景聚焦,回车仅换行) ──
13
19
  var bgCanvas = document.querySelector('.hacker-bg-canvas');
14
20
  if (bgCanvas) {
@@ -107,14 +113,19 @@
107
113
  cancelAnimationFrame(bgAnimationId);
108
114
  bgAnimationId = 0;
109
115
  }
110
- startBackgroundLoop();
111
- document.addEventListener('visibilitychange', function() {
112
- if (document.hidden) {
116
+ if (prefersReducedMotion) {
117
+ renderBg(performance.now());
113
118
  stopBackgroundLoop();
114
- return;
119
+ } else {
120
+ startBackgroundLoop();
121
+ document.addEventListener('visibilitychange', function() {
122
+ if (document.hidden) {
123
+ stopBackgroundLoop();
124
+ return;
125
+ }
126
+ startBackgroundLoop();
127
+ });
115
128
  }
116
- startBackgroundLoop();
117
- });
118
129
 
119
130
  // 点击背景聚焦,点击内容失焦
120
131
  document.addEventListener('click', function(e) {
@@ -207,7 +218,7 @@
207
218
  var backTop = document.querySelector('.hacker-back-to-top');
208
219
  if (backTop) {
209
220
  backTop.addEventListener('click', function() {
210
- window.scrollTo({ top: 0, behavior: 'smooth' });
221
+ window.scrollTo({ top: 0, behavior: prefersReducedMotion ? 'auto' : 'smooth' });
211
222
  });
212
223
  }
213
224
 
@@ -235,6 +246,7 @@
235
246
  return s;
236
247
  }
237
248
  function startDecryptorFlash() {
249
+ if (prefersReducedMotion) return;
238
250
  if (decryptorInterval) clearInterval(decryptorInterval);
239
251
  var keys = 0, sec = 1;
240
252
  decryptorInterval = setInterval(function() {
@@ -444,7 +456,7 @@
444
456
 
445
457
  // ── Mouse glow ──
446
458
  var glow = document.querySelector('.hacker-mouse-glow');
447
- if (glow) {
459
+ if (glow && !prefersReducedMotion) {
448
460
  var glowRaf;
449
461
  var mx = 0, my = 0;
450
462
  document.addEventListener('mousemove', function(e) {
@@ -476,7 +488,14 @@
476
488
 
477
489
  // ── Typewriter section titles ──
478
490
  var titles = document.querySelectorAll('.about-section-title');
479
- if (window.IntersectionObserver && titles.length) {
491
+ if (prefersReducedMotion && titles.length) {
492
+ titles.forEach(function(el) {
493
+ var fullText = el.textContent || '';
494
+ el.setAttribute('data-full-text', fullText);
495
+ el.textContent = fullText;
496
+ el.style.minHeight = '1.2em';
497
+ });
498
+ } else if (window.IntersectionObserver && titles.length) {
480
499
  titles.forEach(function(el) {
481
500
  var fullText = el.textContent || '';
482
501
  el.setAttribute('data-full-text', fullText);
@@ -510,6 +529,13 @@
510
529
  var scan = document.querySelector('.hacker-load-scan');
511
530
  if (regen && article) {
512
531
  regen.addEventListener('click', function() {
532
+ if (prefersReducedMotion) {
533
+ article.classList.add('hacker-flash');
534
+ setTimeout(function() {
535
+ article.classList.remove('hacker-flash');
536
+ }, 120);
537
+ return;
538
+ }
513
539
  regen.disabled = true;
514
540
  article.classList.add('hacker-flash');
515
541
  if (scan) {
@@ -1,6 +1,12 @@
1
- (function() {
2
- function init() {
3
- // 阅读进度条
1
+ (function() {
2
+ function init() {
3
+ var prefersReducedMotion = false;
4
+ try {
5
+ prefersReducedMotion = !!(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches);
6
+ } catch (_e) {
7
+ prefersReducedMotion = false;
8
+ }
9
+ // 阅读进度条
4
10
  var progress = document.querySelector('.ai-read-progress');
5
11
  var article = document.querySelector('.ai-article');
6
12
  var toast = document.querySelector('.ai-stage-toast');
@@ -42,13 +48,13 @@
42
48
  onScroll();
43
49
  window.addEventListener('scroll', onScroll, { passive: true });
44
50
  }
45
- var backTop = document.querySelector('.ai-back-to-top');
46
- if (backTop) {
47
- backTop.addEventListener('click', function() {
48
- window.scrollTo({ top: 0, behavior: 'smooth' });
49
- });
50
- }
51
- function initHeroCanvas() {
51
+ var backTop = document.querySelector('.ai-back-to-top');
52
+ if (backTop) {
53
+ backTop.addEventListener('click', function() {
54
+ window.scrollTo({ top: 0, behavior: prefersReducedMotion ? 'auto' : 'smooth' });
55
+ });
56
+ }
57
+ function initHeroCanvas() {
52
58
  var shell = document.querySelector('.hero-shell');
53
59
  if (!shell) return;
54
60
  var canvas = shell.querySelector('.hero-canvas');
@@ -283,6 +289,11 @@
283
289
  sizeCanvas();
284
290
  buildEdge(img);
285
291
  wrap.classList.add('ready');
292
+ if (prefersReducedMotion) {
293
+ var staticCtx = canvas.getContext('2d');
294
+ if (staticCtx) staticCtx.drawImage(baseCanvas, 0, 0);
295
+ return;
296
+ }
286
297
  heroRaf = requestAnimationFrame(heroRender);
287
298
  };
288
299
  img.src = new URL(src, window.location.href).href;
@@ -294,6 +305,7 @@
294
305
  }
295
306
  }, { passive: true });
296
307
  function onHeroVisibilityChange() {
308
+ if (prefersReducedMotion) return;
297
309
  if (document.hidden) {
298
310
  if (heroRaf) cancelAnimationFrame(heroRaf);
299
311
  heroRaf = 0;
@@ -305,7 +317,7 @@
305
317
  window.addEventListener('beforeunload', function() { cancelAnimationFrame(heroRaf); }, { once: true });
306
318
  }
307
319
  initHeroCanvas();
308
- function initRedQueenTv() {
320
+ function initRedQueenTv() {
309
321
  var shell = document.querySelector('.rq-tv');
310
322
  var stage = document.querySelector('.rq-tv-stage');
311
323
  var toggle = document.querySelector('.rq-tv-toggle');
@@ -361,8 +373,22 @@
361
373
  return new URL(item.url, window.location.href).href;
362
374
  }
363
375
 
364
- var playlist = [{ url: source, type: guessImageType(source), holdLast: 360 }];
365
- if (source2) playlist.push({ url: source2, type: guessImageType(source2), holdLast: 500 });
376
+ var playlist = [{ url: source, type: guessImageType(source), holdLast: 360 }];
377
+ if (source2) playlist.push({ url: source2, type: guessImageType(source2), holdLast: 500 });
378
+ if (prefersReducedMotion) {
379
+ setCollapsed(false);
380
+ var staticImg = new Image();
381
+ staticImg.className = 'rq-tv-screen';
382
+ staticImg.alt = '';
383
+ staticImg.decoding = 'async';
384
+ staticImg.loading = 'lazy';
385
+ staticImg.src = resolveItemUrl(playlist[0]);
386
+ stage.innerHTML = '';
387
+ stage.appendChild(staticImg);
388
+ toggle.hidden = true;
389
+ toggle.setAttribute('aria-hidden', 'true');
390
+ return;
391
+ }
366
392
 
367
393
  function setCollapsed(collapsed) {
368
394
  shell.classList.toggle('rq-tv-collapsed', collapsed);
@@ -0,0 +1,35 @@
1
+ /* Pagination styles for cyber blog list pages */
2
+ body.cyber-page .pagination {
3
+ display: flex;
4
+ justify-content: center;
5
+ align-items: center;
6
+ gap: 0.5rem;
7
+ margin-top: 2.5rem;
8
+ padding: 1.5rem 0;
9
+ }
10
+
11
+ body.cyber-page .pagination a {
12
+ padding: 0.5rem 1rem;
13
+ color: rgb(212, 196, 232);
14
+ text-decoration: none;
15
+ border: 1px solid rgba(198, 156, 224, 0.42);
16
+ border-radius: 4px;
17
+ transition: 0.25s ease;
18
+ text-shadow: 0 0 10px rgba(214, 152, 226, 0.2);
19
+ }
20
+
21
+ body.cyber-page .pagination a:hover {
22
+ border-color: rgba(226, 166, 236, 0.88);
23
+ text-shadow: 0 0 15px rgba(232, 174, 242, 0.5);
24
+ box-shadow: 0 0 15px rgba(222, 154, 228, 0.26);
25
+ }
26
+
27
+ body.cyber-page .pagination a.current {
28
+ background: rgba(188, 122, 214, 0.2);
29
+ border-color: rgb(224, 170, 236);
30
+ color: rgb(244, 224, 252);
31
+ text-shadow: 0 0 15px rgba(232, 174, 242, 0.48);
32
+ box-shadow:
33
+ 0 0 20px rgba(118, 198, 255, 0.24),
34
+ 0 0 12px rgba(220, 152, 230, 0.22);
35
+ }
@@ -1,14 +1,15 @@
1
1
  ---
2
2
  interface Props {
3
3
  date: Date;
4
+ locale?: string;
4
5
  }
5
6
 
6
- const { date } = Astro.props;
7
+ const { date, locale = 'en-US' } = Astro.props as Props;
7
8
  ---
8
9
 
9
10
  <time datetime={date.toISOString()}>
10
11
  {
11
- date.toLocaleDateString('en-us', {
12
+ date.toLocaleDateString(locale, {
12
13
  year: 'numeric',
13
14
  month: 'short',
14
15
  day: 'numeric',
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  import { SITE_TAGLINE, SITE_TITLE } from '@anglefeint/site-config/site';
3
3
  import { SOCIAL_LINKS } from '@anglefeint/site-config/social';
4
+ import SocialMenu from './SocialMenu.astro';
4
5
 
5
6
  const today = new Date();
6
7
  interface Props {
@@ -16,36 +17,7 @@ const { tagline = SITE_TAGLINE, scanlines = false } = Astro.props as Props;
16
17
  &copy; {today.getFullYear()} {SITE_TITLE}. {tagline}
17
18
  {SOCIAL_LINKS.length > 0 && (
18
19
  <div class="social-links">
19
- {SOCIAL_LINKS.map((link) => (
20
- <a href={link.href} target="_blank" rel="noopener noreferrer">
21
- <span class="sr-only">{link.label}</span>
22
- {link.icon === 'mastodon' && (
23
- <svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32">
24
- <path
25
- fill="currentColor"
26
- d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a3.614 3.614 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522c0-.859.22-1.541.66-2.046.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764.442.505.661 1.187.661 2.046v4.203z"
27
- />
28
- </svg>
29
- )}
30
- {link.icon === 'twitter' && (
31
- <svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32">
32
- <path
33
- fill="currentColor"
34
- d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15z"
35
- />
36
- </svg>
37
- )}
38
- {link.icon === 'github' && (
39
- <svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32">
40
- <path
41
- fill="currentColor"
42
- d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
43
- />
44
- </svg>
45
- )}
46
- {!link.icon && <span>{link.label}</span>}
47
- </a>
48
- ))}
20
+ <SocialMenu links={SOCIAL_LINKS} />
49
21
  </div>
50
22
  )}
51
23
  {scanlines && <div class="ai-scanlines" aria-hidden="true"></div>}
@@ -3,6 +3,8 @@ import { SITE_TITLE } from '@anglefeint/site-config/site';
3
3
  import { SOCIAL_LINKS } from '@anglefeint/site-config/social';
4
4
  import { THEME } from '@anglefeint/site-config/theme';
5
5
  import HeaderLink from '../HeaderLink.astro';
6
+ import LangSwitcher from './LangSwitcher.astro';
7
+ import SocialMenu from './SocialMenu.astro';
6
8
  import {
7
9
  DEFAULT_LOCALE,
8
10
  LOCALE_LABELS,
@@ -67,59 +69,12 @@ const localeOptions = SUPPORTED_LOCALES.map((targetLocale) => ({
67
69
  </div>
68
70
  <div class="nav-right">
69
71
  <div class="social-links">
70
- <div class="lang-switcher" aria-label={labels.language}>
71
- <label class="lang-label" for="lang-select">{labels.language}</label>
72
- <select
73
- id="lang-select"
74
- class="lang-select"
75
- aria-label={labels.language}
76
- onchange="if (this.value) window.location.href = this.value;"
77
- >
78
- {
79
- localeOptions.map((option) => (
80
- <option value={option.href} selected={option.locale === locale}>
81
- {option.label}
82
- </option>
83
- ))
84
- }
85
- </select>
86
- </div>
72
+ <LangSwitcher label={labels.language} currentLocale={locale} options={localeOptions} />
87
73
  <div class="nav-status" aria-label="System status">
88
74
  <span class="nav-status-dot" aria-hidden="true"></span>
89
75
  <span class="nav-status-text">{labels.status}</span>
90
76
  </div>
91
- {
92
- SOCIAL_LINKS.map((link) => (
93
- <a href={link.href} target="_blank" rel="noopener noreferrer">
94
- <span class="sr-only">{link.label}</span>
95
- {link.icon === 'mastodon' && (
96
- <svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32">
97
- <path
98
- fill="currentColor"
99
- d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a3.614 3.614 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522c0-.859.22-1.541.66-2.046.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764.442.505.661 1.187.661 2.046v4.203z"
100
- />
101
- </svg>
102
- )}
103
- {link.icon === 'twitter' && (
104
- <svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32">
105
- <path
106
- fill="currentColor"
107
- d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15z"
108
- />
109
- </svg>
110
- )}
111
- {link.icon === 'github' && (
112
- <svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32">
113
- <path
114
- fill="currentColor"
115
- d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
116
- />
117
- </svg>
118
- )}
119
- {!link.icon && <span>{link.label}</span>}
120
- </a>
121
- ))
122
- }
77
+ <SocialMenu links={SOCIAL_LINKS} />
123
78
  </div>
124
79
  </div>
125
80
  </nav>
@@ -0,0 +1,39 @@
1
+ ---
2
+ interface Props {
3
+ name?: 'mastodon' | 'twitter' | 'github';
4
+ size?: number;
5
+ }
6
+
7
+ const { name, size = 32 } = Astro.props as Props;
8
+ ---
9
+
10
+ {
11
+ name === 'mastodon' && (
12
+ <svg viewBox="0 0 16 16" aria-hidden="true" width={size} height={size}>
13
+ <path
14
+ fill="currentColor"
15
+ d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a3.614 3.614 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522c0-.859.22-1.541.66-2.046.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764.442.505.661 1.187.661 2.046v4.203z"
16
+ />
17
+ </svg>
18
+ )
19
+ }
20
+ {
21
+ name === 'twitter' && (
22
+ <svg viewBox="0 0 16 16" aria-hidden="true" width={size} height={size}>
23
+ <path
24
+ fill="currentColor"
25
+ d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15z"
26
+ />
27
+ </svg>
28
+ )
29
+ }
30
+ {
31
+ name === 'github' && (
32
+ <svg viewBox="0 0 16 16" aria-hidden="true" width={size} height={size}>
33
+ <path
34
+ fill="currentColor"
35
+ d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
36
+ />
37
+ </svg>
38
+ )
39
+ }
@@ -0,0 +1,35 @@
1
+ ---
2
+ import type { Locale } from '@anglefeint/site-i18n/config';
3
+
4
+ interface LocaleOption {
5
+ locale: Locale;
6
+ label: string;
7
+ href: string;
8
+ }
9
+
10
+ interface Props {
11
+ label: string;
12
+ currentLocale: Locale;
13
+ options: LocaleOption[];
14
+ }
15
+
16
+ const { label, currentLocale, options } = Astro.props as Props;
17
+ ---
18
+
19
+ <div class="lang-switcher" aria-label={label}>
20
+ <label class="lang-label" for="lang-select">{label}</label>
21
+ <select
22
+ id="lang-select"
23
+ class="lang-select"
24
+ aria-label={label}
25
+ onchange="if (this.value) window.location.href = this.value;"
26
+ >
27
+ {
28
+ options.map((option) => (
29
+ <option value={option.href} selected={option.locale === currentLocale}>
30
+ {option.label}
31
+ </option>
32
+ ))
33
+ }
34
+ </select>
35
+ </div>
@@ -0,0 +1,20 @@
1
+ ---
2
+ import { SOCIAL_LINKS, type SocialLink } from '@anglefeint/site-config/social';
3
+ import Icon from './Icon.astro';
4
+
5
+ interface Props {
6
+ links?: SocialLink[];
7
+ iconSize?: number;
8
+ }
9
+
10
+ const { links = SOCIAL_LINKS, iconSize = 32 } = Astro.props as Props;
11
+ ---
12
+
13
+ {
14
+ links.map((link) => (
15
+ <a href={link.href} target="_blank" rel="noopener noreferrer">
16
+ <span class="sr-only">{link.label}</span>
17
+ {link.icon ? <Icon name={link.icon} size={iconSize} /> : <span>{link.label}</span>}
18
+ </a>
19
+ ))
20
+ }
@@ -75,7 +75,7 @@ const MESSAGES: Record<Locale, Messages> = {
75
75
  related: 'Related',
76
76
  regenerate: 'Regenerate',
77
77
  },
78
- footer: { tagline: 'Built with Astro.' },
78
+ footer: { tagline: ' Astro 构建。' },
79
79
  },
80
80
  ja: {
81
81
  siteTitle: 'Angle Feint',
@@ -243,7 +243,7 @@ for (let i = 0; i < pts.length; i++) {
243
243
  )}
244
244
  <h3 class="ai-related-card-title">{p.data.title}</h3>
245
245
  <p class="ai-related-card-date">
246
- <FormattedDate date={p.data.pubDate} />
246
+ <FormattedDate date={p.data.pubDate} locale={resolvedLocale} />
247
247
  </p>
248
248
  </a>
249
249
  ))}
@@ -32,7 +32,7 @@ const heroText = SITE_HERO_BY_LOCALE[locale] ?? messages.home.hero;
32
32
  {post.data.title}
33
33
  </a>
34
34
  <div class="home-post-meta">
35
- <FormattedDate date={post.data.pubDate} />
35
+ <FormattedDate date={post.data.pubDate} locale={locale} />
36
36
  {post.data.description && <> · {post.data.description}</>}
37
37
  </div>
38
38
  </li>