Files
hermes-ui/packages/client/src/styles/global.scss
T
toor 189270e046 carry the neon language into the sidebar and page header
The nav still used the ink palette: flat fills, oversized rows, and a
tinted background with no marker for the active route. Adopt the CYBER
treatment from the prototype — a 2px glowing cyan bar on the left of the
active item, hairline borders instead of full-contrast ones, monospace
group labels with wide tracking, and a faint cyan wash at the top of the
rail so it does not read as a dead dark slab.

PageSidebarNav gets the same active treatment as AppSidebar so the
conversation routes do not feel like a different product.

Header height stays at 64px — style-system.test.ts asserts it as a
layout contract.
2026-10-19 14:38:08 +08:00

681 lines
19 KiB
SCSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@use 'variables' as *;
@use 'code-block';
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
button,
input,
select,
textarea {
font-family: inherit;
}
:root {
--vh: 1vh;
}
// Fix mobile viewport height (address bar)
@supports (height: 100dvh) {
:root {
--vh: 1dvh;
}
}
// ─── Arco Design token bridge ──────────────────────────────────
// arco-theme.ts 不是运行时来源(没有 ConfigProvider),Arco 的实际外观由 arco.css
// 里的 CSS 变量决定。这里把 CYBER 令牌翻译成 Arco 变量。
//
// 关键:Arco 有两套命名。--primary-6 是 rgb 三元组,--color-primary 是颜色值。
// a-button / a-tag / a-switch 的底色走 rgb(var(--primary-6)),也就是
// --primary-6 → --arcoblue-6 这一族;只覆盖 --color-primary 不会改变按钮底色。
// 两套都必须覆盖,否则会残留 Arco 蓝(22,93,255)。
// 变量表见 node_modules/@arco-design/web-vue/dist/arco.css 的 :root 块。
html[arco-theme='dark'],
:root {
// Arco 的 rgb 三元组族 —— 实心控件(按钮底、开关滑块、进度条)的真正来源。
// 霓虹青 oklch(0.805 0.148 188) ≈ rgb(45,212,191),按亮度排 1→10。
--arcoblue-1: 12, 58, 56;
--arcoblue-2: 16, 84, 78;
--arcoblue-3: 22, 118, 108;
--arcoblue-4: 28, 148, 135;
--arcoblue-5: 34, 172, 156;
--arcoblue-6: 45, 212, 191;
--arcoblue-7: 70, 229, 209;
--arcoblue-8: 120, 239, 224;
--arcoblue-9: 172, 246, 238;
--arcoblue-10: 218, 250, 246;
--green-6: 47, 197, 138;
--green-5: 56, 184, 130;
--green-7: 38, 178, 122;
--orange-6: 226, 175, 32;
--orange-5: 214, 165, 34;
--orange-7: 236, 190, 44;
--red-6: 232, 78, 61;
--red-5: 222, 74, 58;
--red-7: 240, 96, 78;
// Arco 的颜色值族 —— 描边、文字、浅底
--color-primary-light-1: oklch(0.30 0.060 188);
--color-primary-light-2: oklch(0.39 0.075 188);
--color-primary-light-3: oklch(0.50 0.095 188);
--color-primary-light-4: oklch(0.60 0.115 188);
--color-primary-light-5: oklch(0.68 0.130 188);
--color-primary-light-6: oklch(0.805 0.148 188);
--color-primary: oklch(0.805 0.148 188);
--color-primary-dark-1: oklch(0.760 0.144 188);
--color-primary-dark-2: oklch(0.700 0.138 188);
--color-primary-dark-3: oklch(0.640 0.130 188);
--color-primary-dark-4: oklch(0.580 0.120 188);
--color-primary-dark-5: oklch(0.520 0.108 188);
--color-primary-dark-6: oklch(0.460 0.094 188);
--color-success-light-1: oklch(0.28 0.050 165);
--color-success-light-2: oklch(0.42 0.078 165);
--color-success-light-3: oklch(0.58 0.108 165);
--color-success: oklch(0.760 0.140 165);
--color-warning-light-1: oklch(0.30 0.052 85);
--color-warning-light-2: oklch(0.46 0.082 85);
--color-warning-light-3: oklch(0.62 0.112 85);
--color-warning: oklch(0.810 0.145 85);
--color-danger-light-1: oklch(0.28 0.062 22);
--color-danger-light-2: oklch(0.42 0.096 22);
--color-danger-light-3: oklch(0.56 0.138 22);
--color-danger: oklch(0.665 0.185 22);
--color-link-light-1: oklch(0.30 0.060 188);
--color-link-light-2: oklch(0.42 0.084 188);
--color-link-light-3: oklch(0.54 0.110 188);
--color-link-light-4: oklch(0.66 0.136 188);
--color-link-light-5: oklch(0.74 0.148 188);
--color-link-light-6: oklch(0.805 0.148 188);
--color-link: oklch(0.805 0.148 188);
// 容器与文字
--color-text-1: oklch(0.945 0.006 200);
--color-text-2: oklch(0.860 0.008 210);
--color-text-3: oklch(0.705 0.014 230);
--color-text-4: oklch(0.520 0.014 236);
--color-text-disabled: oklch(0.420 0.012 238);
--color-bg-1: oklch(0.155 0.012 250);
--color-bg-2: oklch(0.130 0.012 250);
--color-bg-3: oklch(0.185 0.014 248);
--color-bg-4: oklch(0.225 0.016 246);
--color-bg-5: oklch(0.255 0.018 247);
--color-fill-1: oklch(0.185 0.014 248);
--color-fill-2: oklch(0.225 0.016 246);
--color-fill-3: oklch(0.255 0.018 247);
--color-fill-4: oklch(0.285 0.018 246);
--color-border-1: oklch(0.305 0.018 242);
--color-border-2: oklch(0.255 0.016 244);
--color-border-3: oklch(0.225 0.014 246);
--color-border-4: oklch(0.195 0.014 248);
--color-fill-2-hover: oklch(0.255 0.018 247);
--color-fill-3-hover: oklch(0.285 0.018 246);
--radius-small: 4px;
--radius-medium: 6px;
--radius-large: 8px;
--font-size-body-1: 14px;
--font-size-body-2: 13px;
--font-size-body-3: 12px;
// CYBER 的核心排版特征:标题、页头、数据用等宽
--font-family: #{$font-ui};
--font-family-body: #{$font-ui};
--font-family-code: #{$font-code};
}
// Arco 暗色态:强制走暗色变量(属性挂在 <html> 上,Arco 官方选择器可能匹配 body)
body {
color: $text-primary;
background-color: $bg-primary;
}
// Arco 组件里的滚动条与焦点环对齐 CYBER
.arco-scrollbar-thumb {
background-color: $border-color !important;
}
// ─── Neon tube buttons ─────────────────────────────────────────
// CYBER 的按钮是「霓虹灯管」而不是实心色块:底色透明 / 内部暗,只有 1px 青色管壁
// 与外发光。hover / active 时管壁增亮、光晕收紧,模拟通电。
// 这里直接覆盖 Arco 的按钮外观类,因为 a-button 的默认样式来自 arco.css,
// token 桥接改不到「实心 vs 镂空」这一层。
$neon: var(--accent-primary);
$neon-hv: var(--accent-hover);
// 镂空管壁 + 双层外发光(内紧外散)
@mixin neon-tube($glow: 0.32, $wall: 1px) {
position: relative;
overflow: hidden;
color: $neon;
background: transparent;
border: $wall solid rgba(var(--accent-primary-rgb), 0.55);
box-shadow:
inset 0 0 12px rgba(var(--accent-primary-rgb), $glow * 0.35),
0 0 10px rgba(var(--accent-primary-rgb), $glow),
0 0 22px rgba(var(--accent-primary-rgb), $glow * 0.5);
text-shadow: 0 0 8px rgba(var(--accent-primary-rgb), 0.45);
}
// 扫光:一道白光从左扫到右。周期大部分时间停在暗处,只在 62%→86% 之间走一趟,
// 避免常驻动画造成视觉噪音。
@keyframes neon-sheen {
0%, 62% { transform: translateX(-140%); }
86%, 100% { transform: translateX(320%); }
}
@mixin neon-sheen() {
&::after {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 38%;
background: linear-gradient(90deg,
transparent,
oklch(1 0 0 / 0.28),
transparent);
transform: translateX(-140%);
animation: neon-sheen 3.4s ease-in-out infinite;
pointer-events: none;
}
}
// Arco 为按钮写了 `.arco-btn-primary[type='button']` / `[type='submit']` / `[type='reset']`
// 三个属性选择器变体,把特异性从 (0,1,0) 抬到 (0,2,0);它的 hover 变体则是
// `.arco-btn-primary[type='submit']:hover` → (0,3,0)。所以下面每条规则都用
// `.arco-btn.arco-btn-X[type='...']`(0,3,0)打底,状态规则用 (0,4,0),
// 稳压过 Arco 的一整族变体。只写 `.arco-btn-primary` 会被压过,而 a-button
// 默认 html-type 就是 button —— 漏掉属性选择器等于所有按钮都不生效。
// 默认按钮(secondary / 默认态):不发光的幽灵按钮,只在 hover 时点亮管壁
.arco-btn.arco-btn-secondary,
.arco-btn.arco-btn-secondary[type='button'],
.arco-btn.arco-btn-secondary[type='submit'],
.arco-btn.arco-btn-secondary[type='reset'],
.arco-btn.arco-btn-outline,
.arco-btn.arco-btn-outline[type='button'],
.arco-btn.arco-btn-outline[type='submit'],
.arco-btn.arco-btn-outline[type='reset'],
.arco-btn.arco-btn-default,
.arco-btn.arco-btn-default[type='button'],
.arco-btn.arco-btn-default[type='submit'],
.arco-btn.arco-btn-default[type='reset'] {
color: $text-secondary;
background: transparent;
border: 1px solid $border-color;
box-shadow: none;
transition: color 0.16s ease, border-color 0.16s ease,
box-shadow 0.16s ease, background-color 0.16s ease;
&:not(.arco-btn-disabled):hover {
color: $neon;
border-color: rgba(var(--accent-primary-rgb), 0.55);
background: rgba(var(--accent-primary-rgb), 0.05);
box-shadow: 0 0 10px rgba(var(--accent-primary-rgb), 0.22);
text-shadow: 0 0 8px rgba(var(--accent-primary-rgb), 0.35);
}
}
// 主按钮:霓虹管
.arco-btn.arco-btn-primary,
.arco-btn.arco-btn-primary[type='button'],
.arco-btn.arco-btn-primary[type='submit'],
.arco-btn.arco-btn-primary[type='reset'] {
@include neon-tube(0.34);
@include neon-sheen;
font-weight: 600;
&:not(.arco-btn-disabled):hover {
@include neon-tube(0.52);
color: $neon-hv;
border-color: rgba(var(--accent-primary-rgb), 0.85);
}
&:not(.arco-btn-disabled):active {
// 按下时管壁收窄、光晕内收,像灯管被压住
box-shadow:
inset 0 0 16px rgba(var(--accent-primary-rgb), 0.22),
0 0 6px rgba(var(--accent-primary-rgb), 0.34);
transform: translateY(1px);
}
&:not(.arco-btn-disabled):focus-visible {
outline: none;
box-shadow:
inset 0 0 12px rgba(var(--accent-primary-rgb), 0.12),
0 0 0 3px rgba(var(--accent-primary-rgb), 0.16),
0 0 16px rgba(var(--accent-primary-rgb), 0.4);
}
}
// 危险操作:同构的霓虹管,颜色换成红
@mixin neon-tube-danger($glow: 0.28) {
position: relative;
overflow: hidden;
color: $error;
background: transparent;
border: 1px solid rgba(var(--error-rgb), 0.5);
box-shadow:
inset 0 0 12px rgba(var(--error-rgb), $glow * 0.3),
0 0 10px rgba(var(--error-rgb), $glow);
text-shadow: 0 0 8px rgba(var(--error-rgb), 0.4);
}
.arco-btn.arco-btn-dangerous,
.arco-btn.arco-btn-dangerous[type='button'],
.arco-btn.arco-btn-dangerous[type='submit'],
.arco-btn.arco-btn-dangerous[type='reset'] {
@include neon-tube-danger(0.26);
font-weight: 500;
&:not(.arco-btn-disabled):hover {
@include neon-tube-danger(0.44);
background: rgba(var(--error-rgb), 0.06);
}
}
.arco-btn-dangerous.arco-btn-secondary,
.arco-btn-dangerous.arco-btn-outline {
color: rgba(var(--error-rgb), 0.85);
border-color: rgba(var(--error-rgb), 0.38);
background: transparent;
box-shadow: none;
&:not(.arco-btn-disabled):hover {
@include neon-tube-danger(0.4);
}
}
// 文字 / 链接按钮:无管壁,只让文字带辉光
.arco-btn.arco-btn-text,
.arco-btn.arco-btn-link,
.arco-btn.arco-btn-text[type='button'],
.arco-btn.arco-btn-text[type='submit'],
.arco-btn.arco-btn-text[type='reset'],
.arco-btn.arco-btn-link[type='button'],
.arco-btn.arco-btn-link[type='submit'],
.arco-btn.arco-btn-link[type='reset'] {
background: transparent;
border: 1px solid transparent;
box-shadow: none;
color: $neon;
text-shadow: 0 0 8px rgba(var(--accent-primary-rgb), 0.3);
&:not(.arco-btn-disabled):hover {
color: $neon-hv;
background: rgba(var(--accent-primary-rgb), 0.07);
text-shadow: 0 0 12px rgba(var(--accent-primary-rgb), 0.55);
}
}
// 禁用态:灯管熄灭
.arco-btn.arco-btn-primary.arco-btn-disabled,
.arco-btn.arco-btn-secondary.arco-btn-disabled,
.arco-btn.arco-btn-outline.arco-btn-disabled,
.arco-btn.arco-btn-dangerous.arco-btn-disabled,
.arco-btn.arco-btn-text.arco-btn-disabled,
.arco-btn.arco-btn-link.arco-btn-disabled,
.arco-btn.arco-btn-primary[type='button'].arco-btn-disabled,
.arco-btn.arco-btn-primary[type='submit'].arco-btn-disabled,
.arco-btn.arco-btn-secondary[type='button'].arco-btn-disabled,
.arco-btn.arco-btn-secondary[type='submit'].arco-btn-disabled,
.arco-btn.arco-btn-dangerous[type='button'].arco-btn-disabled,
.arco-btn.arco-btn-dangerous[type='submit'].arco-btn-disabled {
color: var(--text-faint);
background: transparent;
border-color: var(--border-light);
box-shadow: none;
text-shadow: none;
cursor: not-allowed;
}
// 加载态:保留管壁不熄灭,用脉冲表示"通电中"
@keyframes neon-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.62; }
}
.arco-btn.arco-btn-primary.arco-btn-loading,
.arco-btn.arco-btn-primary[type='button'].arco-btn-loading,
.arco-btn.arco-btn-primary[type='submit'].arco-btn-loading {
@include neon-tube(0.4);
// 提交中扫光暂停,用脉冲表示"通电中",避免两个动画叠加
&::after { animation: none; opacity: 0; }
animation: neon-pulse 1.1s ease-in-out infinite;
}
// 尺寸统一:CYBER 的按钮偏小方硬
.arco-btn {
border-radius: $radius-sm;
font-family: $font-code;
letter-spacing: 0.02em;
transition: color 0.16s ease, border-color 0.16s ease,
box-shadow 0.16s ease, background-color 0.16s ease,
transform 0.08s ease;
}
// 兜底:任何未显式分类的实心底按钮(Arco 的 .arco-btn 默认无 type class 时)
// 也不能漏成白块或蓝块。
.arco-btn:not([class*='arco-btn-primary']):not([class*='arco-btn-secondary']):not([class*='arco-btn-outline']):not([class*='arco-btn-text']):not([class*='arco-btn-link']):not([class*='arco-btn-dangerous']) {
background: transparent;
color: $text-secondary;
border: 1px solid $border-color;
&:not(.arco-btn-disabled):hover {
color: $neon;
border-color: rgba(var(--accent-primary-rgb), 0.55);
}
}
// ─── Neon inputs / selects / tags ───────────────────────────────
// 输入区是「凹陷」的:底色比卡片更沉,聚焦时管壁点亮并带一圈内发光。
.arco-input-wrapper,
.arco-textarea-wrapper,
.arco-select-view,
.arco-picker,
.arco-input-tag {
background-color: $bg-input !important;
border-color: $border-color;
box-shadow: none;
transition: border-color 0.16s ease, box-shadow 0.16s ease;
&:hover {
border-color: rgba(var(--accent-primary-rgb), 0.4);
}
}
.arco-input-wrapper-focus,
.arco-textarea-wrapper-focus,
.arco-select-view-focus,
.arco-picker-focused,
.arco-input-tag-focus {
border-color: rgba(var(--accent-primary-rgb), 0.85);
box-shadow: 0 0 0 3px rgba(var(--accent-primary-rgb), 0.12),
inset 0 0 14px rgba(var(--accent-primary-rgb), 0.06);
}
.arco-input,
.arco-textarea,
.arco-select-view-single,
.arco-select-view-value {
color: $text-primary;
}
.arco-input::placeholder,
.arco-textarea::placeholder {
color: var(--text-faint);
}
// 标签:状态语义用发丝线底 + 彩色字,不用实心块
.arco-tag {
border-radius: 4px;
font-family: $font-code;
font-size: 11.5px;
background: transparent;
border: 1px solid currentColor;
}
// 开关 / 复选 / 单选的滑块用霓虹青
.arco-switch-checked {
background-color: rgba(var(--accent-primary-rgb), 0.22);
border-color: rgba(var(--accent-primary-rgb), 0.7);
box-shadow: 0 0 10px rgba(var(--accent-primary-rgb), 0.3);
}
.arco-switch-checked .arco-switch-slider {
background-color: var(--accent-primary);
box-shadow: 0 0 10px rgba(var(--accent-primary-rgb), 0.7);
}
.arco-checkbox-checked .arco-checkbox-inner {
background-color: var(--accent-primary);
border-color: var(--accent-primary);
box-shadow: 0 0 8px rgba(var(--accent-primary-rgb), 0.5);
}
// 下拉 / 弹层的浮层背景
.arco-trigger-popup,
.arco-select-popup,
.arco-picker-dropdown,
.arco-dropdown,
.arco-modal,
.arco-drawer {
background-color: var(--surface-2);
border: 1px solid var(--border-color);
box-shadow: var(--shadow, 0 18px 48px oklch(0 0 0 / 0.55));
}
// 表格:去掉亮色分隔线的突兀感,表头沉下去
.arco-table-th {
background-color: var(--surface-2);
color: $text-muted;
border-color: $border-color;
font-family: $font-code;
font-weight: 600;
}
.arco-table-td {
border-color: var(--border-light);
}
.arco-table-tr:hover .arco-table-td {
background-color: rgba(var(--accent-primary-rgb), 0.04);
}
// 进度条 / 加载条
.arco-progress-line .arco-progress-line-bar,
.arco-slider-track-bar {
background-color: var(--accent-primary);
box-shadow: 0 0 8px rgba(var(--accent-primary-rgb), 0.5);
}
html, body {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
#app {
height: 100%;
width: 100%;
overflow: hidden;
}
body {
font-family: $font-ui;
background-color: $bg-primary;
color: $text-primary;
font-size: 14px;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
// Chat input route CSS is code-split; keep the footer color stable before the
// component-scoped chunk arrives so entering chat does not flash the page bg.
.chat-input-area,
.chat-input-area .input-wrapper {
background-color: $bg-card;
}
.settings-check {
display: inline-flex;
align-items: center;
justify-content: center;
width: 16px;
min-width: 16px;
height: 16px;
color: transparent;
font-size: 13px;
line-height: 16px;
}
.settings-check.active {
color: $accent-primary;
}
code, pre, .mono {
font-family: $font-code;
}
a {
color: $accent-primary;
text-decoration: none;
&:hover {
color: $accent-hover;
}
}
::-webkit-scrollbar {
width: 8px;
height: 8px;
background: transparent;
}
::-webkit-scrollbar-track,
::-webkit-scrollbar-track-piece {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: $border-color;
border-radius: 4px;
&:hover {
background: rgba(var(--accent-primary-rgb), 0.5);
}
}
::-webkit-scrollbar-corner {
background: transparent;
}
* {
scrollbar-width: thin;
scrollbar-color: $border-color transparent;
}
::selection {
background: rgba(var(--accent-primary-rgb), 0.3);
}
// Shared page header
// 页面顶栏:发丝线分隔 + 极淡的青色底,标题用等宽字体带字距。
// 高度保持 64px(ui-baseline 契约,测试有断言)。
.page-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
min-height: 64px;
padding: 14px 20px;
border-bottom: 1px solid var(--border-light);
flex-shrink: 0;
background: linear-gradient(180deg,
rgba(var(--accent-primary-rgb), 0.035),
transparent 70%);
}
.header-title {
margin: 0;
font-family: $font-code;
font-size: 15px;
font-weight: 600;
line-height: 1.4;
letter-spacing: 0.04em;
color: $text-primary;
}
.page-header > .header-actions {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
}
// Responsive utility classes for inline width replacement
.input-sm { width: 90px; }
.input-md { width: 200px; }
.input-lg { width: 300px; }
// Mobile drawer backdrop
.mobile-backdrop {
display: none;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.4);
z-index: 999;
}
// Hamburger button (mobile only)
.hamburger-btn {
display: none;
position: fixed;
top: calc(10px + env(safe-area-inset-top, 0px));
left: 12px;
z-index: 99;
width: 36px;
height: 36px;
border: none;
background: $bg-card;
border-radius: $radius-sm;
cursor: pointer;
align-items: center;
justify-content: center;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
}
// Mobile responsive
@media (max-width: $breakpoint-mobile) {
.mobile-backdrop {
display: block;
}
.hamburger-btn {
display: flex;
}
.page-header {
padding: calc(16px + env(safe-area-inset-top, 0px)) 12px 16px 52px !important;
}
.input-sm,
.input-md,
.input-lg {
width: 100%;
}
}
// Prevent iOS Safari auto-zoom on input focus by ensuring font-size is at least 16px
// Arco 的 a-input 渲染原生 <input>,已被上面的元素选择器覆盖;
// 需要额外兜住的是 a-textarea(渲染 textarea,字体继承自容器)与 Arco 包装层。
@media (max-width: 768px) {
input,
textarea,
select,
.arco-input,
.arco-textarea {
font-size: 16px !important;
}
}