Download frontend/src/index.css from Bin29/ManimCat: direct link, hf CLI and curl.
- Browser
- Download file 29.9 kB
-
https://huggingface.co/spaces/Bin29/ManimCat/resolve/main/frontend/src/index.css
- Command line
-
hf download hf://spaces/Bin29/ManimCat/frontend/src/index.css
-
curl -L -o index.css https://huggingface.co/spaces/Bin29/ManimCat/resolve/main/frontend/src/index.css
29.9 kB
| @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 ; | |
| } | |
| /* 动画淡入效果 */ | |
| @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 ; } | |
| } | |
| /* ========================================================== | |
| 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-<gap>` 在收起时抵消, | |
| 所以 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 ; } | |
| .studio-collapse { transition: none ; } | |
| .studio-drawer-in { animation: none ; } | |
| .studio-content-rise, .studio-content-fall, .studio-content-from-right, .studio-content-from-left { | |
| animation: none ; | |
| } | |
| .studio-content-rise-out, .studio-content-fall-out, .studio-content-to-left, .studio-content-to-right { | |
| animation: none ; | |
| } | |
| .studio-bubble-in { animation: none ; } | |
| .studio-placeholder-in { animation: none ; } | |
| /* 分段控件的选中胶囊:减少动态时直接跳位,不平移。 */ | |
| .studio-selection-pill { transition: none ; } | |
| } | |
| /* 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 ; | |
| } | |
| .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 ; | |
| } | |
| .studio-terminal { | |
| font-family: inherit; | |
| } | |
| .studio-brand-title { | |
| font-family: "LXGW WenKai Screen", "LXGW WenKai TC", "LXGW WenKai Lite", "LXGW WenKai", sans-serif ; | |
| 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 ; | |
| padding: 0; | |
| border-radius: 0; | |
| } | |
| .studio-terminal[data-variant="pure-minimal-bottom"] .react-syntax-highlighter { | |
| background: transparent ; | |
| padding: 0 ; | |
| border-radius: 0 ; | |
| } | |
| .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 ; | |
| border-radius: 0 ; | |
| background: transparent ; | |
| } | |
| .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 ; | |
| } | |
| } | |
| /* 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 ; transition: none ; } | |
| } | |