diff --git a/stash-web/src/App.css b/stash-web/src/App.css index 7734ff0..e12a5eb 100644 --- a/stash-web/src/App.css +++ b/stash-web/src/App.css @@ -1,26 +1,41 @@ -/* 页面级样式:只保留「组件库没有」的部分(背景、外壳、封面比例、正文版式)。 - 颜色 / 间距 / 字号全部取自 @stash/ui 的令牌,组件内部样式不要再在这里覆盖。 */ +/* 页面级样式:只保留「组件库没有」的部分(背景、外壳、封面兜底、正文版式)。 + 颜色 / 间距 / 字号全部取自 @stash/ui 的令牌。 */ .app { - background-color: var(--su-bg-page); + position: relative; + z-index: 1; + /* 透明:让固定背景图从下面透出来(Layout 默认有底色) */ + background-color: transparent; } /* 顶部背景图:固定 65vh,底部渐隐到页面底色 */ .site-background { position: fixed; inset: 0 0 auto; - z-index: -1; + z-index: 0; height: 65vh; background: url('./assets/site-bg.jpg') center top / cover no-repeat; - opacity: 0.16; - mask-image: linear-gradient(to bottom, #000 35%, transparent 100%); + pointer-events: none; } +.site-background::after { + content: ''; + position: absolute; + right: 0; + bottom: 0; + left: 0; + height: 80%; + background: linear-gradient(to bottom, transparent, var(--su-bg-page)); +} + +/* ---------- 页头 ---------- */ + .site-header-inner { display: flex; align-items: center; - gap: var(--su-space-6); - height: 64px; + justify-content: space-between; + gap: var(--su-space-4); + height: var(--su-header-height); } .site-brand { @@ -34,10 +49,10 @@ place-items: center; width: 32px; height: 32px; - border-radius: var(--su-radius-sm); - background-color: var(--su-color-primary); + border-radius: var(--su-radius-pill); + background-color: var(--su-text-main); color: var(--su-white); - font-size: var(--su-font-size-md); + font-size: var(--su-font-size-base); font-weight: var(--su-font-weight-strong); } @@ -56,46 +71,50 @@ margin-left: auto; } +/* ---------- 搜索:悬浮在页头下方居中 ---------- */ + +.search-row { + position: fixed; + top: 76px; + left: 50%; + z-index: var(--su-z-header); + width: min(480px, calc(100vw - 48px)); + height: 44px; + transform: translateX(-50%); +} + +/* ---------- 两栏骨架 ---------- */ + .content { display: flex; flex-direction: column; - gap: var(--su-space-4); - padding-top: var(--su-space-2); -} - -/* 搜索框吸在页头下方,滚动时内容从它下面穿过 */ -.search-row { - position: sticky; - top: 64px; - z-index: var(--su-z-header); - padding: var(--su-space-2) 0; - background-color: color-mix(in srgb, var(--su-bg-page) 88%, transparent); - backdrop-filter: blur(8px); + gap: var(--su-space-5); + padding-top: 88px; + padding-bottom: var(--su-space-10); } .sidebar { - position: sticky; - top: 84px; display: flex; flex-direction: column; - gap: var(--su-space-3); - padding: var(--su-space-4) var(--su-space-4) var(--su-space-4) 0; + gap: var(--su-space-4); + padding-top: 88px; } -.profile-card, -.service-panel, -.nav-card { - flex-shrink: 0; -} +/* ---------- 侧栏卡片 ---------- */ .profile-card { display: flex; flex-direction: column; align-items: center; - gap: var(--su-space-2); + gap: 6px; + padding: 18px 14px 14px; text-align: center; } +.service-panel { + padding: 12px 14px; +} + .service-stats { display: flex; flex-direction: column; @@ -107,10 +126,16 @@ max-width: 140px; } +.nav-card { + padding: var(--su-space-2); +} + +/* ---------- 首页分区 ---------- */ + .home-view { display: flex; flex-direction: column; - gap: var(--su-space-4); + gap: var(--su-space-5); } .section-header { @@ -131,10 +156,9 @@ .app-card { display: flex; align-items: center; - gap: var(--su-space-3); - padding: var(--su-space-2); + gap: 15px; + padding: 6px 0; border-radius: var(--su-radius-sm); - transition: background-color var(--su-duration-fast) var(--su-ease-in-out); } .app-card:hover { @@ -148,50 +172,7 @@ min-width: 0; } -/* ---------- 封面卡(海报 / 图文) ---------- */ - -.cover-card { - position: relative; - display: block; - width: 100%; - overflow: hidden; - border-radius: var(--su-radius-sm); - background-color: var(--su-bg-light); - text-align: left; -} - -.cover { - display: block; - width: 100%; - object-fit: cover; - transition: transform var(--su-duration-base) var(--su-ease-in-out); -} - -.cover--poster { - aspect-ratio: 2 / 3; -} - -.cover--article { - aspect-ratio: 4 / 3; -} - -.cover-card:hover .cover { - transform: scale(1.04); -} - -.cover-caption { - position: absolute; - inset: auto 0 0; - display: -webkit-box; - overflow: hidden; - padding: 24px var(--su-space-2) var(--su-space-2); - background: linear-gradient(transparent, rgb(0 0 0 / 62%)); - color: var(--su-white); - font-size: var(--su-font-size-sm); - line-height: var(--su-line-height-sm); - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; -} +/* ---------- 图文白卡 ---------- */ .ces-card { overflow: hidden; @@ -200,6 +181,7 @@ .ces-body { display: flex; flex-direction: column; + gap: var(--su-space-1); padding: var(--su-space-3); text-align: left; } @@ -218,23 +200,63 @@ .detail-view { display: flex; flex-direction: column; - gap: var(--su-space-4); + gap: var(--su-space-5); +} + +.detail-nav { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px var(--su-space-5); +} + +.detail-back { + color: var(--su-color-primary); + font-size: var(--su-font-size-md); + font-weight: var(--su-font-weight-bold); + cursor: pointer; +} + +.detail-back:hover { + opacity: 0.8; } .detail-hero { display: flex; + flex-direction: column; align-items: center; - gap: var(--su-space-3); + padding: 12px 0 4px; + text-align: center; } .detail-hero-icon { - font-size: 24px; + font-size: 40px; line-height: 1; } +.item-card { + display: flex; + flex-direction: column; + align-items: center; + padding: 24px 28px; +} + +.item-title { + justify-content: center; + gap: 6px; +} + .item-edit { - margin-left: var(--su-space-2); + margin-left: var(--su-space-1); + font-size: var(--su-font-size-sm); + color: var(--su-color-primary); vertical-align: middle; + cursor: pointer; +} + +.item-edit:disabled { + cursor: not-allowed; + opacity: 0.6; } .item-cover { @@ -268,14 +290,64 @@ } .item-quote { - padding: var(--su-space-3) var(--su-space-4); - border-left: 2px solid var(--su-color-primary); - border-radius: 0 var(--su-radius-sm) var(--su-radius-sm) 0; - background-color: var(--su-bg-subtle); + display: flex; + gap: 6px; + width: 100%; + margin-top: 18px; + text-align: left; +} + +.item-quote-mark { + font-size: var(--su-font-size-lg); + line-height: 1; + color: var(--su-color-primary); +} + +.item-quote-text { + font-size: var(--su-font-size-md); + line-height: 1.7; + color: var(--su-text-sub); } .item-actions { - margin-top: var(--su-space-4); + display: flex; + align-items: center; + gap: var(--su-space-2); + width: 100%; + margin-top: 18px; +} + +/* 统计小药丸:浏览 / 点赞的底 */ +.item-stat { + min-width: 56px; + height: 36px; + padding: 0 var(--su-space-3); + border-radius: var(--su-radius-cover); + background-color: var(--su-bg-page); +} + +/* 跳转按钮占满剩余宽度 */ +.item-jump { + display: inline-flex; + flex: 1; + align-items: center; + justify-content: center; + gap: 6px; + height: 36px; + border-radius: var(--su-radius-cover); + background-color: var(--su-color-primary); + color: var(--su-white); + font-size: var(--su-font-size-md); + transition: opacity var(--su-duration-fast) var(--su-ease-in-out); +} + +.item-jump:hover:not(:disabled) { + opacity: 0.8; +} + +.item-jump:disabled { + cursor: not-allowed; + opacity: 0.5; } .item-banner { @@ -292,16 +364,28 @@ gap: var(--su-space-2); } -.article-body { - display: flex; - flex-direction: column; - gap: var(--su-space-3); +.article-heading { + max-width: 760px; + font-size: var(--su-font-size-xl); + font-weight: var(--su-font-weight-strong); + line-height: 1.4; } .article-meta-dot { color: var(--su-text-muted); } +.article-body-card { + overflow: hidden; +} + +.article-body { + display: flex; + flex-direction: column; + gap: var(--su-space-3); + padding: 32px 40px 40px; +} + .article-lead { margin-bottom: var(--su-space-2); } @@ -314,12 +398,27 @@ /* ---------- 推荐阅读 ---------- */ -.related-item { +.related-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 14px 24px; + margin-top: var(--su-space-4); +} + +.related-title { display: flex; align-items: center; gap: var(--su-space-2); - padding: var(--su-space-2); - border-radius: var(--su-radius-sm); + margin-bottom: var(--su-space-4); +} + +.related-item { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + padding: 4px; + border-radius: var(--su-radius-cover); text-align: left; transition: background-color var(--su-duration-fast) var(--su-ease-in-out); } @@ -340,6 +439,7 @@ gap: var(--su-space-1); height: 32px; padding: 0 var(--su-space-3); + border: 1px solid var(--su-border); border-radius: var(--su-radius-sm); font-size: var(--su-font-size-sm); color: var(--su-text-sub); @@ -364,11 +464,10 @@ @media (max-width: 1100px) { .sidebar { - position: static; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--su-space-3); - padding: var(--su-space-4); + padding: 0 var(--su-space-4) var(--su-space-4); } .profile-card { @@ -382,6 +481,17 @@ .header-info { display: none; } + + .search-row { + position: static; + width: 100%; + height: 36px; + transform: none; + } + + .content { + padding-top: var(--su-space-4); + } } @media (max-width: 820px) { @@ -389,17 +499,12 @@ display: none; } - .search-row { - top: 60px; - } - .site-header-inner { - height: 60px; + height: 56px; } } @media (prefers-reduced-motion: reduce) { - .cover, .app-card, .related-item, .link-button { diff --git a/stash-web/src/App.tsx b/stash-web/src/App.tsx index b9edf9b..a35899e 100644 --- a/stash-web/src/App.tsx +++ b/stash-web/src/App.tsx @@ -241,7 +241,16 @@ export default function App() { + return } diff --git a/stash-web/src/components/content/AppGrid.tsx b/stash-web/src/components/content/AppGrid.tsx index 76cd1f5..1154ade 100644 --- a/stash-web/src/components/content/AppGrid.tsx +++ b/stash-web/src/components/content/AppGrid.tsx @@ -2,7 +2,7 @@ import { Grid } from '@stash/ui' import type { AppItem } from '../../data/types' import { AppCard } from './AppCard' -/** 应用网格:默认 4 列,平板 2 列,手机 2 列(组件库 Grid 负责断点) */ +/** 应用网格:桌面 4 列,平板 / 手机 2 列(断点由组件库 Grid 负责) */ type AppGridProps = { items: AppItem[] /** 明细页使用更宽松的间距 */ @@ -13,7 +13,12 @@ type AppGridProps = { export function AppGrid({ items, variant = 'home', onOpen }: AppGridProps) { return ( - + {items.map((item) => ( ))} diff --git a/stash-web/src/components/content/ArticleBody.tsx b/stash-web/src/components/content/ArticleBody.tsx index c0a3565..9d2388a 100644 --- a/stash-web/src/components/content/ArticleBody.tsx +++ b/stash-web/src/components/content/ArticleBody.tsx @@ -123,7 +123,7 @@ export function ArticleBody({ markdown }: ArticleBodyProps) { } return ( - + {renderInline(block.text)} ) diff --git a/stash-web/src/components/content/ArticleCard.tsx b/stash-web/src/components/content/ArticleCard.tsx index 90d71e3..f96f8eb 100644 --- a/stash-web/src/components/content/ArticleCard.tsx +++ b/stash-web/src/components/content/ArticleCard.tsx @@ -1,3 +1,4 @@ +import { Cover } from '@stash/ui' import type { ArticleItem } from '../../data/types' /** @@ -10,20 +11,13 @@ type ArticleCardProps = { } export function ArticleCard({ item, onOpen }: ArticleCardProps) { - const content = ( - <> - {item.title} - {item.title} - - ) - - if (onOpen === undefined) { - return
{content}
- } - return ( - + onOpen(item.id)} + /> ) } diff --git a/stash-web/src/components/content/ArticleDetailView.tsx b/stash-web/src/components/content/ArticleDetailView.tsx index 5867f30..8d998cc 100644 --- a/stash-web/src/components/content/ArticleDetailView.tsx +++ b/stash-web/src/components/content/ArticleDetailView.tsx @@ -1,4 +1,4 @@ -import { Button, Card, Paragraph, Space, Text, Title } from '@stash/ui' +import { Card, Paragraph, Space, Text, Title } from '@stash/ui' import type { ArticleItem } from '../../data/types' import { ArticleBody } from './ArticleBody' @@ -20,23 +20,23 @@ export function ArticleDetailView({ article, categoryTitle, onBack }: ArticleDet return (
- + - - + + {categoryTitle} / 文章
- + <Title level={1} as="h2" className="article-heading"> {article.title} - <Button variant="ghost" size="sm" disabled className="item-edit"> - 编辑 - </Button> + <button type="button" className="item-edit" disabled title="编辑功能待接入"> + [编辑] + </button> @@ -55,9 +55,9 @@ export function ArticleDetailView({ article, categoryTitle, onBack }: ArticleDet
- +
- + {article.desc} diff --git a/stash-web/src/components/content/ArticleGrid.tsx b/stash-web/src/components/content/ArticleGrid.tsx index ee40345..59e7d75 100644 --- a/stash-web/src/components/content/ArticleGrid.tsx +++ b/stash-web/src/components/content/ArticleGrid.tsx @@ -11,7 +11,7 @@ type ArticleGridProps = { export function ArticleGrid({ items, onOpen }: ArticleGridProps) { return ( - + {items.map((item) => ( ))} diff --git a/stash-web/src/components/content/CesCard.tsx b/stash-web/src/components/content/CesCard.tsx index 635a9b9..2d46829 100644 --- a/stash-web/src/components/content/CesCard.tsx +++ b/stash-web/src/components/content/CesCard.tsx @@ -1,4 +1,4 @@ -import { Card, Space, Text } from '@stash/ui' +import { Card, Cover, Space, Text } from '@stash/ui' import type { ArticleItem } from '../../data/types' /** @@ -24,7 +24,14 @@ export function CesCard({ item, onOpen }: CesCardProps) { return ( - {item.title} + onOpen(item.id)} + /> {onOpen === undefined ? (
{body}
) : ( diff --git a/stash-web/src/components/content/CesCardGrid.tsx b/stash-web/src/components/content/CesCardGrid.tsx index 7fa649f..d7f4234 100644 --- a/stash-web/src/components/content/CesCardGrid.tsx +++ b/stash-web/src/components/content/CesCardGrid.tsx @@ -11,7 +11,7 @@ type CesCardGridProps = { export function CesCardGrid({ items, onOpen }: CesCardGridProps) { return ( - + {items.map((item) => ( ))} diff --git a/stash-web/src/components/content/DetailView.tsx b/stash-web/src/components/content/DetailView.tsx index 15749f3..d1dfb41 100644 --- a/stash-web/src/components/content/DetailView.tsx +++ b/stash-web/src/components/content/DetailView.tsx @@ -1,4 +1,4 @@ -import { Badge, Breadcrumb, Button, Card, Empty, Space, Title } from '@stash/ui' +import { Badge, Breadcrumb, Card, Empty, Space, Title } from '@stash/ui' import { categoryCount, type Category } from '../../data/types' import { AppGrid } from './AppGrid' import { CesCardGrid } from './CesCardGrid' @@ -70,11 +70,11 @@ export function DetailView({ category, groupId, onBack, onOpenItem }: DetailView return (
- + - + diff --git a/stash-web/src/components/content/ItemDetailView.tsx b/stash-web/src/components/content/ItemDetailView.tsx index fc76bc8..d8c9e78 100644 --- a/stash-web/src/components/content/ItemDetailView.tsx +++ b/stash-web/src/components/content/ItemDetailView.tsx @@ -1,4 +1,4 @@ -import { Button, Card, Icon, IconButton, Paragraph, Space, Tag, Text, Title } from '@stash/ui' +import { Card, Icon, IconButton, Paragraph, Space, Tag, Text, Title } from '@stash/ui' import type { ItemDetailModel } from '../../data/detail' import type { RelatedItem } from '../../data/types' import { RelatedList } from './RelatedList' @@ -27,23 +27,25 @@ export function ItemDetailView({ }: ItemDetailViewProps) { return (
- + - - - {breadcrumb} - + + + + {breadcrumb} + + - - + <Card padding="none" className="item-card"> + <Title level={2} className="item-title"> {model.title} - <Button variant="ghost" size="sm" disabled className="item-edit"> - 编辑 - </Button> + <button type="button" className="item-edit" disabled title="编辑功能待接入"> + [编辑] + </button>
@@ -57,7 +59,7 @@ export function ItemDetailView({ {model.tags.length > 0 && ( {model.tags.map((tag) => ( - + {tag} ))} @@ -65,9 +67,12 @@ export function ItemDetailView({ )} {model.comment !== undefined && ( - - {model.comment} - +

+ + {model.comment} +

)} {model.paragraphs.map((paragraph, index) => ( @@ -76,36 +81,36 @@ export function ItemDetailView({ ))} - - +
+ - + {model.views} - + {model.likes} - } /> + } /> {/* 占位数据没有真实地址时,按钮置灰而不是打开空白页 */} {model.href === '#' ? ( - + ) : ( - + 跳转到 )} - +
{model.banner !== undefined && ( diff --git a/stash-web/src/components/content/PosterCard.tsx b/stash-web/src/components/content/PosterCard.tsx index fc1d713..bc7823a 100644 --- a/stash-web/src/components/content/PosterCard.tsx +++ b/stash-web/src/components/content/PosterCard.tsx @@ -1,3 +1,4 @@ +import { Cover } from '@stash/ui' import type { PosterItem } from '../../data/types' /** @@ -10,20 +11,13 @@ type PosterCardProps = { } export function PosterCard({ item, onOpen }: PosterCardProps) { - const content = ( - <> - {item.title} - {item.title} - - ) - - if (onOpen === undefined) { - return
{content}
- } - return ( - + onOpen(item.id)} + /> ) } diff --git a/stash-web/src/components/content/PosterGrid.tsx b/stash-web/src/components/content/PosterGrid.tsx index 82ea9b6..1a5e26e 100644 --- a/stash-web/src/components/content/PosterGrid.tsx +++ b/stash-web/src/components/content/PosterGrid.tsx @@ -12,7 +12,7 @@ type PosterGridProps = { export function PosterGrid({ items, onOpen }: PosterGridProps) { return ( - + {items.map((item) => ( ))} diff --git a/stash-web/src/components/content/RelatedList.tsx b/stash-web/src/components/content/RelatedList.tsx index 5c0d841..9c3cc04 100644 --- a/stash-web/src/components/content/RelatedList.tsx +++ b/stash-web/src/components/content/RelatedList.tsx @@ -14,12 +14,13 @@ export function RelatedList({ items, onOpen }: RelatedListProps) { if (items.length === 0) return null return ( - - + <Card padding="md" className="related-card"> + <Title level={3} as="h3" className="related-title"> + <span aria-hidden="true">📚</span> 推荐阅读 - + {items.map((item) => (