feat: 首页门面 Feed 密度与侧栏分组

对齐 SPA 信息密度:头像/摘要/互动、高级搜索收起、浏览/板块分区。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-30 05:55:45 +08:00
parent e0683ef262
commit 6e7b46a2e9
10 changed files with 415 additions and 75 deletions

View File

@@ -10,15 +10,15 @@
| 项 | 值 | | 项 | 值 |
|----|-----| |----|-----|
| **上一刀** | 青主题产品化重绘 + 静态资源 `?v=` 版本号 | | **上一刀** | 首页门面精修Feed 头像/摘要/互动 + 高级搜索收起 + 侧栏分组) |
| **下一刀** | 需点名(移动侧栏抽屉 / OIDC Admin / S3 / Gitea / 表格表情…) | | **下一刀** | 需点名(移动侧栏抽屉 / 帖详情抛光 / OIDC Admin / S3… |
| **工作区** | 应干净 | | **工作区** | 应干净 |
--- ---
## 一句话状态 ## 一句话状态
核心论坛、Admin、Limits/伪静态、嵌套评论、Markdown 编辑器、主题、公开页青主题产品壳与资源版本号已齐;本分支 `routers/api` 仅保留机器入口。余量多为后置与体验打磨。 核心论坛、Admin、Limits/伪静态、嵌套评论、Markdown 编辑器、主题、清青产品壳与资源版本号、首页门面密度已齐;本分支 `routers/api` 仅保留机器入口。余量多为后置与体验打磨。
--- ---
@@ -32,7 +32,7 @@
| Markdown 工具栏 + preview | 已迁 | | Markdown 工具栏 + preview | 已迁 |
| 浅色 / 暗色 / 跟随系统 | 已迁CSS 媒体查询,无 head 防闪脚本) | | 浅色 / 暗色 / 跟随系统 | 已迁CSS 媒体查询,无 head 防闪脚本) |
| 未挂载论坛 JSON API | 已删(对照 `main` | | 未挂载论坛 JSON API | 已删(对照 `main` |
| 公开页设计系统 | 已迁(清青产品 chrome + `?v=`[10-design-system.md](10-design-system.md) | | 公开页设计系统 | 已迁(清青 chrome + `?v=` + 首页 Feed 门面密度 |
--- ---

View File

@@ -82,16 +82,18 @@
| 组件 | 规则 | | 组件 | 规则 |
|------|------| |------|------|
| 顶栏 | 品牌 \| 搜索条 \| 发帖主按钮 + 主题 + 私信 + 用户 +「更多」菜单;告别链接墙 | | 顶栏 | 品牌 \| 搜索条 \|「+ 发帖主按钮 + 主题 + 私信 + 用户 +「更多」;告别链接墙 |
| 侧栏链 | smactive = soft 底 + 左边青条 | | 左栏 | 分区「浏览 / 板块」active = soft 底 + 左边青条 |
| Feed | 排序分段控件;搜索扁平工具条;细线列表;回复数右对齐强调 | | Feed 行 | 头像 · 作者+相对时间 / 标题 / 摘要(最多 2 行)/ 标签 · 右栏回复/赞/阅读;细线分隔非卡片墙 |
| 标签 / 按钮 / 评论 / 门控 | 同青 token列表仍非卡片墙 | | 高级搜索 | `<details>` 默认收起;有筛选条件或错误时展开;顶栏搜索仍 `/?keyword=` |
| 排序 | 分段控件 |
| 标签 / 按钮 / 评论 / 门控 | 同青 token |
--- ---
## 5. 范围边界 ## 5. 范围边界
- **覆盖**:公开页壳层、资源 `?v=`、浅/暗色 - **覆盖**:公开页壳层(含首页门面密度)、资源 `?v=`、浅/暗色
- **不覆盖**Admin 深重绘、移动抽屉、虚拟滚动、TipTap、改站名 - **不覆盖**Admin 深重绘、移动抽屉、虚拟滚动、TipTap、改站名
交叉:[06-pages-ux.md](06-pages-ux.md) · [09-ssr-progress.md](09-ssr-progress.md) 交叉:[06-pages-ux.md](06-pages-ux.md) · [09-ssr-progress.md](09-ssr-progress.md)

View File

@@ -303,14 +303,21 @@ html[data-theme="dark"] .j13-brand__mark {
background: transparent; background: transparent;
color: var(--j13-muted); color: var(--j13-muted);
border-radius: var(--j13-radius); border-radius: var(--j13-radius);
padding: 0.3rem 0.55rem; padding: 0.3rem 0.5rem;
font: inherit; font: inherit;
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 500; font-weight: 500;
cursor: pointer; cursor: pointer;
line-height: 1.3; line-height: 1.3;
display: inline-flex;
align-items: center;
gap: 0.3rem;
transition: color var(--j13-ease), border-color var(--j13-ease), background-color var(--j13-ease); transition: color var(--j13-ease), border-color var(--j13-ease), background-color var(--j13-ease);
} }
.j13-theme-toggle__icon {
flex-shrink: 0;
opacity: 0.9;
}
.j13-theme-toggle:hover { .j13-theme-toggle:hover {
color: var(--j13-accent-ink); color: var(--j13-accent-ink);
border-color: var(--j13-accent); border-color: var(--j13-accent);
@@ -415,6 +422,16 @@ html[data-theme="dark"] .j13-brand__mark {
position: sticky; position: sticky;
top: calc(var(--j13-header-h) + 0.75rem); top: calc(var(--j13-header-h) + 0.75rem);
} }
.j13-aside__section {
margin: 0.85rem 0 0.35rem;
padding: 0 0.55rem;
font-size: 0.7rem;
font-weight: 650;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--j13-muted);
}
.j13-aside__section:first-child { margin-top: 0.15rem; }
.j13-aside__link { .j13-aside__link {
padding: 0.45rem 0.6rem; padding: 0.45rem 0.6rem;
border-radius: var(--j13-radius); border-radius: var(--j13-radius);
@@ -450,7 +467,7 @@ html[data-theme="dark"] .j13-brand__mark {
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 0.75rem 1rem; gap: 0.75rem 1rem;
margin-bottom: 0.75rem; margin-bottom: 0.65rem;
} }
.j13-feed__title { .j13-feed__title {
margin: 0; margin: 0;
@@ -485,13 +502,44 @@ html[data-theme="dark"] .j13-brand__mark {
background: var(--j13-hover); background: var(--j13-hover);
} }
.j13-search-advanced {
margin: 0 0 0.75rem;
scroll-margin-top: 4.5rem;
}
.j13-search-advanced__summary {
list-style: none;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 0.35rem;
color: var(--j13-muted);
font-size: 0.8125rem;
font-weight: 500;
padding: 0.25rem 0;
user-select: none;
}
.j13-search-advanced__summary::-webkit-details-marker { display: none; }
.j13-search-advanced__summary::before {
content: "";
width: 0.4rem;
height: 0.4rem;
border-right: 1.5px solid currentColor;
border-bottom: 1.5px solid currentColor;
transform: rotate(-45deg);
transition: transform var(--j13-ease);
}
.j13-search-advanced[open] > .j13-search-advanced__summary::before {
transform: rotate(45deg);
}
.j13-search-advanced__summary:hover { color: var(--j13-accent); }
.j13-search-advanced .j13-search { margin-top: 0.45rem; }
.j13-search { .j13-search {
margin: 0 0 0.85rem; margin: 0 0 0.85rem;
padding: 0; padding: 0;
border: 0; border: 0;
border-radius: 0; border-radius: 0;
background: transparent; background: transparent;
scroll-margin-top: 4.5rem;
} }
.j13-search--toolbar .j13-search__row { .j13-search--toolbar .j13-search__row {
display: flex; display: flex;
@@ -541,38 +589,114 @@ html[data-theme="dark"] .j13-brand__mark {
.j13-post-list { list-style: none; margin: 0; padding: 0; } .j13-post-list { list-style: none; margin: 0; padding: 0; }
.j13-post-item { .j13-post-item {
padding: 0.85rem 0.4rem; display: grid;
margin: 0 -0.4rem; grid-template-columns: 2.5rem minmax(0, 1fr) auto;
gap: 0.75rem 0.85rem;
padding: 1rem 0.45rem;
margin: 0 -0.45rem;
border-top: 1px solid var(--j13-border); border-top: 1px solid var(--j13-border);
border-radius: var(--j13-radius); border-radius: var(--j13-radius);
transition: background-color var(--j13-ease); transition: background-color var(--j13-ease);
align-items: start;
} }
.j13-post-item:first-child { border-top: 0; } .j13-post-item:first-child { border-top: 0; }
.j13-post-item:hover { background: var(--j13-hover); } .j13-post-item:hover { background: var(--j13-hover); }
.j13-post-item__avatar {
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
overflow: hidden;
flex-shrink: 0;
background: var(--j13-soft);
}
.j13-post-item__avatar img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.j13-post-item__mark {
width: 100%;
height: 100%;
display: grid;
place-items: center;
font-weight: 700;
font-size: 0.95rem;
color: var(--j13-accent-ink);
background: var(--j13-soft);
}
.j13-post-item__body { min-width: 0; }
.j13-post-item__byline {
display: flex;
flex-wrap: wrap;
gap: 0.4rem 0.65rem;
align-items: baseline;
font-size: 0.75rem;
color: var(--j13-muted);
margin-bottom: 0.2rem;
}
.j13-post-item__byline a {
color: var(--j13-accent-ink);
text-decoration: none;
font-weight: 600;
}
.j13-post-item__byline a:hover { color: var(--j13-accent); }
.j13-post-item__title { .j13-post-item__title {
display: inline-block;
color: var(--j13-text); color: var(--j13-text);
text-decoration: none; text-decoration: none;
font-weight: 700; font-weight: 700;
font-size: 1.15rem; font-size: 1.1rem;
letter-spacing: -0.02em; letter-spacing: -0.02em;
line-height: 1.35; line-height: 1.35;
transition: color var(--j13-ease); transition: color var(--j13-ease);
} }
.j13-post-item__title:hover { color: var(--j13-accent); } .j13-post-item__title:hover { color: var(--j13-accent); }
.j13-post-item__excerpt {
margin: 0.3rem 0 0;
font-size: 0.875rem;
line-height: 1.5;
color: var(--j13-muted);
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.j13-post-item__meta { .j13-post-item__meta {
margin-top: 0.35rem; margin-top: 0.4rem;
font-size: 0.75rem; font-size: 0.75rem;
color: var(--j13-muted); color: var(--j13-muted);
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 0.45rem; gap: 0.4rem;
align-items: center; align-items: center;
} }
.j13-post-item__replies { .j13-post-item__stats {
margin-left: auto; display: flex;
flex-direction: column;
gap: 0.2rem;
align-items: flex-end;
font-size: 0.72rem;
color: var(--j13-muted);
font-variant-numeric: tabular-nums;
padding-top: 0.15rem;
white-space: nowrap;
}
.j13-post-item__stats strong {
font-weight: 650; font-weight: 650;
color: var(--j13-accent-ink); color: var(--j13-accent-ink);
font-variant-numeric: tabular-nums; }
@media (max-width: 700px) {
.j13-post-item {
grid-template-columns: 2.25rem minmax(0, 1fr);
}
.j13-post-item__stats {
grid-column: 2;
flex-direction: row;
gap: 0.75rem;
align-items: center;
justify-content: flex-start;
}
} }
.j13-widget__title { .j13-widget__title {

View File

@@ -27,9 +27,15 @@ document.documentElement.dataset.j13Ssr = "1";
} catch (e) {} } catch (e) {}
var btn = document.getElementById("j13-theme-toggle"); var btn = document.getElementById("j13-theme-toggle");
if (btn) { if (btn) {
btn.textContent = LABELS[pref] || LABELS.system; var label = btn.querySelector(".j13-theme-toggle__label");
btn.setAttribute("aria-label", "主题:" + (LABELS[pref] || "")); var text = LABELS[pref] || LABELS.system;
btn.title = "当前:" + (LABELS[pref] || "") + "(点击切换)"; if (label) {
label.textContent = text;
} else {
btn.textContent = text;
}
btn.setAttribute("aria-label", "主题:" + text);
btn.title = "当前:" + text + "(点击切换)";
} }
} }

View File

@@ -169,11 +169,18 @@ type PostListItem struct {
ID uint ID uint
Title string Title string
Href string Href string
AuthorID uint
AuthorName string AuthorName string
AuthorHref string
AvatarURL string
AuthorMark string // 无头像时的单字占位
BoardName string BoardName string
Excerpt string
Pinned bool Pinned bool
Featured bool Featured bool
CommentCount int CommentCount int
LikeCount int
ViewCount int
CreatedLabel string CreatedLabel string
} }
@@ -329,10 +336,26 @@ func (d Deps) Home(c *gin.Context) {
if it.Board.ID > 0 { if it.Board.ID > 0 {
bname = it.Board.Name bname = it.Board.Name
} }
excerptSrc := strings.TrimSpace(it.ContentPlain)
if excerptSrc == "" {
excerptSrc = services.ExcerptFromHTML(it.Content, 100)
} else {
excerptSrc = services.TruncateRunes(excerptSrc, 100)
}
avatar := strings.TrimSpace(it.User.Avatar)
authorID := it.UserID
authorHref := ""
if authorID > 0 {
authorHref = pl.UserPath(authorID)
}
posts = append(posts, PostListItem{ posts = append(posts, PostListItem{
ID: it.ID, Title: it.Title, Href: pl.PostPath(it.ID), AuthorName: authorName, BoardName: bname, ID: it.ID, Title: it.Title, Href: pl.PostPath(it.ID),
Pinned: it.Pinned, Featured: it.Featured, CommentCount: it.CommentCount, AuthorID: authorID, AuthorName: authorName, AuthorHref: authorHref,
CreatedLabel: it.CreatedAt.Local().Format("2006-01-02 15:04"), AvatarURL: avatar, AuthorMark: firstRuneOr(authorName, "姜"),
BoardName: bname, Excerpt: excerptSrc,
Pinned: it.Pinned, Featured: it.Featured,
CommentCount: it.CommentCount, LikeCount: it.LikeCount, ViewCount: it.ViewCount,
CreatedLabel: formatRelativeTime(it.CreatedAt),
}) })
} }
@@ -353,3 +376,29 @@ func normalizeSort(s string) string {
func formatTime(t time.Time) string { func formatTime(t time.Time) string {
return t.Local().Format("2006-01-02 15:04") return t.Local().Format("2006-01-02 15:04")
} }
// formatRelativeTime 列表用相对时间
func formatRelativeTime(t time.Time) string {
if t.IsZero() {
return ""
}
now := time.Now()
d := now.Sub(t)
if d < 0 {
d = 0
}
switch {
case d < time.Minute:
return "刚刚"
case d < time.Hour:
return fmt.Sprintf("%d 分钟前", int(d.Minutes()))
case d < 24*time.Hour:
return fmt.Sprintf("%d 小时前", int(d.Hours()))
case d < 48*time.Hour:
return "昨天"
case d < 30*24*time.Hour:
return fmt.Sprintf("%d 天前", int(d.Hours()/24))
default:
return t.Local().Format("2006-01-02")
}
}

View File

@@ -20,15 +20,20 @@
</form> </form>
<div class="j13-header__actions"> <div class="j13-header__actions">
{{if .LoggedIn}} {{if .LoggedIn}}
<a class="j13-btn j13-btn--compose" href="/compose">发帖</a> <a class="j13-btn j13-btn--compose" href="/compose">+ 发帖</a>
{{else}} {{else}}
<a class="j13-btn j13-btn--compose" href="/login">登录</a> <a class="j13-btn j13-btn--compose" href="/login">登录</a>
<a class="j13-header__textlink" href="/register">注册</a> <a class="j13-header__textlink" href="/register">注册</a>
{{end}} {{end}}
<button type="button" class="j13-theme-toggle" id="j13-theme-toggle" aria-label="切换主题" title="切换主题">主题</button> <button type="button" class="j13-theme-toggle" id="j13-theme-toggle" aria-label="切换主题" title="切换主题">
<svg class="j13-theme-toggle__icon" width="16" height="16" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path fill="currentColor" d="M12 3a1 1 0 0 1 1 1v1.06a7.5 7.5 0 1 1-2 0V4a1 1 0 0 1 1-1Zm0 4.5a5.5 5.5 0 1 0 0 11 5.5 5.5 0 0 0 0-11Z"/>
</svg>
<span class="j13-theme-toggle__label">主题</span>
</button>
{{if .LoggedIn}} {{if .LoggedIn}}
<a class="j13-header__iconlink" href="/messages" title="私信">私信{{if gt .UnreadCount 0}} <span class="j13-badge">{{.UnreadCount}}</span>{{end}}</a> <a class="j13-header__iconlink" href="/messages" title="私信">私信{{if gt .UnreadCount 0}} <span class="j13-badge">{{.UnreadCount}}</span>{{end}}</a>
<a class="j13-header__user" href="/profile" title="个人中心">{{.ViewerName}} · {{.ViewerPoints}}</a> <a class="j13-header__user" href="/profile" title="个人中心">{{.ViewerName}}</a>
{{end}} {{end}}
<details class="j13-more"> <details class="j13-more">
<summary>更多</summary> <summary>更多</summary>

View File

@@ -8,7 +8,9 @@
<a class="{{if eq .Sort "hot"}}is-active{{end}}" href="{{.SortHref "hot"}}">热门讨论</a> <a class="{{if eq .Sort "hot"}}is-active{{end}}" href="{{.SortHref "hot"}}">热门讨论</a>
</div> </div>
</header> </header>
<form id="search" class="j13-search j13-search--toolbar" method="get" action="{{.FormAction}}" role="search"> <details class="j13-search-advanced" id="search"{{if or .HasSearch .TitleOnly .SearchError}} open{{end}}>
<summary class="j13-search-advanced__summary">高级搜索</summary>
<form class="j13-search j13-search--toolbar" method="get" action="{{.FormAction}}" role="search">
{{if and .Sort (ne .Sort "latest")}}<input type="hidden" name="sort" value="{{.Sort}}"/>{{end}} {{if and .Sort (ne .Sort "latest")}}<input type="hidden" name="sort" value="{{.Sort}}"/>{{end}}
<div class="j13-search__row"> <div class="j13-search__row">
<label class="j13-search__field"> <label class="j13-search__field">
@@ -31,20 +33,37 @@
</div> </div>
{{if .SearchError}}<p class="j13-search__err" role="alert">{{.SearchError}}</p>{{end}} {{if .SearchError}}<p class="j13-search__err" role="alert">{{.SearchError}}</p>{{end}}
</form> </form>
</details>
{{if not .Posts}} {{if not .Posts}}
<p class="j13-empty">{{if .SearchError}}请调整搜索条件后重试。{{else if .HasSearch}}未找到匹配帖子。{{else}}暂无帖子。{{end}}</p> <p class="j13-empty">{{if .SearchError}}请调整搜索条件后重试。{{else if .HasSearch}}未找到匹配帖子。{{else}}暂无帖子。{{end}}</p>
{{else}} {{else}}
<ul class="j13-post-list"> <ul class="j13-post-list">
{{range .Posts}} {{range .Posts}}
<li class="j13-post-item"> <li class="j13-post-item">
<div class="j13-post-item__avatar">
{{if .AvatarURL}}
<img src="{{.AvatarURL}}" alt="" width="40" height="40"/>
{{else}}
<span class="j13-post-item__mark" aria-hidden="true">{{.AuthorMark}}</span>
{{end}}
</div>
<div class="j13-post-item__body">
<div class="j13-post-item__byline">
{{if .AuthorHref}}<a href="{{.AuthorHref}}">{{.AuthorName}}</a>{{else}}<span>{{.AuthorName}}</span>{{end}}
<span class="j13-post-item__time">{{.CreatedLabel}}</span>
</div>
<a class="j13-post-item__title" href="{{.Href}}"{{if $.OpenPostsInNewTab}} target="_blank" rel="noopener"{{end}}>{{.Title}}</a> <a class="j13-post-item__title" href="{{.Href}}"{{if $.OpenPostsInNewTab}} target="_blank" rel="noopener"{{end}}>{{.Title}}</a>
{{if .Excerpt}}<p class="j13-post-item__excerpt">{{.Excerpt}}</p>{{end}}
<div class="j13-post-item__meta"> <div class="j13-post-item__meta">
{{if .Pinned}}<span class="j13-tag j13-tag--pin">置顶</span>{{end}} {{if .Pinned}}<span class="j13-tag j13-tag--pin">置顶</span>{{end}}
{{if .Featured}}<span class="j13-tag j13-tag--feat">精华</span>{{end}} {{if .Featured}}<span class="j13-tag j13-tag--feat">精华</span>{{end}}
{{if .BoardName}}<span class="j13-tag">{{.BoardName}}</span>{{end}} {{if .BoardName}}<span class="j13-tag">{{.BoardName}}</span>{{end}}
<span>{{.AuthorName}}</span> </div>
<span>{{.CreatedLabel}}</span> </div>
<span class="j13-post-item__replies">{{.CommentCount}} 回复</span> <div class="j13-post-item__stats" aria-label="互动">
<span title="回复"><strong>{{.CommentCount}}</strong> 回复</span>
<span title="赞"><strong>{{.LikeCount}}</strong> 赞</span>
<span title="阅读"><strong>{{.ViewCount}}</strong> 阅读</span>
</div> </div>
</li> </li>
{{end}} {{end}}

View File

@@ -1,7 +1,12 @@
{{define "shared/board_aside"}} {{define "shared/board_aside"}}
<aside class="j13-aside j13-aside--left"> <aside class="j13-aside j13-aside--left">
<p class="j13-aside__section">浏览</p>
<a class="j13-aside__link{{if and (eq .ActiveBoard 0) (ne .Path "/boards") (ne .Path "/favorites")}} is-active{{end}}" href="/">全部帖子</a>
{{if .LoggedIn}}
<a class="j13-aside__link{{if eq .Path "/favorites"}} is-active{{end}}" href="/favorites">我的收藏</a>
{{end}}
<a class="j13-aside__link{{if eq .Path "/boards"}} is-active{{end}}" href="/boards">全部板块</a> <a class="j13-aside__link{{if eq .Path "/boards"}} is-active{{end}}" href="/boards">全部板块</a>
<a class="j13-aside__link{{if and (eq .ActiveBoard 0) (ne .Path "/boards")}} is-active{{end}}" href="/">全部帖子</a> <p class="j13-aside__section">板块</p>
{{range .Boards}} {{range .Boards}}
<a class="j13-aside__link{{if eq $.ActiveBoard .ID}} is-active{{end}}" href="{{.Href}}">{{.Name}}</a> <a class="j13-aside__link{{if eq $.ActiveBoard .ID}} is-active{{end}}" href="{{.Href}}">{{.Name}}</a>
{{end}} {{end}}

View File

@@ -303,14 +303,21 @@ html[data-theme="dark"] .j13-brand__mark {
background: transparent; background: transparent;
color: var(--j13-muted); color: var(--j13-muted);
border-radius: var(--j13-radius); border-radius: var(--j13-radius);
padding: 0.3rem 0.55rem; padding: 0.3rem 0.5rem;
font: inherit; font: inherit;
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 500; font-weight: 500;
cursor: pointer; cursor: pointer;
line-height: 1.3; line-height: 1.3;
display: inline-flex;
align-items: center;
gap: 0.3rem;
transition: color var(--j13-ease), border-color var(--j13-ease), background-color var(--j13-ease); transition: color var(--j13-ease), border-color var(--j13-ease), background-color var(--j13-ease);
} }
.j13-theme-toggle__icon {
flex-shrink: 0;
opacity: 0.9;
}
.j13-theme-toggle:hover { .j13-theme-toggle:hover {
color: var(--j13-accent-ink); color: var(--j13-accent-ink);
border-color: var(--j13-accent); border-color: var(--j13-accent);
@@ -415,6 +422,16 @@ html[data-theme="dark"] .j13-brand__mark {
position: sticky; position: sticky;
top: calc(var(--j13-header-h) + 0.75rem); top: calc(var(--j13-header-h) + 0.75rem);
} }
.j13-aside__section {
margin: 0.85rem 0 0.35rem;
padding: 0 0.55rem;
font-size: 0.7rem;
font-weight: 650;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--j13-muted);
}
.j13-aside__section:first-child { margin-top: 0.15rem; }
.j13-aside__link { .j13-aside__link {
padding: 0.45rem 0.6rem; padding: 0.45rem 0.6rem;
border-radius: var(--j13-radius); border-radius: var(--j13-radius);
@@ -450,7 +467,7 @@ html[data-theme="dark"] .j13-brand__mark {
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 0.75rem 1rem; gap: 0.75rem 1rem;
margin-bottom: 0.75rem; margin-bottom: 0.65rem;
} }
.j13-feed__title { .j13-feed__title {
margin: 0; margin: 0;
@@ -485,13 +502,44 @@ html[data-theme="dark"] .j13-brand__mark {
background: var(--j13-hover); background: var(--j13-hover);
} }
.j13-search-advanced {
margin: 0 0 0.75rem;
scroll-margin-top: 4.5rem;
}
.j13-search-advanced__summary {
list-style: none;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 0.35rem;
color: var(--j13-muted);
font-size: 0.8125rem;
font-weight: 500;
padding: 0.25rem 0;
user-select: none;
}
.j13-search-advanced__summary::-webkit-details-marker { display: none; }
.j13-search-advanced__summary::before {
content: "";
width: 0.4rem;
height: 0.4rem;
border-right: 1.5px solid currentColor;
border-bottom: 1.5px solid currentColor;
transform: rotate(-45deg);
transition: transform var(--j13-ease);
}
.j13-search-advanced[open] > .j13-search-advanced__summary::before {
transform: rotate(45deg);
}
.j13-search-advanced__summary:hover { color: var(--j13-accent); }
.j13-search-advanced .j13-search { margin-top: 0.45rem; }
.j13-search { .j13-search {
margin: 0 0 0.85rem; margin: 0 0 0.85rem;
padding: 0; padding: 0;
border: 0; border: 0;
border-radius: 0; border-radius: 0;
background: transparent; background: transparent;
scroll-margin-top: 4.5rem;
} }
.j13-search--toolbar .j13-search__row { .j13-search--toolbar .j13-search__row {
display: flex; display: flex;
@@ -541,38 +589,114 @@ html[data-theme="dark"] .j13-brand__mark {
.j13-post-list { list-style: none; margin: 0; padding: 0; } .j13-post-list { list-style: none; margin: 0; padding: 0; }
.j13-post-item { .j13-post-item {
padding: 0.85rem 0.4rem; display: grid;
margin: 0 -0.4rem; grid-template-columns: 2.5rem minmax(0, 1fr) auto;
gap: 0.75rem 0.85rem;
padding: 1rem 0.45rem;
margin: 0 -0.45rem;
border-top: 1px solid var(--j13-border); border-top: 1px solid var(--j13-border);
border-radius: var(--j13-radius); border-radius: var(--j13-radius);
transition: background-color var(--j13-ease); transition: background-color var(--j13-ease);
align-items: start;
} }
.j13-post-item:first-child { border-top: 0; } .j13-post-item:first-child { border-top: 0; }
.j13-post-item:hover { background: var(--j13-hover); } .j13-post-item:hover { background: var(--j13-hover); }
.j13-post-item__avatar {
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
overflow: hidden;
flex-shrink: 0;
background: var(--j13-soft);
}
.j13-post-item__avatar img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.j13-post-item__mark {
width: 100%;
height: 100%;
display: grid;
place-items: center;
font-weight: 700;
font-size: 0.95rem;
color: var(--j13-accent-ink);
background: var(--j13-soft);
}
.j13-post-item__body { min-width: 0; }
.j13-post-item__byline {
display: flex;
flex-wrap: wrap;
gap: 0.4rem 0.65rem;
align-items: baseline;
font-size: 0.75rem;
color: var(--j13-muted);
margin-bottom: 0.2rem;
}
.j13-post-item__byline a {
color: var(--j13-accent-ink);
text-decoration: none;
font-weight: 600;
}
.j13-post-item__byline a:hover { color: var(--j13-accent); }
.j13-post-item__title { .j13-post-item__title {
display: inline-block;
color: var(--j13-text); color: var(--j13-text);
text-decoration: none; text-decoration: none;
font-weight: 700; font-weight: 700;
font-size: 1.15rem; font-size: 1.1rem;
letter-spacing: -0.02em; letter-spacing: -0.02em;
line-height: 1.35; line-height: 1.35;
transition: color var(--j13-ease); transition: color var(--j13-ease);
} }
.j13-post-item__title:hover { color: var(--j13-accent); } .j13-post-item__title:hover { color: var(--j13-accent); }
.j13-post-item__excerpt {
margin: 0.3rem 0 0;
font-size: 0.875rem;
line-height: 1.5;
color: var(--j13-muted);
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.j13-post-item__meta { .j13-post-item__meta {
margin-top: 0.35rem; margin-top: 0.4rem;
font-size: 0.75rem; font-size: 0.75rem;
color: var(--j13-muted); color: var(--j13-muted);
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 0.45rem; gap: 0.4rem;
align-items: center; align-items: center;
} }
.j13-post-item__replies { .j13-post-item__stats {
margin-left: auto; display: flex;
flex-direction: column;
gap: 0.2rem;
align-items: flex-end;
font-size: 0.72rem;
color: var(--j13-muted);
font-variant-numeric: tabular-nums;
padding-top: 0.15rem;
white-space: nowrap;
}
.j13-post-item__stats strong {
font-weight: 650; font-weight: 650;
color: var(--j13-accent-ink); color: var(--j13-accent-ink);
font-variant-numeric: tabular-nums; }
@media (max-width: 700px) {
.j13-post-item {
grid-template-columns: 2.25rem minmax(0, 1fr);
}
.j13-post-item__stats {
grid-column: 2;
flex-direction: row;
gap: 0.75rem;
align-items: center;
justify-content: flex-start;
}
} }
.j13-widget__title { .j13-widget__title {

View File

@@ -27,9 +27,15 @@ document.documentElement.dataset.j13Ssr = "1";
} catch (e) {} } catch (e) {}
var btn = document.getElementById("j13-theme-toggle"); var btn = document.getElementById("j13-theme-toggle");
if (btn) { if (btn) {
btn.textContent = LABELS[pref] || LABELS.system; var label = btn.querySelector(".j13-theme-toggle__label");
btn.setAttribute("aria-label", "主题:" + (LABELS[pref] || "")); var text = LABELS[pref] || LABELS.system;
btn.title = "当前:" + (LABELS[pref] || "") + "(点击切换)"; if (label) {
label.textContent = text;
} else {
btn.textContent = text;
}
btn.setAttribute("aria-label", "主题:" + text);
btn.title = "当前:" + text + "(点击切换)";
} }
} }