/* ============================================================ 待办事项悬浮窗 Demo 样式 配色暂用经典主题暖色系,正式接入时替换为语义 token ============================================================ */ * { margin: 0; padding: 0; box-sizing: border-box; } :root { --surface-base: #faf9f5; --surface-float: #ffffff; --surface-wash: #f5f0e8; --border-subtle: #ece5d8; --border-strong: #e2d9c8; --text-primary: #26241f; --text-secondary: #6f6a5e; --text-tertiary: #a9a294; --accent: #cc785c; --accent-hover: #c06a4e; --hover-wash: rgba(38, 36, 31, 0.04); --shadow-float: 0 1px 2px rgba(38, 36, 31, 0.05), 0 8px 24px rgba(38, 36, 31, 0.10); --row-h: 36px; --window-max-w: 300px; --ease-out: cubic-bezier(0.22, 0.9, 0.28, 1); --ease-strike: cubic-bezier(0.55, 0.06, 0.35, 1); } body { font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", sans-serif; background: var(--surface-base); color: var(--text-primary); min-height: 100vh; } /* ---------------- 模拟对话区 ---------------- */ .mock-chat { max-width: 640px; margin: 0 auto; padding: 64px 24px 220px; display: flex; flex-direction: column; gap: 14px; } .mock-bubble { max-width: 78%; padding: 10px 14px; border-radius: 14px; font-size: 14px; line-height: 1.6; } .mock-bubble--user { align-self: flex-end; background: #efe9de; } .mock-bubble--ai { align-self: flex-start; background: #ffffff; border: 1px solid var(--border-subtle); } /* ---------------- 悬浮窗口栈 ---------------- */ .float-stack { position: fixed; top: 20px; right: 20px; z-index: 100; display: flex; flex-direction: column; align-items: flex-end; /* 多个窗口右对齐,上下排布 */ gap: 12px; /* 整体超出屏幕高度时可滚动,滚动条隐藏 */ max-height: calc(100vh - 40px); overflow-y: auto; scrollbar-width: none; -ms-overflow-style: none; /* padding + 负 margin:扩大滚动盒以容纳窗口阴影,避免被 overflow 裁剪 */ padding: 24px; margin: -24px; } .float-stack::-webkit-scrollbar { display: none; width: 0; height: 0; } /* 窗口:圆角矩形 + 固定宽度(最大宽度的 90%) + 不透明背景 + 克制阴影 */ .float-window { flex: none; /* 禁止被栈 flex 压缩,保持固定高度 */ width: calc(var(--window-max-w) * 0.9); /* 300 * 0.9 = 270px */ background: var(--surface-float); border: 1px solid var(--border-subtle); border-radius: 12px; box-shadow: var(--shadow-float); overflow: hidden; } /* 窗口自身出现 / 消失(高度变化由行展开动画带动,这里只做淡入) */ .float-window.window-enter { animation: window-in 0.3s ease-out; } @keyframes window-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: translateY(0); } } .float-window.window-leave { animation: window-out 0.22s ease-in forwards; } @keyframes window-out { to { opacity: 0; transform: translateY(-4px) scale(0.98); } } /* 窗口标题栏 */ .float-window__header { height: 38px; display: flex; align-items: center; gap: 7px; padding: 0 12px; border-bottom: 1px solid var(--border-subtle); } .float-window__icon { width: 15px; height: 15px; color: var(--accent); flex: none; } .float-window__title { font-size: 12.5px; font-weight: 600; color: var(--text-secondary); letter-spacing: 0.02em; } .todo-counter { margin-left: auto; font-size: 11.5px; color: var(--text-tertiary); font-variant-numeric: tabular-nums; } /* ---------------- 待办列表 ---------------- */ /* 窗口高度固定为 5 行,不足留空、超出内部滚动;滚动条完全隐藏。 无上下 padding:首行 hover 紧贴标题栏分隔线,末行紧贴窗口底边 */ .todo-list { list-style: none; padding: 0; height: calc(var(--row-h) * 5); overflow-y: auto; scrollbar-width: none; /* Firefox */ -ms-overflow-style: none; /* 旧 Edge */ } .todo-list::-webkit-scrollbar { display: none; width: 0; height: 0; } /* 每一行直接铺在窗口上:无背景、无边框、无嵌套卡片 */ /* 待办行不可点击,无 hover 效果 */ .todo-item { height: var(--row-h); display: flex; align-items: center; gap: 9px; padding: 0 12px; overflow: hidden; /* 配合移出动画的高度收拢 */ } /* 状态点 */ .todo-dot { flex: none; width: 6px; height: 6px; border-radius: 50%; border: 1.5px solid var(--text-tertiary); background: transparent; transition: background 0.2s ease, border-color 0.2s ease; } .todo-item.is-done .todo-dot { background: var(--accent); border-color: var(--accent); } /* 任务文字:固定一行,超长省略 */ .todo-text { position: relative; flex: 1; min-width: 0; font-size: 13px; line-height: 1; color: var(--text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; transition: color 0.22s ease 0.12s; /* 变灰略滞后于划线 */ } /* 删除线(默认收起) */ .todo-text::after { content: ''; position: absolute; left: -1px; right: -1px; top: 50%; height: 1.5px; margin-top: -0.75px; background: var(--text-tertiary); border-radius: 1px; transform: scaleX(0); transform-origin: left center; pointer-events: none; } /* 完成态(静态,用于整表渲染时已完成行) */ .todo-item.is-done .todo-text { color: var(--text-tertiary); } .todo-item.is-done .todo-text::after { transform: scaleX(1); } /* 完成动作瞬间:横线从左往右划过 + 状态点弹跳 */ .todo-item.just-done .todo-text::after { animation: strike-in 0.28s var(--ease-strike) forwards; } @keyframes strike-in { from { transform: scaleX(0); } to { transform: scaleX(1); } } .todo-item.just-done .todo-dot { animation: dot-pop 0.32s var(--ease-out); } @keyframes dot-pop { 0% { transform: scale(1); } 45% { transform: scale(1.6); } 100% { transform: scale(1); } } /* 进入动画:从左向右位移 + 淡入(stagger 由 JS 写 animation-delay) */ .todo-item.is-entering { opacity: 0; animation: todo-in 0.34s var(--ease-out) forwards; } @keyframes todo-in { from { opacity: 0; transform: translateX(-14px); } to { opacity: 1; transform: translateX(0); } } /* 移出动画:从右向左位移 + 淡出,随后高度收拢让下方行平滑上移 */ .todo-item.is-leaving { animation: todo-out 0.26s ease-in forwards; } @keyframes todo-out { 0% { opacity: 1; transform: translateX(0); height: var(--row-h); } 55% { opacity: 0; transform: translateX(-14px); height: var(--row-h); } 100% { opacity: 0; transform: translateX(-14px); height: 0; } } /* ---------------- demo 控制台 ---------------- */ .demo-panel { position: fixed; left: 20px; bottom: 20px; z-index: 100; } .demo-panel__hint { font-size: 12px; color: var(--text-tertiary); margin-bottom: 10px; } .demo-panel__buttons { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; } .demo-btn { height: 34px; padding: 0 14px; border-radius: 8px; border: 1px solid var(--border-strong); background: var(--surface-float); color: var(--text-primary); font-size: 13px; cursor: pointer; transition: background 0.15s ease, border-color 0.15s ease; } .demo-btn:hover { background: var(--surface-wash); } .demo-btn--primary { background: var(--accent); border-color: var(--accent); color: #ffffff; } .demo-btn--primary:hover { background: var(--accent-hover); } .demo-btn--ghost { border-style: dashed; color: var(--text-tertiary); }