随机跳转
This commit is contained in:
@@ -0,0 +1,114 @@
|
|||||||
|
---
|
||||||
|
import { articles } from "../data/articles";
|
||||||
|
import { photoTopics } from "../data/photos";
|
||||||
|
import { timeline } from "../data/timeline";
|
||||||
|
|
||||||
|
const memoryGroups = [
|
||||||
|
{
|
||||||
|
type: "photo",
|
||||||
|
items: photoTopics.flatMap((topic) =>
|
||||||
|
topic.photos
|
||||||
|
.filter((photo) => photo.image)
|
||||||
|
.map((photo) => ({
|
||||||
|
href: photo.image,
|
||||||
|
title: photo.title,
|
||||||
|
caption: [topic.title, photo.caption].filter(Boolean).join(" · ")
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "article",
|
||||||
|
items: articles.map((article) => ({
|
||||||
|
href: article.sourceUrl,
|
||||||
|
title: article.title
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "timeline",
|
||||||
|
items: timeline.map((moment, index) => ({
|
||||||
|
href: `/timeline/#moment-${index + 1}`,
|
||||||
|
title: `${moment.date} · ${moment.title}`
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
];
|
||||||
|
---
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="button random-memory-button"
|
||||||
|
id="random-memory-button"
|
||||||
|
type="button"
|
||||||
|
data-memory-groups={JSON.stringify(memoryGroups)}
|
||||||
|
aria-describedby="random-memory-hint"
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-shuffle" aria-hidden="true"></i>
|
||||||
|
<span>随便看看</span>
|
||||||
|
</button>
|
||||||
|
<span class="sr-only" id="random-memory-hint">随机打开一张照片、一篇文章或一个时间节点</span>
|
||||||
|
<span class="sr-only" id="random-memory-status" aria-live="polite"></span>
|
||||||
|
<dialog class="random-photo-dialog" id="random-photo-dialog" aria-labelledby="random-photo-caption">
|
||||||
|
<button class="random-photo-close" type="button" aria-label="关闭照片">
|
||||||
|
<i class="fa-solid fa-xmark" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
<figure>
|
||||||
|
<img id="random-photo-image" alt="" />
|
||||||
|
<figcaption id="random-photo-caption"></figcaption>
|
||||||
|
</figure>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
interface MemoryItem {
|
||||||
|
href: string;
|
||||||
|
title: string;
|
||||||
|
caption?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MemoryGroup {
|
||||||
|
type: "photo" | "article" | "timeline";
|
||||||
|
items: MemoryItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const button = document.querySelector<HTMLButtonElement>("#random-memory-button");
|
||||||
|
const status = document.querySelector<HTMLElement>("#random-memory-status");
|
||||||
|
const photoDialog = document.querySelector<HTMLDialogElement>("#random-photo-dialog");
|
||||||
|
const photoImage = document.querySelector<HTMLImageElement>("#random-photo-image");
|
||||||
|
const photoCaption = document.querySelector<HTMLElement>("#random-photo-caption");
|
||||||
|
const photoClose = document.querySelector<HTMLButtonElement>(".random-photo-close");
|
||||||
|
let previousHref = "";
|
||||||
|
|
||||||
|
photoClose?.addEventListener("click", () => photoDialog?.close());
|
||||||
|
photoDialog?.addEventListener("click", (event) => {
|
||||||
|
if (event.target === photoDialog) photoDialog.close();
|
||||||
|
});
|
||||||
|
photoDialog?.addEventListener("close", () => photoImage?.removeAttribute("src"));
|
||||||
|
|
||||||
|
button?.addEventListener("click", () => {
|
||||||
|
const groups = JSON.parse(button.dataset.memoryGroups ?? "[]") as MemoryGroup[];
|
||||||
|
const availableGroups = groups.filter((group) => group.items.length > 0);
|
||||||
|
if (availableGroups.length === 0) return;
|
||||||
|
|
||||||
|
const group = availableGroups[Math.floor(Math.random() * availableGroups.length)];
|
||||||
|
const candidates = group.items.filter((item) => item.href !== previousHref);
|
||||||
|
const pool = candidates.length > 0 ? candidates : group.items;
|
||||||
|
const item = pool[Math.floor(Math.random() * pool.length)];
|
||||||
|
previousHref = item.href;
|
||||||
|
|
||||||
|
if (status) status.textContent = `这次抽到:${item.title}`;
|
||||||
|
|
||||||
|
if (group.type === "photo") {
|
||||||
|
if (photoDialog && photoImage && photoCaption && typeof photoDialog.showModal === "function") {
|
||||||
|
photoImage.src = item.href;
|
||||||
|
photoImage.alt = item.title;
|
||||||
|
photoCaption.textContent = [item.title, item.caption].filter(Boolean).join(" — ");
|
||||||
|
photoDialog.showModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group.type === "article") {
|
||||||
|
window.open(item.href, "_blank", "noopener,noreferrer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.assign(item.href);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||||
import OnThisDay from "../components/OnThisDay.astro";
|
import OnThisDay from "../components/OnThisDay.astro";
|
||||||
|
import RandomMemory from "../components/RandomMemory.astro";
|
||||||
import { galleryIntro, photoTopics } from "../data/photos";
|
import { galleryIntro, photoTopics } from "../data/photos";
|
||||||
import { featuredMessage, messages, messagesIntro } from "../data/messages";
|
import { featuredMessage, messages, messagesIntro } from "../data/messages";
|
||||||
import { people, peopleIntro } from "../data/people";
|
import { people, peopleIntro } from "../data/people";
|
||||||
@@ -25,6 +26,7 @@ const previewPeople = people.slice(0, 6);
|
|||||||
<p>{site.subtitle}</p>
|
<p>{site.subtitle}</p>
|
||||||
<div class="hero-actions">
|
<div class="hero-actions">
|
||||||
<a class="button primary" href="/timeline/">翻开回忆</a>
|
<a class="button primary" href="/timeline/">翻开回忆</a>
|
||||||
|
<RandomMemory />
|
||||||
<a class="button" href="/messages/">写一句话</a>
|
<a class="button" href="/messages/">写一句话</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ import { timeline, timelineIntro } from "../data/timeline";
|
|||||||
<div class="section-inner">
|
<div class="section-inner">
|
||||||
<div class="timeline">
|
<div class="timeline">
|
||||||
{
|
{
|
||||||
timeline.map((moment) =>
|
timeline.map((moment, index) =>
|
||||||
moment.href ? (
|
moment.href ? (
|
||||||
<a class="moment moment-link" href={moment.href}>
|
<a class="moment moment-link" id={`moment-${index + 1}`} href={moment.href}>
|
||||||
<time>{moment.date}</time>
|
<time>{moment.date}</time>
|
||||||
<div>
|
<div>
|
||||||
<h3>{moment.title}</h3>
|
<h3>{moment.title}</h3>
|
||||||
@@ -29,7 +29,7 @@ import { timeline, timelineIntro } from "../data/timeline";
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
<article class="moment">
|
<article class="moment" id={`moment-${index + 1}`}>
|
||||||
<time>{moment.date}</time>
|
<time>{moment.date}</time>
|
||||||
<div>
|
<div>
|
||||||
<h3>{moment.title}</h3>
|
<h3>{moment.title}</h3>
|
||||||
|
|||||||
@@ -154,16 +154,90 @@ h1 {
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
padding: 10px 18px;
|
padding: 10px 18px;
|
||||||
border: 1px solid rgba(255, 253, 247, 0.45);
|
border: 1px solid rgba(255, 253, 247, 0.45);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: rgba(255, 253, 247, 0.12);
|
background: rgba(255, 253, 247, 0.12);
|
||||||
color: #fffdf7;
|
color: #fffdf7;
|
||||||
|
font: inherit;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.random-memory-button {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.random-memory-button:hover i {
|
||||||
|
transform: rotate(14deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.random-memory-button i {
|
||||||
|
transition: transform 180ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.random-photo-dialog {
|
||||||
|
width: min(920px, calc(100vw - 32px));
|
||||||
|
max-width: none;
|
||||||
|
max-height: calc(100vh - 32px);
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #17201e;
|
||||||
|
color: #fffdf7;
|
||||||
|
box-shadow: 0 28px 90px rgba(8, 14, 13, 0.52);
|
||||||
|
}
|
||||||
|
|
||||||
|
.random-photo-dialog::backdrop {
|
||||||
|
background: rgba(10, 16, 15, 0.82);
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.random-photo-dialog[open] {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.random-photo-dialog figure {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.random-photo-dialog img {
|
||||||
|
width: 100%;
|
||||||
|
max-height: calc(100vh - 116px);
|
||||||
|
object-fit: contain;
|
||||||
|
background: #0e1513;
|
||||||
|
}
|
||||||
|
|
||||||
|
.random-photo-dialog figcaption {
|
||||||
|
min-height: 58px;
|
||||||
|
padding: 16px 64px 16px 20px;
|
||||||
|
color: rgba(255, 253, 247, 0.86);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.random-photo-close {
|
||||||
|
position: absolute;
|
||||||
|
right: 12px;
|
||||||
|
bottom: 10px;
|
||||||
|
z-index: 1;
|
||||||
|
width: 38px;
|
||||||
|
height: 38px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
border: 1px solid rgba(255, 253, 247, 0.22);
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 253, 247, 0.1);
|
||||||
|
color: #fffdf7;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.random-photo-close:hover {
|
||||||
|
background: rgba(255, 253, 247, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
.button.primary {
|
.button.primary {
|
||||||
border-color: #f1c979;
|
border-color: #f1c979;
|
||||||
background: #f1c979;
|
background: #f1c979;
|
||||||
@@ -677,6 +751,12 @@ h2 {
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
box-shadow: 0 8px 28px rgba(39, 55, 52, 0.06);
|
box-shadow: 0 8px 28px rgba(39, 55, 52, 0.06);
|
||||||
|
scroll-margin-top: 96px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moment:target {
|
||||||
|
border-color: rgba(232, 168, 76, 0.92);
|
||||||
|
box-shadow: 0 16px 42px rgba(232, 168, 76, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.moment > div {
|
.moment > div {
|
||||||
|
|||||||
Reference in New Issue
Block a user