---
// 首页自定义 Hero:左文案 + 安装命令,右真实录屏(VHS GIF)装进终端窗口框。
// 用原生 <img>(而非 astro:assets <Image>)以保留 GIF 动画。
import readGif from "../../assets/guides/read.gif";
const base = "/TRNovel";
---
<section class="tn-hero">
<div class="copy">
<h1>在终端里,<span class="accent">优雅地</span><span class="nowrap">读小说</span></h1>
<p class="sub">
本地 TXT 与网络书源双修,AI 一键生成书源,还能 TTS 听书。单二进制,开箱即用。
</p>
<div class="actions">
<a class="btn primary" href={`${base}/guides/intro`}>快速开始</a>
<a class="btn ghost" href="https://github.com/yexiyue/TRNovel">GitHub</a>
</div>
<button class="install" type="button" data-cmd="cargo install trnovel" aria-label="复制安装命令">
<code><span class="prompt">$</span> cargo install trnovel</code>
<span class="hint" data-idle>复制</span>
<span class="hint" data-done>已复制</span>
</button>
</div>
<figure class="window">
<div class="bar">
<span class="dot red"></span>
<span class="dot yellow"></span>
<span class="dot green"></span>
<span class="title">trn</span>
</div>
<div class="crop">
<img src={readGif.src} width={readGif.width} height={readGif.height} alt="TRNovel 阅读界面演示" loading="eager" />
</div>
</figure>
</section>
<script>
for (const btn of document.querySelectorAll<HTMLButtonElement>(".install")) {
btn.addEventListener("click", async () => {
const cmd = btn.dataset.cmd;
if (!cmd) return;
try {
await navigator.clipboard.writeText(cmd);
btn.classList.add("copied");
setTimeout(() => btn.classList.remove("copied"), 1600);
} catch {
}
});
}
</script>
<style>
.tn-hero {
display: grid;
gap: 2.5rem;
align-items: center;
padding-block: 2.5rem 1rem;
}
@media (min-width: 960px) {
.tn-hero {
grid-template-columns: 11fr 13fr;
gap: 3.5rem;
padding-block: 4rem 2rem;
}
}
h1 {
margin: 0;
font-size: clamp(2.2rem, 5vw, 3.4rem);
line-height: 1.12;
letter-spacing: -0.03em;
color: var(--sl-color-white);
}
.accent {
color: var(--tn-accent);
}
.nowrap {
white-space: nowrap;
}
.sub {
margin: 1.1rem 0 0;
font-size: 1.05rem;
line-height: 1.7;
color: var(--sl-color-gray-2);
max-width: 36ch;
}
.actions {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin-top: 1.75rem;
}
.btn {
display: inline-flex;
align-items: center;
padding: 0.6rem 1.5rem;
border-radius: 12px;
font-weight: 600;
font-size: 0.95rem;
text-decoration: none;
transition:
transform 0.15s ease,
border-color 0.15s ease,
background-color 0.15s ease;
}
.btn:active {
transform: scale(0.98);
}
.btn.primary {
background: var(--tn-accent);
color: var(--tn-accent-contrast);
}
.btn.primary:hover {
transform: translateY(-1px);
}
.btn.ghost {
border: 1px solid var(--tn-border);
color: var(--sl-color-white);
}
.btn.ghost:hover {
border-color: var(--tn-accent);
}
.install {
display: inline-flex;
align-items: center;
gap: 1rem;
margin-top: 1.25rem;
padding: 0.55rem 1rem;
border: 1px solid var(--tn-border);
border-radius: 12px;
background: var(--tn-surface);
cursor: pointer;
font-family: var(--tn-mono);
font-size: 0.85rem;
color: var(--sl-color-white);
transition: border-color 0.15s ease;
}
.install:hover {
border-color: var(--tn-accent);
}
.install code {
background: none;
padding: 0;
}
.prompt {
color: var(--tn-accent);
margin-inline-end: 0.4em;
}
.hint {
font-size: 0.75rem;
color: var(--sl-color-gray-3);
}
.hint[data-done] {
display: none;
color: var(--tn-accent);
}
.install.copied .hint[data-idle] {
display: none;
}
.install.copied .hint[data-done] {
display: inline;
}
.window {
margin: 0;
border: 1px solid #30363d;
border-radius: 12px;
overflow: hidden;
background: #0d1117;
box-shadow: 0 24px 60px -28px rgb(13 17 23 / 0.45);
}
.bar {
display: flex;
align-items: center;
gap: 7px;
padding: 10px 14px;
background: #161b22;
border-bottom: 1px solid #30363d;
}
.dot {
width: 10px;
height: 10px;
border-radius: 50%;
}
.dot.red {
background: #ff5f57;
}
.dot.yellow {
background: #febc2e;
}
.dot.green {
background: #28c840;
}
.title {
margin-inline-start: 8px;
font-family: var(--tn-mono);
font-size: 12px;
color: #8b949e;
}
.crop {
overflow: hidden;
background: #1e1e2e;
}
.crop img {
display: block;
width: 100%;
height: auto;
}
@media (prefers-reduced-motion: no-preference) {
.copy > *,
.window {
animation: tn-rise 0.6s cubic-bezier(0.16, 1, 0.3, 1) both;
}
.copy > :nth-child(2) {
animation-delay: 0.06s;
}
.copy > :nth-child(3) {
animation-delay: 0.12s;
}
.copy > :nth-child(4) {
animation-delay: 0.18s;
}
.window {
animation-delay: 0.15s;
}
}
@keyframes tn-rise {
from {
opacity: 0;
transform: translateY(18px);
}
}
</style>