首屏彩蛋:那句话出自王小波《万寿寺》,可跳豆瓣
交互(默认完全看不出可点,保持留白): - 悬停/聚焦:整句话由墨黑转朱红 + 朱红细下划线,署名随之淡入 - 点那句话 → 直接去豆瓣人物页 - 点署名 → 展开一张极简作者卡片,卡片里也能去豆瓣 结构上的几个考量: - 用 <h1><a> 而不是把 h1 变成链接:保住标题语义,点击热区只包住文字 而不是整行 1024px(点旁边空白也跳走会很怪) - 光标 ::after 挂在 h1 上(链接外面),点那个闪烁的竖线不会触发跳转 - 署名占一行固定高度(2.25rem)平时透明,hover 才淡入 —— 空间一直留着, 所以展开卡片时下面的按钮不会往上跳 - 卡片是手风琴式展开(max-height 过渡),留在文档流里,不盖住下面的按钮 - @media (hover: none) 下署名常显 —— 否则手机上这个彩蛋永远看不到 - aria-expanded / aria-controls / aria-label + focus-within 可见焦点, 键盘和读屏用户都能用 - prefers-reduced-motion 下关掉所有过渡 出处核实:多个豆瓣《万寿寺》原文摘录页都收录这句,确认为王小波《万寿寺》。 豆瓣链接在 curl 下会跳 sec.douban.com(反爬),真实浏览器可正常打开。 验证:pnpm build 退出码 0;dist/index.html 里豆瓣链接出现 2 次、 h1 内为 <a>、署名与卡片均在、卡片默认 data-open="false"; 产物 CSS 含 16 条 hero-quote 规则(含 @media(hover:none))
This commit is contained in:
+60
-7
@@ -9,6 +9,9 @@ import { site } from "@/data/site"
|
|||||||
* 原来那四句是 14/11/4/4 字,长短差 3.5 倍,只能靠自适应字号兜底,
|
* 原来那四句是 14/11/4/4 字,长短差 3.5 倍,只能靠自适应字号兜底,
|
||||||
* 结果是每换一句首屏最大的字就跳 1.5 倍。
|
* 结果是每换一句首屏最大的字就跳 1.5 倍。
|
||||||
*/
|
*/
|
||||||
|
/** 王小波的豆瓣人物页。点击那句话或卡片里的链接都去这里。 */
|
||||||
|
const DOUBAN_URL = "https://www.douban.com/personage/27480512/"
|
||||||
|
|
||||||
const phrases = [
|
const phrases = [
|
||||||
{ prefix: "一个人只拥有此生此世", highlight: "是不够的," },
|
{ prefix: "一个人只拥有此生此世", highlight: "是不够的," },
|
||||||
{ prefix: "他还需要一个", highlight: "诗意的世界。" },
|
{ prefix: "他还需要一个", highlight: "诗意的世界。" },
|
||||||
@@ -31,6 +34,8 @@ const HERO_SIZE_CLASSES = "text-[clamp(1.125rem,5.6vw,4rem)]"
|
|||||||
|
|
||||||
export function Hero() {
|
export function Hero() {
|
||||||
const [phraseIndex, setPhraseIndex] = useState(0)
|
const [phraseIndex, setPhraseIndex] = useState(0)
|
||||||
|
/** 「作者卡片」展开状态:点署名展开,再点收起 */
|
||||||
|
const [cardOpen, setCardOpen] = useState(false)
|
||||||
const [displayText, setDisplayText] = useState("")
|
const [displayText, setDisplayText] = useState("")
|
||||||
const [isDeleting, setIsDeleting] = useState(false)
|
const [isDeleting, setIsDeleting] = useState(false)
|
||||||
// Pause the typewriter loop when the hero is offscreen or the tab is hidden;
|
// Pause the typewriter loop when the hero is offscreen or the tab is hidden;
|
||||||
@@ -159,14 +164,62 @@ export function Hero() {
|
|||||||
{site.tagline}
|
{site.tagline}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* 光标是 CSS ::after 画的一个竖线字符,见 styles/global.css 的 .hero-typewriter */}
|
{/*
|
||||||
<h1
|
彩蛋:这句话出自王小波《万寿寺》。
|
||||||
className={`hero-typewriter font-sans ${HERO_SIZE_CLASSES} font-bold tracking-tight text-foreground leading-[1.15] mb-12 min-h-[1.2em] whitespace-nowrap`}
|
- 悬停/聚焦:整句话由墨黑变朱红,下方淡入署名(无 hover 的设备常显署名)
|
||||||
>
|
- 点那句话 → 直接去豆瓣人物页
|
||||||
{renderText()}
|
- 点署名 → 展开一张极简卡片,卡片里也能去豆瓣
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
|
结构上用 <h1><a> 而不是把 h1 本身变成链接:保住标题语义,
|
||||||
|
而且点击热区只包住文字,不会连整行空白一起可点。
|
||||||
|
光标 ::after 挂在 h1 上(链接外面),点光标不会触发跳转。
|
||||||
|
*/}
|
||||||
|
<div className="hero-quote">
|
||||||
|
<h1
|
||||||
|
className={`hero-typewriter font-sans ${HERO_SIZE_CLASSES} font-bold tracking-tight text-foreground leading-[1.15] min-h-[1.2em] whitespace-nowrap`}
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
className="hero-quote-link"
|
||||||
|
href={DOUBAN_URL}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
aria-label="这句话出自王小波《万寿寺》,点击查看他的豆瓣主页"
|
||||||
|
>
|
||||||
|
{renderText()}
|
||||||
|
</a>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
{/* 固定高度的一行,内容 hover 才淡入 —— 这样展开卡片时按钮不会跳 */}
|
||||||
|
<div className="hero-quote-credit-row">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="hero-quote-credit"
|
||||||
|
onClick={() => setCardOpen((open) => !open)}
|
||||||
|
aria-expanded={cardOpen}
|
||||||
|
aria-controls="hero-quote-card"
|
||||||
|
>
|
||||||
|
—— 王小波 ·《万寿寺》
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="hero-quote-card" className="hero-quote-card" data-open={cardOpen}>
|
||||||
|
<div className="hero-quote-card-inner">
|
||||||
|
<span className="hero-quote-card-name">
|
||||||
|
王小波<span className="hero-quote-card-years">1952–1997</span>
|
||||||
|
</span>
|
||||||
|
<a
|
||||||
|
className="hero-quote-card-link"
|
||||||
|
href={DOUBAN_URL}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
豆瓣主页 <span aria-hidden="true">→</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||||
<a
|
<a
|
||||||
href="#home-main"
|
href="#home-main"
|
||||||
onClick={handleBrowseClick}
|
onClick={handleBrowseClick}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"updatedAt": "2026-09-16T09:46:47.801Z",
|
"updatedAt": "2026-09-16T10:03:27.302Z",
|
||||||
"source": "weread",
|
"source": "weread",
|
||||||
"books": [
|
"books": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -802,6 +802,133 @@ a.card-github:hover .github-logo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ——— 首屏彩蛋:那句话出自王小波《万寿寺》 ———
|
||||||
|
*
|
||||||
|
* 默认完全看不出可点。悬停/聚焦时整句话由墨黑转朱红、浮出细下划线,
|
||||||
|
* 署名随之淡入。点那句话直接去豆瓣;点署名展开一张极简作者卡片。
|
||||||
|
*/
|
||||||
|
|
||||||
|
.hero-quote {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-quote-link {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 240ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-quote:hover .hero-quote-link,
|
||||||
|
.hero-quote:focus-within .hero-quote-link {
|
||||||
|
color: var(--primary);
|
||||||
|
text-decoration: underline;
|
||||||
|
text-decoration-thickness: 2px;
|
||||||
|
text-underline-offset: 0.14em;
|
||||||
|
text-decoration-color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 署名占一行固定高度:平时透明,hover 才淡入。
|
||||||
|
空间一直留着,所以展开卡片时下面的按钮不会往上跳。 */
|
||||||
|
.hero-quote-credit-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 2.25rem;
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-quote-credit {
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border: 0;
|
||||||
|
background: none;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
color: var(--muted-foreground);
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-0.25rem);
|
||||||
|
transition: opacity 240ms ease, transform 240ms ease, color 200ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-quote:hover .hero-quote-credit,
|
||||||
|
.hero-quote:focus-within .hero-quote-credit,
|
||||||
|
.hero-quote-credit[aria-expanded="true"] {
|
||||||
|
opacity: 1;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-quote-credit:hover {
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 没有 hover 的设备(手机/平板)署名常显 —— 否则这个彩蛋永远看不到 */
|
||||||
|
@media (hover: none) {
|
||||||
|
.hero-quote-credit {
|
||||||
|
opacity: 1;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 作者卡片:手风琴式展开,留在文档流里,不会盖住下面的按钮 */
|
||||||
|
.hero-quote-card {
|
||||||
|
max-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
transition: max-height 320ms cubic-bezier(0.22, 1, 0.36, 1), opacity 240ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-quote-card[data-open="true"] {
|
||||||
|
max-height: 8rem;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-quote-card-inner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
max-width: 21rem;
|
||||||
|
margin: 0.5rem auto 0;
|
||||||
|
padding: 0.7rem 1rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
background-color: var(--card);
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-quote-card-name {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-quote-card-years {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
font-weight: 400;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
color: var(--muted-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-quote-card-link {
|
||||||
|
white-space: nowrap;
|
||||||
|
color: var(--primary);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: opacity 200ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-quote-card-link:hover {
|
||||||
|
opacity: 0.72;
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: 0.16em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.hero-quote-link,
|
||||||
|
.hero-quote-credit,
|
||||||
|
.hero-quote-card {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ——— 偶得 (thoughts) view ——— */
|
/* ——— 偶得 (thoughts) view ——— */
|
||||||
@keyframes thoughts-fade-up {
|
@keyframes thoughts-fade-up {
|
||||||
from { opacity: 0; transform: translateY(8px); }
|
from { opacity: 0; transform: translateY(8px); }
|
||||||
|
|||||||
Reference in New Issue
Block a user