The project now has exactly one UI library. Every naive-ui component in
packages/client/src is replaced by its Arco equivalent across 24 views and
roughly 80 components, and naive-ui is dropped from package.json along with
its transitive vueuc / @css-render / vdirs / seemly dependencies.
The migration is rendering-layer only. No store logic, no API contract, and
no /chat-run payload was touched.
Three classes of leftover were invisible to `grep naive-ui` and are now
covered by tests/client/no-naive-ui.test.ts, which asserts all three:
- dead .n-* class names in style blocks
- dead --n-* custom properties
- naive-ui in package.json
Notable behavioural traps handled along the way, recorded in
docs/arco-migration-rules.md:
- a-input-password defaults to masked; passing default-visibility="false"
would have exposed API keys in clear text
- a-upload fires a real XHR unless auto-upload is false
- nested Trigger components fight over the first element child, so the
group-chat settings dropdown was flattened
- Arco renders tooltip default-slot text inline, which was silently
collapsing the chat and group-chat toolbars
273 lines
5.0 KiB
SCSS
273 lines
5.0 KiB
SCSS
@use 'variables' as *;
|
|
@use 'code-block';
|
|
|
|
@font-face {
|
|
font-family: 'Comic Neue';
|
|
font-style: normal;
|
|
font-weight: 400;
|
|
font-display: swap;
|
|
src: url('/fonts/ComicNeue-Bold.ttf') format('truetype');
|
|
}
|
|
|
|
@font-face {
|
|
font-family: 'ZCOOL KuaiLe';
|
|
font-style: normal;
|
|
font-weight: 400;
|
|
font-display: swap;
|
|
src: url('/fonts/ZCOOLKuaiLe-Regular.ttf') format('truetype');
|
|
}
|
|
|
|
@font-face {
|
|
font-family: 'Zen Maru Gothic';
|
|
font-style: normal;
|
|
font-weight: 400;
|
|
font-display: swap;
|
|
src: url('/fonts/ZenMaruGothic-Regular.ttf') format('truetype');
|
|
}
|
|
|
|
@font-face {
|
|
font-family: 'Zen Maru Gothic';
|
|
font-style: normal;
|
|
font-weight: 700;
|
|
font-display: swap;
|
|
src: url('/fonts/ZenMaruGothic-Bold.ttf') format('truetype');
|
|
}
|
|
|
|
@font-face {
|
|
font-family: 'Gaegu';
|
|
font-style: normal;
|
|
font-weight: 400;
|
|
font-display: swap;
|
|
src: url('/fonts/Gaegu-Regular.ttf') format('truetype');
|
|
}
|
|
|
|
@font-face {
|
|
font-family: 'Gaegu';
|
|
font-style: normal;
|
|
font-weight: 700;
|
|
font-display: swap;
|
|
src: url('/fonts/Gaegu-Bold.ttf') format('truetype');
|
|
}
|
|
|
|
*,
|
|
*::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;
|
|
}
|
|
}
|
|
|
|
// Theme transition (applied programmatically on toggle, not on load)
|
|
html.theme-transitioning,
|
|
html.theme-transitioning *,
|
|
html.theme-transitioning *::before,
|
|
html.theme-transitioning *::after {
|
|
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease,
|
|
box-shadow 0.3s ease, fill 0.3s ease, stroke 0.3s ease !important;
|
|
}
|
|
|
|
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: 6px;
|
|
height: 6px;
|
|
background: transparent;
|
|
}
|
|
|
|
::-webkit-scrollbar-track,
|
|
::-webkit-scrollbar-track-piece {
|
|
background: transparent;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb {
|
|
background: $border-color;
|
|
border-radius: 3px;
|
|
|
|
&:hover {
|
|
background: $text-muted;
|
|
}
|
|
}
|
|
|
|
::-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
|
|
.page-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
min-height: 64px;
|
|
padding: 14px 20px;
|
|
border-bottom: 1px solid $border-color;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.header-title {
|
|
margin: 0;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
line-height: 1.4;
|
|
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;
|
|
}
|
|
}
|