@import url('https://cdn.jsdelivr.net/npm/lxgw-wenkai-screen-webfont@1.1.0/style.css'); @tailwind base; @tailwind components; @tailwind utilities; /* 亮色主题变量 */ :root { --bg-primary-rgb: 245, 245, 245; --bg-secondary-rgb: 238, 238, 238; --bg-tertiary-rgb: 224, 224, 224; /* 舞台 = 工作室自己的场,和页面同色:等待态不摆一块面板,墨直接画在屋里,也就没有"边"可看。 屏只在真的有东西上屏时才出现。暗色主题是例外(见 .dark)——那里需要一块深色的屏。 */ --stage-rgb: 245, 245, 245; --text-primary-rgb: 33, 33, 33; --text-secondary-rgb: 117, 117, 117; --text-tertiary-rgb: 158, 158, 158; --accent-rgb: 66, 66, 66; --accent-hover-rgb: 48, 48, 48; --border-rgb: 0, 0, 0; /* 动效曲线(HIG 分工):不是一条通吃。 ui 微交互(hover / 变色 / 焦点)——克制,几乎察觉不到 enter 进场浮现(弹层 / 气泡 / 媒体上屏)——减速落定 exit 退场——加速离开,不拖尾巴 move 位移 / 尺寸 / 翻页(侧栏宽度、舞台形状)——长距离柔和收尾 soft 透明度 / 颜色交叉溶解(深底溶解、占位浮现)——慢起手 + 长收尾 spring 回弹强调(猫) */ --ease-ui: cubic-bezier(0.25, 0.1, 0.25, 1); --ease-enter: cubic-bezier(0.16, 1, 0.3, 1); --ease-exit: cubic-bezier(0.4, 0, 1, 1); --ease-move: cubic-bezier(0.22, 1, 0.36, 1); /* 推卡:两张同向移动、两端都在静止 → 对称 S 曲线(UIKit curveEaseInOut) */ --ease-swap: cubic-bezier(0.42, 0, 0.58, 1); --ease-soft: cubic-bezier(0.45, 0, 0.25, 1); --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); } /* iOS 风格主题变量 */ .warm { --bg-primary-rgb: 242, 242, 247; --bg-secondary-rgb: 255, 253, 248; --bg-tertiary-rgb: 209, 209, 214; --stage-rgb: 242, 242, 247; --text-primary-rgb: 0, 0, 0; --text-secondary-rgb: 60, 60, 67; --text-tertiary-rgb: 142, 142, 147; --accent-rgb: 120, 180, 240; --accent-hover-rgb: 100, 160, 220; --border-rgb: 198, 198, 200; } /* 暗黑主题变量 */ .dark { --bg-primary-rgb: 18, 18, 18; --bg-secondary-rgb: 28, 28, 28; --bg-tertiary-rgb: 38, 38, 38; --stage-rgb: 16, 20, 21; --text-primary-rgb: 245, 245, 245; --text-secondary-rgb: 189, 189, 189; --text-tertiary-rgb: 117, 117, 117; --accent-rgb: 138, 138, 138; --accent-hover-rgb: 158, 158, 158; --border-rgb: 255, 255, 255; } body { font-family: "LXGW WenKai Screen", "LXGW WenKai TC", "LXGW WenKai Lite", "LXGW WenKai", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; @apply bg-bg-primary text-text-primary transition-colors duration-300; } /* 自定义下拉菜单样式 */ select option { padding: 10px 16px; background-color: #ffffff; color: #212121; } select option:hover, select option:focus { background-color: #f5f5f5; } select option:checked { background-color: #424242; color: #ffffff; font-weight: 500; } /* 极简滚动条 - MD3 风格 */ ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: rgba(0, 0, 0, 0.1); border-radius: 9999px; } .dark ::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.15); } ::-webkit-scrollbar-thumb:hover { background: rgba(0, 0, 0, 0.2); } .dark ::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.25); } /* 代码高亮主题适配暗黑模式 */ .dark .react-syntax-highlighter { background: transparent !important; } /* 动画淡入效果 */ @keyframes fade-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } .animate-fade-in { animation: fade-in 0.3s ease-out; } @keyframes fade-in-soft { from { opacity: 0; transform: translateY(12px) scale(0.992); } to { opacity: 1; transform: translateY(0) scale(1); } } .animate-fade-in-soft { animation: fade-in-soft 0.58s cubic-bezier(0.22, 1, 0.36, 1) forwards; } @keyframes message-enter { from { opacity: 0.01; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } } .animate-message-enter { animation: message-enter 0.22s cubic-bezier(0.22, 1, 0.36, 1) forwards; will-change: transform, opacity; } @keyframes fade-out-soft { from { opacity: 1; transform: translateY(0) scale(1); } to { opacity: 0; transform: translateY(12px) scale(0.992); } } .animate-fade-out-soft { animation: fade-out-soft 0.4s cubic-bezier(0.22, 1, 0.36, 1) forwards; } @keyframes overlay-wash-in { from { opacity: 0; backdrop-filter: blur(0px); } to { opacity: 1; backdrop-filter: blur(6px); } } .animate-overlay-wash-in { animation: overlay-wash-in 0.4s cubic-bezier(0.22, 1, 0.36, 1) forwards; } @keyframes overlay-wash-out { from { opacity: 1; backdrop-filter: blur(6px); } to { opacity: 0; backdrop-filter: blur(0px); } } .animate-overlay-wash-out { animation: overlay-wash-out 0.32s cubic-bezier(0.32, 0, 0.67, 0) forwards; } /* Modal 弹体:Apple spring overshoot 曲线,scale 差值放大到 0.96 才能弹出来 */ @keyframes modal-pop-in { 0% { opacity: 0; transform: translateY(8px) scale(0.97); } 100% { opacity: 1; transform: translateY(0) scale(1); } } .animate-modal-pop-in { animation: modal-pop-in 0.45s cubic-bezier(0.16, 1, 0.3, 1) forwards; will-change: transform, opacity; } @keyframes modal-pop-out { 0% { opacity: 1; transform: translateY(0) scale(1); } 100% { opacity: 0; transform: translateY(6px) scale(0.97); } } .animate-modal-pop-out { animation: modal-pop-out 0.32s cubic-bezier(0.32, 0, 0.67, 0) forwards; will-change: transform, opacity; } @keyframes paw-stamp { 0%, 100% { transform: translateY(0) rotate(-6deg) scale(0.96); opacity: 0.26; } 35% { transform: translateY(7px) rotate(3deg) scale(1.04); opacity: 0.78; } 60% { transform: translateY(-1px) rotate(-2deg) scale(0.99); opacity: 0.5; } } .problem-paw { animation: paw-stamp 1.2s ease-in-out infinite; } @keyframes studio-entrance { 0% { opacity: 0; transform: scale(0.98); filter: blur(12px); } 100% { opacity: 1; transform: scale(1); filter: blur(0px); } } @keyframes classic-entrance { 0% { opacity: 0; transform: scale(1.02); filter: blur(8px); } 100% { opacity: 1; transform: scale(1); filter: blur(0px); } } .animate-studio-entrance { animation: studio-entrance 1.8s cubic-bezier(0.16, 1, 0.3, 1) forwards; } .animate-studio-exit { animation: studio-entrance 1.2s cubic-bezier(0.16, 1, 0.3, 1) reverse forwards; } /* Workspace 退场:纯 CSS animation(不走 transition,避免入场 animation 移除时跳变) */ @keyframes workspace-exit { 0% { opacity: 1; transform: scale(1); filter: blur(0px); } 100% { opacity: 0; transform: scale(1.02); filter: blur(16px); } } .animate-workspace-exit { animation: workspace-exit 0.6s cubic-bezier(0.42, 0, 0.58, 1) forwards; will-change: transform, opacity, filter; } .animate-classic-entrance { animation: classic-entrance 1.5s cubic-bezier(0.16, 1, 0.3, 1) forwards; } /* ========================================================== 进场:studio 部件错峰组装浮现 + home 淡出退场 退出:studio 右滑出 ‖ home 左滑入(iOS pop,无模糊/无等待) ========================================================== */ @keyframes page-slide-out-right { 0% { transform: translateX(0); } 100% { transform: translateX(100%); } } @keyframes page-slide-in-left { 0% { transform: translateX(-30%); } 100% { transform: translateX(0); } } /* Studio 部件错峰组装浮现 */ @keyframes assemble-slide-left { 0% { transform: translateX(-100%); } 100% { transform: translateX(0); } } @keyframes assemble-drop { 0% { opacity: 0; transform: translateY(-6px); } 100% { opacity: 1; transform: translateY(0); } } @keyframes assemble-fade { 0% { opacity: 0; } 100% { opacity: 1; } } .page-assemble [data-assemble="sidebar"] { animation: assemble-slide-left 0.48s cubic-bezier(0.22, 1, 0.36, 1) both; } .page-assemble [data-assemble="header"] { animation: assemble-drop 0.32s cubic-bezier(0.22, 1, 0.36, 1) 0.06s both; } .page-assemble [data-assemble="strip"] { animation: assemble-fade 0.32s cubic-bezier(0.22, 1, 0.36, 1) 0.12s both; } /* 舞台晚于其余部件落定,并且只做透明度——一大块深色表面缩放会读成“胀出来”。 浅色机架(header/strip/composer)先就位,屏幕像被点亮一样随后浮现。 透明度用 soft(慢起手),不是 move:这里没有运动去遮挡快起手。 */ .page-assemble [data-assemble="stage"] { animation: assemble-fade 0.58s var(--ease-soft) 0.16s both; } .page-assemble [data-assemble="composer"] { animation: assemble-fade 0.40s cubic-bezier(0.22, 1, 0.36, 1) 0.08s both; } /* 表单页(设置 / 提示词)沿用同一份 data-assemble 约定。studio 的部件要“拼”出一台机器: sidebar 整块从画外推进来。表单页部件少、距离短,位移收到 12px + 淡入,读成“落位”而不是“滑进来”。 */ @keyframes assemble-rise { 0% { opacity: 0; transform: translateY(6px); } 100% { opacity: 1; transform: translateY(0); } } @keyframes assemble-in-left { 0% { opacity: 0; transform: translateX(-12px); } 100% { opacity: 1; transform: translateX(0); } } .page-assemble [data-assemble="rail"] { animation: assemble-in-left 0.34s var(--ease-move) 0.04s both; } .page-assemble [data-assemble="panel"] { animation: assemble-rise 0.38s var(--ease-move) 0.10s both; } /* home 退场覆盖层:淡出 + 微缩,悄悄退到 studio 组装之下 */ @keyframes home-leave { 0% { opacity: 1; transform: scale(1); } 100% { opacity: 0; transform: scale(0.985); } } .page-home-leave { animation: home-leave 0.32s ease both; } /* 退出滑动(iOS pop):studio 右滑出,home 左滑入 */ .page-slide-out { animation: page-slide-out-right 0.4s cubic-bezier(0.22, 1, 0.36, 1) both; will-change: transform; } .page-slide-in { animation: page-slide-in-left 0.4s cubic-bezier(0.22, 1, 0.36, 1) both; will-change: transform; } /* 下拉菜单:从触发按钮下方长出来。位移很小,只用来交代“我从哪来”;缩放原点是顶边中点。 */ @keyframes menu-in { 0% { opacity: 0; transform: translateY(-6px) scale(0.98); } 100% { opacity: 1; transform: translateY(0) scale(1); } } .menu-in { animation: menu-in 0.18s var(--ease-enter) both; transform-origin: top center; } /* 分区切换:面板落位。内容换了才播,所以挂在以 section 为 key 的元素上(见 SettingsPage)。 */ @keyframes panel-in { 0% { opacity: 0; transform: translateY(8px); } 100% { opacity: 1; transform: translateY(0); } } .panel-in { animation: panel-in 0.26s var(--ease-move) both; } /* 同一层里左右两格互切(CatKey / BYOK、弹窗的选择→表单):新内容从它对应的那一侧进来, 方向跟着手指走,接上药丸的位移。 */ @keyframes panel-in-right { 0% { opacity: 0; transform: translateX(10px); } 100% { opacity: 1; transform: translateX(0); } } @keyframes panel-in-left { 0% { opacity: 0; transform: translateX(-10px); } 100% { opacity: 1; transform: translateX(0); } } .panel-in-right { animation: panel-in-right 0.26s var(--ease-move) both; } .panel-in-left { animation: panel-in-left 0.26s var(--ease-move) both; } @media (prefers-reduced-motion: reduce) { .page-slide-in, .page-slide-out, .page-assemble [data-assemble], .page-home-leave, .menu-in, .panel-in, .panel-in-right, .panel-in-left, [data-assemble] { animation: none !important; } } /* ========================================================== Studio 细节过渡(HIG 曲线分工) 出现/消失是挂载/卸载,没有可过渡的中间态 —— 所以一律走 animation(keyframe), 而不是 transition;能留挂载的位移/尺寸才用 transition。 ========================================================== */ @keyframes studio-fade-in { from { opacity: 0; } to { opacity: 1; } } @keyframes studio-fade-out { from { opacity: 1; } to { opacity: 0; } } @keyframes studio-rise-in { from { opacity: 0; transform: translateY(6px) scale(0.985); } to { opacity: 1; transform: translateY(0) scale(1); } } @keyframes studio-pop-in { from { opacity: 0; transform: translateY(-4px) scale(0.97); } to { opacity: 1; transform: translateY(0) scale(1); } } @keyframes studio-pop-out { from { opacity: 1; transform: translateY(0) scale(1); } to { opacity: 0; transform: translateY(-4px) scale(0.98); } } /* Modal panels: the all-Scenes grid and the delete question. Slower and more decelerating than a pop-in — a dialog that arrives as fast as a chip reads as a glitch, and the extra 100ms is what makes the arrival feel placed rather than switched on. The exit keeps the shared exit curve, so leaving still reads as leaving. */ @keyframes studio-sheet-in { from { opacity: 0; transform: translateY(-8px) scale(0.965); } to { opacity: 1; transform: translateY(0) scale(1); } } @keyframes studio-sheet-out { from { opacity: 1; transform: translateY(0) scale(1); } to { opacity: 0; transform: translateY(-6px) scale(0.98); } } @keyframes studio-media-in { from { opacity: 0; transform: scale(0.965); } to { opacity: 1; transform: scale(1); } } @keyframes studio-slide-in-right { from { opacity: 0; transform: translateX(28px); } to { opacity: 1; transform: translateX(0); } } @keyframes studio-slide-out-right { from { opacity: 1; transform: translateX(0); } to { opacity: 0; transform: translateX(28px); } } @keyframes studio-slide-in-left { from { transform: translateX(-100%); } to { transform: translateX(0); } } @keyframes studio-slide-out-left { from { transform: translateX(0); } to { transform: translateX(-100%); } } .studio-fade-in { animation: studio-fade-in 0.18s var(--ease-enter) both; } .studio-fade-out { animation: studio-fade-out 0.15s var(--ease-exit) both; } .studio-rise-in { animation: studio-rise-in 0.26s var(--ease-enter) both; } .studio-pop-in { animation: studio-pop-in 0.2s var(--ease-enter) both; } .studio-pop-out { animation: studio-pop-out 0.15s var(--ease-exit) both; } .studio-sheet-in { animation: studio-sheet-in 0.3s var(--ease-move) both; } .studio-sheet-out { animation: studio-sheet-out 0.2s var(--ease-exit) both; } .studio-media-in { animation: studio-media-in 0.42s var(--ease-enter) both; } /* 屏幕里的占位字:短而柔的浮现。不能拉长——换 state 时旧字立刻卸载,这段长度就是“黑底无字”的长度。 */ @keyframes studio-placeholder-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } } .studio-placeholder-in { animation: studio-placeholder-in 0.26s var(--ease-soft) backwards; } .studio-slide-in-right { animation: studio-slide-in-right 0.26s var(--ease-enter) both; } .studio-slide-out-right { animation: studio-slide-out-right 0.18s var(--ease-exit) both; } /* Conversation motion is independent of stage layout: animate only its painted surface. */ @keyframes cinema-history-in { from { opacity: 0; transform: translateX(100%); } to { opacity: 1; transform: translateX(0); } } @keyframes cinema-history-out { from { opacity: 1; transform: translateX(0); } to { opacity: 0; transform: translateX(100%); } } .cinema-history-in { animation: cinema-history-in 0.34s cubic-bezier(0.22, 1, 0.36, 1) both; } .cinema-history-out { animation: cinema-history-out 0.34s cubic-bezier(0.4, 0, 0.2, 1) both; } .studio-slide-in-left { animation: studio-slide-in-left 0.3s var(--ease-move) both; } .studio-slide-out-left { animation: studio-slide-out-left 0.22s var(--ease-exit) both; } /* 内容换卡:换对话 = 竖轴,换场景 = 横轴。这是“推”:两张卡同向移动、彼此紧贴, 所以中间不会露出页面底色(不是从两边往中间对撞)。离场卡同时缩小 = 卡片飞走。 只走位移和缩放,不走透明度:透明度一低,卡后面的纸色就透出来(就是之前的白闪)。 用 backwards/forwards 而不都是 both:进场卡结束后不留 transform, 否则这个包裹层会变成 `position: fixed` 的包含块,把场景条里的选择器弹层定位弄歪。 */ @keyframes content-rise { from { transform: translateY(100%) scale(0.94); } to { transform: translateY(0) scale(1); } } @keyframes content-fall { from { transform: translateY(-100%) scale(0.94); } to { transform: translateY(0) scale(1); } } @keyframes content-from-right { from { transform: translateX(100%) scale(0.94); } to { transform: translateX(0) scale(1); } } @keyframes content-from-left { from { transform: translateX(-100%) scale(0.94); } to { transform: translateX(0) scale(1); } } /* 缩放的原点必须落在“接缝边”(两卡相邻的那条边),否则缩放在飞行中点会把接缝撕开 一条纸色带(默认以中心缩放,两张各往回缩 5%+3%)。 接缝边 = 旧卡的“被推进方向的那条边” = 新卡的“进入方向的那条边”: 往上推:旧卡底边 / 新卡顶边;往下推:旧卡顶边 / 新卡底边;往左推:旧卡右边 / 新卡左边。 */ .studio-content-rise { animation: content-rise 0.42s var(--ease-swap) backwards; transform-origin: center top; } .studio-content-fall { animation: content-fall 0.42s var(--ease-swap) backwards; transform-origin: center bottom; } .studio-content-from-right { animation: content-from-right 0.42s var(--ease-swap) backwards; transform-origin: left center; } .studio-content-from-left { animation: content-from-left 0.42s var(--ease-swap) backwards; transform-origin: right center; } /* 离场卡:往它来的反方向飞走(同时缩小)。用 forwards 保留终点,卡片停在画面外直到卸载。 */ @keyframes content-rise-out { from { transform: translateY(0) scale(1); } to { transform: translateY(-100%) scale(0.9); } } @keyframes content-fall-out { from { transform: translateY(0) scale(1); } to { transform: translateY(100%) scale(0.9); } } @keyframes content-to-left { from { transform: translateX(0) scale(1); } to { transform: translateX(-100%) scale(0.9); } } @keyframes content-to-right { from { transform: translateX(0) scale(1); } to { transform: translateX(100%) scale(0.9); } } .studio-content-rise-out { animation: content-rise-out 0.42s var(--ease-swap) forwards; transform-origin: center bottom; } .studio-content-fall-out { animation: content-fall-out 0.42s var(--ease-swap) forwards; transform-origin: center top; } .studio-content-to-left { animation: content-to-left 0.42s var(--ease-swap) forwards; transform-origin: right center; } .studio-content-to-right { animation: content-to-right 0.42s var(--ease-swap) forwards; transform-origin: left center; } /* 猫开口:比通用淡入更慢、带一点由下往上,读起来是“说出来”而不是“跳出来”。 */ @keyframes studio-bubble-in { from { opacity: 0; transform: translateY(8px) scale(0.96); } to { opacity: 1; transform: translateY(0) scale(1); } } .studio-bubble-in { animation: studio-bubble-in 0.34s var(--ease-enter) backwards; } /* 高度折叠(工具详情 / 提示行):留挂载,0fr → 1fr,位移用 move 曲线。 折叠时高度归零,但父容器的 gap 还会占一格 —— 需要时用 `-mb-` 在收起时抵消, 所以 margin-bottom 也在过渡里(跟高度同曲线同时长,不会跳)。 */ .studio-collapse { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 0.24s var(--ease-move), opacity 0.24s var(--ease-move), margin-bottom 0.24s var(--ease-move); opacity: 0; } .studio-collapse[data-open="true"] { grid-template-rows: 1fr; opacity: 1; } .studio-collapse > * { min-height: 0; overflow: hidden; } /* 面板底部的渐隐带:最底那一条固定什么都不放,内容在滚到底之前先淡出。 用 mask 而不是盖一层渐变 —— 不用配色,亮/暖/暗三套主题自动一致,也不会盖住滚动条。 滚动容器要留出同样高度的底部内边距(pb-14 = 56px),否则最后一行会停在渐隐带里。 */ .studio-fade-bottom { --studio-fade-bottom: 56px; -webkit-mask-image: linear-gradient( to bottom, #000 calc(100% - var(--studio-fade-bottom)), transparent ); mask-image: linear-gradient(to bottom, #000 calc(100% - var(--studio-fade-bottom)), transparent); } /* 窄屏的会话抽屉:只在 lg 以下滑入(lg 以上侧栏是在流列,宽度过渡已经够了)。 */ @media (max-width: 1023px) { .studio-drawer-in { animation: studio-slide-in-left 0.3s var(--ease-move) both; } } @media (prefers-reduced-motion: reduce) { .studio-fade-in, .studio-fade-out, .studio-rise-in, .studio-pop-in, .studio-pop-out, .studio-sheet-in, .studio-sheet-out, .studio-media-in, .studio-slide-in-right, .studio-slide-out-right, .cinema-history-in, .cinema-history-out, .studio-slide-in-left, .studio-slide-out-left { animation: none !important; } .studio-collapse { transition: none !important; } .studio-drawer-in { animation: none !important; } .studio-content-rise, .studio-content-fall, .studio-content-from-right, .studio-content-from-left { animation: none !important; } .studio-content-rise-out, .studio-content-fall-out, .studio-content-to-left, .studio-content-to-right { animation: none !important; } .studio-bubble-in { animation: none !important; } .studio-placeholder-in { animation: none !important; } /* 分段控件的选中胶囊:减少动态时直接跳位,不平移。 */ .studio-selection-pill { transition: none !important; } } /* Studio 终端光标闪烁 */ @keyframes blink-cursor { 0%, 100% { opacity: 1; } 50% { opacity: 0; } } /* Studio 猫爪轻浮动 */ @keyframes studio-paw-float { 0%, 100% { transform: translateY(0) rotate(-3deg); } 50% { transform: translateY(-3px) rotate(3deg); } } .studio-paw-float { animation: studio-paw-float 3s ease-in-out infinite; display: inline-block; } .studio-cursor { animation: blink-cursor 1s step-end infinite; color: rgba(var(--text-secondary-rgb), 0.4); font-family: "SF Mono", "Fira Code", "Cascadia Code", monospace; } @keyframes studio-dot-bounce { 0%, 80%, 100% { transform: translateY(0) scale(0.9); opacity: 0.28; } 40% { transform: translateY(-4px) scale(1); opacity: 0.88; } } @keyframes studio-type-caret-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.18; } } /* Studio 终端基础样式 */ .studio-shell-root { font-family: "LXGW WenKai Screen", "LXGW WenKai TC", "LXGW WenKai Lite", "LXGW WenKai", sans-serif !important; } .studio-shell-root * { font-family: inherit; } .studio-shell-root .font-mono, .studio-terminal .font-mono, .studio-cursor { font-family: "LXGW WenKai Screen", "LXGW WenKai TC", "LXGW WenKai Lite", "LXGW WenKai", sans-serif !important; } .studio-terminal { font-family: inherit; } .studio-brand-title { font-family: "LXGW WenKai Screen", "LXGW WenKai TC", "LXGW WenKai Lite", "LXGW WenKai", sans-serif !important; letter-spacing: 0.18em; } .studio-terminal ::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.08); } .studio-terminal ::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.15); } .studio-thinking-dots { display: inline-flex; align-items: center; gap: 0.35rem; } .studio-thinking-dots span { width: 0.34rem; height: 0.34rem; border-radius: 9999px; background: rgba(var(--accent-rgb), 0.55); animation: studio-dot-bounce 1.1s ease-in-out infinite; } .studio-thinking-dots span:nth-child(2) { animation-delay: 0.14s; } .studio-thinking-dots span:nth-child(3) { animation-delay: 0.28s; } .studio-type-caret { margin-left: 0.18rem; color: rgba(var(--accent-rgb), 0.62); animation: studio-type-caret-blink 0.9s step-end infinite; } .studio-markdown { word-break: break-word; } .studio-markdown > :first-child { margin-top: 0; } .studio-markdown > :last-child { margin-bottom: 0; } .studio-markdown p, .studio-markdown ul, .studio-markdown ol, .studio-markdown blockquote, .studio-markdown table, .studio-markdown hr { margin: 0.9rem 0; } .studio-markdown ul, .studio-markdown ol { padding-left: 1.4rem; } .studio-markdown li + li { margin-top: 0.3rem; } .studio-terminal[data-variant="pure-minimal-bottom"] .studio-markdown p, .studio-terminal[data-variant="pure-minimal-bottom"] .studio-markdown ul, .studio-terminal[data-variant="pure-minimal-bottom"] .studio-markdown ol, .studio-terminal[data-variant="pure-minimal-bottom"] .studio-markdown blockquote, .studio-terminal[data-variant="pure-minimal-bottom"] .studio-markdown table, .studio-terminal[data-variant="pure-minimal-bottom"] .studio-markdown hr { margin: 0.55rem 0; } .studio-terminal[data-variant="pure-minimal-bottom"] .studio-markdown blockquote { border-left: 0; padding-left: 0; color: rgba(var(--accent-rgb), 0.6); } .studio-terminal[data-variant="pure-minimal-bottom"] .studio-markdown code { background: transparent !important; padding: 0; border-radius: 0; } .studio-terminal[data-variant="pure-minimal-bottom"] .react-syntax-highlighter { background: transparent !important; padding: 0 !important; border-radius: 0 !important; } .studio-terminal[data-variant="pure-minimal-bottom"] .studio-markdown img, .studio-terminal[data-variant="pure-minimal-bottom"] .studio-markdown th, .studio-terminal[data-variant="pure-minimal-bottom"] .studio-markdown td { border: 0 !important; border-radius: 0 !important; background: transparent !important; } .studio-terminal[data-variant="pure-minimal-bottom"] .studio-markdown hr { border-top-color: rgba(var(--accent-rgb), 0.08); } .studio-terminal[data-variant="pure-minimal-bottom"] .studio-markdown-inline > p:first-child { display: inline; margin: 0; } .studio-terminal[data-variant="pure-minimal-bottom"] .studio-markdown-inline > p:first-child + * { margin-top: 0.55rem; } .studio-terminal[data-variant="pure-minimal-bottom"] .studio-markdown-inline > p:only-child { display: inline; margin: 0; } .no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; } .no-scrollbar::-webkit-scrollbar { display: none; } .studio-markdown .katex-display { margin: 1rem 0; overflow-x: auto; overflow-y: hidden; padding: 0.1rem 0; } .canvas-transition-grid { background-image: linear-gradient(rgba(var(--border-rgb), 0.05) 1px, transparent 1px), linear-gradient(90deg, rgba(var(--border-rgb), 0.05) 1px, transparent 1px); background-size: 64px 64px; mask-image: radial-gradient(circle at center, black, transparent 80%); } .canvas-paper-grid { background: repeating-linear-gradient( to bottom, transparent 0, transparent 62px, rgba(80, 80, 80, 0.12) 62px, rgba(80, 80, 80, 0.12) 63px, transparent 63px, transparent 124px ), repeating-linear-gradient( to right, transparent 0, transparent 70px, rgba(100, 100, 100, 0.08) 70px, rgba(100, 100, 100, 0.08) 71px, transparent 71px, transparent 142px ); } /* Cat character animations (doc §7.3). Low-frequency, CSS-only; turned off under reduced motion. */ @keyframes cat-blink { 0%, 92%, 100% { transform: scaleY(1); } 96% { transform: scaleY(0.1); } } @keyframes cat-breathe { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.02); } } @keyframes cat-lean { 0%, 100% { transform: rotate(-1.5deg); } 50% { transform: rotate(1.5deg); } } .cinema-cat-character[data-pose="idle"] { animation: cat-breathe 4s ease-in-out infinite; } .cinema-cat-character[data-pose="idle"] [data-part="eye"] { transform-box: fill-box; transform-origin: center; animation: cat-blink 5s ease-in-out infinite; } .cinema-cat-character[data-pose="busy"] { animation: cat-lean 2.4s ease-in-out infinite; } @media (prefers-reduced-motion: reduce) { .cinema-cat-character, .cinema-cat-character [data-part="eye"] { animation: none !important; } } /* Cat jumping into the sidebar header (doc §3.2): the sidebar cat scales+fades in. */ @keyframes cat-enter { from { opacity: 0; transform: scale(0.5) translateY(8px); } to { opacity: 1; transform: scale(1) translateY(0); } } .cinema-cat-enter { animation: cat-enter 220ms cubic-bezier(0.16, 1, 0.3, 1); } @media (prefers-reduced-motion: reduce) { .cinema-cat-enter { animation: none; } } /* Conversation rows emerge (doc §7.1): each new message fades in and floats up. */ @keyframes cinema-row-enter { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .cinema-row-enter { animation: cinema-row-enter 240ms cubic-bezier(0.16, 1, 0.3, 1) both; } /* The cat composing: three dots pulse while the assistant has produced no text yet. */ @keyframes cinema-thinking { 0%, 80%, 100% { opacity: 0.25; transform: translateY(0); } 40% { opacity: 1; transform: translateY(-2px); } } .cinema-thinking-dot { animation: cinema-thinking 1.2s infinite ease-in-out; } .cinema-thinking-dot:nth-child(2) { animation-delay: 0.15s; } .cinema-thinking-dot:nth-child(3) { animation-delay: 0.3s; } @media (prefers-reduced-motion: reduce) { .cinema-row-enter { animation: none; } .cinema-thinking-dot { animation: none; opacity: 0.6; } } /* 删除一行:这个过渡在 Tailwind 类上(grid-rows 1fr → 0fr 收高度、同时淡出), 所以这里只需给 prefers-reduced-motion 一个兜底。顺带把删除确认框用到的 fade/pop 也一起关掉——它们原本就漏在这道闸门外。 */ @media (prefers-reduced-motion: reduce) { .studio-row-removing, .studio-fade-in, .studio-fade-out, .studio-pop-in, .studio-pop-out, .studio-sheet-in, .studio-sheet-out { animation: none !important; transition: none !important; } }