This commit is contained in:
2026-07-18 11:51:56 +08:00 Unverified
parent 4e42797981
commit 85a21e2358
2 changed files with 392 additions and 117 deletions
+189 -63
View File
@@ -1,16 +1,26 @@
<section class="on-this-day-band" aria-labelledby="on-this-day-title"> <section class="on-this-day-band" aria-labelledby="on-this-day-title">
<div class="section-inner on-this-day-shell" data-on-this-day> <div class="section-inner on-this-day-shell" data-on-this-day>
<div class="memory-date-card" aria-hidden="true"> <div class="memory-calendar" aria-label="记忆日历">
<span data-memory-month>今日</span> <div class="memory-calendar-head">
<strong data-memory-day>--</strong> <div class="memory-calendar-nav">
<small>北京时间</small> <button type="button" data-calendar-previous aria-label="上一个月">←</button>
<strong data-calendar-label aria-live="polite">本月</strong>
<button type="button" data-calendar-next aria-label="下一个月">→</button>
</div>
<button class="memory-calendar-today" type="button" data-calendar-today>回到今天</button>
</div>
<div class="memory-calendar-weekdays" aria-hidden="true">
<span>日</span><span>一</span><span>二</span><span>三</span><span>四</span><span>五</span><span>六</span>
</div>
<div class="memory-calendar-grid" data-calendar-grid></div>
<p class="memory-calendar-legend"><span aria-hidden="true"></span>圆点表示这一天收录了班级记忆</p>
</div> </div>
<div class="on-this-day-content"> <div class="on-this-day-content">
<div class="on-this-day-heading"> <div class="on-this-day-heading">
<div> <div>
<p class="eyebrow">ON THIS DAY · 那年今日</p> <p class="eyebrow">ON THIS DAY · 那年今日</p>
<h2 id="on-this-day-title">今天,记忆翻到了这一页</h2> <h2 id="on-this-day-title" data-memory-title>今天,记忆翻到了这一页</h2>
</div> </div>
<p data-memory-summary aria-live="polite">正在寻找同月同日的班级记忆……</p> <p data-memory-summary aria-live="polite">正在寻找同月同日的班级记忆……</p>
</div> </div>
@@ -50,84 +60,200 @@
day: "numeric" day: "numeric"
}).formatToParts(new Date()); }).formatToParts(new Date());
const dateValues = Object.fromEntries(dateParts.map((part) => [part.type, part.value])); const dateValues = Object.fromEntries(dateParts.map((part) => [part.type, part.value]));
const month = Number(dateValues.month); const todayMonth = Number(dateValues.month);
const day = Number(dateValues.day); const todayDay = Number(dateValues.day);
const currentYear = Number(dateValues.year); const currentYear = Number(dateValues.year);
const monthDay = `${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`; let calendarYear = currentYear;
const matches = onThisDayMemories.filter((memory) => memory.monthDay === monthDay); let calendarMonth = todayMonth;
const visibleMatches = matches.slice(0, 6); let selectedMonth = todayMonth;
let selectedDay = todayDay;
const monthLabel = root.querySelector<HTMLElement>("[data-memory-month]"); const titleLabel = root.querySelector<HTMLElement>("[data-memory-title]");
const dayLabel = root.querySelector<HTMLElement>("[data-memory-day]");
const summary = root.querySelector<HTMLElement>("[data-memory-summary]"); const summary = root.querySelector<HTMLElement>("[data-memory-summary]");
const grid = root.querySelector<HTMLElement>("[data-memory-grid]"); const grid = root.querySelector<HTMLElement>("[data-memory-grid]");
const empty = root.querySelector<HTMLElement>("[data-memory-empty]"); const empty = root.querySelector<HTMLElement>("[data-memory-empty]");
const calendarLabel = root.querySelector<HTMLElement>("[data-calendar-label]");
const calendarGrid = root.querySelector<HTMLElement>("[data-calendar-grid]");
const previousButton = root.querySelector<HTMLButtonElement>("[data-calendar-previous]");
const nextButton = root.querySelector<HTMLButtonElement>("[data-calendar-next]");
const todayButton = root.querySelector<HTMLButtonElement>("[data-calendar-today]");
if (monthLabel) monthLabel.textContent = `${month}月`; const getMonthDay = (month: number, day: number) =>
if (dayLabel) dayLabel.textContent = String(day).padStart(2, "0"); `${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
if (summary) { const getMemories = (month: number, day: number) => {
summary.textContent = matches.length const monthDay = getMonthDay(month, day);
? `${month}月${day}日,找到了 ${matches.length} 条跨越不同年份的班级记忆。` return onThisDayMemories.filter((memory) => memory.monthDay === monthDay);
: `${month}月${day}日,今天暂时没有同月同日的记录。`; };
}
const renderMemories = (month: number, day: number) => {
selectedMonth = month;
selectedDay = day;
const matches = getMemories(month, day);
const visibleMatches = matches.slice(0, 6);
const isToday = month === todayMonth && day === todayDay;
if (titleLabel) {
titleLabel.textContent = isToday
? "今天,记忆翻到了这一页"
: `${month}月${day}日,记忆翻到了这一页`;
}
if (summary) {
summary.textContent = matches.length
? `${month}月${day}日,找到了 ${matches.length} 条跨越不同年份的班级记忆。`
: `${month}月${day}日,暂时没有同月同日的记录。`;
}
if (grid) {
const fragment = document.createDocumentFragment();
visibleMatches.forEach((memory) => {
const card = document.createElement("a");
card.className = `memory-card memory-card-${memory.kind}`;
card.href = memory.href;
card.setAttribute("aria-label", `${memory.year}年${memory.kindLabel}${memory.title}`);
if (memory.external) {
card.target = "_blank";
card.rel = "noreferrer";
}
if (memory.image) {
const image = document.createElement("img");
image.src = memory.image;
image.alt = "";
image.loading = "lazy";
card.append(image);
} else {
const visual = document.createElement("span");
visual.className = "memory-card-visual";
visual.setAttribute("aria-hidden", "true");
visual.textContent = "记";
card.append(visual);
}
const copy = document.createElement("span");
copy.className = "memory-card-copy";
const meta = document.createElement("span");
meta.className = "memory-card-meta";
const yearsAgo = currentYear - memory.year;
meta.textContent = `${memory.year}年 · ${yearsAgo > 0 ? `${yearsAgo}年前 · ` : ""}${memory.kindLabel}`;
const title = document.createElement("strong");
title.textContent = memory.title;
const text = document.createElement("span");
text.className = "memory-card-text";
text.textContent = memory.text;
const action = document.createElement("span");
action.className = "memory-card-action";
action.textContent = memory.external ? "阅读旧文章 ↗" : "翻开这段记忆 →";
copy.append(meta, title, text, action);
card.append(copy);
fragment.append(card);
});
grid.replaceChildren(fragment);
grid.hidden = matches.length === 0;
grid.setAttribute("aria-busy", "false");
}
if (empty) empty.hidden = matches.length > 0;
};
const renderCalendar = () => {
if (calendarLabel) calendarLabel.textContent = `${calendarYear}年${calendarMonth}月`;
if (!calendarGrid) return;
if (grid) {
const fragment = document.createDocumentFragment(); const fragment = document.createDocumentFragment();
const firstWeekday = new Date(calendarYear, calendarMonth - 1, 1).getDay();
const daysInMonth = new Date(calendarYear, calendarMonth, 0).getDate();
visibleMatches.forEach((memory) => { for (let index = 0; index < firstWeekday; index += 1) {
const card = document.createElement("a"); const spacer = document.createElement("span");
card.className = `memory-card memory-card-${memory.kind}`; spacer.className = "memory-calendar-spacer";
card.href = memory.href; spacer.setAttribute("aria-hidden", "true");
card.setAttribute("aria-label", `${memory.year}年${memory.kindLabel}${memory.title}`); fragment.append(spacer);
if (memory.external) { }
card.target = "_blank";
card.rel = "noreferrer"; for (let day = 1; day <= daysInMonth; day += 1) {
const matches = getMemories(calendarMonth, day);
const button = document.createElement("button");
button.type = "button";
button.dataset.calendarDay = String(day);
button.className = "memory-calendar-day";
button.setAttribute(
"aria-label",
matches.length ? `${calendarMonth}月${day}日,有${matches.length}条记忆` : `${calendarMonth}月${day}日`
);
if (matches.length) {
button.classList.add("has-memory");
button.dataset.memoryCount = String(matches.length);
}
if (calendarMonth === selectedMonth && day === selectedDay) {
button.classList.add("is-selected");
button.setAttribute("aria-pressed", "true");
}
if (calendarYear === currentYear && calendarMonth === todayMonth && day === todayDay) {
button.classList.add("is-today");
} }
if (memory.image) { const number = document.createElement("span");
const image = document.createElement("img"); number.textContent = String(day);
image.src = memory.image; button.append(number);
image.alt = "";
image.loading = "lazy"; if (matches.length) {
card.append(image); const dot = document.createElement("i");
} else { dot.setAttribute("aria-hidden", "true");
const visual = document.createElement("span"); button.append(dot);
visual.className = "memory-card-visual";
visual.setAttribute("aria-hidden", "true");
visual.textContent = "记";
card.append(visual);
} }
const copy = document.createElement("span"); fragment.append(button);
copy.className = "memory-card-copy"; }
const meta = document.createElement("span"); calendarGrid.replaceChildren(fragment);
meta.className = "memory-card-meta"; };
const yearsAgo = currentYear - memory.year;
meta.textContent = `${memory.year}年 · ${yearsAgo > 0 ? `${yearsAgo}年前 · ` : ""}${memory.kindLabel}`;
const title = document.createElement("strong"); calendarGrid?.addEventListener("click", (event) => {
title.textContent = memory.title; const target = event.target;
if (!(target instanceof Element)) return;
const button = target.closest<HTMLButtonElement>("[data-calendar-day]");
if (!button) return;
const text = document.createElement("span"); renderMemories(calendarMonth, Number(button.dataset.calendarDay));
text.className = "memory-card-text"; renderCalendar();
text.textContent = memory.text; });
const action = document.createElement("span"); previousButton?.addEventListener("click", () => {
action.className = "memory-card-action"; calendarMonth -= 1;
action.textContent = memory.external ? "阅读旧文章 ↗" : "翻开这段记忆 →"; if (calendarMonth === 0) {
calendarMonth = 12;
calendarYear -= 1;
}
renderCalendar();
});
copy.append(meta, title, text, action); nextButton?.addEventListener("click", () => {
card.append(copy); calendarMonth += 1;
fragment.append(card); if (calendarMonth === 13) {
}); calendarMonth = 1;
calendarYear += 1;
}
renderCalendar();
});
grid.replaceChildren(fragment); todayButton?.addEventListener("click", () => {
grid.hidden = matches.length === 0; calendarYear = currentYear;
grid.setAttribute("aria-busy", "false"); calendarMonth = todayMonth;
} renderMemories(todayMonth, todayDay);
renderCalendar();
});
if (empty) empty.hidden = matches.length > 0; renderMemories(todayMonth, todayDay);
renderCalendar();
} }
</script> </script>
+203 -54
View File
@@ -225,56 +225,16 @@ h1 {
position: relative; position: relative;
z-index: 1; z-index: 1;
display: grid; display: grid;
grid-template-columns: 150px minmax(0, 1fr); grid-template-columns: minmax(280px, 1fr) minmax(0, 2fr);
gap: clamp(28px, 5vw, 64px); gap: clamp(28px, 5vw, 64px);
align-items: start; align-items: start;
} }
.memory-date-card {
position: sticky;
top: 26px;
overflow: hidden;
border: 1px solid rgba(55, 109, 90, 0.2);
border-radius: 14px;
background: #fffdf7;
box-shadow: 0 18px 42px rgba(39, 55, 52, 0.11);
text-align: center;
}
.memory-date-card span,
.memory-date-card small {
display: block;
}
.memory-date-card span {
padding: 8px 12px;
background: var(--green);
color: #fffdf7;
font-size: 14px;
font-weight: 800;
}
.memory-date-card strong {
display: block;
padding: 12px 12px 2px;
color: var(--ink);
font-family: Georgia, "Times New Roman", serif;
font-size: 62px;
line-height: 1;
}
.memory-date-card small {
padding: 8px 12px 13px;
color: var(--muted);
font-size: 11px;
letter-spacing: 0.08em;
}
.on-this-day-heading { .on-this-day-heading {
display: grid; display: grid;
grid-template-columns: minmax(0, 1fr) minmax(240px, 0.8fr); grid-template-columns: 1fr;
gap: clamp(20px, 4vw, 52px); gap: 12px;
align-items: end; align-items: start;
margin-bottom: 30px; margin-bottom: 30px;
} }
@@ -287,6 +247,162 @@ h1 {
color: var(--muted); color: var(--muted);
} }
.memory-calendar {
position: sticky;
top: 26px;
padding: 18px;
border: 1px solid rgba(31, 43, 42, 0.12);
border-radius: 14px;
background: rgba(255, 255, 255, 0.72);
box-shadow: 0 8px 28px rgba(39, 55, 52, 0.06);
backdrop-filter: blur(10px);
}
.memory-calendar-head,
.memory-calendar-nav {
display: flex;
align-items: center;
}
.memory-calendar-head {
display: grid;
gap: 10px;
margin-bottom: 16px;
}
.memory-calendar-nav {
justify-content: space-between;
gap: 10px;
}
.memory-calendar-nav strong {
min-width: 126px;
text-align: center;
font-size: 18px;
}
.memory-calendar-nav button,
.memory-calendar-today {
min-height: 38px;
border: 1px solid var(--line);
background: #fffdf7;
color: var(--ink);
font: inherit;
font-weight: 800;
cursor: pointer;
transition: border-color 160ms ease, background 160ms ease, color 160ms ease;
}
.memory-calendar-nav button {
width: 38px;
padding: 0;
border-radius: 50%;
}
.memory-calendar-today {
width: 100%;
padding: 7px 13px;
border-radius: 999px;
color: var(--green);
font-size: 13px;
}
.memory-calendar-nav button:hover,
.memory-calendar-today:hover {
border-color: var(--green);
background: var(--green);
color: #fff;
}
.memory-calendar-weekdays,
.memory-calendar-grid {
display: grid;
grid-template-columns: repeat(7, minmax(0, 1fr));
gap: 7px;
}
.memory-calendar-weekdays {
margin-bottom: 7px;
color: var(--muted);
text-align: center;
font-size: 12px;
font-weight: 800;
}
.memory-calendar-day,
.memory-calendar-spacer {
min-width: 0;
aspect-ratio: 1.45;
}
.memory-calendar-day {
position: relative;
display: grid;
place-items: center;
align-content: center;
gap: 3px;
padding: 4px;
border: 1px solid transparent;
border-radius: 9px;
background: rgba(244, 247, 238, 0.72);
color: var(--muted);
font: inherit;
font-size: 13px;
cursor: pointer;
transition: transform 150ms ease, border-color 150ms ease, background 150ms ease, color 150ms ease;
}
.memory-calendar-day:hover {
transform: translateY(-1px);
border-color: rgba(55, 109, 90, 0.3);
color: var(--green);
background: #fff;
}
.memory-calendar-day.has-memory {
color: var(--ink);
background: rgba(232, 168, 76, 0.12);
font-weight: 800;
}
.memory-calendar-day i {
width: 5px;
height: 5px;
border-radius: 50%;
background: var(--coral);
}
.memory-calendar-day.is-today {
border-color: rgba(55, 109, 90, 0.55);
}
.memory-calendar-day.is-selected {
border-color: var(--green);
background: var(--green);
color: #fffdf7;
box-shadow: 0 6px 16px rgba(55, 109, 90, 0.2);
}
.memory-calendar-day.is-selected i {
background: #f3cf8b;
}
.memory-calendar-legend {
display: flex;
align-items: center;
gap: 7px;
margin: 12px 0 0;
color: var(--muted);
font-size: 12px;
}
.memory-calendar-legend > span {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--coral);
}
.memory-grid { .memory-grid {
display: grid; display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -296,7 +412,7 @@ h1 {
.memory-card { .memory-card {
min-height: 190px; min-height: 190px;
display: grid; display: grid;
grid-template-columns: 150px minmax(0, 1fr); grid-template-columns: 120px minmax(0, 1fr);
overflow: hidden; overflow: hidden;
border: 1px solid rgba(31, 43, 42, 0.12); border: 1px solid rgba(31, 43, 42, 0.12);
border-radius: 12px; border-radius: 12px;
@@ -2366,7 +2482,21 @@ h2 {
transform: translate(-50%, 0); transform: translate(-50%, 0);
} }
@media (max-width: 1040px) and (min-width: 821px) {
.memory-grid {
grid-template-columns: 1fr;
}
.memory-calendar {
position: static;
}
}
@media (max-width: 820px) { @media (max-width: 820px) {
.memory-calendar {
position: static;
}
body:has(.score-sidebar) { body:has(.score-sidebar) {
padding-bottom: 86px; padding-bottom: 86px;
} }
@@ -2659,6 +2789,34 @@ h2 {
min-height: 0; min-height: 0;
} }
.memory-calendar {
margin-inline: -4px;
padding: 13px 9px;
}
.memory-calendar-head {
align-items: stretch;
flex-direction: column;
}
.memory-calendar-nav {
justify-content: space-between;
}
.memory-calendar-today {
width: 100%;
}
.memory-calendar-weekdays,
.memory-calendar-grid {
gap: 4px;
}
.memory-calendar-day,
.memory-calendar-spacer {
aspect-ratio: 1;
}
.hero-actions { .hero-actions {
display: grid; display: grid;
} }
@@ -2673,15 +2831,6 @@ h2 {
min-height: auto; min-height: auto;
} }
.memory-date-card {
position: static;
width: 104px;
}
.memory-date-card strong {
font-size: 48px;
}
.memory-grid { .memory-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }