/* 修改记录动画 —— 智能体持笔在 3D 倾斜纸上改写内容, 纸摊平后绿行抽出展开为红绿 diff。无边框无底色,融于星空;无 glow。 */ :root { --bg-0: #0c1120; --bg-1: #111830; --bg-panel: #151c31; --text-1: #eef1f8; --text-2: #a7b0c4; --text-3: #6b7488; --line: rgba(168, 180, 210, 0.14); --accent: #a9c2f0; --accent-strong: #c1d4f6; --danger: #d47b6b; --ok: #7ba889; /* 收盒文件:与编辑中的 3D 纸同款底色,边框用更亮的白边(叠放分层清晰) */ --file-paper: var(--bg-panel); --file-line: rgba(238, 240, 246, 0.35); --font-sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", "PingFang SC", "Noto Sans SC", "Helvetica Neue", "Microsoft YaHei", sans-serif; --font-mono: ui-monospace, "SF Mono", "SFMono-Regular", Menlo, Consolas, monospace; } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: var(--font-sans); background: linear-gradient(180deg, var(--bg-1) 0%, var(--bg-0) 60%); min-height: 100vh; display: flex; align-items: center; justify-content: center; color: var(--text-1); } .stage { display: flex; flex-direction: column; align-items: center; gap: 18px; } .hint { font-size: 13px; color: var(--text-3); } /* ───────── 动画容器 ───────── */ .anim3-window { width: 540px; height: 380px; } .anim3-stage { position: relative; width: 100%; height: 100%; overflow: hidden; cursor: pointer; user-select: none; /* 演示区禁止选中文本,避免鼠标划过时出现系统选中高亮(黑光) */ -webkit-user-select: none; } /* ───────── 智能体(右上) ───────── */ .writer { position: absolute; right: 58px; top: 26px; width: 84px; height: 84px; } .writer-avatar { display: block; width: 84px; height: 84px; color: var(--text-1); } .writer-avatar .astrion-avatar { width: 100%; height: 100%; } /* ───────── 笔(双层结构) ───────── 外层 pen-pos:JS 逐帧/过渡定位,使笔尖精确落在文字进度点上; 内层 svg:以笔尖为圆心旋转摇摆(transform-origin 在笔尖), 既有晃动,笔尖又不会脱离位置。 */ .pen-pos { position: absolute; left: 0; top: 0; z-index: 40; /* 最高层:高于 3D 纸、盒子(30)与堆叠纸(21~23),笔不能被挡 */ width: 40px; height: 40px; color: var(--accent); will-change: transform; transition: transform 0.45s cubic-bezier(.4,0,.2,1); /* 飞行/移动用过渡 */ } .pen-pos.is-live { transition: none; /* 写字逐帧驱动时关闭过渡 */ } .writer-pen { width: 40px; height: 40px; /* origin = 笔尖视觉触点(SVG (2.65,21.02) → 40px 盒内 (4.4,35.0)),与 nibRef 同点: 摇摆时笔尖钉住不动,只有笔身晃 */ transform-origin: 11% 87.6%; } .writer-pen.is-writing { animation: pen-write 0.42s ease-in-out infinite; } /* 擦除:origin 切到笔尾橡皮触点(SVG (19.2,4.8) → 盒内 (32,8)),与 eraserRef 同点 */ .writer-pen.is-erasing { transform-origin: 80% 20%; } @keyframes pen-write { 0%, 100% { transform: rotate(-5deg); } 50% { transform: rotate(6deg); } } /* ───────── 3D 倾斜的纸 ───────── */ /* perspective 让纸呈平行四边形(不倾斜就只是一条线) */ .paper3d { position: absolute; left: 280px; top: 130px; width: 192px; height: 122px; transform: perspective(760px) rotateX(42deg) rotateZ(-7deg); transform-style: preserve-3d; } .paper-sheet { width: 100%; height: 100%; background: var(--bg-panel); border: 1px solid var(--line); border-radius: 6px; padding: 18px 16px; display: flex; flex-direction: column; gap: 14px; } /* 内容行:横线代表一行内容(行宽由 JS 逐帧驱动,不用 transition;opacity 过渡供轮间换内容淡出) */ .pline { position: relative; height: 4px; border-radius: 2px; background: var(--text-3); opacity: 0.75; transition: opacity 0.28s ease; } .paper3d.is-refilling .pline { opacity: 0; } /* 线右端锚点:0 尺寸,随线的 3D/倾斜变换,供 JS 实测真实右端点 */ .pline-end { position: absolute; right: 0; top: 50%; width: 0; height: 0; margin-top: -0px; /* 点本身无尺寸,top:50% 即线厚中心 */ } /* 写入行不再用绿色:与其他行同色(修改记录靠的是 diff 区,不是纸面变色) */ /* ───────── diff 区 ───────── */ .diff-view { position: absolute; left: 134px; /* 盒子水平中线(86+48),diff 中心对齐盒子中线 */ transform: translateX(-50%); /* 克隆切片内 transform 已被覆盖,不受影响 */ top: 0; font-family: var(--font-mono); font-size: 12.5px; line-height: 1.5; display: flex; flex-direction: column; gap: 3px; z-index: 20; /* 盒内堆叠纸(21~23)之下——genie 时已隐藏,由切片接管 */ border-radius: 6px; transition: background 0.25s ease; } /* genie 前:四行 diff 合为一张纸的底板(不加 outline:shorthand 不可过渡,会闪白框) */ .diff-view.is-sheet { background: rgba(24, 30, 52, 0.92); } /* 轮间重置:禁用过渡,红绿底色/符号瞬时消失(否则恢复显示时会闪一下空白红绿块) */ .diff-view.no-fade { transition: none; } .diff-view.no-fade .dline::before { transition: none; } .diff-view.no-fade .dsign { transition: none; } /* ───────── 收盒文件卡片(单元素裁切式 genie) ───────── 卡片全程同一个 DOM 元素:clip-path 多边形裁切(不压缩内容), body 每帧定位到轮廓包围盒,文字矢量缩小——全程清晰无损 */ .file-flight { position: absolute; pointer-events: none; opacity: 0; transition: opacity 0.45s ease; /* SVG 视口默认 hidden 会把描边外半侧(0.5px)裁掉——visible 让边缘描边完整 */ overflow: visible; } .file-flight.is-on { opacity: 1; } /* SVG 卡片:path 轮廓即卡片外形——fill 纸色 + stroke 描边跟随轮廓(S 曲线斜边也有描边) */ .file-flight-shape { fill: var(--file-paper); stroke: var(--file-line); stroke-width: 1; } .file-cover-name { font-family: var(--font-mono); font-size: 15px; letter-spacing: 0.02em; fill: var(--text-3); /* 和纸上线条同色 */ } .file-cover-stat { font-family: var(--font-mono); font-size: 11px; } .file-cover-stat .add { fill: var(--ok); } .file-cover-stat .del { fill: var(--danger); } .dline { position: relative; display: flex; gap: 12px; padding: 3px 10px; border-radius: 4px; /* 容器本身始终不透明:转正落位的字符立即可见,不经历消失→淡入; 红/绿底色由 ::before 独立淡入,+/- 符号独立淡入 */ white-space: nowrap; } .dline::before { content: ''; position: absolute; inset: 0; border-radius: 4px; opacity: 0; transition: opacity 0.3s ease; pointer-events: none; } .dline.is-del::before { background: rgba(212, 123, 107, 0.08); } .dline.is-add::before { background: rgba(123, 168, 137, 0.10); } .dline.is-shown::before { opacity: 1; } .dline .dsign { flex: none; width: 10px; opacity: 0; transition: opacity 0.3s ease; } .dline.is-shown .dsign { opacity: 1; } .dline.is-del { color: var(--danger); } .dline.is-add { color: var(--ok); } /* diff 行内单字符:占位时透明;落位转正后 is-lit 实色显示 */ .dch { opacity: 0; } .dch.is-lit { opacity: 1; } /* 飘行的字母:从笔的擦/写位置飞向 diff 行字符位,落位后转正为流内字符 */ .dch-ghost { position: absolute; left: 0; top: 0; font-family: var(--font-mono); font-size: 12.5px; line-height: 1.5; pointer-events: none; /* 不设 will-change:合成层位图与转正后的矢量重绘之间会出现大小/清晰度跳变 */ transition: transform 0.34s cubic-bezier(.35,.1,.25,1); z-index: 35; /* 高于盒前壁(30),飞行字母不被盒子挡 */ } /* 参考点(校准期红色可见,校准后改回透明) */ /* 笔尖/橡皮定位参考点:visibility 隐藏(保留几何盒供 getBoundingClientRect 实测), 同时清掉 fill/stroke 防止从 SVG 根继承描边显形 */ .pen-ref { visibility: hidden; fill: none; stroke: none; pointer-events: none; } /* ───────── 收件箱(genie 收盒) ───────── 后壁 z1(纸挡住它)、前壁 z4(它挡住纸)——纸从前后壁之间沉入 */ .inbox-part { position: absolute; left: 86px; /* 中心线 x=134 */ top: 166px; width: 96px; height: 96px; color: var(--text-1); opacity: 0; transition: opacity 0.35s ease; pointer-events: none; } .inbox-part.is-shown { opacity: 1; } .box-back { z-index: 10; } .box-front { z-index: 30; } /* 前壁盒身填实色:不能透出后面收进去的纸(凹口线保持描边不填) */ .box-front path { fill: var(--bg-1); } /* 盒内纸叠:genie 终态的文件卡片(.file-flight)本身就是插在盒子里的纸 */