-
常读标签
-
-
+
+
+
+
@@ -142,4 +194,78 @@ onMounted(async () => {
color: var(--faint);
font-size: 11px;
}
+
+/* 页脚小字区:无卡片边框,Twitter 式侧栏页脚。classic 的页脚信息只在这里
+ 出现(通栏 ClassicFoot 已删):右栏在窄屏会堆到信息流下方,卡片之后
+ 正好接页脚小字,所有宽度都不缺位。 */
+.rail-foot {
+ padding: 4px 6px 0;
+ font-size: 12px;
+ line-height: 1.9;
+ color: var(--faint);
+}
+
+.foot-links a {
+ color: var(--faint);
+ transition: color 0.15s;
+}
+
+.foot-links a:hover {
+ color: var(--accent);
+}
+
+.dot {
+ margin: 0 4px;
+ opacity: 0.6;
+}
+
+/* 图标 + 版权同排:源码等入口是单色小图标,hover 才亮成强调色 */
+.social-row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px;
+ margin-top: 2px;
+}
+
+.foot-meta {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ /* 项目信息行和上面的社交账号排拉开一截:它们是两类信息 */
+ margin-top: 16px;
+}
+
+.social-icon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 22px;
+ height: 22px;
+ padding: 0 3px;
+ border-radius: 6px;
+ color: var(--faint);
+ font-size: 12px;
+ transition: color 0.15s;
+}
+
+.social-icon:hover {
+ color: var(--accent);
+}
+
+.social-icon svg {
+ width: 16px;
+ height: 16px;
+ fill: currentColor;
+}
+
+.copyright {
+ color: var(--faint);
+}
+
+/* 窄屏:底部留白加高,右下角浮动主题钮压不到页脚小字 */
+@media (max-width: 780px) {
+ .rail-right {
+ padding-bottom: 96px;
+ }
+}
diff --git a/frontend/src/components/TopBar.vue b/frontend/src/components/TopBar.vue
index 108b7e2..211350d 100644
--- a/frontend/src/components/TopBar.vue
+++ b/frontend/src/components/TopBar.vue
@@ -1,18 +1,127 @@
-
-
+
+
{{ site.site_title || 'ONE' }}
-
+
+
+
+
+
+
+
+
+
+
@@ -23,16 +132,28 @@ import { site } from '../site'
@media (max-width: 780px) {
.topbar {
- display: flex;
- align-items: baseline;
- gap: 14px;
+ /* 三列网格:brand 落在正中列居中,汉堡钉死右列;下滑整条上移隐藏 */
+ display: grid;
+ grid-template-columns: 44px 1fr 44px;
+ align-items: center;
position: sticky;
top: 0;
z-index: 20;
- padding: 10px 18px;
+ padding: 9px 12px;
background: color-mix(in srgb, var(--paper) 80%, transparent);
backdrop-filter: blur(10px) saturate(160%);
border-bottom: 1px solid var(--line);
+ transition: transform 0.32s var(--ease, ease);
+ }
+
+ .topbar.hidden {
+ transform: translateY(-110%);
+ }
+
+ @media (prefers-reduced-motion: reduce) {
+ .topbar {
+ transition: none;
+ }
}
@media (prefers-reduced-transparency: reduce) {
@@ -43,19 +164,105 @@ import { site } from '../site'
}
.brand {
+ grid-column: 2;
+ justify-self: center;
font-family: var(--serif);
font-size: 17px;
+ color: var(--ink);
+ }
+
+ .burger {
+ grid-column: 3;
+ justify-self: end;
+ }
+
+ /* 幕布在自己的顶栏层叠上下文里,z -1 垫在 brand / 汉堡 / 纸片下面,
+ 但整个上下文(z 20)仍盖过页面内容 */
+ .scrim {
+ position: fixed;
+ inset: 0;
+ z-index: -1;
+ background: color-mix(in srgb, var(--ink) 24%, transparent);
+ }
+
+ /* 浮动纸片:贴着顶栏下方悬一层,和右栏卡片同一套「纸」的语言 */
+ .sheet {
+ position: absolute;
+ top: calc(100% + 6px);
+ left: 8px;
+ right: 8px;
+ padding: 6px;
+ border: 1px solid var(--line);
+ border-radius: 14px;
+ background: color-mix(in srgb, var(--paper) 96%, transparent);
+ backdrop-filter: blur(20px) saturate(160%);
+ box-shadow: 0 18px 50px -18px rgba(28, 28, 30, 0.35);
+ }
+
+ @media (prefers-reduced-transparency: reduce) {
+ .sheet {
+ background: var(--paper);
+ backdrop-filter: none;
+ }
}
.links {
display: flex;
- gap: 12px;
- font-size: 13px;
- color: var(--muted);
+ flex-direction: column;
+ padding: 2px;
}
- .links a.router-link-exact-active {
+ .links a {
+ display: block;
+ padding: 12px 14px;
+ border-radius: 10px;
+ font-size: 15px;
+ font-weight: 600;
+ text-align: center;
+ color: var(--ink-soft);
+ transition: background-color 0.2s, color 0.2s;
+ }
+
+ .links a.on {
color: var(--accent);
+ background: var(--accent-soft);
+ }
+
+ .links a:active {
+ color: var(--accent);
+ background: var(--accent-soft);
+ }
+
+ .c-sheet-enter-active {
+ transition: opacity 0.22s ease, transform 0.3s cubic-bezier(0.22, 1.2, 0.36, 1);
+ }
+ .c-sheet-enter-from {
+ opacity: 0;
+ transform: translateY(-8px) scale(0.98);
+ }
+ .c-sheet-leave-active {
+ transition: opacity 0.15s ease, transform 0.15s ease;
+ }
+ .c-sheet-leave-to {
+ opacity: 0;
+ transform: translateY(-6px);
+ }
+ .c-scrim-enter-active,
+ .c-scrim-leave-active {
+ transition: opacity 0.25s ease;
+ }
+ .c-scrim-enter-from,
+ .c-scrim-leave-to {
+ opacity: 0;
+ }
+
+ @media (prefers-reduced-motion: reduce) {
+ .c-sheet-enter-active,
+ .c-sheet-leave-active,
+ .c-scrim-enter-active,
+ .c-scrim-leave-active {
+ transition: none;
+ }
}
}
diff --git a/frontend/src/components/comments/CommentItem.vue b/frontend/src/components/comments/CommentItem.vue
index 27607a1..3bb4094 100644
--- a/frontend/src/components/comments/CommentItem.vue
+++ b/frontend/src/components/comments/CommentItem.vue
@@ -1,7 +1,9 @@
+
+
+
+
+
+
+
+
+
+
+ {{ g.label }}
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/comments/emoji.js b/frontend/src/components/comments/emoji.js
new file mode 100644
index 0000000..bcbc848
--- /dev/null
+++ b/frontend/src/components/comments/emoji.js
@@ -0,0 +1,58 @@
+// 评论表情选择的数据(仅前端用,后端只存 Markdown 文本,emoji 就是普通字符)。
+// emoji 手挑常用的一百来个,分组只是为了在面板里好找;颜文字选能打的经典款。
+// 刻意不引第三方 emoji 库:面板是静态数据 + 两列 tab,一个依赖都不值得加。
+
+export const EMOJI_GROUPS = [
+ {
+ label: '笑脸',
+ items: [
+ '😀', '😃', '😄', '😁', '😆', '😅', '😂', '🤣',
+ '😊', '😇', '🙂', '😉', '😍', '🥰', '😘', '😗',
+ '😙', '😚', '😋', '😜', '🤪', '🤨', '🧐', '🤓'
+ ]
+ },
+ {
+ label: '心情',
+ items: [
+ '😎', '🥳', '😏', '😒', '😞', '😔', '😟', '😕',
+ '🙁', '☹️', '😣', '😖', '😫', '😩', '🥺', '😢',
+ '😭', '😤', '😠', '😡', '🤬', '😱', '😨', '😰'
+ ]
+ },
+ {
+ label: '状态',
+ items: [
+ '🤔', '🤭', '🤫', '🤥', '😶', '😐', '😑', '😬',
+ '🙄', '😯', '😦', '😧', '😮', '😲', '🥱', '😴',
+ '🤤', '😪', '😵', '🤐', '🤢', '🤮', '🤧', '🤒'
+ ]
+ },
+ {
+ label: '手势',
+ items: [
+ '👍', '👎', '👊', '✊', '🤛', '🤜', '✌️', '🤞',
+ '🤟', '🤘', '🤙', '👌', '🤌', '👈', '👉', '👆',
+ '👇', '☝️', '✋', '🤚', '🖐️', '🖖', '👋', '🤝'
+ ]
+ },
+ {
+ label: '其他',
+ items: [
+ '🙏', '💪', '👀', '🧠', '❤️', '🧡', '💛', '💚',
+ '💙', '💜', '🖤', '🤍', '💯', '💢', '💥', '💫',
+ '💦', '💨', '🎉', '🎊', '🎁', '🌸', '🌙', '⭐',
+ '✨', '🔥', '💡', '📚', '✏️', '🎮', '🎵', '🚀'
+ ]
+ }
+]
+
+// 颜文字一行一个;反斜杠记得转义
+export const KAOMOJI = [
+ '(。◕‿◕。)', 'ヾ(≧▽≦*)o', '(^▽^)', '( ̄▽ ̄)', '(*^▽^*)', 'o(≧∇≦)o',
+ '٩(๑❛ᴗ❛๑)۶', '✧(≖ ◡ ≖✿)', '(๑•̀ㅂ•́)و✧', '( •̀ᴗ•́ )و ̑̑', '( ¬‿¬)', '( ˘▽˘ )っ♨',
+ '(´,,•ω•,,)♡', '(。•́‿•̀。)', '(๑•́ ₃ •̀๑)', '♡(˃͈ દ ˂͈ ༶ )', '(´・ω・`)', '(・ω・)',
+ '¯\\_(ツ)_/¯', '┐(´д`)┌', '╮(╯▽╰)╭', '( ̄_ ̄|||)', 'o_O', '( ^_^ )',
+ '(╥﹏╥)', '(ㄒoㄒ)', 'ಥ_ಥ', '(ToT)', 'QAQ', 'T_T',
+ '(╯°□°)╯︵ ┻━┻', '(╬ Ò﹏Ó)', '┬─┬ ノ( ゜-゜ノ)', 'orz', '_(:з」∠)_', 'ᕕ( ᐛ )ᕗ',
+ '(っ˘̩╭╮˘̩)っ', '(ง •_•)ง'
+]
diff --git a/frontend/src/customJs.js b/frontend/src/customJs.js
new file mode 100644
index 0000000..3b84ea9
--- /dev/null
+++ b/frontend/src/customJs.js
@@ -0,0 +1,43 @@
+import { site } from './site'
+
+// 站主自定义 JS 的注入管线(Google Analytics / Plausible / Umami 这类统计脚本)。
+//
+// 后台贴的是完整的