Просмотр исходного кода

fix: remove unsolicited demo navigation and fixed view branding

pengzhikang 1 день назад
Родитель
Сommit
c25a880d36

+ 13 - 13
deploy/five-views-files-20261103.json

@@ -1,12 +1,12 @@
 {
   "version": "ksf.five-views.publish.v1",
-  "release": "ksf.five-views.20261103.v2",
+  "release": "ksf.workshop-board.20261103.v3",
   "homepageSha256": "dfeb6a6d0b63e8e1190f9308bf6619a5ca523a5a2f91af5f7ca5ad5fc9642db3",
   "files": [
     {
       "path": "src/layout/kf-app.js",
-      "bytes": 31169,
-      "sha256": "068f8b5eea1c68af00b5006ea698f853811ced50b31b911ceab030265691330d",
+      "bytes": 31168,
+      "sha256": "4d0d140a0361bbafd75983862e0b2931962b91305238f43838573e3084c6ee6c",
       "allowMissing": false,
       "priorNormalizedSha256": [
         "ac936167c2820fc2cd8a122cd3686ad741bfde2b7628f59bd1e6de157df77272"
@@ -14,8 +14,8 @@
     },
     {
       "path": "src/layout/kf-nav.js",
-      "bytes": 10350,
-      "sha256": "699f2d51b1b79f6c6bc979b05a72b2426ccc956ce72f793ad2b0944000cd2abc",
+      "bytes": 9948,
+      "sha256": "bf550dbea6596c612378bf219c0270032e967fd3399bc50bef11a46a39f9028c",
       "allowMissing": false,
       "priorNormalizedSha256": [
         "6e3fb092b13320e799cebbacdc87878ca0c2e56b58114ca561512bbfcef1a5fa"
@@ -23,8 +23,8 @@
     },
     {
       "path": "src/layout/sample-shell.js",
-      "bytes": 7669,
-      "sha256": "25bb95548a3c4850a18efc524c1035c735e77d5717e3ea29a14f208ebb76bcb1",
+      "bytes": 7674,
+      "sha256": "dd2a775ef89bbe520e434bff9c77961c72c03a2befd4fadbd9faf592ff6ee817",
       "allowMissing": true,
       "priorNormalizedSha256": []
     },
@@ -85,8 +85,8 @@
     },
     {
       "path": "src/pages/sample-base.js",
-      "bytes": 21505,
-      "sha256": "3ee78ee16d738dc3fd49e3d87dbe2106f8daffbe2e3e1d6835a4a3be58b98ad8",
+      "bytes": 21605,
+      "sha256": "b4d04204eee8c5726544b25cd3e0429bfa4e0ed51e1f60714c41f36556c050b4",
       "allowMissing": true,
       "priorNormalizedSha256": []
     },
@@ -134,15 +134,15 @@
     },
     {
       "path": "src/pages/view-showcase.js",
-      "bytes": 39928,
-      "sha256": "3149116a31bed76ff10a0fc5eb6f0813c4a833d5bbc26398b552e1bbfd033b84",
+      "bytes": 40061,
+      "sha256": "28543611852d9bc3a122341618b1de3c7dee348a3a0245467eca2dbe2f95e1da",
       "allowMissing": true,
       "priorNormalizedSha256": []
     },
     {
       "path": "src/pages/showcase-content.js",
-      "bytes": 8775,
-      "sha256": "e200aae7477d889fbd389073432a11b9c5dc443c39ba5dc29a29307df653cadf",
+      "bytes": 8796,
+      "sha256": "10299cd7b4fecfab2a72e61ca643e4574db2b9734c2231e7e227c471916f1ac1",
       "allowMissing": true,
       "priorNormalizedSha256": []
     },

+ 2 - 2
docs/showcase/api.md

@@ -4,10 +4,10 @@
 
 `createSampleService()`读取基础与扩展两个静态JSON,合并只读索引。加载失败显示错误和重试,不回退线上接口。
 
-`filterSample(model, filters)`统一派生五端范围;`sampleDetail(model,type,id)`只返回本模拟空间记录,缺失时显示本机错误。
+`filterSample(model, filters)`统一派生各页面的数据范围;`sampleDetail(model,type,id)`只返回本模拟空间记录,缺失时显示本机错误。
 
 `sampleService.mutate(action,input,revision)`支持status、score、comment。验证署名、ID、分数和revision后写入独立localStorage;失败不显示成功,不进行网络重试。`reset()`仅重置本机演示键。
 
-演示外壳不引用boardService;kf-app在介绍页与五端暂停生产轮询,忽略全局生产写事件。返回原路由恢复原有读取;演示ID即使从旧路由误发也被拦截。
+演示外壳不引用boardService;kf-app在介绍页与各演示页面暂停生产轮询,忽略全局生产写事件。返回原路由恢复原有读取;演示ID即使从旧路由误发也被拦截。
 
 生产公开读取及状态/评分函数保留原合同,本任务不新增云函数,不改Schema、ACL、Skill或业务记录。

+ 7 - 6
docs/showcase/deploy.md

@@ -1,12 +1,12 @@
-# 运维001:五端演示与介绍页部署
+# 运维001:产品创新看板与系统介绍部署
 
-本包为修复中文裁切后的v2版本,替代此前r1包,勿先后叠加发布。本包尚未部署。只更新30个静态文件,不执行业务函数、不改数据库、首页、nginx、Schema、ACL、Skill或wrapper,不需要OBS凭据、服务密钥或重启。
+本包为移除额外导航入口和固定端数表述后的v3版本,包含中文裁切修复,替代此前r1、r2包,勿先后叠加发布。本包尚未部署。只更新30个静态文件,不执行业务函数、不改数据库、首页、nginx、Schema、ACL、Skill或wrapper,不需要OBS凭据、服务密钥或重启。
 
 ## 包与目录
 
-发布版本:`ksf.five-views.20261103.v2`。脚本需要Node.js 20或以上,无npm安装步骤。
+发布版本:`ksf.workshop-board.20261103.v3`。脚本需要Node.js 20或以上,无npm安装步骤。
 
-将zip解压到站点以外的新目录,例如`/opt/ksf-five-views-20261103/`。不要解压到站点内。进入解包根目录,执行`sha256sum -c FILES.sha256`,必须全部通过。
+将zip解压到站点以外的新目录,例如`/opt/ksf-workshop-board-20261103-r3/`。不要解压到站点内。进入解包根目录,执行`sha256sum -c FILES.sha256`,必须全部通过。
 
 此前维护人核实的站点为`/opt/kangshifu-web`,执行前再次从生效的nginx配置确认真实root。若不同,向脚本传入实际绝对路径,不猜目录。
 
@@ -26,10 +26,11 @@ node scripts/publish-five-views.mjs --target-dir /opt/kangshifu-web --apply
 ## 外部HTTPS与浏览器验收
 
 1. 按manifest对30个文件做HTTPS GET并计算原始SHA256;JSON应为JSON、JS应为JS,不能把SPA的200 HTML兜底当作文件成功。首页哈希保持不变。
-2. 打开并刷新`/showcase`与其五个介绍页:`/showcase/leadership`、`/showcase/research`、`/showcase/collaboration`、`/showcase/validation`、`/showcase/classroom`。五张图与五端入口均可用。
-3. 打开`/cockpit`、`/intel`、`/coop`、`/review`、`/class`。A端12项目/36人/24卡/44报告/3受阻;B端12,000声音/720商品/360引文;课堂18单作业。基线334条,扩展16,446条另计。
+2. 打开并刷新`/showcase`与其五个介绍页:`/showcase/leadership`、`/showcase/research`、`/showcase/collaboration`、`/showcase/validation`、`/showcase/classroom`。五张图与系统入口均可用。
+3. 打开`/cockpit`、`/intel`、`/coop`、`/review`、`/class`。驾驶舱12项目/36人/24卡/44报告/3受阻;情报页12,000声音/720商品/360引文;课堂18单作业。基线334条,扩展16,446条另计。
 4. 用模拟卡验证本机状态和评分、同名重评,打开来源、报告、四条价格,刷新保留,重置后只清本机演示修改。用浏览器网络日志确认新路由没有生产业务请求。
 5. 检查报告中心标题与五类名称的上下笔画完整,点击分类后页标题正确,再检查P01/P03/P10动线、小组/成员筛选、分页、手机布局和旧看板入口。旧报告错误链接应自动恢复;原公开卡详情应正常。对生产状态/评分只检查入口与合同,未经另外指示不为验收改真实记录。
+6. 现有看板桌面侧栏和手机导航保持原范围,无新增独立演示集合入口;介绍页和交互页按功能命名。
 
 本机已完成69项Node测试、13项静态检查及115项Edge验收;详见`docs/showcase/validation-result.json`。本机回归中的旧公开操作由浏览器拦截并本地回放,生产请求实际发送数为0。本机验证不等于生产验收:部署回执和生产浏览器验收后才能记录`browserVerified=true`。
 

+ 2 - 2
docs/showcase/design.md

@@ -1,10 +1,10 @@
 # 页面与链接
 
-复用现有Lit、设计令牌、渲染期中英混排与History路由。六个介绍页采用自然滚动的图文长页,配五端入口、图片放大、逐步案例与数据口径表;五个操作页使用一致的筛选、分页与本机详情。
+复用现有Lit、设计令牌、渲染期中英混排与History路由。六个介绍页采用自然滚动的图文长页,配系统入口、图片放大、逐步案例与数据口径表;五个操作页使用一致的筛选、分页与本机详情。
 
 五张3840×2160图片为用户提供的HTML/CDP设计预览,明确标明来源,不冒充skill-image结果。真实交互页面链接独立提供。页面随视口自然滚动,表格允许内部横向滚动;不为凑满固定画布拉高空卡。
 
-原有看板导航新增模拟入口,手机保留五个原主Tab,另加模拟介绍快捷入口。链接均保留部署前缀,支持根路径或子目录部署与刷新。
+原有看板桌面导航和手机主Tab保持原范围,不增加独立演示集合入口。介绍页按独立地址访问,按工作环节链接交互页面。链接均保留部署前缀,支持根路径或子目录部署与刷新。
 
 介绍及演示外壳在生产数据判断之前渲染,生产服务不可用时仍可进入静态演示。署名不作为身份认证,所有新增资料明确标模拟。
 

+ 13 - 11
docs/showcase/prd.md

@@ -1,21 +1,23 @@
-# 五端演示与六个介绍长页
+# 产品创新看板与系统介绍
 
-本分支接收王刚001的2429478,在远端d14d02f基线之上整合五端交互与六个介绍页。原始导入分支独立保留;现有公开机会卡、报告阅读和旧错误链接恢复修复全部保留。
+本分支接收王刚001的2429478,在远端d14d02f基线之上整合看板交互与六个介绍页。原始导入分支独立保留;现有公开机会卡、报告阅读和旧错误链接恢复修复全部保留。
 
 | 入口 | 作用 |
 |---|---|
-| /showcase | 总介绍、五端入口、课程、三条产品案例 |
-| /showcase/leadership | A端创新驾驶舱介绍 |
-| /showcase/research | B端情报与洞察介绍 |
-| /showcase/collaboration | C端产品共创介绍 |
-| /showcase/validation | D端验证与评审介绍 |
-| /showcase/classroom | E端课堂与成果介绍 |
-| /cockpit /intel /coop /review /class | 五端可交互演示 |
+| /showcase | 总介绍、系统入口、课程、三条产品案例 |
+| /showcase/leadership | 创新驾驶舱介绍 |
+| /showcase/research | 情报与洞察介绍 |
+| /showcase/collaboration | 产品共创介绍 |
+| /showcase/validation | 验证与评审介绍 |
+| /showcase/classroom | 课堂与成果介绍 |
+| /cockpit /intel /coop /review /class | 各工作环节的交互页面 |
 
-五端使用334条原始模拟基线,另附16,446条独立扩展,不连接生产业务接口。支持小组、成员、项目、阶段与状态筛选,声音搜索,分页和本机详情。机会卡状态、评分和评论只存本机独立命名空间;同一署名重评替换旧分,跨窗口过期版本被拒绝。
+看板使用334条原始模拟基线,另附16,446条独立扩展,不连接生产业务接口。支持小组、成员、项目、阶段与状态筛选,声音搜索,分页和本机详情。机会卡状态、评分和评论只存本机独立命名空间;同一署名重评替换旧分,跨窗口过期版本被拒绝。
 
 P01展示创意到课堂完成;P03展示目标场景与制备条件冲突;P10展示证据不足时保留其他解释。所有故事步骤可进入对应记录;未产生的后续阶段保持为空。三个半天和夜间研究任务贯通,18单作业指向44份报告。
 
-公开浏览无需登录。演示署名为自填信息,不代表员工身份认证。新入口可从既有看板导航进入。
+公开浏览无需登录。演示署名为自填信息,不代表员工身份认证。介绍页及其交互页可按独立地址访问,现有看板主导航保持原范围。
 
 本任务交付源码与运维部署包;生产部署由运维001执行。模拟资料可用于方案演示,界面持续标明模拟,不冒充真实研究或销量。
+
+需求边界更正:用户要求分析需要哪些主要看板,没有指定固定端数或新增独立演示入口。现有工作台主导航移除额外的集合入口;按资料搜集、机会卡、协作、验证和课堂成果等实际功能组织已完成页面。介绍页不再宣称固定端数。

+ 5 - 3
docs/showcase/validation-result.json

@@ -1,5 +1,5 @@
 {
-  "version": "ksf.five-views.validation.20261103.v2",
+  "version": "ksf.workshop-board.validation.20261103.v3",
   "nodeTests": {
     "total": 69,
     "passed": 69,
@@ -24,11 +24,13 @@
       768,
       390
     ],
-    "chineseGlyphClipping": 0
+    "chineseGlyphClipping": 0,
+    "extraNavigationEntryRemoved": true,
+    "evidence": "work/showcase-browser-1793704627554/acceptance.json"
   },
   "scope": {
     "baseline": 334,
     "extension": 16446,
     "deployed": false
   }
-}
+}

+ 1 - 1
scripts/publish-five-views.mjs

@@ -3,7 +3,7 @@ import path from 'node:path';
 import crypto from 'node:crypto';
 import { fileURLToPath } from 'node:url';
 
-export const RELEASE = 'ksf.five-views.20261103.v2';
+export const RELEASE = 'ksf.workshop-board.20261103.v3';
 export const FILES = Object.freeze([
   'src/layout/kf-app.js', 'src/layout/kf-nav.js', 'src/layout/sample-shell.js',
   'src/utils/router.js', 'src/utils/http.js', 'src/services/workshop-data.js',

+ 1 - 1
scripts/verify-showcase-browser.mjs

@@ -263,7 +263,7 @@ try {
   // Existing live route regression uses a local interception fixture, never production.
   await evaluate(`import(${JSON.stringify(origin + mount + 'src/utils/router.js')}).then(m=>m.navigate('/'))`); await pause(600);
   check('existing live route resumes loading', forbidden.some((r) => r.body.includes('ksfGetPublicBoard')));
-  check('existing desktop and mobile navigation expose base-safe demo entry', await evaluate(`(()=>{const root=document.querySelector('kf-app').shadowRoot.querySelector('kf-nav').shadowRoot;return root.querySelector('a[data-path="/showcase"]').href.includes('/preview-test/showcase')&&root.querySelector('.showcase-entry').href.includes('/preview-test/showcase');})()`));
+  check('existing navigation preserves its original scope without an extra demonstration entry', await evaluate(`(()=>{const root=document.querySelector('kf-app').shadowRoot.querySelector('kf-nav').shadowRoot;return !root.querySelector('a[data-path="/showcase"]')&&!root.querySelector('.showcase-entry')&&root.querySelectorAll('.tabbar a').length===5&&[...root.querySelectorAll('a[data-path]')].every(a=>a.href.includes('/preview-test/'));})()`));
   const legacyNavigate = async (route) => { await evaluate(`import(${JSON.stringify(origin + mount + 'src/utils/router.js')}).then(m=>m.navigate(${JSON.stringify(route)}))`); await pause(450); };
   const measureTitles = async () => evaluate(`(()=>{
     const root=document.querySelector('kf-app').shadowRoot;

+ 1 - 1
src/layout/kf-app.js

@@ -543,7 +543,7 @@ export class KfApp extends SignalWatcher(LitElement) {
     @media (max-width: 767px) {
       .shell { grid-template-columns: minmax(0, 1fr); }
       .nav { position: fixed; left: 0; right: 0; bottom: 0; top: auto; height: auto; z-index: 30; }
-      .view { padding: 8px 16px calc(110px + env(safe-area-inset-bottom)); overflow-x: clip; }
+      .view { padding: 8px 16px calc(76px + env(safe-area-inset-bottom)); overflow-x: clip; }
       /* 手机上所有文字落在玻璃上都要看得清:原 14px 正文升到 15,行高 1.55 */
       .view { font-size: 14px; line-height: 1.55; }
       .one { font-size: 14px; line-height: 1.55; }

+ 0 - 3
src/layout/kf-nav.js

@@ -20,7 +20,6 @@ const ITEMS = [
   { path: '/report', label: '分析报告', en: 'Reports', icon: 'doc', short: '报告' },
   { path: '/ledger', label: '证据台账', en: 'Ledger', icon: 'ledger', mobileHidden: true },
   { path: '/members', label: '成员', en: 'Members', icon: 'users', badge: 'stuck' },
-  { path: '/showcase', label: '五端模拟演示', en: 'Innovation demo', icon: 'bars', mobileHidden: true },
 ];
 
 export const NAV_ITEMS = ITEMS;
@@ -109,7 +108,6 @@ export class KfNav extends LitElement {
         border-radius: 0 0 2px 2px; background: var(--kf-accent); }
       /* 移动 Tab 角标:不绝对定位出头(v10 实测悬在栏外 20px 处,形成一条悬空的小色块) */
       .tabbar .badge { position: static; margin: -4px 0 0 20px; min-width: 16px; height: 16px; padding: 0 4px; font-size: 12px; border-radius: var(--r-sm); }
-      .tabbar a.showcase-entry { grid-column: 1 / -1; height: 28px; font-size: 11px; color: var(--kf-accent-ink); border-top: 1px solid var(--kf-line-soft); margin-top: 4px; }
     }
     @media (min-width: 768px) { .tabbar { display: none; } }
   `;
@@ -151,7 +149,6 @@ export class KfNav extends LitElement {
             <span class="ico">${icon(it.icon, 22, 'ln')}${icon(`${it.icon}Solid`, 22, 'sl')}${badge(it, '')}</span>
             <span class="short">${it.short || it.label}</span>
           </a>`)}
-        <a class="showcase-entry" href=${href('/showcase')} data-link>五端模拟演示与方案介绍 ↗</a>
       </nav>
     `;
   }

+ 2 - 2
src/layout/sample-shell.js

@@ -10,7 +10,7 @@ import '../pages/view-review.js';
 import '../pages/view-class.js';
 import '../pages/sample-detail.js';
 
-const NAV = [['cockpit', '创新驾驶舱', 'A'], ['intel', '情报与洞察', 'B'], ['coop', '产品共创', 'C'], ['review', '验证与评审', 'D'], ['class', '课堂与成果', 'E']];
+const NAV = [['cockpit', '创新驾驶舱'], ['intel', '情报与洞察'], ['coop', '产品共创'], ['review', '验证与评审'], ['class', '课堂与成果']];
 export class SampleShell extends LitElement {
   static properties = { view: { type: String }, filters: { type: Object }, _model: { state: true }, _error: { state: true }, _message: { state: true } };
   constructor() { super(); this.view = 'cockpit'; this.filters = {}; this._model = null; this._error = ''; this._message = ''; }
@@ -44,7 +44,7 @@ export class SampleShell extends LitElement {
     }
   }
   render() {
-    return html`<header><a class="brand" href=${href('/showcase')} data-link><b>康</b><span>产品创新工作坊<small>五个角色视图 · 同一产品链</small></span></a><nav aria-label="五端演示">${NAV.map(([key, label, letter]) => html`<a href=${href('/' + key)} data-link aria-current=${this.view === key ? 'page' : nothing}><span>${letter}</span>${label}</a>`)}</nav><a class="intro-link" href=${href('/showcase')} data-link>方案介绍 ↗</a></header>
+    return html`<header><a class="brand" href=${href('/showcase')} data-link><b>康</b><span>产品创新工作坊<small>资料、协作与产品进程</small></span></a><nav aria-label="工作坊功能导航">${NAV.map(([key, label]) => html`<a href=${href('/' + key)} data-link aria-current=${this.view === key ? 'page' : nothing}>${label}</a>`)}</nav><a class="intro-link" href=${href('/showcase')} data-link>方案介绍 ↗</a></header>
       <div class="sample-strip"><strong>模拟演示</strong><span>${t('334条基线 · 12个项目 · 公开浏览,无需登录')}</span><button @click=${() => this.reset()} ?disabled=${!this._model}>重置本机演示</button></div>
       ${this._message ? html`<div class="message" role="status">${this._message}<button aria-label="关闭提示" @click=${() => { this._message = ''; }}>×</button></div>` : nothing}
       <main @sample-open=${(e) => this.open(e)}>${this._error ? html`<div class="empty" role="alert"><h1>模拟资料读取失败</h1><p>${this._error}</p><button @click=${() => this.load()}>重试</button></div>` : this._model ? this.renderView() : html`<div class="empty" role="status">正在读取模拟资料…</div>`}</main>

Разница между файлами не показана из-за своего большого размера
+ 1 - 1
src/pages/sample-base.js


+ 5 - 5
src/pages/showcase-content.js

@@ -1,7 +1,7 @@
 /** Editorial copy for the six introduction pages; business numbers come from the seed. */
 export const SYSTEMS = [
   {
-    key: 'leadership', letter: 'A', name: '创新驾驶舱', question: '现在怎么样,哪里需要支持?',
+    key: 'leadership', name: '创新驾驶舱', question: '现在怎么样,哪里需要支持?',
     title: '把全员创意,变成看得清的产品组合。',
     intro: '先看团队正在探索哪些人群和场景、哪些方向进入评估、哪些缺口需要协调,再展开具体产品的判断依据。',
     audience: '业务负责人 · 产品负责人 · 教练', demoPath: '/cockpit',
@@ -15,7 +15,7 @@ export const SYSTEMS = [
     defaultCase: 'P03', related: ['collaboration', 'validation'],
   },
   {
-    key: 'research', letter: 'B', name: '情报与用户洞察', question: '我们掌握了什么依据?',
+    key: 'research', name: '情报与用户洞察', question: '我们掌握了什么依据?',
     title: '让每张机会卡,都能回到原始依据。',
     intro: '将用户声音、商品观察、工艺资料和团队经验放在一处。先核原话与使用条件,再看相反解释,资料可以被不同产品复用。',
     audience: '研究人员 · 产品经理 · 资料采集者', demoPath: '/intel',
@@ -29,7 +29,7 @@ export const SYSTEMS = [
     defaultCase: 'P10', related: ['collaboration', 'validation'],
   },
   {
-    key: 'collaboration', letter: 'C', name: '产品共创工作台', question: '机会怎样变成项目,谁来接力?',
+    key: 'collaboration', name: '产品共创工作台', question: '机会怎样变成项目,谁来接力?',
     title: '多人同时做产品,每一步都有接力。',
     intro: '围绕同一产品持续协作:成员知道自己负责什么,证据与方案版本跟着产品走,旧版本保留,下一位接手者能看见变化和原因。',
     audience: '小组成员 · 项目负责人 · 研发协作者', demoPath: '/coop',
@@ -43,7 +43,7 @@ export const SYSTEMS = [
     defaultCase: 'P01', related: ['research', 'validation', 'classroom'],
   },
   {
-    key: 'validation', letter: 'D', name: '验证与评审', question: '结论靠不靠得住,先补什么?',
+    key: 'validation', name: '验证与评审', question: '结论靠不靠得住,先补什么?',
     title: '把有吸引力的想法,变成有条件的判断。',
     intro: '同时看支持材料、相反材料和未知条件。制备、风味、规格、成本与工艺缺项摆在明处,下一步补什么、谁负责,都能说清。',
     audience: '评审专家 · 研发人员 · 产品负责人', demoPath: '/review',
@@ -57,7 +57,7 @@ export const SYSTEMS = [
     defaultCase: 'P03', related: ['research', 'collaboration', 'leadership'],
   },
   {
-    key: 'classroom', letter: 'E', name: '课堂运营与成果', question: '三个半天怎样跑,交了什么?',
+    key: 'classroom', name: '课堂运营与成果', question: '三个半天怎样跑,交了什么?',
     title: '三个半天,每组都有清楚的产出。',
     intro: '课表、作业、提交、点评与路演放在一条时间线上。教练按卡点安排支持,团队能回看每个产品从原始资料走到阶段成果的过程。',
     audience: '课程运营 · 教练 · 学员 · 管理者', demoPath: '/class',

+ 9 - 9
src/pages/view-showcase.js

@@ -9,7 +9,7 @@ import { SYSTEMS, COURSE, CASES } from './showcase-content.js';
 const shortId = (id) => String(id || '').replace(/^DEMO-/, '');
 const systemPath = (key) => key === 'overview' ? '/showcase' : `/showcase/${key}`;
 
-/** One read-only introduction shell; the five operational views remain separate. */
+/** Read-only system introductions with shared sample data. */
 export class ViewShowcase extends LitElement {
   static properties = {
     system: { type: String }, routeQuery: { type: Object },
@@ -76,10 +76,10 @@ export class ViewShowcase extends LitElement {
     const m = this._model;
     return html`<section class="hero">
       <div>
-        <p class="eyebrow">${system ? `${system.letter} / ${system.name}` : '康师傅 · 产品创新工作坊'}</p>
+        <p class="eyebrow">${system ? system.name : '康师傅 · 产品创新工作坊'}</p>
         <h1>${system ? system.title : html`让每个产品方向,<br><em>都有证据、协作与下一步。</em>`}</h1>
-        <p class="intro">${system?.intro || '三个半天,多人并行探索多个产品。从创意、资料搜集到机会卡,再将评估和取舍带进产品概念。五个工作视图,共用一条产品线。'}</p>
-        <div class="hero-actions">${system ? this._demo(system) : html`<button class="button primary" @click=${() => this._jump('systems')}>了解五个工作视图 ↓</button>`}
+        <p class="intro">${system?.intro || '三个半天,多人并行探索多个产品。从创意、资料搜集到机会卡,再将评估和取舍带进产品概念。资料与协作围绕同一产品持续衔接。'}</p>
+        <div class="hero-actions">${system ? this._demo(system) : html`<button class="button primary" @click=${() => this._jump('systems')}>了解系统功能 ↓</button>`}
           <button class="button secondary" @click=${() => this._jump('stories')}>跟随产品案例 ↓</button>
         </div>
         <p class="caption">${system?.audience || '负责人看全局 · 小组做产品 · 专家看依据 · 教练追过程'}</p>
@@ -102,7 +102,7 @@ export class ViewShowcase extends LitElement {
         <img src=${this._image(system)} width="3840" height="2160" loading="lazy" alt=${system.preview}>
         <span class="zoom-label">放大预览 ↗</span>
       </button>
-      <figcaption><span>${system.letter} · ${system.name}</span><span>HTML / CDP 设计预览 · 数据为模拟 · 点击放大</span></figcaption>
+      <figcaption><span>${system.name}</span><span>HTML / CDP 设计预览 · 数据为模拟 · 点击放大</span></figcaption>
     </figure>`;
   }
 
@@ -116,8 +116,8 @@ export class ViewShowcase extends LitElement {
       <section class="section" id="flow">${this._heading('一条产品链', '从一个想法,到一份能追问的方案', '资料、机会、项目和成果相互关联,判断可以回到依据,未解决的问题继续跟着产品走。')}
         <ol class="flow">${['创意', '搜集', '洞察', '机会卡', '验证', '概念', '路演'].map((label, i) => html`<li><span>${String(i + 1).padStart(2, '0')}</span><strong>${label}</strong></li>`)}</ol>
       </section>
-      <section class="section" id="systems">${this._heading('五个工作视图', '各自回答一个问题,接力同一个产品')}
-        <div class="system-grid">${SYSTEMS.map((s) => html`<article class="system-card"><span class="letter">${s.letter}</span>
+      <section class="section" id="systems">${this._heading('系统能力', '按工作环节组织资料与协作')}
+        <div class="system-grid">${SYSTEMS.map((s) => html`<article class="system-card">
           <p class="card-audience">${s.audience}</p><h3>${s.name}</h3><p class="question">${s.question}</p>
           <p>${s.intro}</p>${this._link(systemPath(s.key), '了解这个系统 →', 'text-link')}</article>`)}</div>
       </section>`;
@@ -232,7 +232,7 @@ export class ViewShowcase extends LitElement {
   _handoff(system) {
     return html`<section class="section handoff">${this._heading('上下游怎样衔接', '一个方向,在不同角色之间接力')}
       <ol>${system.handoff.map((line, i) => html`<li><span>${i + 1}</span>${line}</li>`)}</ol>
-      <div class="related">${system.related.map((key) => { const s = SYSTEMS.find((x) => x.key === key); return this._link(systemPath(key), `${s.letter} ${s.name} →`, 'text-link'); })}</div>
+      <div class="related">${system.related.map((key) => { const s = SYSTEMS.find((x) => x.key === key); return this._link(systemPath(key), `${s.name} →`, 'text-link'); })}</div>
     </section>`;
   }
 
@@ -247,7 +247,7 @@ export class ViewShowcase extends LitElement {
         ${!system || system.key === 'classroom' ? this._course() : nothing}
         ${system ? this._handoff(system) : nothing}
         ${this._scope()}
-        <section class="final-cta"><div><p class="eyebrow">让下一步更清楚</p><h2>${system ? '带着同一套依据,继续做产品。' : '先看全局,再顺着一个产品走一遍。'}</h2><p>五个角色视图,同一份资料和产品关系。</p></div>${system ? this._demo(system) : this._link('/showcase/collaboration', '了解产品共创工作台 →', 'button primary')}</section>
+        <section class="final-cta"><div><p class="eyebrow">让下一步更清楚</p><h2>${system ? '带着同一套依据,继续做产品。' : '先看全局,再顺着一个产品走一遍。'}</h2><p>各环节共享同一份资料和产品关系。</p></div>${system ? this._demo(system) : this._link('/showcase/collaboration', '了解产品共创工作台 →', 'button primary')}</section>
       `}
     </main><footer><span>康师傅 · 产品创新工作坊</span><div>${this._link('/showcase', '方案总览')}${this._link('/showcase/classroom', '课堂与成果')}${this._link('/', '现有看板')}</div><p>设计预览与模拟数据用于流程展示;课堂完成不代表需求已验证。</p></footer>
     <dialog aria-label="系统设计预览大图" @close=${() => { this._preview = null; }}><div class="dialog-toolbar"><strong>${this._preview?.name || ''}</strong><button class="button secondary small" @click=${() => this.shadowRoot.querySelector('dialog').close()}>关闭 ×</button></div>${this._preview ? html`<img src=${this._image(this._preview)} alt=${this._preview.preview}>` : nothing}<p>设计预览 · 模拟数据 · 可按 Esc 关闭</p></dialog>`;

Некоторые файлы не были показаны из-за большого количества измененных файлов