diff --git a/.gitignore b/.gitignore
index f0a64ed..b08eb11 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,5 @@ CMakeCache.txt
__pycache__/
test-output/
screenshots/
+.DS_Store
+.playwright-cli/
diff --git a/ui/README.md b/ui/README.md
new file mode 100644
index 0000000..16bf4af
--- /dev/null
+++ b/ui/README.md
@@ -0,0 +1,13 @@
+# VDI Cloud UI 设计稿
+
+基于「百工万象」定版界面的深色 / 浅色视觉语言,为 VDI Cloud 云桌面控制台整理的可交互设计稿。
+
+- `index.html`:设计稿入口,可切换深色 / 浅色与页面
+- `screens/`:渲染后的页面截图
+- `assets/`:品牌标识
+
+页面范围:云桌面总览、桌面资源池、远程会话、终端与策略设置。
+
+## ARM 终端设计稿
+
+真实终端界面请打开 `terminal/index.html`。它对应 `terminal/kiosk-shell/src/login_page.cpp`、`desktop_list_page.cpp`、`desktop_settings_dialog.cpp` 和 `main_window.cpp` 的流程,覆盖登录、桌面列表、连接中、重连、管理菜单五个状态。
diff --git a/ui/assets/brand-logo.svg b/ui/assets/brand-logo.svg
new file mode 100644
index 0000000..156d7bd
--- /dev/null
+++ b/ui/assets/brand-logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ui/assets/brand-mark.svg b/ui/assets/brand-mark.svg
new file mode 100644
index 0000000..da3a44d
--- /dev/null
+++ b/ui/assets/brand-mark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ui/design-spec.md b/ui/design-spec.md
new file mode 100644
index 0000000..f28e7bd
--- /dev/null
+++ b/ui/design-spec.md
@@ -0,0 +1,21 @@
+# VDI Cloud UI 设计说明
+
+## 视觉继承
+
+- **色彩**:以百工万象定版的蜂蜜金橙 `#BD713B` 为主动作色,配合深色 `#151D27 / #1D2733 / #253241` 与浅色 `#F5F7FA / #FFFFFF / #F0F3F7`。
+- **结构**:固定左侧组织与导航;右侧采用页面标题、动作区、卡片分组的工作台结构。
+- **组件**:18px 大圆角、轻阴影、低对比边框;状态使用绿/黄/红三色,保持与数字员工运行中心一致。
+- **交互**:页面在入口内可切换深浅主题;导航可以切换总览、资源池、远程会话、终端策略四个页面。
+
+## VDI 页面映射
+
+| 页面 | 目标 | 关键内容 |
+| --- | --- | --- |
+| 云桌面总览 | 管理员快速判断系统状态 | 在线桌面、活跃会话、延迟、告警、趋势、资源健康 |
+| 桌面资源池 | 管理桌面模板与分配 | Windows / Ubuntu 桌面卡片、GPU、分辨率、状态与连接 |
+| 远程会话 | 观察实时串流与诊断 | 画面预览、2K/60 FPS、延迟、GPU/显存、断开操作 |
+| 终端与策略 | 统一配置终端体验 | 显示规格、自动恢复、独立光标、USB、断线保护 |
+
+## 使用方式
+
+直接打开 `index.html`,点击左侧导航查看页面;点击右上角月亮/太阳按钮切换主题。
diff --git a/ui/index.html b/ui/index.html
new file mode 100644
index 0000000..ed4cf93
--- /dev/null
+++ b/ui/index.html
@@ -0,0 +1,12 @@
+
+
VDI Cloud · 百工万象 UI 设计稿VDI CLOUD / WORKSPACE
云桌面总览
+计算节点24 / 24
串流网关12 / 12
存储容量76%
认证服务正常
+桌面资源池
统一管理 Windows / Linux 云桌面与分配策略
全部 128运行中 86待分配 42
设计工作站 · 2K
Windows 11 · NVIDIA GPU
2K / 60 FPS低延迟
研发环境 · Ubuntu
Ubuntu 24.04 · 开发工具链
GPU 加速USB 重定向
办公桌面 · 标准
Windows 10 · 通用办公模板
1080p / 45 FPS自动休眠
+远程画面预览 · 2K / 60 FPS · 18 ms
远程会话
设计工作站 · 王工
连接状态● 已连接
串流质量优秀
帧率60 FPS
网络延迟18 ms
+
+
diff --git a/ui/terminal/README.md b/ui/terminal/README.md
new file mode 100644
index 0000000..bfe8f2d
--- /dev/null
+++ b/ui/terminal/README.md
@@ -0,0 +1,48 @@
+# 越欣科技 ARM VDI 终端交互设计
+
+打开 `index.html`,从登录进入桌面列表。保留原有登录页 Logo、互动图形和深浅色外观,补充表单校验、登录状态及弹窗操作;登录后沿用深蓝灰 / 浅色和铜橙色按钮。
+
+## 登录体验
+
+公开体验账号:`demo`,密码:`Demo123!`。点击登录页的「体验账号」可查看并填入,再点击登录或按 Enter 继续。
+
+- 账号、密码为空时提示并聚焦;错误账号或密码会提示重试,清空密码。
+- 支持显示 / 隐藏密码、登录中状态和防重复提交。
+- 「记住工作账号」只保存成功登录的账号,不保存密码;取消勾选立即清除已记住的账号。退出后清空并隐藏密码。
+- 顶部「云端服务」打开连接设置,可切换云端服务 / 企业部署。企业地址需为 HTTPS,本机预览允许 HTTP;设置可保存、取消并在刷新后恢复。
+- 「企业 SSO」先检查企业连接配置,再展示体验身份;确认后进入桌面列表,取消或 Esc 会终止等待。
+- 「忘记密码」提供联系越欣科技管理员的说明及体验账号入口。
+- 「离线体验」确认后以独立体验身份进入桌面列表。
+
+这是交互原型,账号校验和 SSO 均为本地模拟,不会访问真实认证服务或远程桌面。请使用公开体验信息。设备和桌面数据为示例,未移植到 Qt 或部署到 ARM 设备。
+
+## 文件
+
+- `index.html`:登录界面和工作区挂载位置。
+- `login-interactions.js` / `login-interactions.css`:登录状态、校验、密码操作、连接设置、SSO 和弹窗样式。
+- `workspace.css`:登录后工作区的布局、配色、字号和适配。
+- `workspace.js`:桌面筛选、收藏、搜索、配置保存、连接状态、登录身份与设置导航。
+- `assets/systems/`:Windows、Ubuntu、麒麟识别资源。
+- [references/README.md](references/README.md):Microsoft Windows App、Amazon WorkSpaces 实际截图,以及 Horizon 官方操作流程与设计采用理由。
+- `output/playwright/`:浏览器渲染与验证证据。
+
+## 评审入口
+
+- [review.html](review.html):评审页,主入口从登录开始。
+- `index.html`:登录页。
+- `index.html?preview=desktops&theme=dark`:直接预览深色桌面列表。
+- `index.html?preview=desktops&theme=light`:直接预览浅色桌面列表。
+- `index.html?preview=settings&theme=dark`:桌面设置。
+- `index.html?preview=connecting&theme=dark`:连接中。
+- `index.html?preview=reconnect&theme=dark`:重连中。
+- `index.html?preview=error&theme=dark`:连接异常。
+
+正常流程:登录 → 桌面列表 → 设置或连接;`Ctrl+Alt+Shift+M` 打开管理设置,`Esc` 返回列表。设置中的「设备信息」提供退出登录。直接预览参数和管理快捷键仅用于原型评审,不代表生产权限控制。
+
+收藏、搜索、全部 / 已收藏筛选、设置分类、参数校验、保存、取消均可交互。账号、企业入口和桌面连接设置仅保存在浏览器本地。
+
+## 验证记录
+
+本次登录交互验证:[login-verification.json](output/playwright/login-verification.json),包含账号登录、记住账号、服务地址校验、SSO 取消、离线体验、键盘操作和深浅色布局检查;截图以 `login-` 开头。
+
+此前桌面工作区记录:[verification.json](output/playwright/verification.json)。它记录工作区的 27 项流程与布局检查及补充检查;其中「登录与旧基线完全一致」「点击直接进入桌面」描述的是添加登录交互之前的版本,已由本次登录验证取代。桌面列表记录的最小文字对比度:深色 5.04:1、浅色 4.74:1。1280×720、1366×768、1920×1080 的浏览器检查不代表 ARM 实机验收。
diff --git a/ui/terminal/assets/login-art.js b/ui/terminal/assets/login-art.js
new file mode 100644
index 0000000..fbcd203
--- /dev/null
+++ b/ui/terminal/assets/login-art.js
@@ -0,0 +1,61 @@
+(() => {
+ const host=document.getElementById('ill');
+ host.querySelectorAll('.px,.work-art,#sky').forEach(e=>e.remove());
+ const canvas=document.createElement('canvas');canvas.className='kinetic-art';
+ canvas.setAttribute('role','button');canvas.tabIndex=0;
+ canvas.setAttribute('aria-label','互动树形 Logo:移动鼠标探索,点击或按回车释放光环');
+ host.prepend(canvas);
+ const W=780,H=1060,dpr=Math.min(devicePixelRatio||1,2),ctx=canvas.getContext('2d');
+ canvas.width=W*dpr;canvas.height=H*dpr;ctx.scale(dpr,dpr);
+ const dark=()=>document.body.dataset.theme==='dark';
+ const reduced=matchMedia('(prefers-reduced-motion: reduce)');
+ const mark=new Image();mark.src='assets/mark.svg';
+ const plate=document.createElement('canvas');plate.width=1000;plate.height=850;
+ const paint=plate.getContext('2d');
+ const grain=document.createElement('canvas');grain.width=1000;grain.height=850;
+ const grainPaint=grain.getContext('2d');let seed=37;
+ const random=()=>{seed=(seed*16807)%2147483647;return seed/2147483647;};
+ for(let i=0;i<2200;i++){const x=random()*1000,y=random()*850;grainPaint.strokeStyle=i%3?'rgba(255,255,255,.12)':'rgba(153,111,54,.06)';grainPaint.lineWidth=.5;grainPaint.beginPath();grainPaint.moveTo(x,y);grainPaint.lineTo(x+8+random()*45,y-1);grainPaint.stroke();}
+ const p={x:0,y:0,tx:0,ty:0,mx:-1000,my:-1000,inside:false};
+ let ready=false,frame=0,clicks=0,last=0,raf=0;
+ const rings=[];
+ const leaves=[[.10,.46],[.25,.26],[.50,.13],[.75,.26],[.90,.46]];
+ function locate(e){const r=host.getBoundingClientRect();p.mx=(e.clientX-r.left)*W/r.width;p.my=(e.clientY-r.top)*H/r.height;p.tx=(p.mx/W-.5)*2;p.ty=(p.my/H-.5)*2;p.inside=true;}
+ host.addEventListener('pointermove',locate);
+ host.addEventListener('pointerleave',()=>{p.inside=false;p.tx=p.ty=0;p.mx=p.my=-1000;});
+ function pulse(){clicks++;canvas.dataset.clicks=clicks;if(!reduced.matches)rings.push({start:performance.now()});}
+ canvas.addEventListener('pointerdown',e=>{locate(e);pulse();});
+ canvas.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();pulse();}});
+ mark.onload=()=>{ready=true;canvas.dataset.ready='true';};
+ function draw(now){
+ const dt=Math.min(40,now-last||16);last=now;frame++;
+ const smooth=1-Math.exp(-dt/140);p.x+=(p.tx-p.x)*smooth;p.y+=(p.ty-p.y)*smooth;
+ ctx.clearRect(0,0,W,H);
+ const bg=ctx.createLinearGradient(0,0,W,H);bg.addColorStop(0,dark()?'#1B222B':'#FCF8F5');bg.addColorStop(1,dark()?'#262930':'#F5E6DA');ctx.fillStyle=bg;ctx.fillRect(0,0,W,H);
+ const t=reduced.matches?0:now*.001;
+ const cx=344+(reduced.matches?0:p.x*22),cy=658+(reduced.matches?0:p.y*15+Math.sin(t*.7)*7);
+ const w=470,h=398,left=cx-w/2,top=cy-h/2;
+ const halo=ctx.createRadialGradient(cx,cy,30,cx,cy,345);halo.addColorStop(0,dark()?'rgba(230,161,94,.10)':'rgba(255,255,255,.95)');halo.addColorStop(1,'rgba(230,161,94,0)');ctx.fillStyle=halo;ctx.fillRect(0,330,780,730);
+ ctx.save();ctx.translate(cx,cy+235);ctx.scale(1,.13);const shadow=ctx.createRadialGradient(0,0,10,0,0,245);shadow.addColorStop(0,dark()?'rgba(0,0,0,.22)':'rgba(151,92,46,.09)');shadow.addColorStop(1,'rgba(151,92,46,0)');ctx.fillStyle=shadow;ctx.fillRect(-260,-260,520,520);ctx.restore();
+ if(ready){
+ paint.clearRect(0,0,1000,850);paint.globalCompositeOperation='source-over';paint.drawImage(mark,0,0,1000,850);paint.globalCompositeOperation='source-in';
+ const lightX=reduced.matches?0:p.x*120+Math.sin(t*.35)*35;
+ const g=paint.createLinearGradient(-100+lightX,50,950+lightX,760);g.addColorStop(0,'#FFF0CE');g.addColorStop(.23,'#F1CF92');g.addColorStop(.43,'#E3B76E');g.addColorStop(.58,'#F9DFA8');g.addColorStop(.72,'#E9BF7B');g.addColorStop(1,'#F7DCA5');paint.fillStyle=g;paint.fillRect(0,0,1000,850);
+ paint.globalCompositeOperation='source-atop';
+ const sheen=paint.createLinearGradient(lightX,0,1000+lightX,500);sheen.addColorStop(0,'rgba(255,255,255,0)');sheen.addColorStop(.42,'rgba(255,255,255,0)');sheen.addColorStop(.53,'rgba(255,252,237,.32)');sheen.addColorStop(.64,'rgba(255,255,255,0)');sheen.addColorStop(1,'rgba(255,255,255,0)');paint.fillStyle=sheen;paint.fillRect(0,0,1000,850);paint.drawImage(grain,0,0);
+ ctx.globalAlpha=.07;for(let i=4;i>0;i-=1)ctx.drawImage(mark,left+i*.45,top+i*.7,w,h);ctx.globalAlpha=1;
+ ctx.save();ctx.filter='brightness(1.12)';ctx.drawImage(plate,left-.6,top-.8,w,h);ctx.restore();
+ ctx.drawImage(plate,left,top,w,h);
+ leaves.forEach(([x,y],i)=>{const lx=left+x*w,ly=top+y*h,d=Math.hypot(lx-p.mx,ly-p.my),active=p.inside?Math.max(0,1-d/115):0;
+ if(active>0){ctx.fillStyle=`rgba(255,231,188,${active*.6})`;ctx.shadowColor='#F8CE91';ctx.shadowBlur=22*active;ctx.beginPath();ctx.arc(lx,ly,5+active*8,0,Math.PI*2);ctx.fill();ctx.shadowBlur=0;}
+ });
+ }
+ // Sparse orbital sparks react locally to the pointer, leaving the silhouette clear.
+ for(let i=0;i<38;i++){const a=i*2.399+t*.09,r=255+(i%5)*13;let x=cx+Math.cos(a)*r,y=cy+Math.sin(a)*r*.81;const d=Math.hypot(x-p.mx,y-p.my);if(p.inside&&d<130&&!reduced.matches){x+=(p.mx-x)*.22;y+=(p.my-y)*.22;ctx.strokeStyle='rgba(217,139,70,.2)';ctx.beginPath();ctx.moveTo(x,y);ctx.lineTo(p.mx,p.my);ctx.stroke();}ctx.fillStyle=dark()?'rgba(246,196,133,.55)':'rgba(189,113,59,.42)';ctx.beginPath();ctx.arc(x,y,i%4===0?2:1,0,Math.PI*2);ctx.fill();}
+ for(let i=rings.length-1;i>=0;i--){const age=(now-rings[i].start)/1500;if(age>=1){rings.splice(i,1);continue;}ctx.strokeStyle=`rgba(230,161,94,${(1-age)*.6})`;ctx.lineWidth=1.5;ctx.beginPath();ctx.ellipse(cx,cy,70+age*300,50+age*235,0,0,Math.PI*2);ctx.stroke();}
+ canvas.dataset.pointer=`${p.x.toFixed(3)},${p.y.toFixed(3)}`;canvas.dataset.frame=frame;canvas.dataset.rings=rings.length;canvas.dataset.reduced=String(reduced.matches);
+ raf=requestAnimationFrame(draw);
+ }
+ document.addEventListener('visibilitychange',()=>{cancelAnimationFrame(raf);if(!document.hidden){last=0;raf=requestAnimationFrame(draw);}});
+ raf=requestAnimationFrame(draw);
+})();
diff --git a/ui/terminal/assets/logo.svg b/ui/terminal/assets/logo.svg
new file mode 100644
index 0000000..156d7bd
--- /dev/null
+++ b/ui/terminal/assets/logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ui/terminal/assets/mark.svg b/ui/terminal/assets/mark.svg
new file mode 100644
index 0000000..da3a44d
--- /dev/null
+++ b/ui/terminal/assets/mark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ui/terminal/assets/systems/kylinsoft.png b/ui/terminal/assets/systems/kylinsoft.png
new file mode 100644
index 0000000..415ec82
Binary files /dev/null and b/ui/terminal/assets/systems/kylinsoft.png differ
diff --git a/ui/terminal/assets/systems/ubuntu.svg b/ui/terminal/assets/systems/ubuntu.svg
new file mode 100644
index 0000000..227f86a
--- /dev/null
+++ b/ui/terminal/assets/systems/ubuntu.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ui/terminal/index.html b/ui/terminal/index.html
new file mode 100644
index 0000000..056dd77
--- /dev/null
+++ b/ui/terminal/index.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+越欣科技 · VDI 云终端
+
+
+
+
+
+
+
+
+
+
+

百工协作,
万象更新。连接数字员工,让对话、流程与电脑操作自然协作。
+
+
+
+
+
+
+
+
diff --git a/ui/terminal/login-interactions.css b/ui/terminal/login-interactions.css
new file mode 100644
index 0000000..c4975ec
--- /dev/null
+++ b/ui/terminal/login-interactions.css
@@ -0,0 +1,64 @@
+/* Login interactions extend the established login design; brand art and layout remain owned by index.html. */
+.login-view .login-card.auth-enhanced{height:auto;min-height:70vh}
+@media(max-width:1300px){.login-view .login-card.auth-enhanced{min-height:0}}
+.login-view .auth-intro{display:flex;align-items:center;justify-content:space-between;gap:12px}
+.login-view .auth-text-button{border:0;background:none;padding:4px 0;color:#e6ab7d;font-size:12px;line-height:1.5;white-space:nowrap;cursor:pointer}
+.login-view .auth-password{position:relative}
+.login-view .auth-password .input{display:block;padding-right:49px!important}
+.login-view .auth-eye{display:grid;place-items:center;position:absolute;right:5px;top:50%;transform:translateY(-50%);width:40px;height:40px;padding:0;border:0;border-radius:8px;background:none;color:#bec9d5;cursor:pointer}
+.auth-eye svg{width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
+.login-view .auth-eye:hover{background:rgba(150,164,180,.12)}
+.login-view .auth-enhanced .primary{display:inline-flex;justify-content:center;align-items:center;gap:10px;margin-top:20px!important;cursor:pointer}
+.login-view .auth-feedback{min-height:20px;margin:10px 0 0;line-height:20px;letter-spacing:0;color:#b6c2cf;font-size:12px}
+.login-view .auth-feedback.error{color:#ffb7ad}
+.login-view .auth-enhanced .input[aria-invalid=true]{border:1px solid #e7978a!important}
+.login-view .auth-enhanced .card-footer{margin-top:18px!important}
+.login-view .auth-enhanced[aria-busy=true] .primary{opacity:.8}
+.login-view .auth-enhanced :disabled{cursor:wait}
+.login-view .auth-enhanced input:disabled{opacity:.75}
+.login-view .auth-enhanced :focus-visible,#auth-service:focus-visible{outline:2px solid #edb78f;outline-offset:3px}
+.auth-spinner{width:15px;height:15px;border-radius:50%;border:2px solid rgba(255,255,255,.35);border-top-color:#fff;animation:auth-spin .9s linear infinite}
+@keyframes auth-spin{to{transform:rotate(360deg)}}
+.auth-dialog{color-scheme:dark;--auth-bg:#242d38;--auth-input:#293540;--auth-text:#edf1f5;--auth-secondary:#bac5cf;--auth-line:#445362;--auth-accent:#efb38a;--auth-wash:#382e28;--auth-error:#ffb7ad;--auth-button:#a95829;width:min(480px,calc(100% - 40px));max-height:calc(100vh - 40px);max-height:calc(100dvh - 40px);overflow:auto;margin:auto;padding:30px;border:1px solid var(--auth-line);border-radius:24px;background:var(--auth-bg);color:var(--auth-text);box-shadow:0 25px 80px rgba(0,0,0,.3);font-size:14px;line-height:1.65}
+.auth-dialog:not([open]){display:none}
+.auth-dialog::backdrop{background:rgba(9,15,23,.6);backdrop-filter:blur(3px)}
+.auth-dialog-header{display:flex;justify-content:space-between;align-items:flex-start;gap:18px;margin-bottom:21px}
+.auth-dialog-header h2{font-size:23px;line-height:1.4;margin:0;font-weight:650;letter-spacing:-.3px}
+.auth-dialog-sub{font-size:13px;color:var(--auth-secondary);margin:8px 0 0}
+.auth-dialog-close{display:grid;place-items:center;width:36px;height:36px;border:0;border-radius:9px;background:var(--auth-input);color:var(--auth-secondary);font-size:23px;line-height:1;flex-shrink:0}
+.auth-dialog :focus-visible{outline:2px solid var(--auth-accent);outline-offset:3px}
+.auth-dialog-tabs{display:grid;grid-template-columns:1fr 1fr;gap:5px;padding:4px;background:var(--auth-input);border:1px solid var(--auth-line);border-radius:12px;margin-bottom:24px}
+.auth-dialog-tabs button{min-height:40px;border:0;border-radius:8px;background:none;color:var(--auth-secondary);font-size:14px}
+.auth-dialog-tabs button[aria-selected=true]{color:var(--auth-accent);background:var(--auth-wash);font-weight:650}
+.auth-dialog .auth-field{display:grid;gap:8px;margin:18px 0;color:var(--auth-text);font-size:14px}
+.auth-dialog .auth-input{height:46px;width:100%;min-width:0;border:1px solid var(--auth-line);border-radius:11px;padding:0 13px;background:var(--auth-input);color:var(--auth-text);outline:none}
+.auth-dialog .auth-input[aria-invalid=true]{border-color:var(--auth-error)}
+.auth-dialog .auth-input::placeholder{color:var(--auth-secondary)}
+.auth-dialog small{font-size:12px;color:var(--auth-secondary);line-height:1.7}
+.auth-dialog .auth-note{padding:14px 16px;border:1px solid var(--auth-line);border-radius:12px;background:var(--auth-input);color:var(--auth-secondary);font-size:13px;line-height:1.8;margin:20px 0}
+.auth-dialog .auth-note strong{color:var(--auth-text);font-weight:600}
+.auth-dialog .auth-note p{margin:5px 0 0}
+.auth-dialog .auth-note dd{margin:0;overflow-wrap:anywhere;color:var(--auth-text)}
+.auth-dialog .auth-note dt{color:var(--auth-secondary);font-size:12px}
+.auth-dialog .auth-note>div+div{margin-top:12px}
+.auth-dialog-error{min-height:21px;font-size:13px;color:var(--auth-error);margin:12px 0 0}
+.auth-dialog-actions{display:flex;justify-content:flex-end;align-items:center;gap:11px;margin-top:23px}
+.auth-dialog-button{display:inline-flex;align-items:center;justify-content:center;gap:10px;min-height:44px;padding:0 17px;border:1px solid var(--auth-line);border-radius:11px;background:transparent;color:var(--auth-text);font-size:14px;white-space:nowrap}
+.auth-dialog-button.primary{width:auto;margin:0;letter-spacing:0;border-color:var(--auth-button);background:var(--auth-button);color:#fff;box-shadow:none;height:auto}
+.auth-dialog-button:disabled{opacity:.7;cursor:wait}
+.auth-dialog .auth-checkbox{display:flex;align-items:flex-start;gap:9px;font-size:13px;color:var(--auth-secondary);margin-top:18px}
+.auth-dialog .auth-checkbox input{accent-color:var(--auth-button);width:17px;height:17px;flex-shrink:0;margin-top:3px}
+.auth-demo-credentials{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin:20px 0;background:var(--auth-input);border:1px solid var(--auth-line);border-radius:13px;padding:18px}
+.auth-demo-credentials dt{color:var(--auth-secondary);font-size:13px}
+.auth-demo-credentials dd{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:17px;color:var(--auth-text);margin:5px 0 0}
+body[data-theme=light] .login-view .auth-text-button{color:#8b4720}
+body[data-theme=light] .login-view .auth-eye{color:#667785}
+body[data-theme=light] .login-view .auth-feedback{color:#536372}
+body[data-theme=light] .login-view .auth-feedback.error{color:#aa3f32}
+body[data-theme=light] .login-view .auth-enhanced .input[aria-invalid=true]{border-color:#bd5344!important}
+body[data-theme=light] .login-view .auth-enhanced :focus-visible,body[data-theme=light] #auth-service:focus-visible{outline-color:#954b22}
+body[data-theme=light] .auth-dialog{color-scheme:light;--auth-bg:#fff;--auth-input:#f5f6f7;--auth-text:#283440;--auth-secondary:#536372;--auth-line:#d8dfe5;--auth-accent:#954b22;--auth-wash:#faeee5;--auth-error:#aa3f32;--auth-button:#a95829;box-shadow:0 25px 80px rgba(25,38,50,.18)}
+@media(max-width:600px){.auth-dialog{padding:24px 20px;border-radius:20px}.auth-dialog-header h2{font-size:21px}.auth-dialog-actions{flex-wrap:wrap}.auth-dialog-button{flex:1}.auth-demo-credentials{gap:12px;padding:14px}}
+@media(prefers-reduced-motion:reduce){.auth-spinner{animation:none}}
+
+.auth-dialog-status{min-height:21px;font-size:13px;color:var(--auth-secondary);margin:12px 0 0}
diff --git a/ui/terminal/login-interactions.js b/ui/terminal/login-interactions.js
new file mode 100644
index 0000000..4b66a0a
--- /dev/null
+++ b/ui/terminal/login-interactions.js
@@ -0,0 +1,187 @@
+(() => {
+ 'use strict';
+ const form = document.getElementById('auth-form');
+ const username = document.getElementById('username');
+ const password = document.getElementById('password');
+ const remember = document.getElementById('auth-remember');
+ const submit = document.getElementById('auth-submit');
+ const feedback = document.getElementById('auth-feedback');
+ const serviceButton = document.getElementById('auth-service');
+ const serviceName = document.getElementById('auth-service-name');
+ const subtitle = document.getElementById('auth-subtitle');
+ const passwordToggle = document.getElementById('auth-toggle-password');
+ const dialog = document.getElementById('auth-dialog');
+ const dialogContent = document.getElementById('auth-dialog-content');
+ const DEMO = Object.freeze({username:'demo', password:'Demo123!'});
+ const keys = {account:'yuexin-terminal-login-account',connection:'yuexin-terminal-login-connection'};
+ const escape = value => String(value).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
+ const read = key => {try{return JSON.parse(localStorage.getItem(key)||'null');}catch{return null;}};
+ const write = (key,value) => {try{if(value===null)localStorage.removeItem(key);else localStorage.setItem(key,JSON.stringify(value));return true;}catch{return false;}};
+ function validEndpoint(value) {
+ try {
+ const u = new URL(value);
+ if (u.username || u.password || u.search || u.hash) return null;
+ if (u.protocol !== 'https:' && !(u.protocol === 'http:' && ['localhost','127.0.0.1','[::1]'].includes(u.hostname))) return null;
+ return u.href.replace(/\/$/,'');
+ } catch {return null;}
+ }
+ const savedConnection = read(keys.connection);
+ let connection = savedConnection?.mode === 'enterprise' && validEndpoint(savedConnection.url)
+ ? {mode:'enterprise',url:validEndpoint(savedConnection.url),organization:String(savedConnection.organization||'').slice(0,80)}
+ : {mode:'cloud',url:'',organization:''};
+ let mode = connection.mode, serviceIntent = '', busy = false, loginTimer, modalTimer, modalBusy = false;
+ let credentials = null;
+ let serviceDraft = {url:connection.url,organization:connection.organization};
+ function restoreAccount() {
+ const saved = read(keys.account);
+ username.value = typeof saved?.username === 'string' ? saved.username.slice(0,100) : '';
+ remember.checked = saved?.remember !== false;
+ }
+ function updateService() {
+ serviceName.textContent = connection.mode === 'enterprise' ? '企业部署' : '云端服务';
+ serviceButton.setAttribute('aria-label',serviceName.textContent+',打开连接设置');
+ serviceButton.title = connection.mode === 'enterprise' ? connection.url : '云端服务';
+ subtitle.textContent = connection.mode === 'enterprise' ? '使用企业工作账号继续' : '使用云端工作账号继续';
+ }
+ function message(text,error = false) {
+ feedback.textContent = text;
+ feedback.classList.toggle('error',error);
+ }
+ function clearInvalid() {
+ username.removeAttribute('aria-invalid');password.removeAttribute('aria-invalid');
+ }
+ function setBusy(value) {
+ busy = value;
+ form.setAttribute('aria-busy',String(value));
+ form.querySelectorAll('input,button').forEach(el=>{el.disabled=value;});
+ serviceButton.disabled=value;
+ submit.innerHTML=value?'登录中…':'登 录';
+ }
+ function concealPassword() {
+ password.type='password';passwordToggle.setAttribute('aria-pressed','false');passwordToggle.setAttribute('aria-label','显示密码');
+ }
+ function enterWorkspace(identity) {
+ clearTimeout(loginTimer);credentials=null;password.value='';concealPassword();setBusy(false);message('');
+ document.dispatchEvent(new CustomEvent('terminal:login',{detail:identity}));
+ }
+ function submitLogin(event) {
+ event.preventDefault();
+ if(busy)return;
+ clearInvalid();
+ const account=username.value.trim();
+ if(!account){username.setAttribute('aria-invalid','true');message('请输入工作账号',true);username.focus();return;}
+ if(!password.value){password.setAttribute('aria-invalid','true');message('请输入密码',true);password.focus();return;}
+ credentials={username:account,password:password.value};
+ setBusy(true);message('正在验证工作账号…');
+ loginTimer=setTimeout(()=>{
+ const accepted=credentials?.username===DEMO.username&&credentials?.password===DEMO.password;
+ credentials=null;setBusy(false);
+ if(!accepted){password.value='';concealPassword();password.setAttribute('aria-invalid','true');message('账号或密码不正确,请重试或查看体验账号',true);password.focus();return;}
+ write(keys.account,{username:remember.checked?account:'',remember:remember.checked});
+ enterWorkspace({name:account,mode:'account',service:connection.mode,organization:connection.organization});
+ },900);
+ }
+ const heading = (title,sub) => ``;
+ const cancel = (label='取消') => ``;
+ function showDialog(html) {
+ clearTimeout(modalTimer);modalBusy=false;
+ dialogContent.innerHTML=html;
+ if(!dialog.open)dialog.showModal();
+ }
+ function closeDialog() {clearTimeout(modalTimer);modalBusy=false;dialog.close();}
+ function rememberDraft() {
+ const url=dialogContent.querySelector('#auth-enterprise-url'),org=dialogContent.querySelector('#auth-enterprise-org');
+ if(url){serviceDraft.url=url.value;serviceDraft.organization=org.value;}
+ }
+ function serviceDialog(intent='') {
+ serviceIntent=intent;mode=intent==='sso'?'enterprise':connection.mode;
+ serviceDraft={url:connection.url,organization:connection.organization};
+ renderService();
+ }
+ function renderService() {
+ showDialog(heading('连接设置','选择云端服务或企业部署入口。')+
+ ``+
+ ``);
+ }
+ function saveService(event) {
+ event.preventDefault();
+ rememberDraft();
+ let url='';
+ if(mode==='enterprise'){
+ const input=dialogContent.querySelector('#auth-enterprise-url');url=validEndpoint(input.value.trim());
+ if(!url){input.setAttribute('aria-invalid','true');dialogContent.querySelector('#auth-service-error').textContent='请输入有效的 HTTPS 企业服务地址';input.focus();return;}
+ }
+ connection=mode==='enterprise'?{mode,url,organization:serviceDraft.organization.trim()}:{mode:'cloud',url:'',organization:''};
+ const stored=write(keys.connection,connection);updateService();
+ if(serviceIntent==='sso'&&connection.mode==='enterprise'){ssoDialog();return;}
+ closeDialog();message(stored?'连接设置已保存':'连接设置已应用,本次预览内有效');username.focus();
+ }
+ function demoDialog() {
+ showDialog(heading('体验账号','用于预览登录、桌面选择和连接设置流程。')+
+ `- 工作账号
- ${DEMO.username}
- 密码
- ${DEMO.password}
这是交互原型的公开体验账号,未连接真实认证服务。请使用体验信息。
${cancel('返回')}
`);
+ }
+ function forgotDialog() {
+ showDialog(heading('忘记密码','企业工作账号由管理员统一管理。')+
+ `请联系越欣科技管理员重置密码可提供工作账号与设备序列号 VDI-8F42A1,协助管理员定位。
原型体验账号无法修改密码,可重新查看体验信息。
${cancel('返回登录')}
`);
+ }
+ function ssoDialog() {
+ if(connection.mode!=='enterprise'){serviceDialog('sso');return;}
+ showDialog(heading('企业账号登录','通过企业统一身份进入云桌面。')+
+ `- 企业服务
- ${escape(connection.url)}
${connection.organization?`- 组织
- ${escape(connection.organization)}
`:''}- 体验身份
- 越欣科技 · 体验用户
当前为统一身份登录预览,不会跳转或访问真实企业身份服务。
${cancel('取消登录')}
`);
+ }
+ function ssoContinue(button) {
+ if(modalBusy)return;
+ modalBusy=true;button.disabled=true;button.innerHTML='登录中…';
+ dialogContent.querySelector('#auth-sso-status').textContent='正在验证企业身份…';
+ modalTimer=setTimeout(()=>{closeDialog();enterWorkspace({name:'企业体验用户',mode:'sso',service:'enterprise',organization:connection.organization});},900);
+ }
+ function offlineDialog() {
+ showDialog(heading('离线体验','先了解桌面列表和终端设置。')+
+ `无需工作账号可预览桌面、收藏和连接设置,设备与桌面信息为示例。配置仅保存在当前浏览器,不连接真实远程桌面。
${cancel()}
`);
+ }
+ form.addEventListener('submit',submitLogin);
+ form.addEventListener('input',event=>{
+ if(event.target===username||event.target===password){event.target.removeAttribute('aria-invalid');if(!busy)message('');}
+ });
+ remember.addEventListener('change',()=>{
+ if(!remember.checked)write(keys.account,{username:'',remember:false});
+ });
+ passwordToggle.addEventListener('click',()=>{
+ const reveal=password.type==='password';password.type=reveal?'text':'password';passwordToggle.setAttribute('aria-pressed',String(reveal));passwordToggle.setAttribute('aria-label',reveal?'隐藏密码':'显示密码');
+ });
+ serviceButton.addEventListener('click',()=>serviceDialog());
+ document.getElementById('auth-demo').addEventListener('click',demoDialog);
+ document.getElementById('auth-forgot').addEventListener('click',forgotDialog);
+ document.getElementById('auth-sso').addEventListener('click',ssoDialog);
+ document.getElementById('auth-offline').addEventListener('click',offlineDialog);
+ dialog.addEventListener('submit',event=>{if(event.target.id==='auth-service-form')saveService(event);});
+ dialog.addEventListener('input',event=>{event.target.removeAttribute('aria-invalid');const error=dialogContent.querySelector('#auth-service-error');if(error)error.textContent='';});
+ dialog.addEventListener('click',event=>{
+ const button=event.target.closest('[data-auth-action]');
+ if(!button||button.disabled)return;
+ switch(button.dataset.authAction){
+ case 'close':closeDialog();break;
+ case 'service-mode':rememberDraft();mode=button.dataset.mode;renderService();dialogContent.querySelector(`[data-mode="${mode}"]`).focus();break;
+ case 'fill-demo':username.value=DEMO.username;password.value=DEMO.password;concealPassword();clearInvalid();message('已填入体验账号,点击登录继续');closeDialog();submit.focus();break;
+ case 'demo':demoDialog();break;
+ case 'sso-continue':ssoContinue(button);break;
+ case 'offline-continue':closeDialog();enterWorkspace({name:'离线体验',mode:'offline',service:'offline',organization:'越欣科技'});break;
+ }
+ });
+ dialog.addEventListener('keydown',event=>{
+ const tab=event.target.closest('[role=tab]');
+ if(!tab||!['ArrowLeft','ArrowRight','Home','End'].includes(event.key))return;
+ event.preventDefault();rememberDraft();
+ mode=event.key==='Home'?'cloud':event.key==='End'?'enterprise':mode==='cloud'?'enterprise':'cloud';
+ renderService();dialogContent.querySelector(`[data-mode="${mode}"]`).focus();
+ });
+ dialog.addEventListener('cancel',()=>{clearTimeout(modalTimer);modalBusy=false;});
+ dialog.addEventListener('close',()=>{clearTimeout(modalTimer);modalBusy=false;});
+ document.addEventListener('terminal:logout',()=>{
+ clearTimeout(loginTimer);clearTimeout(modalTimer);credentials=null;setBusy(false);password.value='';concealPassword();clearInvalid();restoreAccount();message('已退出登录');username.focus();
+ });
+ document.addEventListener('terminal:open-workspace',()=>{
+ clearTimeout(loginTimer);credentials=null;setBusy(false);password.value='';concealPassword();if(dialog.open)closeDialog();
+ });
+ restoreAccount();updateService();
+})();
diff --git a/ui/terminal/output/playwright/connecting-dark.png b/ui/terminal/output/playwright/connecting-dark.png
new file mode 100644
index 0000000..3859424
Binary files /dev/null and b/ui/terminal/output/playwright/connecting-dark.png differ
diff --git a/ui/terminal/output/playwright/connecting-light.png b/ui/terminal/output/playwright/connecting-light.png
new file mode 100644
index 0000000..3f144b9
Binary files /dev/null and b/ui/terminal/output/playwright/connecting-light.png differ
diff --git a/ui/terminal/output/playwright/desktops-system-logos-light.png b/ui/terminal/output/playwright/desktops-system-logos-light.png
new file mode 100644
index 0000000..7e0401b
Binary files /dev/null and b/ui/terminal/output/playwright/desktops-system-logos-light.png differ
diff --git a/ui/terminal/output/playwright/desktops-system-logos.png b/ui/terminal/output/playwright/desktops-system-logos.png
new file mode 100644
index 0000000..2fbc1c4
Binary files /dev/null and b/ui/terminal/output/playwright/desktops-system-logos.png differ
diff --git a/ui/terminal/output/playwright/error-dark.png b/ui/terminal/output/playwright/error-dark.png
new file mode 100644
index 0000000..b6e4c51
Binary files /dev/null and b/ui/terminal/output/playwright/error-dark.png differ
diff --git a/ui/terminal/output/playwright/error-light.png b/ui/terminal/output/playwright/error-light.png
new file mode 100644
index 0000000..b215a7f
Binary files /dev/null and b/ui/terminal/output/playwright/error-light.png differ
diff --git a/ui/terminal/output/playwright/login-baseline-dark.png b/ui/terminal/output/playwright/login-baseline-dark.png
new file mode 100644
index 0000000..64f7c6b
Binary files /dev/null and b/ui/terminal/output/playwright/login-baseline-dark.png differ
diff --git a/ui/terminal/output/playwright/login-demo-dark.png b/ui/terminal/output/playwright/login-demo-dark.png
new file mode 100644
index 0000000..c8ae5c0
Binary files /dev/null and b/ui/terminal/output/playwright/login-demo-dark.png differ
diff --git a/ui/terminal/output/playwright/login-demo-light.png b/ui/terminal/output/playwright/login-demo-light.png
new file mode 100644
index 0000000..59e1761
Binary files /dev/null and b/ui/terminal/output/playwright/login-demo-light.png differ
diff --git a/ui/terminal/output/playwright/login-dialog-390.png b/ui/terminal/output/playwright/login-dialog-390.png
new file mode 100644
index 0000000..cbe3f0c
Binary files /dev/null and b/ui/terminal/output/playwright/login-dialog-390.png differ
diff --git a/ui/terminal/output/playwright/login-dialog-820.png b/ui/terminal/output/playwright/login-dialog-820.png
new file mode 100644
index 0000000..28993f3
Binary files /dev/null and b/ui/terminal/output/playwright/login-dialog-820.png differ
diff --git a/ui/terminal/output/playwright/login-enterprise-dark.png b/ui/terminal/output/playwright/login-enterprise-dark.png
new file mode 100644
index 0000000..b537d54
Binary files /dev/null and b/ui/terminal/output/playwright/login-enterprise-dark.png differ
diff --git a/ui/terminal/output/playwright/login-enterprise-light.png b/ui/terminal/output/playwright/login-enterprise-light.png
new file mode 100644
index 0000000..f1a0c31
Binary files /dev/null and b/ui/terminal/output/playwright/login-enterprise-light.png differ
diff --git a/ui/terminal/output/playwright/login-forgot-dark.png b/ui/terminal/output/playwright/login-forgot-dark.png
new file mode 100644
index 0000000..bfb60d4
Binary files /dev/null and b/ui/terminal/output/playwright/login-forgot-dark.png differ
diff --git a/ui/terminal/output/playwright/login-forgot-light.png b/ui/terminal/output/playwright/login-forgot-light.png
new file mode 100644
index 0000000..7c15e9c
Binary files /dev/null and b/ui/terminal/output/playwright/login-forgot-light.png differ
diff --git a/ui/terminal/output/playwright/login-interactive-dark-1280.png b/ui/terminal/output/playwright/login-interactive-dark-1280.png
new file mode 100644
index 0000000..019689a
Binary files /dev/null and b/ui/terminal/output/playwright/login-interactive-dark-1280.png differ
diff --git a/ui/terminal/output/playwright/login-interactive-dark-1366.png b/ui/terminal/output/playwright/login-interactive-dark-1366.png
new file mode 100644
index 0000000..5a7e34d
Binary files /dev/null and b/ui/terminal/output/playwright/login-interactive-dark-1366.png differ
diff --git a/ui/terminal/output/playwright/login-interactive-dark-1920.png b/ui/terminal/output/playwright/login-interactive-dark-1920.png
new file mode 100644
index 0000000..9c253a3
Binary files /dev/null and b/ui/terminal/output/playwright/login-interactive-dark-1920.png differ
diff --git a/ui/terminal/output/playwright/login-interactive-dark.png b/ui/terminal/output/playwright/login-interactive-dark.png
new file mode 100644
index 0000000..db67e02
Binary files /dev/null and b/ui/terminal/output/playwright/login-interactive-dark.png differ
diff --git a/ui/terminal/output/playwright/login-interactive-light-1280.png b/ui/terminal/output/playwright/login-interactive-light-1280.png
new file mode 100644
index 0000000..eb435c3
Binary files /dev/null and b/ui/terminal/output/playwright/login-interactive-light-1280.png differ
diff --git a/ui/terminal/output/playwright/login-interactive-light-1366.png b/ui/terminal/output/playwright/login-interactive-light-1366.png
new file mode 100644
index 0000000..a8abc32
Binary files /dev/null and b/ui/terminal/output/playwright/login-interactive-light-1366.png differ
diff --git a/ui/terminal/output/playwright/login-interactive-light-1920.png b/ui/terminal/output/playwright/login-interactive-light-1920.png
new file mode 100644
index 0000000..38b20ef
Binary files /dev/null and b/ui/terminal/output/playwright/login-interactive-light-1920.png differ
diff --git a/ui/terminal/output/playwright/login-interactive-light.png b/ui/terminal/output/playwright/login-interactive-light.png
new file mode 100644
index 0000000..df63271
Binary files /dev/null and b/ui/terminal/output/playwright/login-interactive-light.png differ
diff --git a/ui/terminal/output/playwright/login-offline-dark.png b/ui/terminal/output/playwright/login-offline-dark.png
new file mode 100644
index 0000000..e032404
Binary files /dev/null and b/ui/terminal/output/playwright/login-offline-dark.png differ
diff --git a/ui/terminal/output/playwright/login-offline-light.png b/ui/terminal/output/playwright/login-offline-light.png
new file mode 100644
index 0000000..8d31807
Binary files /dev/null and b/ui/terminal/output/playwright/login-offline-light.png differ
diff --git a/ui/terminal/output/playwright/login-preserved-dark.png b/ui/terminal/output/playwright/login-preserved-dark.png
new file mode 100644
index 0000000..64f7c6b
Binary files /dev/null and b/ui/terminal/output/playwright/login-preserved-dark.png differ
diff --git a/ui/terminal/output/playwright/login-preserved-light.png b/ui/terminal/output/playwright/login-preserved-light.png
new file mode 100644
index 0000000..59e3127
Binary files /dev/null and b/ui/terminal/output/playwright/login-preserved-light.png differ
diff --git a/ui/terminal/output/playwright/login-sso-dark.png b/ui/terminal/output/playwright/login-sso-dark.png
new file mode 100644
index 0000000..cf5b40f
Binary files /dev/null and b/ui/terminal/output/playwright/login-sso-dark.png differ
diff --git a/ui/terminal/output/playwright/login-sso-light.png b/ui/terminal/output/playwright/login-sso-light.png
new file mode 100644
index 0000000..f731723
Binary files /dev/null and b/ui/terminal/output/playwright/login-sso-light.png differ
diff --git a/ui/terminal/output/playwright/login-success-dark.png b/ui/terminal/output/playwright/login-success-dark.png
new file mode 100644
index 0000000..8dce3f4
Binary files /dev/null and b/ui/terminal/output/playwright/login-success-dark.png differ
diff --git a/ui/terminal/output/playwright/login-validation-dark.png b/ui/terminal/output/playwright/login-validation-dark.png
new file mode 100644
index 0000000..4bf66d6
Binary files /dev/null and b/ui/terminal/output/playwright/login-validation-dark.png differ
diff --git a/ui/terminal/output/playwright/login-validation-light.png b/ui/terminal/output/playwright/login-validation-light.png
new file mode 100644
index 0000000..83d9964
Binary files /dev/null and b/ui/terminal/output/playwright/login-validation-light.png differ
diff --git a/ui/terminal/output/playwright/login-verification.json b/ui/terminal/output/playwright/login-verification.json
new file mode 100644
index 0000000..61d29f3
--- /dev/null
+++ b/ui/terminal/output/playwright/login-verification.json
@@ -0,0 +1,1054 @@
+{
+ "verifiedAt": "2026-11-09T17:03:18.593143+08:00",
+ "scope": "Local HTML login interaction prototype, Chromium browser. No real authentication, remote desktop, Qt port, or ARM device acceptance.",
+ "result": "passed",
+ "checkCount": 90,
+ "checks": [
+ {
+ "group": "flows",
+ "check": "Blank account is rejected and focused",
+ "passed": true
+ },
+ {
+ "group": "flows",
+ "check": "Empty password is rejected and focused via Enter",
+ "passed": true
+ },
+ {
+ "group": "flows",
+ "check": "Password reveal updates accessible state",
+ "passed": true
+ },
+ {
+ "group": "flows",
+ "check": "Password conceal works",
+ "passed": true
+ },
+ {
+ "group": "flows",
+ "check": "Login exposes busy state and blocks duplicate clicks",
+ "passed": true
+ },
+ {
+ "group": "flows",
+ "check": "Invalid credentials stay on login and clear password",
+ "passed": true
+ },
+ {
+ "group": "flows",
+ "check": "Demo helper shows public credentials",
+ "passed": true
+ },
+ {
+ "group": "flows",
+ "check": "Demo helper fills credentials without bypassing login",
+ "passed": true
+ },
+ {
+ "group": "flows",
+ "check": "Valid credentials log in via Enter with correct identity",
+ "passed": true
+ },
+ {
+ "group": "flows",
+ "check": "Successful login clears and conceals password",
+ "passed": true
+ },
+ {
+ "group": "flows",
+ "check": "Only remembered account is persisted; no password stored",
+ "passed": true
+ },
+ {
+ "group": "flows",
+ "check": "Logout returns login with remembered account and empty password",
+ "passed": true
+ },
+ {
+ "group": "flows",
+ "check": "Reload restores remembered account only",
+ "passed": true
+ },
+ {
+ "group": "flows",
+ "check": "Unchecking remember removes saved account across reload",
+ "passed": true
+ },
+ {
+ "group": "flows",
+ "check": "Forgot password provides administrator path",
+ "passed": true
+ },
+ {
+ "group": "flows",
+ "check": "Forgot password links to demo recovery",
+ "passed": true
+ },
+ {
+ "group": "flows",
+ "check": "Escape closes modal and restores trigger focus",
+ "passed": true
+ },
+ {
+ "group": "flows",
+ "check": "Offline experience has distinct identity and footer",
+ "passed": true
+ },
+ {
+ "group": "flows",
+ "check": "Admin shortcut cancels pending login without later redirect",
+ "passed": true
+ },
+ {
+ "group": "flows",
+ "check": "No browser runtime errors during account flows",
+ "passed": true
+ },
+ {
+ "group": "flows",
+ "check": "No external authentication or remote desktop requests",
+ "passed": true
+ },
+ {
+ "group": "enterprise",
+ "check": "Cancel does not save service draft",
+ "passed": true
+ },
+ {
+ "group": "enterprise",
+ "check": "SSO requires enterprise connection before identity preview",
+ "passed": true
+ },
+ {
+ "group": "enterprise",
+ "check": "Reject unsafe enterprise endpoint: (empty)",
+ "passed": true
+ },
+ {
+ "group": "enterprise",
+ "check": "Reject unsafe enterprise endpoint: invalid",
+ "passed": true
+ },
+ {
+ "group": "enterprise",
+ "check": "Reject unsafe enterprise endpoint: http://vdi.example.com",
+ "passed": true
+ },
+ {
+ "group": "enterprise",
+ "check": "Reject unsafe enterprise endpoint: https://user:secret@vdi.example.com",
+ "passed": true
+ },
+ {
+ "group": "enterprise",
+ "check": "Reject unsafe enterprise endpoint: https://vdi.example.com/?token=secret",
+ "passed": true
+ },
+ {
+ "group": "enterprise",
+ "check": "Reject unsafe enterprise endpoint: https://vdi.example.com/#secret",
+ "passed": true
+ },
+ {
+ "group": "enterprise",
+ "check": "Service tabs support keyboard Home",
+ "passed": true
+ },
+ {
+ "group": "enterprise",
+ "check": "Switching service mode preserves in-progress draft",
+ "passed": true
+ },
+ {
+ "group": "enterprise",
+ "check": "Save-and-continue opens correct SSO identity preview",
+ "passed": true
+ },
+ {
+ "group": "enterprise",
+ "check": "Escape cancels in-flight SSO without delayed login",
+ "passed": true
+ },
+ {
+ "group": "enterprise",
+ "check": "Cancel button cancels in-flight SSO",
+ "passed": true
+ },
+ {
+ "group": "enterprise",
+ "check": "SSO prototype enters workspace with enterprise identity",
+ "passed": true
+ },
+ {
+ "group": "enterprise",
+ "check": "Successful login moves keyboard focus to workspace title",
+ "passed": true
+ },
+ {
+ "group": "enterprise",
+ "check": "Saved enterprise mode survives reload",
+ "passed": true
+ },
+ {
+ "group": "enterprise",
+ "check": "Normalized enterprise URL and organization persist",
+ "passed": true
+ },
+ {
+ "group": "enterprise",
+ "check": "Local HTTP endpoint is accepted for prototype",
+ "passed": true
+ },
+ {
+ "group": "enterprise",
+ "check": "Service can return to cloud mode",
+ "passed": true
+ },
+ {
+ "group": "enterprise",
+ "check": "Account login still succeeds after SSO and service changes",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "dark 1280×720 login and footer fit viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "dark 1280 error state fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "dark 1280 #auth-demo dialog fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "dark 1280 #auth-forgot dialog fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "dark 1280 #auth-offline dialog fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "dark 1280 #auth-sso dialog fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "dark 1366×768 login and footer fit viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "dark 1366 error state fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "dark 1366 #auth-demo dialog fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "dark 1366 #auth-forgot dialog fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "dark 1366 #auth-offline dialog fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "dark 1366 #auth-sso dialog fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "dark 1920×1080 login and footer fit viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "dark 1920 error state fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "dark 1920 #auth-demo dialog fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "dark 1920 #auth-forgot dialog fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "dark 1920 #auth-offline dialog fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "dark 1920 #auth-sso dialog fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "dark dialog traps keyboard focus",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "light 1280×720 login and footer fit viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "light 1280 error state fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "light 1280 #auth-demo dialog fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "light 1280 #auth-forgot dialog fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "light 1280 #auth-offline dialog fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "light 1280 #auth-sso dialog fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "light 1366×768 login and footer fit viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "light 1366 error state fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "light 1366 #auth-demo dialog fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "light 1366 #auth-forgot dialog fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "light 1366 #auth-offline dialog fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "light 1366 #auth-sso dialog fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "light 1920×1080 login and footer fit viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "light 1920 error state fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "light 1920 #auth-demo dialog fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "light 1920 #auth-forgot dialog fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "light 1920 #auth-offline dialog fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "light 1920 #auth-sso dialog fits viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "light dialog traps keyboard focus",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "Narrow login 820 has no horizontal overflow",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "Narrow dialog 820 remains inside viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "Narrow enterprise validation remains reachable 820",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "Narrow login 390 has no horizontal overflow",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "Narrow dialog 390 remains inside viewport",
+ "passed": true
+ },
+ {
+ "group": "layout",
+ "check": "Narrow enterprise validation remains reachable 390",
+ "passed": true
+ },
+ {
+ "group": "source",
+ "check": "Original brand HTML preserved against pre-task backup",
+ "passed": true
+ },
+ {
+ "group": "source",
+ "check": "Original inline design CSS preserved against pre-task backup",
+ "passed": true
+ },
+ {
+ "group": "source",
+ "check": "Workspace CSS preserved against pre-task backup",
+ "passed": true
+ },
+ {
+ "group": "source",
+ "check": "Login and workspace JavaScript pass node --check",
+ "passed": true
+ },
+ {
+ "group": "source",
+ "check": "Measured new helper and dialog text contrast is at least 4.5:1 in both themes",
+ "passed": true
+ }
+ ],
+ "demo": {
+ "username": "demo",
+ "password": "Demo123!",
+ "publicPrototypeCredentials": true
+ },
+ "geometry": {
+ "dark-1280": {
+ "card": {
+ "top": 129.59375,
+ "bottom": 647.59375,
+ "left": 645.109375,
+ "right": 1077.734375
+ },
+ "footerBottom": 618.59375,
+ "width": 1280,
+ "height": 720
+ },
+ "dark-1366": {
+ "card": {
+ "top": 138.234375,
+ "bottom": 735.640625,
+ "left": 688.453125,
+ "right": 1150.15625
+ },
+ "footerBottom": 693.9375,
+ "width": 1366,
+ "height": 768
+ },
+ "dark-1920": {
+ "card": {
+ "top": 194.390625,
+ "bottom": 950.390625,
+ "left": 967.671875,
+ "right": 1616.625
+ },
+ "footerBottom": 766.625,
+ "width": 1920,
+ "height": 1080
+ },
+ "light-1280": {
+ "card": {
+ "top": 129.59375,
+ "bottom": 647.59375,
+ "left": 645.109375,
+ "right": 1077.734375
+ },
+ "footerBottom": 618.59375,
+ "width": 1280,
+ "height": 720
+ },
+ "light-1366": {
+ "card": {
+ "top": 138.234375,
+ "bottom": 735.640625,
+ "left": 688.453125,
+ "right": 1150.15625
+ },
+ "footerBottom": 693.9375,
+ "width": 1366,
+ "height": 768
+ },
+ "light-1920": {
+ "card": {
+ "top": 194.390625,
+ "bottom": 950.390625,
+ "left": 967.671875,
+ "right": 1616.625
+ },
+ "footerBottom": 766.625,
+ "width": 1920,
+ "height": 1080
+ }
+ },
+ "newInteractionTextContrast": {
+ "dark#auth-demo": [
+ {
+ "text": "体验账号",
+ "ratio": 6.96
+ },
+ {
+ "text": "账号或密码不正确,请重试或查看体验账号",
+ "ratio": 8.38
+ },
+ {
+ "text": "体验账号",
+ "ratio": 12.27
+ },
+ {
+ "text": "用于预览登录、桌面选择和连接设置流程。",
+ "ratio": 7.94
+ },
+ {
+ "text": "×",
+ "ratio": 7.14
+ },
+ {
+ "text": "工作账号",
+ "ratio": 7.14
+ },
+ {
+ "text": "demo",
+ "ratio": 11.03
+ },
+ {
+ "text": "密码",
+ "ratio": 7.14
+ },
+ {
+ "text": "Demo123!",
+ "ratio": 11.03
+ },
+ {
+ "text": "这是交互原型的公开体验账号,未连接真实认证服务。请使用体验信息。",
+ "ratio": 7.14
+ },
+ {
+ "text": "返回",
+ "ratio": 12.27
+ },
+ {
+ "text": "填入体验账号",
+ "ratio": 5.1
+ }
+ ],
+ "dark#auth-forgot": [
+ {
+ "text": "体验账号",
+ "ratio": 6.96
+ },
+ {
+ "text": "账号或密码不正确,请重试或查看体验账号",
+ "ratio": 8.38
+ },
+ {
+ "text": "忘记密码",
+ "ratio": 12.27
+ },
+ {
+ "text": "企业工作账号由管理员统一管理。",
+ "ratio": 7.94
+ },
+ {
+ "text": "×",
+ "ratio": 7.14
+ },
+ {
+ "text": "请联系越欣科技管理员重置密码",
+ "ratio": 11.03
+ },
+ {
+ "text": "可提供工作账号与设备序列号 VDI-8F42A1,协助管理员定位。",
+ "ratio": 7.14
+ },
+ {
+ "text": "原型体验账号无法修改密码,可重新查看体验信息。",
+ "ratio": 7.94
+ },
+ {
+ "text": "返回登录",
+ "ratio": 12.27
+ },
+ {
+ "text": "查看体验账号",
+ "ratio": 5.1
+ }
+ ],
+ "dark#auth-offline": [
+ {
+ "text": "体验账号",
+ "ratio": 6.96
+ },
+ {
+ "text": "账号或密码不正确,请重试或查看体验账号",
+ "ratio": 8.38
+ },
+ {
+ "text": "离线体验",
+ "ratio": 12.27
+ },
+ {
+ "text": "先了解桌面列表和终端设置。",
+ "ratio": 7.94
+ },
+ {
+ "text": "×",
+ "ratio": 7.14
+ },
+ {
+ "text": "无需工作账号",
+ "ratio": 11.03
+ },
+ {
+ "text": "可预览桌面、收藏和连接设置,设备与桌面信息为示例。配置仅保存在当前浏览器,不连接",
+ "ratio": 7.14
+ },
+ {
+ "text": "取消",
+ "ratio": 12.27
+ },
+ {
+ "text": "进入离线体验",
+ "ratio": 5.1
+ }
+ ],
+ "dark#auth-sso": [
+ {
+ "text": "体验账号",
+ "ratio": 6.96
+ },
+ {
+ "text": "账号或密码不正确,请重试或查看体验账号",
+ "ratio": 8.38
+ },
+ {
+ "text": "连接设置",
+ "ratio": 12.27
+ },
+ {
+ "text": "选择云端服务或企业部署入口。",
+ "ratio": 7.94
+ },
+ {
+ "text": "×",
+ "ratio": 7.14
+ },
+ {
+ "text": "云端服务",
+ "ratio": 7.14
+ },
+ {
+ "text": "企业部署",
+ "ratio": 7.23
+ },
+ {
+ "text": "企业服务地址使用 HTTPS 地址;HTTP 仅支持本机预览。",
+ "ratio": 12.27
+ },
+ {
+ "text": "使用 HTTPS 地址;HTTP 仅支持本机预览。",
+ "ratio": 7.94
+ },
+ {
+ "text": "租户或组织标识 选填",
+ "ratio": 12.27
+ },
+ {
+ "text": "选填",
+ "ratio": 7.94
+ },
+ {
+ "text": "取消",
+ "ratio": 12.27
+ },
+ {
+ "text": "保存并继续",
+ "ratio": 5.1
+ }
+ ],
+ "dark-sso": [
+ {
+ "text": "体验账号",
+ "ratio": 6.96
+ },
+ {
+ "text": "账号或密码不正确,请重试或查看体验账号",
+ "ratio": 8.38
+ },
+ {
+ "text": "企业账号登录",
+ "ratio": 12.27
+ },
+ {
+ "text": "通过企业统一身份进入云桌面。",
+ "ratio": 7.94
+ },
+ {
+ "text": "×",
+ "ratio": 7.14
+ },
+ {
+ "text": "企业服务",
+ "ratio": 7.14
+ },
+ {
+ "text": "https://vdi.example.com",
+ "ratio": 11.03
+ },
+ {
+ "text": "组织",
+ "ratio": 7.14
+ },
+ {
+ "text": "越欣科技",
+ "ratio": 11.03
+ },
+ {
+ "text": "体验身份",
+ "ratio": 7.14
+ },
+ {
+ "text": "越欣科技 · 体验用户",
+ "ratio": 11.03
+ },
+ {
+ "text": "当前为统一身份登录预览,不会跳转或访问真实企业身份服务。",
+ "ratio": 7.94
+ },
+ {
+ "text": "取消登录",
+ "ratio": 12.27
+ },
+ {
+ "text": "使用体验身份继续",
+ "ratio": 5.1
+ }
+ ],
+ "light#auth-demo": [
+ {
+ "text": "体验账号",
+ "ratio": 6.95
+ },
+ {
+ "text": "账号或密码不正确,请重试或查看体验账号",
+ "ratio": 6.06
+ },
+ {
+ "text": "体验账号",
+ "ratio": 12.69
+ },
+ {
+ "text": "用于预览登录、桌面选择和连接设置流程。",
+ "ratio": 6.18
+ },
+ {
+ "text": "×",
+ "ratio": 5.72
+ },
+ {
+ "text": "工作账号",
+ "ratio": 5.72
+ },
+ {
+ "text": "demo",
+ "ratio": 11.72
+ },
+ {
+ "text": "密码",
+ "ratio": 5.72
+ },
+ {
+ "text": "Demo123!",
+ "ratio": 11.72
+ },
+ {
+ "text": "这是交互原型的公开体验账号,未连接真实认证服务。请使用体验信息。",
+ "ratio": 5.72
+ },
+ {
+ "text": "返回",
+ "ratio": 12.69
+ },
+ {
+ "text": "填入体验账号",
+ "ratio": 5.1
+ }
+ ],
+ "light#auth-forgot": [
+ {
+ "text": "体验账号",
+ "ratio": 6.95
+ },
+ {
+ "text": "账号或密码不正确,请重试或查看体验账号",
+ "ratio": 6.06
+ },
+ {
+ "text": "忘记密码",
+ "ratio": 12.69
+ },
+ {
+ "text": "企业工作账号由管理员统一管理。",
+ "ratio": 6.18
+ },
+ {
+ "text": "×",
+ "ratio": 5.72
+ },
+ {
+ "text": "请联系越欣科技管理员重置密码",
+ "ratio": 11.72
+ },
+ {
+ "text": "可提供工作账号与设备序列号 VDI-8F42A1,协助管理员定位。",
+ "ratio": 5.72
+ },
+ {
+ "text": "原型体验账号无法修改密码,可重新查看体验信息。",
+ "ratio": 6.18
+ },
+ {
+ "text": "返回登录",
+ "ratio": 12.69
+ },
+ {
+ "text": "查看体验账号",
+ "ratio": 5.1
+ }
+ ],
+ "light#auth-offline": [
+ {
+ "text": "体验账号",
+ "ratio": 6.95
+ },
+ {
+ "text": "账号或密码不正确,请重试或查看体验账号",
+ "ratio": 6.06
+ },
+ {
+ "text": "离线体验",
+ "ratio": 12.69
+ },
+ {
+ "text": "先了解桌面列表和终端设置。",
+ "ratio": 6.18
+ },
+ {
+ "text": "×",
+ "ratio": 5.72
+ },
+ {
+ "text": "无需工作账号",
+ "ratio": 11.72
+ },
+ {
+ "text": "可预览桌面、收藏和连接设置,设备与桌面信息为示例。配置仅保存在当前浏览器,不连接",
+ "ratio": 5.72
+ },
+ {
+ "text": "取消",
+ "ratio": 12.69
+ },
+ {
+ "text": "进入离线体验",
+ "ratio": 5.1
+ }
+ ],
+ "light#auth-sso": [
+ {
+ "text": "体验账号",
+ "ratio": 6.95
+ },
+ {
+ "text": "账号或密码不正确,请重试或查看体验账号",
+ "ratio": 6.06
+ },
+ {
+ "text": "连接设置",
+ "ratio": 12.69
+ },
+ {
+ "text": "选择云端服务或企业部署入口。",
+ "ratio": 6.18
+ },
+ {
+ "text": "×",
+ "ratio": 5.72
+ },
+ {
+ "text": "云端服务",
+ "ratio": 5.72
+ },
+ {
+ "text": "企业部署",
+ "ratio": 5.57
+ },
+ {
+ "text": "企业服务地址使用 HTTPS 地址;HTTP 仅支持本机预览。",
+ "ratio": 12.69
+ },
+ {
+ "text": "使用 HTTPS 地址;HTTP 仅支持本机预览。",
+ "ratio": 6.18
+ },
+ {
+ "text": "租户或组织标识 选填",
+ "ratio": 12.69
+ },
+ {
+ "text": "选填",
+ "ratio": 6.18
+ },
+ {
+ "text": "取消",
+ "ratio": 12.69
+ },
+ {
+ "text": "保存并继续",
+ "ratio": 5.1
+ }
+ ],
+ "light-sso": [
+ {
+ "text": "体验账号",
+ "ratio": 6.95
+ },
+ {
+ "text": "账号或密码不正确,请重试或查看体验账号",
+ "ratio": 6.06
+ },
+ {
+ "text": "企业账号登录",
+ "ratio": 12.69
+ },
+ {
+ "text": "通过企业统一身份进入云桌面。",
+ "ratio": 6.18
+ },
+ {
+ "text": "×",
+ "ratio": 5.72
+ },
+ {
+ "text": "企业服务",
+ "ratio": 5.72
+ },
+ {
+ "text": "https://vdi.example.com",
+ "ratio": 11.72
+ },
+ {
+ "text": "组织",
+ "ratio": 5.72
+ },
+ {
+ "text": "越欣科技",
+ "ratio": 11.72
+ },
+ {
+ "text": "体验身份",
+ "ratio": 5.72
+ },
+ {
+ "text": "越欣科技 · 体验用户",
+ "ratio": 11.72
+ },
+ {
+ "text": "当前为统一身份登录预览,不会跳转或访问真实企业身份服务。",
+ "ratio": 6.18
+ },
+ {
+ "text": "取消登录",
+ "ratio": 12.69
+ },
+ {
+ "text": "使用体验身份继续",
+ "ratio": 5.1
+ }
+ ]
+ },
+ "minMeasuredNewTextContrast": 5.1,
+ "screenshots": [
+ "login-demo-dark.png",
+ "login-demo-light.png",
+ "login-dialog-390.png",
+ "login-dialog-820.png",
+ "login-enterprise-dark.png",
+ "login-enterprise-light.png",
+ "login-forgot-dark.png",
+ "login-forgot-light.png",
+ "login-interactive-dark-1280.png",
+ "login-interactive-dark-1366.png",
+ "login-interactive-dark-1920.png",
+ "login-interactive-dark.png",
+ "login-interactive-light-1280.png",
+ "login-interactive-light-1366.png",
+ "login-interactive-light-1920.png",
+ "login-interactive-light.png",
+ "login-offline-dark.png",
+ "login-offline-light.png",
+ "login-sso-dark.png",
+ "login-sso-light.png",
+ "login-success-dark.png",
+ "login-validation-dark.png",
+ "login-validation-light.png"
+ ],
+ "browserErrors": [],
+ "externalRequestsDuringAccountFlows": [],
+ "sourceHashes": {
+ "index.html": "02488524b72f857c3fa307e8c17d25d3a34fd38c8c761f3c9bac611410d524d1",
+ "login-interactions.js": "8b48c046b824d8ef48cffffba988d950c6d78de13b5a207ab3150b2e1d506342",
+ "login-interactions.css": "d468dd8619b2230bcef2de3b040acd47f3e429c3c749ba54f670f862fe4db62d",
+ "workspace.js": "eb42cd659e2d731a05b64a8e2a6fc92ec4cd14e47d36c558432d4ff2979eb968",
+ "workspace.css": "c1a0e30cf11a1c5f3e539ef4446890b3f54e204f6c2e00c84c694972ece4ea2d",
+ "review.html": "9244afde8b12ea3f1ff2c1fd26b743f36d13c0fd96d36023c34655d3460b9378"
+ },
+ "priorReport": "verification.json describes the preceding workspace iteration. Its original login-baseline and direct-login checks are historical and superseded by this report."
+}
diff --git a/ui/terminal/output/playwright/reconnect-dark.png b/ui/terminal/output/playwright/reconnect-dark.png
new file mode 100644
index 0000000..ef24bc4
Binary files /dev/null and b/ui/terminal/output/playwright/reconnect-dark.png differ
diff --git a/ui/terminal/output/playwright/reconnect-light.png b/ui/terminal/output/playwright/reconnect-light.png
new file mode 100644
index 0000000..b5559cf
Binary files /dev/null and b/ui/terminal/output/playwright/reconnect-light.png differ
diff --git a/ui/terminal/output/playwright/review.png b/ui/terminal/output/playwright/review.png
new file mode 100644
index 0000000..cf9f464
Binary files /dev/null and b/ui/terminal/output/playwright/review.png differ
diff --git a/ui/terminal/output/playwright/settings-dark.png b/ui/terminal/output/playwright/settings-dark.png
new file mode 100644
index 0000000..df0b063
Binary files /dev/null and b/ui/terminal/output/playwright/settings-dark.png differ
diff --git a/ui/terminal/output/playwright/settings-light.png b/ui/terminal/output/playwright/settings-light.png
new file mode 100644
index 0000000..be5c141
Binary files /dev/null and b/ui/terminal/output/playwright/settings-light.png differ
diff --git a/ui/terminal/output/playwright/verification.json b/ui/terminal/output/playwright/verification.json
new file mode 100644
index 0000000..1d9a4f3
--- /dev/null
+++ b/ui/terminal/output/playwright/verification.json
@@ -0,0 +1,1308 @@
+{
+ "checks": [
+ "Login geometry and computed visual styles match baseline",
+ "Login opens all three desktop cards",
+ "Workspace contains only Yuexin organization",
+ "Unconfigured desktop connection is disabled with visible reason",
+ "Favorites filter reflects changed favorite state",
+ "Search finds desktop by operating system",
+ "Search empty state is actionable",
+ "Connection settings validate endpoint with visible message",
+ "Saved settings update desktop card",
+ "Desktop settings persist across reload",
+ "Connection page identifies selected desktop",
+ "Cancel connection returns to resource selector",
+ "Management keyboard shortcut opens settings",
+ "Network settings tab is functional",
+ "Device information tab is functional",
+ "dark visible text meets WCAG AA contrast: []",
+ "dark 1280×720 fits without scroll or clipping",
+ "dark 1366×768 fits without scroll or clipping",
+ "dark 1920×1080 fits without scroll or clipping",
+ "light visible text meets WCAG AA contrast: []",
+ "light 1280×720 fits without scroll or clipping",
+ "light 1366×768 fits without scroll or clipping",
+ "light 1920×1080 fits without scroll or clipping",
+ "Recovery cancel clears pending recovery and returns to selector",
+ "Error state provides settings recovery action",
+ "Tablet layout has no horizontal overflow",
+ "Narrow fallback has no horizontal overflow"
+ ],
+ "screenshots": [
+ "workspace-dark-1280.png",
+ "workspace-dark-1366.png",
+ "workspace-dark-1920.png",
+ "workspace-light-1280.png",
+ "workspace-light-1366.png",
+ "workspace-light-1920.png"
+ ],
+ "contrast": {
+ "dark": [
+ {
+ "text": "越欣科技",
+ "color": "rgb(237, 241, 245)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 12.13,
+ "minimum": 4.5
+ },
+ {
+ "text": "云桌面终端",
+ "color": "rgb(186, 197, 207)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 7.85,
+ "minimum": 4.5
+ },
+ {
+ "text": "终端设置",
+ "color": "rgb(237, 241, 245)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 12.13,
+ "minimum": 4.5
+ },
+ {
+ "text": "终端用户",
+ "color": "rgb(186, 197, 207)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 7.85,
+ "minimum": 4.5
+ },
+ {
+ "text": "我的云桌面",
+ "color": "rgb(237, 241, 245)",
+ "background": [
+ 27,
+ 35,
+ 45
+ ],
+ "ratio": 13.96,
+ "minimum": 3
+ },
+ {
+ "text": "选择工作环境,连接后即可开始使用。",
+ "color": "rgb(186, 197, 207)",
+ "background": [
+ 27,
+ 35,
+ 45
+ ],
+ "ratio": 9.04,
+ "minimum": 4.5
+ },
+ {
+ "text": "2 台可连接 / 共 3 台",
+ "color": "rgb(186, 197, 207)",
+ "background": [
+ 27,
+ 35,
+ 45
+ ],
+ "ratio": 9.04,
+ "minimum": 4.5
+ },
+ {
+ "text": "全部桌面3",
+ "color": "rgb(237, 172, 125)",
+ "background": [
+ 60,
+ 48,
+ 41
+ ],
+ "ratio": 6.56,
+ "minimum": 4.5
+ },
+ {
+ "text": "3",
+ "color": "rgb(237, 172, 125)",
+ "background": [
+ 60,
+ 48,
+ 41
+ ],
+ "ratio": 6.56,
+ "minimum": 4.5
+ },
+ {
+ "text": "已收藏1",
+ "color": "rgb(186, 197, 207)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 7.85,
+ "minimum": 4.5
+ },
+ {
+ "text": "1",
+ "color": "rgb(186, 197, 207)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 7.85,
+ "minimum": 4.5
+ },
+ {
+ "text": "Windows 10",
+ "color": "rgb(237, 241, 245)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 12.13,
+ "minimum": 4.5
+ },
+ {
+ "text": "64 位 · Windows",
+ "color": "rgb(186, 197, 207)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 7.85,
+ "minimum": 4.5
+ },
+ {
+ "text": "设计工作站",
+ "color": "rgb(237, 241, 245)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 12.13,
+ "minimum": 4.5
+ },
+ {
+ "text": "可连接",
+ "color": "rgb(156, 219, 188)",
+ "background": [
+ 38,
+ 61,
+ 53
+ ],
+ "ratio": 7.37,
+ "minimum": 4.5
+ },
+ {
+ "text": "分辨率",
+ "color": "rgb(186, 197, 207)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 7.85,
+ "minimum": 4.5
+ },
+ {
+ "text": "2560 × 1440",
+ "color": "rgb(237, 241, 245)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 12.13,
+ "minimum": 4.5
+ },
+ {
+ "text": "帧率",
+ "color": "rgb(186, 197, 207)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 7.85,
+ "minimum": 4.5
+ },
+ {
+ "text": "60 FPS",
+ "color": "rgb(237, 241, 245)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 12.13,
+ "minimum": 4.5
+ },
+ {
+ "text": "图形设计与日常办公",
+ "color": "rgb(186, 197, 207)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 7.85,
+ "minimum": 4.5
+ },
+ {
+ "text": "设置",
+ "color": "rgb(237, 241, 245)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 12.13,
+ "minimum": 4.5
+ },
+ {
+ "text": "连接桌面",
+ "color": "rgb(255, 255, 255)",
+ "background": [
+ 170,
+ 89,
+ 40
+ ],
+ "ratio": 5.04,
+ "minimum": 4.5
+ },
+ {
+ "text": "Ubuntu",
+ "color": "rgb(237, 241, 245)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 12.13,
+ "minimum": 4.5
+ },
+ {
+ "text": "24.04 LTS · Linux",
+ "color": "rgb(186, 197, 207)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 7.85,
+ "minimum": 4.5
+ },
+ {
+ "text": "研发环境",
+ "color": "rgb(237, 241, 245)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 12.13,
+ "minimum": 4.5
+ },
+ {
+ "text": "可连接",
+ "color": "rgb(156, 219, 188)",
+ "background": [
+ 38,
+ 61,
+ 53
+ ],
+ "ratio": 7.37,
+ "minimum": 4.5
+ },
+ {
+ "text": "分辨率",
+ "color": "rgb(186, 197, 207)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 7.85,
+ "minimum": 4.5
+ },
+ {
+ "text": "2560 × 1440",
+ "color": "rgb(237, 241, 245)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 12.13,
+ "minimum": 4.5
+ },
+ {
+ "text": "帧率",
+ "color": "rgb(186, 197, 207)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 7.85,
+ "minimum": 4.5
+ },
+ {
+ "text": "30 FPS",
+ "color": "rgb(237, 241, 245)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 12.13,
+ "minimum": 4.5
+ },
+ {
+ "text": "开发工具与研发工作环境",
+ "color": "rgb(186, 197, 207)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 7.85,
+ "minimum": 4.5
+ },
+ {
+ "text": "设置",
+ "color": "rgb(237, 241, 245)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 12.13,
+ "minimum": 4.5
+ },
+ {
+ "text": "连接桌面",
+ "color": "rgb(255, 255, 255)",
+ "background": [
+ 170,
+ 89,
+ 40
+ ],
+ "ratio": 5.04,
+ "minimum": 4.5
+ },
+ {
+ "text": "银河麒麟",
+ "color": "rgb(237, 241, 245)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 12.13,
+ "minimum": 4.5
+ },
+ {
+ "text": "桌面操作系统 · Linux",
+ "color": "rgb(186, 197, 207)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 7.85,
+ "minimum": 4.5
+ },
+ {
+ "text": "办公桌面",
+ "color": "rgb(237, 241, 245)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 12.13,
+ "minimum": 4.5
+ },
+ {
+ "text": "待配置",
+ "color": "rgb(239, 205, 142)",
+ "background": [
+ 65,
+ 58,
+ 42
+ ],
+ "ratio": 7.4,
+ "minimum": 4.5
+ },
+ {
+ "text": "分辨率",
+ "color": "rgb(186, 197, 207)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 7.85,
+ "minimum": 4.5
+ },
+ {
+ "text": "1920 × 1080",
+ "color": "rgb(237, 241, 245)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 12.13,
+ "minimum": 4.5
+ },
+ {
+ "text": "帧率",
+ "color": "rgb(186, 197, 207)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 7.85,
+ "minimum": 4.5
+ },
+ {
+ "text": "30 FPS",
+ "color": "rgb(237, 241, 245)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 12.13,
+ "minimum": 4.5
+ },
+ {
+ "text": "暂未配置服务器,请联系管理员",
+ "color": "rgb(186, 197, 207)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 7.85,
+ "minimum": 4.5
+ },
+ {
+ "text": "设置",
+ "color": "rgb(237, 241, 245)",
+ "background": [
+ 36,
+ 46,
+ 57
+ ],
+ "ratio": 12.13,
+ "minimum": 4.5
+ },
+ {
+ "text": "暂不可连接",
+ "color": "rgb(164, 178, 192)",
+ "background": [
+ 43,
+ 55,
+ 67
+ ],
+ "ratio": 5.61,
+ "minimum": 4.5
+ },
+ {
+ "text": "网络已连接",
+ "color": "rgb(156, 219, 188)",
+ "background": [
+ 27,
+ 35,
+ 45
+ ],
+ "ratio": 10.02,
+ "minimum": 4.5
+ },
+ {
+ "text": "设备 SN:VDI-8F42A1",
+ "color": "rgb(186, 197, 207)",
+ "background": [
+ 27,
+ 35,
+ 45
+ ],
+ "ratio": 9.04,
+ "minimum": 4.5
+ },
+ {
+ "text": "RK3528 · ARM64",
+ "color": "rgb(186, 197, 207)",
+ "background": [
+ 27,
+ 35,
+ 45
+ ],
+ "ratio": 9.04,
+ "minimum": 4.5
+ },
+ {
+ "text": "交互原型 · 设备与桌面状态为示例",
+ "color": "rgb(164, 178, 192)",
+ "background": [
+ 27,
+ 35,
+ 45
+ ],
+ "ratio": 7.33,
+ "minimum": 4.5
+ }
+ ],
+ "light": [
+ {
+ "text": "越欣科技",
+ "color": "rgb(37, 49, 61)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 13.25,
+ "minimum": 4.5
+ },
+ {
+ "text": "云桌面终端",
+ "color": "rgb(83, 99, 114)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 6.18,
+ "minimum": 4.5
+ },
+ {
+ "text": "终端设置",
+ "color": "rgb(37, 49, 61)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 13.25,
+ "minimum": 4.5
+ },
+ {
+ "text": "终端用户",
+ "color": "rgb(83, 99, 114)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 6.18,
+ "minimum": 4.5
+ },
+ {
+ "text": "我的云桌面",
+ "color": "rgb(37, 49, 61)",
+ "background": [
+ 248,
+ 249,
+ 250
+ ],
+ "ratio": 12.57,
+ "minimum": 3
+ },
+ {
+ "text": "选择工作环境,连接后即可开始使用。",
+ "color": "rgb(83, 99, 114)",
+ "background": [
+ 248,
+ 249,
+ 250
+ ],
+ "ratio": 5.87,
+ "minimum": 4.5
+ },
+ {
+ "text": "2 台可连接 / 共 3 台",
+ "color": "rgb(83, 99, 114)",
+ "background": [
+ 248,
+ 249,
+ 250
+ ],
+ "ratio": 5.87,
+ "minimum": 4.5
+ },
+ {
+ "text": "全部桌面3",
+ "color": "rgb(169, 81, 33)",
+ "background": [
+ 249,
+ 238,
+ 230
+ ],
+ "ratio": 4.74,
+ "minimum": 4.5
+ },
+ {
+ "text": "3",
+ "color": "rgb(169, 81, 33)",
+ "background": [
+ 249,
+ 238,
+ 230
+ ],
+ "ratio": 4.74,
+ "minimum": 4.5
+ },
+ {
+ "text": "已收藏1",
+ "color": "rgb(83, 99, 114)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 6.18,
+ "minimum": 4.5
+ },
+ {
+ "text": "1",
+ "color": "rgb(83, 99, 114)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 6.18,
+ "minimum": 4.5
+ },
+ {
+ "text": "Windows 10",
+ "color": "rgb(37, 49, 61)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 13.25,
+ "minimum": 4.5
+ },
+ {
+ "text": "64 位 · Windows",
+ "color": "rgb(83, 99, 114)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 6.18,
+ "minimum": 4.5
+ },
+ {
+ "text": "设计工作站",
+ "color": "rgb(37, 49, 61)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 13.25,
+ "minimum": 3
+ },
+ {
+ "text": "可连接",
+ "color": "rgb(36, 113, 79)",
+ "background": [
+ 233,
+ 245,
+ 238
+ ],
+ "ratio": 5.28,
+ "minimum": 4.5
+ },
+ {
+ "text": "分辨率",
+ "color": "rgb(83, 99, 114)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 6.18,
+ "minimum": 4.5
+ },
+ {
+ "text": "2560 × 1440",
+ "color": "rgb(37, 49, 61)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 13.25,
+ "minimum": 4.5
+ },
+ {
+ "text": "帧率",
+ "color": "rgb(83, 99, 114)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 6.18,
+ "minimum": 4.5
+ },
+ {
+ "text": "60 FPS",
+ "color": "rgb(37, 49, 61)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 13.25,
+ "minimum": 4.5
+ },
+ {
+ "text": "图形设计与日常办公",
+ "color": "rgb(83, 99, 114)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 6.18,
+ "minimum": 4.5
+ },
+ {
+ "text": "设置",
+ "color": "rgb(37, 49, 61)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 13.25,
+ "minimum": 4.5
+ },
+ {
+ "text": "连接桌面",
+ "color": "rgb(255, 255, 255)",
+ "background": [
+ 169,
+ 81,
+ 33
+ ],
+ "ratio": 5.4,
+ "minimum": 4.5
+ },
+ {
+ "text": "Ubuntu",
+ "color": "rgb(37, 49, 61)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 13.25,
+ "minimum": 4.5
+ },
+ {
+ "text": "24.04 LTS · Linux",
+ "color": "rgb(83, 99, 114)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 6.18,
+ "minimum": 4.5
+ },
+ {
+ "text": "研发环境",
+ "color": "rgb(37, 49, 61)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 13.25,
+ "minimum": 3
+ },
+ {
+ "text": "可连接",
+ "color": "rgb(36, 113, 79)",
+ "background": [
+ 233,
+ 245,
+ 238
+ ],
+ "ratio": 5.28,
+ "minimum": 4.5
+ },
+ {
+ "text": "分辨率",
+ "color": "rgb(83, 99, 114)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 6.18,
+ "minimum": 4.5
+ },
+ {
+ "text": "2560 × 1440",
+ "color": "rgb(37, 49, 61)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 13.25,
+ "minimum": 4.5
+ },
+ {
+ "text": "帧率",
+ "color": "rgb(83, 99, 114)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 6.18,
+ "minimum": 4.5
+ },
+ {
+ "text": "30 FPS",
+ "color": "rgb(37, 49, 61)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 13.25,
+ "minimum": 4.5
+ },
+ {
+ "text": "开发工具与研发工作环境",
+ "color": "rgb(83, 99, 114)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 6.18,
+ "minimum": 4.5
+ },
+ {
+ "text": "设置",
+ "color": "rgb(37, 49, 61)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 13.25,
+ "minimum": 4.5
+ },
+ {
+ "text": "连接桌面",
+ "color": "rgb(255, 255, 255)",
+ "background": [
+ 169,
+ 81,
+ 33
+ ],
+ "ratio": 5.4,
+ "minimum": 4.5
+ },
+ {
+ "text": "银河麒麟",
+ "color": "rgb(37, 49, 61)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 13.25,
+ "minimum": 4.5
+ },
+ {
+ "text": "桌面操作系统 · Linux",
+ "color": "rgb(83, 99, 114)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 6.18,
+ "minimum": 4.5
+ },
+ {
+ "text": "办公桌面",
+ "color": "rgb(37, 49, 61)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 13.25,
+ "minimum": 3
+ },
+ {
+ "text": "待配置",
+ "color": "rgb(129, 88, 27)",
+ "background": [
+ 249,
+ 241,
+ 223
+ ],
+ "ratio": 5.58,
+ "minimum": 4.5
+ },
+ {
+ "text": "分辨率",
+ "color": "rgb(83, 99, 114)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 6.18,
+ "minimum": 4.5
+ },
+ {
+ "text": "1920 × 1080",
+ "color": "rgb(37, 49, 61)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 13.25,
+ "minimum": 4.5
+ },
+ {
+ "text": "帧率",
+ "color": "rgb(83, 99, 114)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 6.18,
+ "minimum": 4.5
+ },
+ {
+ "text": "30 FPS",
+ "color": "rgb(37, 49, 61)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 13.25,
+ "minimum": 4.5
+ },
+ {
+ "text": "暂未配置服务器,请联系管理员",
+ "color": "rgb(83, 99, 114)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 6.18,
+ "minimum": 4.5
+ },
+ {
+ "text": "设置",
+ "color": "rgb(37, 49, 61)",
+ "background": [
+ 255,
+ 255,
+ 255
+ ],
+ "ratio": 13.25,
+ "minimum": 4.5
+ },
+ {
+ "text": "暂不可连接",
+ "color": "rgb(89, 106, 121)",
+ "background": [
+ 240,
+ 243,
+ 245
+ ],
+ "ratio": 5.01,
+ "minimum": 4.5
+ },
+ {
+ "text": "网络已连接",
+ "color": "rgb(36, 113, 79)",
+ "background": [
+ 248,
+ 249,
+ 250
+ ],
+ "ratio": 5.61,
+ "minimum": 4.5
+ },
+ {
+ "text": "设备 SN:VDI-8F42A1",
+ "color": "rgb(83, 99, 114)",
+ "background": [
+ 248,
+ 249,
+ 250
+ ],
+ "ratio": 5.87,
+ "minimum": 4.5
+ },
+ {
+ "text": "RK3528 · ARM64",
+ "color": "rgb(83, 99, 114)",
+ "background": [
+ 248,
+ 249,
+ 250
+ ],
+ "ratio": 5.87,
+ "minimum": 4.5
+ },
+ {
+ "text": "交互原型 · 设备与桌面状态为示例",
+ "color": "rgb(89, 106, 121)",
+ "background": [
+ 248,
+ 249,
+ 250
+ ],
+ "ratio": 5.29,
+ "minimum": 4.5
+ }
+ ]
+ },
+ "geometry": {
+ "dark-1280": {
+ "h": 720,
+ "w": 1280,
+ "viewportH": 720,
+ "viewportW": 1280,
+ "cards": [
+ {
+ "x": 32,
+ "right": 422.65625,
+ "bottom": 630.953125
+ },
+ {
+ "x": 444.65625,
+ "right": 835.328125,
+ "bottom": 630.953125
+ },
+ {
+ "x": 857.328125,
+ "right": 1247.984375,
+ "bottom": 630.953125
+ }
+ ]
+ },
+ "dark-1366": {
+ "h": 768,
+ "w": 1366,
+ "viewportH": 768,
+ "viewportW": 1366,
+ "cards": [
+ {
+ "x": 43,
+ "right": 455,
+ "bottom": 630.953125
+ },
+ {
+ "x": 477,
+ "right": 889,
+ "bottom": 630.953125
+ },
+ {
+ "x": 911,
+ "right": 1323,
+ "bottom": 630.953125
+ }
+ ]
+ },
+ "dark-1920": {
+ "h": 1080,
+ "w": 1920,
+ "viewportH": 1080,
+ "viewportW": 1920,
+ "cards": [
+ {
+ "x": 320,
+ "right": 729.328125,
+ "bottom": 770.828125
+ },
+ {
+ "x": 755.328125,
+ "right": 1164.65625,
+ "bottom": 770.828125
+ },
+ {
+ "x": 1190.65625,
+ "right": 1600,
+ "bottom": 770.828125
+ }
+ ]
+ },
+ "light-1280": {
+ "h": 720,
+ "w": 1280,
+ "viewportH": 720,
+ "viewportW": 1280,
+ "cards": [
+ {
+ "x": 32,
+ "right": 422.65625,
+ "bottom": 630.953125
+ },
+ {
+ "x": 444.65625,
+ "right": 835.328125,
+ "bottom": 630.953125
+ },
+ {
+ "x": 857.328125,
+ "right": 1247.984375,
+ "bottom": 630.953125
+ }
+ ]
+ },
+ "light-1366": {
+ "h": 768,
+ "w": 1366,
+ "viewportH": 768,
+ "viewportW": 1366,
+ "cards": [
+ {
+ "x": 43,
+ "right": 455,
+ "bottom": 630.953125
+ },
+ {
+ "x": 477,
+ "right": 889,
+ "bottom": 630.953125
+ },
+ {
+ "x": 911,
+ "right": 1323,
+ "bottom": 630.953125
+ }
+ ]
+ },
+ "light-1920": {
+ "h": 1080,
+ "w": 1920,
+ "viewportH": 1080,
+ "viewportW": 1920,
+ "cards": [
+ {
+ "x": 320,
+ "right": 729.328125,
+ "bottom": 770.828125
+ },
+ {
+ "x": 755.328125,
+ "right": 1164.65625,
+ "bottom": 770.828125
+ },
+ {
+ "x": 1190.65625,
+ "right": 1600,
+ "bottom": 770.828125
+ }
+ ]
+ }
+ },
+ "date": "2026-11-09",
+ "scope": "HTML interaction prototype, no ARM or backend verification",
+ "login_sha256": "9f84a6961c32d854e7ede4ba7bfd732b8ca0dd21bae39726f14a22486ba4e017",
+ "login_assets_unchanged": true,
+ "additional": {
+ "lightLoginPreserved": true,
+ "contrast": {
+ "dark-settings": {
+ "minimum": 5.04,
+ "count": 23
+ },
+ "dark-connecting": {
+ "minimum": 6.92,
+ "count": 18
+ },
+ "dark-reconnect": {
+ "minimum": 6.92,
+ "count": 18
+ },
+ "dark-error": {
+ "minimum": 5.04,
+ "count": 14
+ },
+ "light-settings": {
+ "minimum": 4.74,
+ "count": 23
+ },
+ "light-connecting": {
+ "minimum": 5.29,
+ "count": 18
+ },
+ "light-reconnect": {
+ "minimum": 5.29,
+ "count": 18
+ },
+ "light-error": {
+ "minimum": 5.29,
+ "count": 14
+ }
+ },
+ "stateLayouts": {
+ "dark-settings": "no horizontal clipping",
+ "dark-connecting": "no horizontal clipping",
+ "dark-reconnect": "no horizontal clipping",
+ "dark-error": "no horizontal clipping",
+ "light-settings": "no horizontal clipping",
+ "light-connecting": "no horizontal clipping",
+ "light-reconnect": "no horizontal clipping",
+ "light-error": "no horizontal clipping"
+ },
+ "connectionPreviewTerminates": true,
+ "errorRecovery": true,
+ "logout": true
+ },
+ "source_files": {
+ "index.html": "49d01270b527030a13a086347af28f5a3fd47ec8c1063442e20acece4b9e7a4d",
+ "workspace.css": "c1a0e30cf11a1c5f3e539ef4446890b3f54e204f6c2e00c84c694972ece4ea2d",
+ "workspace.js": "79e4899622aa80a7119b0566fa9769d27c5fdd7be2fd044b26b5194802522326"
+ },
+ "browserConsole": {
+ "errors": 0,
+ "warnings": 0
+ },
+ "reviewPage": {
+ "themeSwitcher": true,
+ "localImagesLoaded": true
+ }
+}
\ No newline at end of file
diff --git a/ui/terminal/output/playwright/workspace-dark-1280.png b/ui/terminal/output/playwright/workspace-dark-1280.png
new file mode 100644
index 0000000..613ed72
Binary files /dev/null and b/ui/terminal/output/playwright/workspace-dark-1280.png differ
diff --git a/ui/terminal/output/playwright/workspace-dark-1366.png b/ui/terminal/output/playwright/workspace-dark-1366.png
new file mode 100644
index 0000000..df4937c
Binary files /dev/null and b/ui/terminal/output/playwright/workspace-dark-1366.png differ
diff --git a/ui/terminal/output/playwright/workspace-dark-1920.png b/ui/terminal/output/playwright/workspace-dark-1920.png
new file mode 100644
index 0000000..0f5dfa5
Binary files /dev/null and b/ui/terminal/output/playwright/workspace-dark-1920.png differ
diff --git a/ui/terminal/output/playwright/workspace-dark-390.png b/ui/terminal/output/playwright/workspace-dark-390.png
new file mode 100644
index 0000000..e7e12bc
Binary files /dev/null and b/ui/terminal/output/playwright/workspace-dark-390.png differ
diff --git a/ui/terminal/output/playwright/workspace-dark-820.png b/ui/terminal/output/playwright/workspace-dark-820.png
new file mode 100644
index 0000000..5f03689
Binary files /dev/null and b/ui/terminal/output/playwright/workspace-dark-820.png differ
diff --git a/ui/terminal/output/playwright/workspace-light-1280.png b/ui/terminal/output/playwright/workspace-light-1280.png
new file mode 100644
index 0000000..a21ca30
Binary files /dev/null and b/ui/terminal/output/playwright/workspace-light-1280.png differ
diff --git a/ui/terminal/output/playwright/workspace-light-1366.png b/ui/terminal/output/playwright/workspace-light-1366.png
new file mode 100644
index 0000000..112b5fe
Binary files /dev/null and b/ui/terminal/output/playwright/workspace-light-1366.png differ
diff --git a/ui/terminal/output/playwright/workspace-light-1920.png b/ui/terminal/output/playwright/workspace-light-1920.png
new file mode 100644
index 0000000..74efb3b
Binary files /dev/null and b/ui/terminal/output/playwright/workspace-light-1920.png differ
diff --git a/ui/terminal/references/README.md b/ui/terminal/references/README.md
new file mode 100644
index 0000000..f23da4e
--- /dev/null
+++ b/ui/terminal/references/README.md
@@ -0,0 +1,61 @@
+# VDI 终端界面参考与设计依据
+
+核对日期:2026-11-09。以下截图从官方文档的图片链接取得,作为研究材料保存在本目录,不放入产品界面。原始截图中的第三方品牌、组织和账号不用于越欣科技终端。
+
+## 1. Microsoft Windows App:桌面资源选择
+
+- 官方说明:https://learn.microsoft.com/en-us/windows-app/get-started-connect-devices-desktops-apps
+- Windows 客户端截图:https://learn.microsoft.com/en-us/windows-app/media/get-started-connect-devices-desktops-apps/windows-devices.png
+- Web 客户端截图:https://learn.microsoft.com/en-us/windows-app/media/get-started-connect-devices-desktops-apps/web-devices.png
+- 本地材料:[Windows 客户端](windows-app-devices.png)、[Web 客户端](windows-app-web-devices.png)。
+
+实际界面包含:设备页、桌面名称、服务类型、配置或组织、可连接状态、连接按钮,以及资源搜索和收藏入口。
+
+采用:以桌面名称为主标题,状态靠近名称;每张桌面有独立连接入口;支持搜索和收藏。
+
+调整:不采用截图的壁纸和文字叠层。越欣终端使用纯色卡片,让系统标识、名称、状态、配置、操作从上到下排列,消除深浅背景交叉造成的对比问题。三张桌面直接展示,不引入复杂导航或多工作区概念。
+
+## 2. Amazon WorkSpaces:资源管理、设置与会话诊断
+
+- 官方用户手册:https://docs.aws.amazon.com/workspaces/latest/userguide/amazon-workspaces-windows-client.html
+- 资源列表截图:https://docs.aws.amazon.com/images/workspaces/latest/userguide/images/wsp-windows-newclient-workspace-list.png
+- 显示设置截图:https://docs.aws.amazon.com/images/workspaces/latest/userguide/images/wsp-windows-newclient-display-fullscreen.png
+- 会话诊断截图:https://docs.aws.amazon.com/images/workspaces/latest/userguide/images/wsp-windows-newclient-session-health-panel1.png
+- 本地材料:[资源列表](workspaces-list.png)、[显示设置](workspaces-settings.png)、[会话诊断](workspaces-session-health.png)。
+
+版本说明:官方手册将该组新客户端界面标为 Public Preview;它是成熟 WorkSpaces 产品中的新界面参考,不表述为已全面发布版本。
+
+实际界面包含:收藏、资源名称与操作分离;设置按连接、显示、外观等类别组织;会话健康度以明确的网络状态和指标呈现。
+
+采用:收藏与连接互不混用;设置入口固定;配置字段有名称与说明;连接中、取消、错误恢复有明确路径。
+
+调整:当前 UI 原型不接入设备遥测或 WorkSpaces 服务,不显示捏造的延迟、带宽或实时健康度。网络状态只作示例,并在页脚标明。复杂诊断图表不塞入三台桌面的选择页。
+
+## 3. Omnissa Horizon:按资源设置与连接流程
+
+- 官方指南:https://docs.omnissa.com/HorizonClient-WindowsGuide-V2609/ConnecttoaRemoteDesktoporPublishedApplicationfromHorizonWindowsClient
+
+已核对官方流程文字:登录后进入桌面与应用选择器;可在桌面资源上打开 Settings 调整显示;连接以具体资源为单位。本文仅作为操作流程依据,没有将其称为已取得的界面截图。
+
+采用:每台桌面独立“设置”和“连接”;返回列表不退出账号;未配置地址时解释原因并保留设置入口。
+
+## 4. 与本仓库代码的对应
+
+- `terminal/kiosk-shell/src/desktop_list_page.cpp`:每张卡片都有“设置”,可用桌面有“连接”,忙碌时禁止重复操作。
+- `terminal/kiosk-shell/src/desktop_settings_dialog.cpp`:服务器地址、应用名、分辨率、帧率;地址留空表示未分配,桌面不可连接。
+- 原型的配置字段与上述真实代码对应;不增加没有来源的 CPU、内存、显卡容量或实时质量指标。
+
+## 5. 保留的视觉系统与可读性规则
+
+- 登录界面的 Logo、互动图形和深浅色视觉沿用原稿。后续按要求补充表单校验、登录状态、企业连接、SSO 和离线体验;相关浏览器验证见 `../output/playwright/login-verification.json`。
+- 登录后沿用深蓝灰背景、铜橙色强调、圆角卡片与低对比边框;去掉整块浅蓝、浅绿色带。
+- 系统识别只占卡片左上角:Windows 窗格图形、Ubuntu 官方形态图形、麒麟软件官方品牌图片,加明确的系统名称。色彩局部使用,连接按钮统一铜橙。
+- Ubuntu 图形来自 Simple Icons 的 Ubuntu 资源:https://raw.githubusercontent.com/simple-icons/simple-icons/develop/icons/ubuntu.svg 。
+- 麒麟软件图片直接来自官网:https://www.kylinos.cn/upload/1/appLogo/20250820/1957999505938382848.png 。该图是麒麟软件公司标识,不冒充单独的银河麒麟产品 Logo。
+- 主要桌面名称 22–24 px;正文和操作 14–16 px;辅助规格和设备状态 13 px。小字号正文按 WCAG AA 4.5:1 校验;大字号至少 3:1。
+- 常用操作至少 44 px 高,有文字标签;收藏按钮同时提供名称与选中状态;不可连接状态同时提供原因。
+- 深色、浅色、1280×720 / 1366×768 / 1920×1080 都独立渲染;不将“语法检查通过”等同于视觉验收。
+
+## 6. 交付边界
+
+这是 `ui/terminal` 下的 HTML 交互设计原型;连接配置保存在当前浏览器的本地存储供评审使用。真实终端认证、Moonlight 会话、设备遥测和 Qt 落地仍由 kiosk-shell 接入。原型不伪装远程连接成功,连接状态在预览等待后进入说明页,并提供检查设置的恢复路径。
diff --git a/ui/terminal/references/sources.json b/ui/terminal/references/sources.json
new file mode 100644
index 0000000..2d61a0f
--- /dev/null
+++ b/ui/terminal/references/sources.json
@@ -0,0 +1,32 @@
+[
+ {
+ "file": "windows-app-devices.png",
+ "url": "https://learn.microsoft.com/en-us/windows-app/media/get-started-connect-devices-desktops-apps/windows-devices.png",
+ "sha256": "aabb6d5577ab57f665b9a975851d5b965d1841158d6d419b6e9bccd6de93b5e2",
+ "retrieved_on": "2026-11-09"
+ },
+ {
+ "file": "windows-app-web-devices.png",
+ "url": "https://learn.microsoft.com/en-us/windows-app/media/get-started-connect-devices-desktops-apps/web-devices.png",
+ "sha256": "8092d18695fc6e98272dd2019698a82ffc21ddc987dcaebe990ba0520dde3964",
+ "retrieved_on": "2026-11-09"
+ },
+ {
+ "file": "workspaces-list.png",
+ "url": "https://docs.aws.amazon.com/images/workspaces/latest/userguide/images/wsp-windows-newclient-workspace-list.png",
+ "sha256": "04290c829884ad531a8b2a75fb7901370ffcea00753b684ff0de22c8d892955f",
+ "retrieved_on": "2026-11-09"
+ },
+ {
+ "file": "workspaces-settings.png",
+ "url": "https://docs.aws.amazon.com/images/workspaces/latest/userguide/images/wsp-windows-newclient-display-fullscreen.png",
+ "sha256": "169323403765285c30ac6106b8b53a59d6e17a6c43ef9edd30d37c44bd61811b",
+ "retrieved_on": "2026-11-09"
+ },
+ {
+ "file": "workspaces-session-health.png",
+ "url": "https://docs.aws.amazon.com/images/workspaces/latest/userguide/images/wsp-windows-newclient-session-health-panel1.png",
+ "sha256": "16bdcd0ea25fffdd9957c0d9bb53f5aac474c6be81267c503c6db5987edb2a0e",
+ "retrieved_on": "2026-11-09"
+ }
+]
\ No newline at end of file
diff --git a/ui/terminal/references/windows-app-devices.png b/ui/terminal/references/windows-app-devices.png
new file mode 100644
index 0000000..62516c4
Binary files /dev/null and b/ui/terminal/references/windows-app-devices.png differ
diff --git a/ui/terminal/references/windows-app-web-devices.png b/ui/terminal/references/windows-app-web-devices.png
new file mode 100644
index 0000000..a72a61f
Binary files /dev/null and b/ui/terminal/references/windows-app-web-devices.png differ
diff --git a/ui/terminal/references/workspaces-list.png b/ui/terminal/references/workspaces-list.png
new file mode 100644
index 0000000..b450979
Binary files /dev/null and b/ui/terminal/references/workspaces-list.png differ
diff --git a/ui/terminal/references/workspaces-session-health.png b/ui/terminal/references/workspaces-session-health.png
new file mode 100644
index 0000000..9879aa0
Binary files /dev/null and b/ui/terminal/references/workspaces-session-health.png differ
diff --git a/ui/terminal/references/workspaces-settings.png b/ui/terminal/references/workspaces-settings.png
new file mode 100644
index 0000000..3a44067
Binary files /dev/null and b/ui/terminal/references/workspaces-settings.png differ
diff --git a/ui/terminal/review.html b/ui/terminal/review.html
new file mode 100644
index 0000000..385b6bc
--- /dev/null
+++ b/ui/terminal/review.html
@@ -0,0 +1,12 @@
+
+越欣科技 VDI · 界面评审
+
+
1366 × 768 实际浏览器渲染。卡片使用统一底色,系统识别、桌面名称、状态、规格、操作依次排列。
+登录交互
体验账号:demo · 密码:Demo123!。支持输入校验、记住账号、密码显示、企业连接、SSO 取消、忘记密码和离线体验;认证为本地模拟。
+5.04 : 1深色桌面列表的最小文字对比度
4.74 : 1浅色桌面列表的最小文字对比度
1280 × 720三张卡片、连接操作与状态条完整可见
+参考真实 VDI 界面,保留自己的视觉风格
采用的结构
- 系统标识与桌面名称分层,状态靠近桌面名称。
- 搜索、收藏、单资源连接入口各自独立。
- 设置与连接分开,未配置时显示原因。
针对 ARM 终端调整
- 取消大块彩色卡头、壁纸叠字和重复系统名称。
- 连接操作至少 44 px,适配 720p / 1080p 显示器。
- 连接设置对应 kiosk-shell 的地址、应用、分辨率、帧率。
另核对 Omnissa Horizon 官方按桌面设置与连接流程。完整来源和采用理由见 设计依据。
+同一套风格覆盖设置与状态页
连接设置:目标桌面、地址、应用名、分辨率与帧率。
连接过程:选定桌面、当前步骤、取消和恢复路径。
+
+
diff --git a/ui/terminal/workspace.css b/ui/terminal/workspace.css
new file mode 100644
index 0000000..d4d56df
--- /dev/null
+++ b/ui/terminal/workspace.css
@@ -0,0 +1,134 @@
+/* Login has its own established styles. All rules here are scoped to the terminal workspace. */
+body.in-workspace .topbar{display:none}
+.terminal-workspace[hidden]{display:none!important}
+.terminal-workspace{--ws-bg:#f8f9fa;--ws-panel:#fff;--ws-soft:#f0f3f5;--ws-hover:#f5f7f8;--ws-line:#dce2e7;--ws-ink:#25313d;--ws-secondary:#536372;--ws-muted:#596a79;--ws-accent:#a95121;--ws-button:#a95121;--ws-button-hover:#93451b;--ws-tint:#f9eee6;--ws-good:#24714f;--ws-good-bg:#e9f5ee;--ws-warn:#81581b;--ws-warn-bg:#f9f1df;--ws-shadow:0 6px 22px rgba(33,48,61,.045);min-height:100vh;background:var(--ws-bg);color:var(--ws-ink);font-size:15px;line-height:1.55;display:flex;flex-direction:column}
+body[data-theme=dark] .terminal-workspace{color-scheme:dark;--ws-bg:#1b232d;--ws-panel:#242e39;--ws-soft:#2b3743;--ws-hover:#2b3642;--ws-line:#405060;--ws-ink:#edf1f5;--ws-secondary:#bac5cf;--ws-muted:#a4b2c0;--ws-accent:#edac7d;--ws-button:#aa5928;--ws-button-hover:#9c5125;--ws-tint:#3c3029;--ws-good:#9cdbbc;--ws-good-bg:#263d35;--ws-warn:#efcd8e;--ws-warn-bg:#413a2a;--ws-shadow:none}
+.terminal-workspace *{box-sizing:border-box}
+.terminal-workspace button,.terminal-workspace input,.terminal-workspace select{font:inherit}
+.terminal-workspace button{cursor:pointer}
+.terminal-workspace button:disabled{cursor:not-allowed}
+.terminal-workspace :focus-visible{outline:3px solid var(--ws-accent);outline-offset:4px}
+.ws-icon{width:20px;height:20px;display:block;flex-shrink:0;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
+.ws-header{height:84px;flex-shrink:0;padding:0 max(32px,calc((100vw - 1280px)/2));display:flex;align-items:center;justify-content:space-between;gap:24px;border-bottom:1px solid var(--ws-line);background:var(--ws-panel)}
+.ws-brand{display:flex;align-items:center;gap:18px;white-space:nowrap}
+.ws-brand strong{font-size:22px;letter-spacing:2px;font-weight:650}
+.ws-brand span{border-left:1px solid var(--ws-line);padding-left:18px;color:var(--ws-secondary);font-size:15px}
+.ws-header-actions,.ws-action-group{display:flex;align-items:center;gap:10px}
+.ws-btn{display:inline-flex;align-items:center;justify-content:center;gap:9px;min-height:44px;padding:0 16px;border:1px solid var(--ws-line);border-radius:11px;color:var(--ws-ink);background:var(--ws-panel);font-size:14px;font-weight:550;line-height:1.25;white-space:nowrap}
+.ws-btn:hover{background:var(--ws-hover)}
+.ws-btn.ws-primary{border-color:var(--ws-button);background:var(--ws-button);color:#fff}
+.ws-btn.ws-primary:hover{background:var(--ws-button-hover);border-color:var(--ws-button-hover)}
+.ws-btn:disabled{background:var(--ws-soft);border-color:var(--ws-line);color:var(--ws-muted)}
+.ws-btn.ws-icon-btn{width:44px;padding:0}
+.ws-account{display:flex;align-items:center;gap:10px;margin-left:8px;padding-left:20px;border-left:1px solid var(--ws-line);color:var(--ws-secondary);font-size:14px}
+.ws-account .ws-icon{width:18px;height:18px}
+.ws-main{width:min(1280px,calc(100% - 64px));margin:0 auto;padding:50px 0 46px;flex:1}
+.ws-heading{display:flex;justify-content:space-between;align-items:flex-end;gap:24px;margin-bottom:30px}
+.ws-heading h1{font-size:30px;letter-spacing:-.5px;font-weight:650;line-height:1.3;margin:0 0 9px}
+.ws-heading p{margin:0;color:var(--ws-secondary);font-size:15px}
+.ws-count{font-size:14px;color:var(--ws-secondary);padding-bottom:3px;white-space:nowrap}
+.ws-toolbar{display:flex;align-items:center;justify-content:space-between;gap:20px;margin-bottom:24px}
+.ws-tabs{display:flex;gap:6px;padding:4px;border:1px solid var(--ws-line);border-radius:12px;background:var(--ws-panel)}
+.ws-tab{border:0;background:transparent;color:var(--ws-secondary);border-radius:8px;min-height:38px;padding:0 16px;font-size:14px}
+.ws-tab[aria-selected=true]{color:var(--ws-accent);background:var(--ws-tint);font-weight:650}
+.ws-tab span{margin-left:7px;font-size:12px}
+.ws-search{display:flex;align-items:center;gap:10px;width:260px;min-height:46px;padding:0 14px;border:1px solid var(--ws-line);border-radius:11px;background:var(--ws-panel);color:var(--ws-muted)}
+.ws-search input{width:100%;border:0;outline:0;background:transparent;color:var(--ws-ink);font-size:14px;min-width:0}
+.ws-search:focus-within{outline:2px solid var(--ws-accent);outline-offset:2px}
+.ws-search input::placeholder{color:var(--ws-muted)}
+.ws-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:22px}
+.ws-card{display:flex;flex-direction:column;min-width:0;border:1px solid var(--ws-line);border-radius:20px;background:var(--ws-panel);box-shadow:var(--ws-shadow);padding:26px 26px 22px;transition:border-color .15s}
+.ws-card:hover{border-color:var(--ws-muted)}
+.ws-card-top{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:26px}
+.ws-system{display:flex;align-items:center;gap:14px;min-width:0}
+.ws-os-mark{display:grid;place-items:center;width:64px;height:64px;border-radius:16px;flex-shrink:0}
+.ws-os-mark svg{width:32px;height:32px;display:block;fill:currentColor}
+.ws-os-mark img{width:34px;height:34px;object-fit:contain;display:block}
+.ws-os-mark.windows{background:#e1edf9;color:#1268ab}
+.ws-os-mark.ubuntu{background:#fbe5dc;color:#d24413}
+.ws-os-mark.ubuntu img{filter:invert(31%) sepia(81%) saturate(2291%) hue-rotate(354deg) brightness(89%) contrast(96%)}
+.ws-os-mark.kylin{background:#fff;border:1px solid #e2e6eb}
+.ws-os-mark.kylin img{width:54px;height:38px}
+body[data-theme=dark] .ws-os-mark.windows{background:#273e52;color:#7fc3f7}
+body[data-theme=dark] .ws-os-mark.ubuntu{background:#4b3127;color:#ffa77c}
+body[data-theme=dark] .ws-os-mark.ubuntu img{filter:invert(75%) sepia(44%) saturate(721%) hue-rotate(321deg) brightness(102%) contrast(102%)}
+.ws-system-name{font-size:16px;font-weight:650;line-height:1.5;white-space:nowrap}
+.ws-system-version{color:var(--ws-secondary);font-size:13px;line-height:1.5;margin-top:3px}
+.ws-favorite{border:0;background:transparent;color:var(--ws-muted);width:36px;height:40px;display:grid;place-items:center;border-radius:9px;flex-shrink:0}
+.ws-favorite:hover{background:var(--ws-soft)}
+.ws-favorite[aria-pressed=true]{color:var(--ws-accent)}
+.ws-favorite[aria-pressed=true] svg{fill:var(--ws-tint)}
+.ws-card h2{font-size:22px;font-weight:650;letter-spacing:-.3px;line-height:1.35;margin:0 0 11px}
+.ws-status{display:inline-flex;align-items:center;gap:7px;border-radius:6px;font-size:13px;font-weight:550;color:var(--ws-good);background:var(--ws-good-bg);width:fit-content;padding:3px 9px;line-height:1.6}
+.ws-status:before,.ws-online:before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor;flex-shrink:0}
+.ws-status.pending{color:var(--ws-warn);background:var(--ws-warn-bg)}
+.ws-specs{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:26px 0 0;padding:20px 0;border-top:1px solid var(--ws-line);border-bottom:1px solid var(--ws-line)}
+.ws-specs div+div{border-left:1px solid var(--ws-line);padding-left:22px}
+.ws-specs dt{color:var(--ws-secondary);font-size:13px;margin-bottom:5px}
+.ws-specs dd{margin:0;font-size:15px;font-weight:550;white-space:nowrap}
+.ws-card-note{margin:17px 0 22px;color:var(--ws-secondary);font-size:13px;min-height:21px}
+.ws-card-actions{margin-top:auto;display:grid;grid-template-columns:1fr 1.45fr;gap:10px}
+.ws-card-actions .ws-btn{min-height:46px}
+.ws-footer{width:min(1280px,calc(100% - 64px));margin:0 auto;border-top:1px solid var(--ws-line);display:flex;align-items:center;justify-content:space-between;gap:18px;padding:20px 0 24px;color:var(--ws-secondary);font-size:13px}
+.ws-footer-left{display:flex;align-items:center;gap:22px;flex-wrap:wrap}
+.ws-online{display:inline-flex;align-items:center;gap:7px;color:var(--ws-good)}
+.ws-prototype{font-size:12px;color:var(--ws-muted)}
+.ws-empty{grid-column:1/-1;padding:70px 24px;border:1px dashed var(--ws-line);border-radius:18px;text-align:center;color:var(--ws-secondary)}
+.ws-empty h2{font-size:20px;color:var(--ws-ink)}
+.ws-empty p{margin:8px 0 22px}
+.ws-view-title{display:flex;align-items:center;gap:16px;margin-bottom:26px}
+.ws-view-title h1{font-size:26px;margin:0;font-weight:650}
+.ws-settings{display:grid;grid-template-columns:218px 1fr;gap:26px;align-items:start}
+.ws-settings-nav{padding:10px;border:1px solid var(--ws-line);border-radius:16px;background:var(--ws-panel);display:grid;gap:4px}
+.ws-settings-nav button{display:flex;align-items:center;gap:11px;min-height:48px;border:0;border-radius:9px;padding:0 14px;background:transparent;color:var(--ws-secondary);text-align:left;font-size:14px}
+.ws-settings-nav button[aria-current=page]{background:var(--ws-tint);color:var(--ws-accent);font-weight:650}
+.ws-settings-panel{padding:30px 34px;border:1px solid var(--ws-line);border-radius:18px;background:var(--ws-panel);min-height:460px}
+.ws-settings-panel h2{font-size:21px;line-height:1.4;font-weight:650;margin:0 0 9px}
+.ws-settings-panel>p{font-size:14px;margin:0 0 28px;color:var(--ws-secondary)}
+.ws-form-grid{display:grid;grid-template-columns:1fr 1fr;gap:22px}
+.ws-field{display:grid;gap:9px;font-size:14px}
+.ws-field.full{grid-column:1/-1}
+.ws-input{width:100%;height:46px;border:1px solid var(--ws-line);border-radius:10px;background:var(--ws-soft);color:var(--ws-ink);padding:0 13px;outline:none;min-width:0}
+.ws-field small{color:var(--ws-secondary);font-size:13px}
+.ws-field.ws-invalid .ws-input{border-color:#d47d71}
+.ws-error-text{color:var(--ws-warn);font-size:14px;margin:16px 0 0;min-height:24px}
+.ws-form-actions{display:flex;justify-content:flex-end;gap:12px;margin-top:25px;padding-top:23px;border-top:1px solid var(--ws-line)}
+.ws-info-list{margin:12px 0 0}
+.ws-info-list>div{display:flex;justify-content:space-between;gap:26px;border-bottom:1px solid var(--ws-line);padding:17px 0;font-size:14px}
+.ws-info-list dt{color:var(--ws-secondary)}
+.ws-info-list dd{margin:0;text-align:right;overflow-wrap:anywhere}
+.ws-session{width:min(660px,100%);margin:16px auto 20px;border:1px solid var(--ws-line);border-radius:22px;background:var(--ws-panel);padding:38px 42px;text-align:center}
+.ws-session>.ws-os-mark{margin:0 auto 22px;width:72px;height:72px}
+.ws-session h1{font-size:26px;font-weight:650;line-height:1.35;margin:0 0 9px}
+.ws-session-sub{color:var(--ws-secondary);font-size:15px;margin:0}
+.ws-session-progress{height:4px;margin:30px 0 27px;background:var(--ws-soft);border-radius:6px;overflow:hidden}
+.ws-session-progress span{display:block;height:100%;width:28%;background:var(--ws-accent);border-radius:6px;transition:width .2s}
+.ws-steps{list-style:none;text-align:left;padding:0;margin:0 0 28px;display:grid;gap:18px}
+.ws-steps li{display:flex;align-items:center;gap:13px;font-size:14px;color:var(--ws-secondary)}
+.ws-step-mark{display:grid;place-items:center;width:25px;height:25px;border-radius:50%;border:1px solid var(--ws-line);font-size:12px}
+.ws-steps li.done{color:var(--ws-good)}
+.ws-steps li.active{color:var(--ws-ink);font-weight:550}
+.ws-steps li.active .ws-step-mark{border-color:var(--ws-accent);color:var(--ws-accent)}
+.ws-session-note{background:var(--ws-soft);color:var(--ws-secondary);font-size:13px;text-align:left;border-radius:10px;padding:13px 16px;line-height:1.7;margin-bottom:25px}
+.ws-session-actions{display:flex;justify-content:center;gap:12px;flex-wrap:wrap}
+.ws-session-actions .ws-btn{min-width:140px}
+.ws-spinner{width:14px;height:14px;border-radius:50%;border:2px solid var(--ws-line);border-top-color:var(--ws-accent);animation:ws-spin 1s linear infinite}
+@keyframes ws-spin{to{transform:rotate(360deg)}}
+.ws-toast{position:fixed;bottom:30px;left:50%;transform:translateX(-50%);padding:13px 22px;background:var(--ws-ink);color:var(--ws-panel);border-radius:12px;box-shadow:var(--ws-shadow);font-size:14px;z-index:15}
+@media(min-width:1600px){.ws-main{padding-top:64px}.ws-card{padding:30px}.ws-grid{gap:26px}.ws-card h2{font-size:24px}.ws-card-note{margin-bottom:27px}}
+@media(max-width:1120px){.ws-header{padding:0 26px}.ws-brand span{display:none}.ws-main,.ws-footer{width:calc(100% - 52px)}.ws-main{padding-top:35px}.ws-card{padding:22px 18px}.ws-system{gap:10px}.ws-os-mark{width:52px;height:52px;border-radius:13px}.ws-system-name{font-size:15px}.ws-favorite{width:28px}.ws-specs div+div{padding-left:12px}.ws-card h2{font-size:21px}.ws-card-actions{grid-template-columns:1fr 1.3fr}.ws-btn{padding:0 13px}}
+@media(max-width:900px){.ws-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.ws-account{display:none}.ws-settings{grid-template-columns:175px 1fr;gap:18px}.ws-settings-panel{padding:25px}.ws-footer-left{gap:12px}}
+@media(max-width:620px){.ws-header{height:72px;padding:0 18px}.ws-brand strong{font-size:19px}.ws-header-actions{gap:8px}.ws-header-actions .ws-settings-label{display:none}.ws-main,.ws-footer{width:calc(100% - 36px)}.ws-main{padding-top:28px}.ws-heading{align-items:flex-start;gap:12px}.ws-heading h1{font-size:26px}.ws-count{font-size:12px}.ws-toolbar{align-items:stretch;flex-direction:column;gap:14px}.ws-search{width:100%}.ws-grid{grid-template-columns:1fr}.ws-card{padding:24px}.ws-system-name{font-size:16px}.ws-os-mark{width:60px;height:60px}.ws-settings{grid-template-columns:1fr}.ws-settings-nav{grid-template-columns:repeat(3,1fr);padding:5px}.ws-settings-nav button{justify-content:center;padding:0 6px;font-size:13px;gap:6px}.ws-settings-nav .ws-icon{display:none}.ws-form-grid{grid-template-columns:1fr}.ws-settings-panel{padding:22px}.ws-session{padding:28px 24px;margin-top:0}.ws-footer{align-items:flex-start;flex-direction:column}.ws-footer-left{gap:12px;font-size:12px}.ws-prototype{font-size:11px}}
+@media(prefers-reduced-motion:reduce){.terminal-workspace *{animation:none!important;transition:none!important}}
+/* A 720p ARM display must keep the primary actions and device status in view. */
+@media(min-width:901px) and (max-height:820px){
+ .ws-header{height:72px}
+ .ws-main{padding-top:28px;padding-bottom:26px}
+ .ws-heading{margin-bottom:20px}.ws-heading h1{font-size:28px;margin-bottom:6px}.ws-heading p{font-size:14px}
+ .ws-toolbar{margin-bottom:20px}.ws-tabs{padding:3px}.ws-tab{min-height:36px}
+ .ws-card{padding:22px 22px 20px}.ws-card-top{margin-bottom:20px}.ws-os-mark{height:54px;width:54px;border-radius:13px}
+ .ws-card h2{font-size:22px;margin-bottom:9px}.ws-specs{margin-top:20px;padding:15px 0}.ws-card-note{margin:14px 0 18px}
+ .ws-card-actions .ws-btn{min-height:44px}.ws-footer{padding-top:16px;padding-bottom:18px}
+ .ws-session{margin-top:0;padding:28px 38px}.ws-session>.ws-os-mark{width:56px;height:56px;margin-bottom:15px}
+ .ws-session-progress{margin:24px 0}.ws-steps{gap:13px;margin-bottom:22px}.ws-settings-panel{padding:24px 30px;min-height:0}
+}
diff --git a/ui/terminal/workspace.js b/ui/terminal/workspace.js
new file mode 100644
index 0000000..a5ea4cb
--- /dev/null
+++ b/ui/terminal/workspace.js
@@ -0,0 +1,183 @@
+(() => {
+ 'use strict';
+ const root = document.getElementById('workspace');
+ const login = document.getElementById('login');
+ const paths = {
+ monitor: '',
+ settings: '',
+ sun: '',
+ moon: '',
+ user: '',
+ star: '',
+ search: '',
+ refresh: '',
+ arrow: '',
+ back: '',
+ network: '',
+ info: '',
+ logout: '',
+ check: '',
+ close: ''
+ };
+ const icon = name => ``;
+ const escape = value => String(value).replace(/[&<>"']/g, char => ({ '&':'&', '<':'<', '>':'>', '"':'"', "'":''' }[char]));
+ const defaults = [
+ { id:'windows', os:'Windows 10', version:'64 位 · Windows', name:'设计工作站', resolution:'2560x1440', fps:60, endpoint:'192.168.1.50', app:'Desktop', note:'图形设计与日常办公', favorite:true },
+ { id:'ubuntu', os:'Ubuntu', version:'24.04 LTS · Linux', name:'研发环境', resolution:'2560x1440', fps:30, endpoint:'192.168.1.51', app:'Desktop', note:'开发工具与研发工作环境', favorite:false },
+ { id:'kylin', os:'银河麒麟', version:'桌面操作系统 · Linux', name:'办公桌面', resolution:'1920x1080', fps:30, endpoint:'', app:'Desktop', note:'暂未配置服务器,请联系管理员', favorite:false }
+ ];
+ const read = () => { try { return JSON.parse(localStorage.getItem('yuexin-terminal-ui-v3') || 'null'); } catch { return null; } };
+ const saved = read();
+ let desktops = defaults.map(d => {
+ const local = Array.isArray(saved) && saved.find(v => v.id === d.id);
+ return local ? { ...d, endpoint:String(local.endpoint ?? d.endpoint), app:String(local.app || d.app), resolution:['1920x1080','2560x1440','3840x2160','1280x720'].includes(local.resolution) ? local.resolution : d.resolution, fps:[24,30,60].includes(local.fps) ? local.fps : d.fps, favorite:!!local.favorite } : { ...d };
+ });
+ const persist = () => { try { localStorage.setItem('yuexin-terminal-ui-v3', JSON.stringify(desktops)); return true; } catch { return false; } };
+ let tab = 'all', search = '', view = 'desktops', settingsTab = 'desktop', selectedId = 'windows', sessionState = 'connecting';
+ let timer, toastTimer;
+ let identity = {name:'终端用户',mode:'preview',service:'cloud',organization:'越欣科技'};
+ const selected = () => desktops.find(d => d.id === selectedId) || desktops[0];
+ const mark = d => {
+ const art = d.id === 'windows'
+ ? ''
+ : `
`;
+ return `${art}
`;
+ };
+ const prettyResolution = d => d.resolution.replace('x', ' × ');
+ const themeLabel = () => document.body.dataset.theme === 'dark' ? '切换浅色' : '切换深色';
+ const header = () => ``;
+ const footer = () => ``;
+ function card(d) {
+ const available = !!d.endpoint;
+ return `${mark(d)}
${escape(d.os)}
${escape(d.version)}
${escape(d.name)}
${available ? '可连接' : '待配置'}- 分辨率
- ${prettyResolution(d)}
- 帧率
- ${d.fps} FPS
${available ? escape(d.id === 'kylin' ? '国产办公工作环境' : d.note) : '暂未配置服务器,请联系管理员'}
`;
+ }
+ function list() {
+ return `${desktops.filter(d => d.endpoint).length} 台可连接 / 共 ${desktops.length} 台
`;
+ }
+ function renderGrid() {
+ const rows = desktops.filter(d => (tab !== 'favorites' || d.favorite) && (d.os + d.name).toLowerCase().includes(search.trim().toLowerCase()));
+ root.querySelector('#ws-grid').innerHTML = rows.length ? rows.map(card).join('') : `${icon('search')}
${search ? '没有找到匹配的桌面' : '还没有收藏的桌面'}
${search ? '试试系统名称,或清除搜索条件。' : '点击桌面卡片右上角的星标即可收藏。'}
`;
+ }
+ const option = (value, label, current) => ``;
+ function desktopSettings() {
+ const d = selected();
+ return `桌面连接设置
配置当前桌面的连接地址和显示参数。
`;
+ }
+ function settings() {
+ let panel;
+ if (settingsTab === 'desktop') panel = desktopSettings();
+ if (settingsTab === 'network') panel = `网络状态
连接远程桌面前,请确认终端已接入工作网络。
- 连接方式
- 有线网络
- 网络状态
- 已连接
- 地址获取
- 自动获取(DHCP)
此处为设备状态示例。实际网络配置由终端系统管理。
`;
+ if (settingsTab === 'device') panel = `设备信息
联系管理员时,可提供设备序列号协助定位。
- 设备序列号
- VDI-8F42A1
- 设备型号
- RK3528 云终端
- 处理器架构
- ARM64
- 管理快捷键
- Ctrl + Alt + Shift + M
`;
+ return `终端设置
`;
+ }
+ function session() {
+ const d = selected(), failure = sessionState === 'error', recovery = sessionState === 'reconnect';
+ const title = failure ? '暂时无法连接' : recovery ? '正在恢复连接' : '正在连接桌面';
+ const steps = recovery ? ['检测网络连接','恢复桌面会话','恢复桌面画面'] : ['检查连接配置','建立远程会话','准备桌面画面'];
+ return `${mark(d)}
${title}
${escape(d.os)} · ${escape(d.name)}
${failure ? '
' : '
'}${failure ? `
尚未接入远程桌面服务
当前为交互原型,未向服务器发送连接请求。实际终端需由桌面服务返回连接结果。
` : `
${steps.map((step,i) => `- ${i === 0 ? '✓' : i+1}${step}${i === 1 ? '' : ''}
`).join('')}
${recovery ? '连接中断后将尝试恢复原会话,请保持设备联网。' : '请保持设备联网。连接完成后,桌面将全屏打开。'}
当前预览不发起真实远程连接。
`}
${failure ? `` : ''}
`;
+ }
+ function render() {
+ root.innerHTML = header() + `${view === 'desktops' ? list() : view === 'settings' ? settings() : session()}` + footer();
+ if (view === 'desktops') renderGrid();
+ }
+ function showWorkspace(next = 'desktops') {
+ clearTimeout(timer);
+ document.dispatchEvent(new CustomEvent('terminal:open-workspace'));
+ view = next;
+ login.classList.remove('active');
+ root.hidden = false;
+ document.body.classList.add('in-workspace');
+ render();
+ window.scrollTo(0,0);
+ }
+ function home() { showWorkspace(); }
+ function leave() {
+ clearTimeout(timer);
+ root.hidden = true;
+ document.body.classList.remove('in-workspace');
+ login.classList.add('active');
+ identity = {name:'终端用户',mode:'preview',service:'cloud',organization:'越欣科技'};
+ tab='all';search='';
+ document.dispatchEvent(new CustomEvent('terminal:logout'));
+ window.scrollTo(0,0);
+ }
+ function notify(message) {
+ clearTimeout(toastTimer);
+ root.querySelector('.ws-toast')?.remove();
+ const toast = document.createElement('div');
+ toast.className = 'ws-toast'; toast.setAttribute('role','status'); toast.textContent = message; root.append(toast);
+ toastTimer = setTimeout(() => toast.remove(), 3000);
+ }
+ function start(id, state = 'connecting') {
+ selectedId = id;
+ if (!selected().endpoint) return;
+ sessionState = state;
+ showWorkspace('session');
+ if (state !== 'error') timer = setTimeout(() => { if (view === 'session') { sessionState = 'error'; render(); } }, 12000);
+ }
+ function toggleTheme() {
+ const dark = document.body.dataset.theme === 'dark';
+ document.body.dataset.theme = dark ? 'light' : 'dark';
+ const oldTheme = document.getElementById('theme');
+ oldTheme.textContent = dark ? '◉ 浅色外观' : '▣ 深色外观';
+ if (!root.hidden) {
+ const b = root.querySelector('[data-action="theme"]');
+ b.innerHTML = icon(dark ? 'moon' : 'sun');b.setAttribute('aria-label',themeLabel());b.title = themeLabel();
+ }
+ }
+ // Authentication interactions own login entry; direct preview URLs remain available for design review.
+ document.addEventListener('terminal:login', event => {
+ identity = {...identity,...event.detail};tab='all';search='';home();
+ const title = root.querySelector('h1');
+ if (title) { title.tabIndex = -1; title.focus({preventScroll:true}); }
+ });
+ document.getElementById('theme').addEventListener('click', toggleTheme);
+ root.addEventListener('input', event => {
+ if (event.target.id === 'ws-search') { search = event.target.value; renderGrid(); }
+ });
+ root.addEventListener('change', event => {
+ if (event.target.id === 'ws-target') { selectedId = event.target.value; render(); }
+ });
+ root.addEventListener('click', event => {
+ const b = event.target.closest('[data-action]');
+ if (!b || b.disabled) return;
+ switch (b.dataset.action) {
+ case 'home': home(); break;
+ case 'logout': leave(); break;
+ case 'theme': toggleTheme(); break;
+ case 'settings': settingsTab = 'desktop'; showWorkspace('settings'); break;
+ case 'edit': selectedId = b.dataset.id; settingsTab = 'desktop'; showWorkspace('settings'); break;
+ case 'settings-tab': settingsTab = b.dataset.tab; render(); break;
+ case 'connect': start(b.dataset.id); break;
+ case 'favorite': { const d = desktops.find(v => v.id === b.dataset.id); d.favorite = !d.favorite; persist(); render(); break; }
+ case 'tab': tab = b.dataset.tab; render(); break;
+ case 'clear-search': search = ''; tab = 'all'; render(); break;
+ case 'refresh': render(); notify('示例桌面列表已刷新'); break;
+ }
+ });
+ root.addEventListener('submit', event => {
+ if (event.target.id !== 'ws-settings-form') return;
+ event.preventDefault();
+ const form = event.target, values = new FormData(form), endpoint = String(values.get('endpoint')).trim(), app = String(values.get('app')).trim();
+ const field = form.querySelector('[name="endpoint"]');
+ if (endpoint && !/^(?:[a-z\d](?:[a-z\d.-]*[a-z\d])?)(?::\d{1,5})?$/i.test(endpoint)) {
+ form.querySelector('#ws-form-error').textContent = '请输入 IP 地址或域名,可附加端口号。';field.setAttribute('aria-invalid','true');field.focus();return;
+ }
+ if (!app) { form.querySelector('#ws-form-error').textContent = '请填写应用名,例如 Desktop。';form.querySelector('[name="app"]').focus();return; }
+ const d = selected();
+ Object.assign(d,{endpoint,app,resolution:String(values.get('resolution')),fps:Number(values.get('fps'))});
+ const stored = persist();home();notify(stored ? '设置已保存到本机预览' : '设置已应用,本次会话内有效');
+ });
+ document.addEventListener('keydown', event => {
+ if (event.ctrlKey && event.altKey && event.shiftKey && event.key.toLowerCase() === 'm') { event.preventDefault();settingsTab='desktop';showWorkspace('settings'); }
+ if (event.key === 'Escape' && !root.hidden && view !== 'desktops') home();
+ });
+ const params = new URLSearchParams(location.search);
+ const preview = params.get('preview');
+ if (preview) {
+ if (['dark','light'].includes(params.get('theme'))) document.body.dataset.theme = params.get('theme');
+ if (['connecting','reconnect','error'].includes(preview)) start('windows',preview);
+ else if (preview === 'settings') showWorkspace('settings');
+ else showWorkspace();
+ }
+})();