diff --git a/public/js/random.js b/public/js/random.js
index 481ee1a..91d37b5 100644
--- a/public/js/random.js
+++ b/public/js/random.js
@@ -1,4 +1,72 @@
-function randomPost() {
+function randomPost(trigger) {
+ const source = trigger instanceof Element
+ ? trigger
+ : document.activeElement?.closest?.('.nav-random, [onclick*="randomPost"]');
+ const overlay = document.createElement('div');
+ const reduceMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
+
+ overlay.className = 'random-post-transition';
+ overlay.setAttribute('role', 'status');
+ overlay.setAttribute('aria-live', 'polite');
+ overlay.innerHTML = `
+
+
+
+ \u6b63\u5728\u62bd\u53d6\u4e00\u7bc7\u6587\u7ae0
+
+ `;
+
+ source?.classList.add('is-randomizing');
+ source?.setAttribute('aria-busy', 'true');
+ document.body.appendChild(overlay);
+ requestAnimationFrame(() => overlay.classList.add('is-visible'));
+
+ const finish = href => {
+ const delay = reduceMotion ? 80 : 1250;
+ window.setTimeout(() => {
+ location.href = href;
+ }, delay);
+ };
+
+ const startCardFlight = posts => {
+ if (reduceMotion) return;
+
+ const flight = overlay.querySelector('.random-post-flight');
+ if (!flight) return;
+
+ const candidates = posts
+ .filter(post => post && typeof post.href === 'string' && post.href.startsWith('/posts/'))
+ .map(post => ({
+ title: typeof post.title === 'string' && post.title.trim() ? post.title.trim() : '\u672a\u547d\u540d\u6587\u7ae0',
+ meta: typeof post.category === 'string' && post.category.trim() ? post.category.trim() : '\u968f\u673a\u6587\u7ae0',
+ }));
+
+ const pool = candidates.length ? candidates : [{ title: '\u968f\u673a\u6587\u7ae0', meta: '\u6b63\u5728\u62bd\u53d6' }];
+ const count = Math.min(9, Math.max(5, pool.length));
+
+ for (let index = 0; index < count; index += 1) {
+ const post = pool[Math.floor(Math.random() * pool.length)];
+ const card = document.createElement('div');
+ card.className = 'random-post-flying-card';
+ card.style.setProperty('--flight-delay', `${index * 0.11}s`);
+ card.style.setProperty('--flight-y', `${-42 + Math.random() * 84}px`);
+ card.style.setProperty('--flight-rotate', `${-8 + Math.random() * 16}deg`);
+ const meta = document.createElement('small');
+ const title = document.createElement('strong');
+ meta.textContent = post.meta;
+ title.textContent = post.title;
+ card.append(meta, title);
+ flight.appendChild(card);
+ }
+ };
+
+ const reset = () => {
+ source?.classList.remove('is-randomizing');
+ source?.removeAttribute('aria-busy');
+ overlay.classList.remove('is-visible');
+ window.setTimeout(() => overlay.remove(), reduceMotion ? 0 : 180);
+ };
+
fetch('/api/posts.json')
.then(res => {
if (!res.ok) throw new Error(`Failed to load posts: ${res.status}`);
@@ -11,12 +79,15 @@ function randomPost() {
if (postLinks.length === 0) {
console.warn('No posts available for random navigation.');
+ reset();
return;
}
- location.href = postLinks[Math.floor(Math.random() * postLinks.length)];
+ startCardFlight(posts);
+ finish(postLinks[Math.floor(Math.random() * postLinks.length)]);
})
.catch(error => {
console.error('Random post navigation failed:', error);
+ reset();
});
}
diff --git a/src/components/CustomContextMenu.astro b/src/components/CustomContextMenu.astro
index a4bff7b..a30f9f2 100644
--- a/src/components/CustomContextMenu.astro
+++ b/src/components/CustomContextMenu.astro
@@ -57,6 +57,10 @@
标签
+