diff --git a/README.md b/README.md index 5505a7e..084da0a 100644 --- a/README.md +++ b/README.md @@ -56,9 +56,9 @@ WebMCP 可以理解为“网页给 AI Agent 暴露的一组工具清单和接口 可以用 `navigator.modelContext.registerTool` 作为概念示例。需要强调:WebMCP 仍是实验性方向,适合作为渐进增强,不应该假设所有浏览器都已经稳定支持。 -### 5. Chrome 新特性快闪 +### 5. Chrome 新特性演示 -这一部分只做快速展示,不展开细节: +这一部分分成正式演示和快闪: - **Modern Web Guidance**:给 AI 编程工具补现代 Web 知识,避免写过时代码。 - **Chrome DevTools for agents**:让 AI 能读取 console、network、performance、Lighthouse 等真实浏览器信息。 @@ -69,6 +69,14 @@ WebMCP 可以理解为“网页给 AI Agent 暴露的一组工具清单和接口 这一章服务于主线:Chrome 正在让 Web 同时面向人、开发者工具和 AI Agent 进化。 +正式 demo 入口: + +- `public/demo/webmcp/`:WebMCP 工具契约演示。 +- `scripts/demo-modern-web-guidance.sh` 和 `public/demo/modern-web-guidance/`:Modern Web Guidance 官方 CLI 演示。 +- `public/demo/devtools-agents/`:DevTools for agents 检查目标页。 +- `public/html-in-canvas/`:HTML-in-Canvas origin trial / fallback 演示。 +- `public/demo/view-transitions/`:View Transitions 渐进增强演示。 + ### 6. 收束:开发者现在该记住什么 - Web 页面未来不只是给人看的,也会被 Agent 使用。 @@ -88,6 +96,9 @@ WebMCP 可以理解为“网页给 AI Agent 暴露的一组工具清单和接口 - [slides.md](./slides.md):默认首页、章节入口和全局顺序 - [pages/outline.md](./pages/outline.md):分享大纲 - [pages/demo/](./pages/demo/):模拟展示详情页 +- [public/demo/](./public/demo/):可运行浏览器 demo 入口 +- [public/html-in-canvas/](./public/html-in-canvas/):HTML-in-Canvas 专门演示 +- [scripts/demo-modern-web-guidance.sh](./scripts/demo-modern-web-guidance.sh):Modern Web Guidance 官方 CLI 演示脚本 - [style.css](./style.css):主题微调 ## 常用脚本 diff --git a/pages/demo/adoption-plan.md b/pages/demo/adoption-plan.md index c369d29..995a030 100644 --- a/pages/demo/adoption-plan.md +++ b/pages/demo/adoption-plan.md @@ -9,6 +9,7 @@ - 官方资料导航 - 三路径对比仪表盘 - WebMCP 可运行工程入口 +- MWG / DevTools / Canvas / View Transitions 演示入口 - 现场讲述卡片 - 网络不可用时继续演示 @@ -30,7 +31,7 @@
-打开总入口:/demo/index.html;WebMCP 工程入口:/demo/webmcp/index.html。 +打开总入口:/demo/index.html。正式 demo 入口都从这里进入。
diff --git a/pages/demo/html-in-canvas.md b/pages/demo/html-in-canvas.md new file mode 100644 index 0000000..7fe6991 --- /dev/null +++ b/pages/demo/html-in-canvas.md @@ -0,0 +1,40 @@ +# Demo 7:HTML-in-Canvas + +
+ +
+
官方能力
+
+ +- 把真实 DOM 绘制进 Canvas / WebGL / WebGPU 场景 +- 保留可访问性、搜索、翻译等浏览器能力 +- 当前是 origin trial,环境要求高 +- 现场必须准备 fallback + +
+
+ +
+
本地演示
+
+ +- 打开 /html-in-canvas/index.html +- 支持时调用 drawElementImage() +- 不支持时显示同一 DOM 表单的 fallback 预览 +- 讲清楚“DOM 进入画布”而不是“canvas 手绘控件” + +
+
+ +
+ +
+运行条件:Chrome Canary 149+,开启 chrome://flags/#canvas-draw-element;Chrome 官方说明该 API 在 Chrome 148-150 处于 origin trial。 +
+ + diff --git a/pages/demo/modern-web-guidance.md b/pages/demo/modern-web-guidance.md new file mode 100644 index 0000000..a0a1bdf --- /dev/null +++ b/pages/demo/modern-web-guidance.md @@ -0,0 +1,41 @@ +# Demo 5:Modern Web Guidance 官方 CLI + +
+ +
+
现场命令
+ +```bash +npx modern-web-guidance@latest search "animate a dialog modal backdrop" +npx modern-web-guidance@latest retrieve "animate-to-from-top-layer" +``` + +
+ +
+
讲什么
+
+ +- AI coding agent 容易写旧方案 +- MWG 把现代 Web 指南变成可检索技能 +- search 找 use case,retrieve 拿具体指南 +- 指南会带 Baseline / fallback 意识 + +
+
+ +
+ +
+本地辅助页:/demo/modern-web-guidance/index.html;脚本:scripts/demo-modern-web-guidance.sh。 +
+ + diff --git a/pages/demo/platform-api.md b/pages/demo/platform-api.md index 540f2ff..3bef8fb 100644 --- a/pages/demo/platform-api.md +++ b/pages/demo/platform-api.md @@ -1,4 +1,4 @@ -# Demo 6:Chrome 官方新特性快闪 +# Demo 9:最后快闪与串联
@@ -7,9 +7,10 @@
- WebMCP:页面暴露工具给 Agent -- Modern Web Guidance:给 AI 编程工具补现代 Web 知识 -- DevTools for agents:让 Agent 看见浏览器运行时 -- Prompt API:浏览器内置 AI 能力 +- WebMCP:页面暴露工具给 Agent +- Modern Web Guidance:已演示官方 CLI +- DevTools for agents:已演示官方 MCP 流程 +- Prompt API:官方 demo 快闪
@@ -18,9 +19,9 @@
体验与平台能力
-- HTML-in-Canvas:真实 DOM 进入 Canvas / 3D +- HTML-in-Canvas:已演示本地页和官方限制 - Baseline:判断哪些 Web 能力可以放心评估 -- View Transitions:页面状态切换更自然 +- View Transitions:已演示本地页 - Navigation API:更现代的导航状态管理
@@ -29,7 +30,7 @@
-只讲位置,不展开 API:这些官方 demo 共同说明 Chrome 正在同时升级 Agent 交互、开发工作流和用户体验。 +最后用这页收束:Chrome 新能力不是孤立 API,而是同时升级 Agent 交互、开发工作流和用户体验。
diff --git a/pages/demo/release-scan.md b/pages/demo/release-scan.md index 2b80151..938ec90 100644 --- a/pages/demo/release-scan.md +++ b/pages/demo/release-scan.md @@ -28,13 +28,14 @@
-
官方快闪
+
新增正式 demo
- Modern Web Guidance - DevTools for agents -- Prompt API / HTML-in-Canvas -- Baseline / View Transitions +- HTML-in-Canvas +- View Transitions +- Prompt API / Baseline 保留快闪
@@ -42,7 +43,7 @@
-原则:WebMCP 至少要有可运行工程;其他 Chrome 新能力用官方 demo 快闪,避免把现场变成环境配置课。 +原则:WebMCP 是主 demo;Modern Web Guidance 和 DevTools for agents 按官方流程演示;HTML-in-Canvas 和 View Transitions 用本地页面辅助展示。
diff --git a/plan/part2.md b/plan/part2.md index 45b566e..727dc2b 100644 --- a/plan/part2.md +++ b/plan/part2.md @@ -5,7 +5,7 @@ 第二部分用于支撑 40 分钟分享中的 demo 段,目标不是泛泛介绍概念,而是生成两类明确产物: 1. **PPT 主线**:分别介绍 Chrome 最近新增或正在推进的关键能力,说明每个功能解决什么问题、处在什么成熟度、适合用什么官方材料展示。 -2. **项目代码演示**:创建并维护一个配套前端项目。WebMCP 必须有可运行原始工程,其他实验能力可以用稳定页面串起官方 demo、功能对比和现场备用流程。 +2. **项目代码演示**:创建并维护一个配套前端项目。WebMCP 必须有可运行原始工程;Modern Web Guidance、DevTools for agents、HTML-in-Canvas、View Transitions 都要有明确演示入口。 它不是落地教程,也不是厂商 SDK 接入课;目标是让对 Chrome 新特性不熟悉的 Web 开发者看懂: @@ -19,7 +19,7 @@ 1. **官方优先**:优先使用 Chrome 官方文档、官方 demo、官方 demo collection、官方命令示例和官方截图/录屏作为展示材料。 2. **WebMCP 必须可运行**:WebMCP 是第二部分的主 demo,当前仓库必须提供可打开、可点击、可看源码的本地工程。 3. **官方优先但不空讲**:官方材料用于确认标准口径和 API 边界;本地工程用于承接现场演示。 -4. **不复刻重实验能力**:不在本地项目里复刻 Prompt API、HTML-in-Canvas、Chrome DevTools for agents 等环境依赖重、版本敏感或 origin trial 依赖强的能力。 +4. **不复刻重实验能力**:不在本地项目里复刻 Prompt API 等环境依赖重、版本敏感的能力;HTML-in-Canvas 只做官方 API 方向和 fallback 展示。 5. **保持科普口径**:所有 token、耗时、成功率数据都作为具体案例,不当作普适结论;WebMCP 讲“提高可靠性”,不讲“零错误率”。 6. **实验能力明确标注**:WebMCP、Prompt API、HTML-in-Canvas 等能力需要标注实验性、origin trial、渐进增强或浏览器支持边界。 7. **剔除厂商私货**:OpenTiny、VAgent、WebAgent、Remote、WebSkills、遥控器组件、SDK 版本等只可作为原始材料背景,不作为 PPT 主概念。若需要表达类似思想,使用中立说法:“Agent 操作说明”“流程说明”“工具调用编排”。 @@ -31,8 +31,8 @@ 先给观众一张总览:第二部分会按“新增功能介绍”和“项目代码演示”展开。 - 新增功能介绍:WebMCP、Modern Web Guidance、DevTools for agents、Prompt API、HTML-in-Canvas、Baseline / View Transitions。 -- 项目代码演示:WebMCP 提供可运行工程,其他能力用本地前端页面承接官方链接、功能卡片、三路径对比和现场备用讲述。 -- 兜底原则:WebMCP 要能跑;其他 Chrome API 不做完整复刻,只承担稳定讲述和演示组织。 +- 项目代码演示:WebMCP、DevTools for agents、HTML-in-Canvas、View Transitions 提供本地页面;Modern Web Guidance 提供官方 CLI 脚本和辅助页。 +- 兜底原则:正式 demo 都要有入口;高风险实验能力不承诺原生 API 一定可用。 ### 2. 主线演示:AI 操作 Web 的三种方式 @@ -48,27 +48,29 @@ 1. PPT 用表格和流程图描述三种路径。 2. WebMCP 打开 `/demo/webmcp/index.html` 现场跑工程。 -3. 其他能力用官方 demo 和 `/demo/index.html` 做导航、对比和兜底。 +3. 其他正式 demo 从 `/demo/index.html` 进入,或运行 `scripts/demo-modern-web-guidance.sh`。 ### 3. 新增功能介绍:官方 demo 快闪 每个能力只讲“它是什么、解决什么问题、现场怎么展示”,不展开完整 API 课。 - **WebMCP**:使用官方文档确认 API 口径,同时打开 `/demo/webmcp/index.html` 演示声明式表单工具、指令式 `registerTool`、工具 schema 和调用日志。 -- **Modern Web Guidance**:使用官方 `npx modern-web-guidance@latest install` 和 `search/retrieve` 命令说明“给 AI 编程工具补现代 Web 知识”。 -- **Chrome DevTools for agents**:使用官方 DevTools MCP 资料说明“让 AI 看到浏览器运行时、性能 trace、network、console”。 +- **Modern Web Guidance**:运行官方 `search/retrieve` CLI,并打开 `/demo/modern-web-guidance/index.html` 辅助讲解。 +- **Chrome DevTools for agents**:使用官方 DevTools MCP 配置,让 Agent 检查 `/demo/devtools-agents/index.html` 的 console、network 和性能问题。 - **Prompt API**:使用官方 Prompt API demos / playground 说明“浏览器内置 AI 能力,适合渐进增强”。 -- **HTML-in-Canvas**:使用官方 origin trial 文章和 demo collection 说明“DOM 进入 Canvas / 3D 场景,同时保留可访问性、搜索、翻译等能力”。 -- **Baseline / View Transitions**:使用 webstatus.dev、Baseline、view-transitions.chrome.dev 说明“哪些能力可以评估,哪些能力提升交互体验”。 +- **HTML-in-Canvas**:打开 `/html-in-canvas/index.html`,支持时跑原生 API,不支持时展示 fallback 和官方运行条件。 +- **Baseline / View Transitions**:Baseline 做方法快闪;View Transitions 打开 `/demo/view-transitions/index.html`。 ## 项目代码演示职责 -当前仓库的本地 demo 承担四件事: +当前仓库的本地 demo 承担六件事: 1. 提供一个稳定的“官方 demo 导航页”,列出每个新增功能对应的官方资料、推荐讲法和现场备用链接。 2. 提供一个可运行的 WebMCP 原始工程:`public/demo/webmcp/index.html`、`styles.css`、`app.js`。 -3. 提供一个“AI 操作 Web 三路径对比”页面,用表格、流程和状态卡讲清楚视觉识别、DOM/可访问性识别、WebMCP/工具调用的差异。 -4. 提供一个“本地兜底说明”:如果官方 demo 现场不可用,如何用项目页面和 PPT 中的截图/流程继续讲,不中断分享。 +3. 提供 Modern Web Guidance 官方 CLI 脚本和辅助页面。 +4. 提供 DevTools for agents 可检查目标页,故意包含 console、network、performance 问题。 +5. 提供 HTML-in-Canvas 和 View Transitions 演示页面。 +6. 提供一个“本地兜底说明”:如果官方 demo 现场不可用,如何用项目页面和 PPT 中的截图/流程继续讲,不中断分享。 不做以下事情: @@ -83,10 +85,11 @@ 第二部分建议 15 分钟左右: - 2 分钟:demo 交付物总览。 -- 7 分钟:WebMCP 可运行工程演示。 -- 4 分钟:三种 AI 操作 Web 路径对比。 -- 4 分钟:Chrome 官方 demo 快闪。 -- 2 分钟:本地兜底和关键风险提示。 +- 6 分钟:WebMCP 可运行工程演示。 +- 4 分钟:Modern Web Guidance 官方 CLI。 +- 4 分钟:DevTools for agents 检查本地问题页。 +- 4 分钟:HTML-in-Canvas / View Transitions。 +- 2 分钟:Prompt API / Baseline 快闪和关键风险提示。 ## 参考资料 diff --git a/public/demo/baseline/baseline.js b/public/demo/baseline/baseline.js new file mode 100644 index 0000000..1e7c283 --- /dev/null +++ b/public/demo/baseline/baseline.js @@ -0,0 +1,54 @@ +import { escapeHtml, logTo, setText } from "../shared.js"; + +const features = [ + { + name: "View Transitions API", + available: () => "startViewTransition" in document, + advice: "可作为体验增强;不支持时直接更新 DOM。", + }, + { + name: "Popover API", + available: () => "popover" in HTMLElement.prototype, + advice: "适合替代一部分自研浮层;保留 dialog/menu fallback。", + }, + { + name: "CSS Container Queries", + available: () => CSS.supports("container-type", "inline-size"), + advice: "适合组件级响应式;老环境使用传统 media query。", + }, + { + name: "Prompt API", + available: () => "LanguageModel" in window, + advice: "实验/受环境限制;必须做能力检测和 mock 或云端 fallback。", + }, + { + name: "WebMCP", + available: () => "modelContext" in navigator, + advice: "实验方向;适合演示 Agentic Web 的工具契约。", + }, +]; + +const table = document.querySelector("#feature-table"); +const counts = { ready: 0, enhanced: 0, blocked: 0 }; + +table.innerHTML = features + .map((feature) => { + const ok = feature.available(); + const type = ok ? "ready" : feature.name.includes("API") ? "enhanced" : "blocked"; + counts[type] += 1; + logTo("#log", "baseline.feature", { + feature: feature.name, + available: ok, + advice: feature.advice, + }); + return ` + ${escapeHtml(feature.name)} + ${ok ? "available" : "not detected"} + ${escapeHtml(feature.advice)} + `; + }) + .join(""); + +setText("#ready-count", String(counts.ready)); +setText("#enhanced-count", String(counts.enhanced)); +setText("#blocked-count", String(counts.blocked)); diff --git a/public/demo/baseline/index.html b/public/demo/baseline/index.html new file mode 100644 index 0000000..04f65d5 --- /dev/null +++ b/public/demo/baseline/index.html @@ -0,0 +1,70 @@ + + + + + + Baseline Demo + + + +
+ + +
+
+
+

Can I ship it?

+

从“Chrome 支持了”升级到“目标用户能稳定用”。

+

+ Baseline 的价值不是替代 feature detection,而是把团队讨论从版本号转成特性成熟度和采用策略。 +

+
+ +
+ +
+
+

Feature matrix

+

本地特性检测

+ + + + + + + + + +
能力运行检测采用建议
+
+ + +
+
+
+ + + + diff --git a/public/demo/devtools-agents/app.js b/public/demo/devtools-agents/app.js new file mode 100644 index 0000000..04a5b9b --- /dev/null +++ b/public/demo/devtools-agents/app.js @@ -0,0 +1,14 @@ +console.warn("Demo warning: this page intentionally includes diagnosable issues."); + +const longTaskButton = document.querySelector("#long-task"); +const taskResult = document.querySelector("#task-result"); + +longTaskButton.addEventListener("click", () => { + const start = performance.now(); + while (performance.now() - start < 650) { + Math.sqrt(Math.random() * Number.MAX_SAFE_INTEGER); + } + const duration = Math.round(performance.now() - start); + taskResult.textContent = `已触发约 ${duration}ms 的主线程长任务。`; + console.error("Demo error: long task was triggered for DevTools MCP inspection."); +}); diff --git a/public/demo/devtools-agents/index.html b/public/demo/devtools-agents/index.html new file mode 100644 index 0000000..937afa6 --- /dev/null +++ b/public/demo/devtools-agents/index.html @@ -0,0 +1,68 @@ + + + + + + DevTools for Agents Demo Target + + + +
+
+

Chrome DevTools for agents

+

给 Agent 一个真实浏览器现场

+

+ 这个页面故意包含 console warning、404 图片、可触发长任务和布局溢出。 + 演示时让 Chrome DevTools MCP 连接到这个页面,要求 Agent 读取 console、network、DOM 和 performance。 +

+
+ +
+
+

Official setup

+

MCP 配置

+
{
+  "mcpServers": {
+    "chrome-devtools": {
+      "command": "npx",
+      "args": ["chrome-devtools-mcp@latest"]
+    }
+  }
+}
+
+ +
+

Prompt to run

+

现场提问

+
Open http://localhost:3030/demo/devtools-agents/index.html.
+Inspect console and network errors.
+Then click "触发长任务" and record a performance trace.
+Summarize what you found.
+
+
+ +
+
+ Network +

这张图片故意 404

+ Missing dashboard screenshot +
+ +
+ Layout +

这段文本故意撑破容器

+

SuperLongUnbrokenEnterpriseDashboardIdentifier_ChromeAgentRuntimeDebugging_2026_Overflow

+
+ +
+ Performance +

触发可检测的长任务

+ +

等待触发。

+
+
+
+ + + + diff --git a/public/demo/devtools-agents/styles.css b/public/demo/devtools-agents/styles.css new file mode 100644 index 0000000..c7ede89 --- /dev/null +++ b/public/demo/devtools-agents/styles.css @@ -0,0 +1,139 @@ +:root { + color-scheme: light; + --ink: #111827; + --muted: #667085; + --paper: #f7f8fa; + --panel: #ffffff; + --line: #d7dce2; + --red: #b42318; + --blue: #1849a9; + --code: #111827; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + color: var(--ink); + background: + linear-gradient(90deg, rgb(17 24 39 / 0.05) 1px, transparent 1px), + linear-gradient(rgb(17 24 39 / 0.05) 1px, transparent 1px), + var(--paper); + background-size: 28px 28px; + font-family: ui-sans-serif, "PingFang SC", "Microsoft YaHei", sans-serif; +} + +.shell { + width: min(1180px, calc(100vw - 32px)); + margin: 0 auto; + padding: 42px 0 60px; +} + +.eyebrow { + margin: 0 0 10px; + color: var(--red); + font-size: 12px; + font-weight: 900; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +h1, +h2, +p { + margin-top: 0; +} + +h1 { + max-width: 760px; + margin-bottom: 18px; + font-family: Georgia, "Times New Roman", serif; + font-size: clamp(42px, 7vw, 86px); + line-height: 0.9; +} + +.hero p, +.broken-card p { + max-width: 760px; + color: var(--muted); + line-height: 1.75; +} + +.prompt-board, +.target { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 18px; + margin-top: 24px; +} + +.target { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +article, +.broken-card { + border: 1px solid var(--line); + background: rgb(255 255 255 / 0.92); + box-shadow: 0 18px 58px rgb(17 24 39 / 0.12); + padding: 20px; +} + +pre { + overflow: auto; + margin: 0; + border: 1px solid #2f3947; + background: var(--code); + color: #e7edf4; + padding: 16px; + white-space: pre-wrap; + line-height: 1.55; +} + +.broken-card span { + display: inline-flex; + margin-bottom: 12px; + border: 1px solid rgb(180 35 24 / 0.35); + padding: 4px 9px; + color: var(--red); + font-size: 12px; + font-weight: 900; +} + +.broken-card img { + display: block; + width: 100%; + min-height: 150px; + border: 1px dashed var(--red); + background: #fff5f4; +} + +.overflow { + overflow: hidden; +} + +.overflow p { + width: max-content; + color: var(--blue); + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; +} + +button { + min-height: 42px; + border: 1px solid var(--ink); + background: var(--ink); + color: white; + cursor: pointer; + padding: 10px 14px; + font: inherit; + font-weight: 900; +} + +@media (max-width: 900px) { + .prompt-board, + .target { + grid-template-columns: 1fr; + } +} diff --git a/public/demo/index.html b/public/demo/index.html index 6446d63..31cce52 100644 --- a/public/demo/index.html +++ b/public/demo/index.html @@ -3,581 +3,146 @@ - Part 2 Demo - Agentic Web - + Chrome API Demo Desk +
-

Core Message

-

从“AI 猜 UI”到“网页暴露工具”。

+

Run with npm run dev

+

一个入口,分别展示不同 Chrome API 能力。

- 视觉识别和 DOM 识别让 Agent 尝试理解页面表象;WebMCP 的方向是让页面主动提供结构化能力, - 让 Agent 调用工具、传入参数、拿到结果。 + 总览页负责现场导航;每个能力页都可以独立打开、独立演示,不依赖 PPT 路由。 + 适合分享时从幻灯片跳出,展示真实浏览器中的 API 边界和 fallback。

-
-
-

同一个后台任务,三种 Agent 操作路径

-
- - - + + + Guidance +

Modern Web Guidance

+

展示官方 CLI 如何给 AI coding agent 检索现代 Web 用法和 Baseline 边界。

+
    +
  • 重点:把现代 Web 知识注入 agent
  • +
  • 兜底:可复制命令和预期输出
  • +
+
+ + Origin Trial +

HTML-in-Canvas

+

展示真实 DOM 表单绘制进 canvas 的 API 方向;不支持时显示 fallback 预览。

+
    +
  • 能力检测:drawElementImage / requestPaint
  • +
  • 重点:不是 canvas 手绘控件
  • +
+
+ + Compatibility +

Baseline

+

把“能不能放心用”从浏览器版本猜测,改成特性状态和 fallback 策略。

+
    +
  • 重点:跨浏览器采用判断
  • +
  • 兜底:本地特性矩阵
  • +
+
+ + UX API +

View Transitions

+

展示 same-document 页面切换动画,并在不支持时退化为普通 DOM 更新。

+
    +
  • 能力检测:document.startViewTransition
  • +
  • 重点:体验增强而非业务依赖
  • +
+
-

对比讲述表

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
维度视觉识别DOM / A11yWebMCP
交互方式观察截图后猜测点击位置读取结构后定位元素调用页面声明的语义工具
稳定性受视觉布局影响明显受 DOM 结构和组件状态影响受工具设计、权限和业务逻辑影响
现场讲法“像让 AI 蒙着眼睛找按钮”“给了地图,但地图可能不完整”“给了工具入口和参数说明”
-
+

本地运行

+
npm run dev
 
-        
-

WebMCP 原始工程 demo

-
-
-

运行入口

-

- 打开 /demo/webmcp/index.html,展示声明式表单工具、 - 指令式 JS 工具、工具 schema 面板和复合任务调用日志。 -

-
-
-

讲述重点

-

- 先让人提交表单,再让工具面板执行同一类任务。观众会看到 Agent 不需要猜 UI, - 它看到的是工具名、描述、参数 schema 和执行结果。 -

-
-
-
+# Slidev dev server 启动后访问: +http://localhost:3030/demo/index.html +http://localhost:3030/demo/webmcp/index.html +http://localhost:3030/demo/prompt-api/index.html +http://localhost:3030/demo/devtools-agents/index.html +http://localhost:3030/demo/modern-web-guidance/index.html +http://localhost:3030/html-in-canvas/index.html +http://localhost:3030/demo/baseline/index.html +http://localhost:3030/demo/view-transitions/index.html -
-

官方 demo 与资料入口

- -
- -
-

现场兜底讲法

-
-
-

如果官方 demo 打不开

-

- 继续使用本页的卡片和对比表讲主线:AI 不擅长猜复杂 UI,WebMCP 的价值是结构化暴露工具。 - 官方链接可作为会后资料。 -

-
-
-

如果有人追问落地

-

- 只回答原则:渐进增强、权限继承、高危确认、审计日志、工具按需暴露。 - 不在本场扩展到厂商 SDK 选型和企业架构。 -

-
-
+# Modern Web Guidance 官方 CLI +scripts/demo-modern-web-guidance.sh
- - diff --git a/public/demo/modern-web-guidance/app.js b/public/demo/modern-web-guidance/app.js new file mode 100644 index 0000000..e6b1226 --- /dev/null +++ b/public/demo/modern-web-guidance/app.js @@ -0,0 +1,16 @@ +const dialog = document.querySelector("#guided-dialog"); +const openDialog = document.querySelector("#open-dialog"); +const copyButton = document.querySelector("#copy"); +const commands = document.querySelector("#commands"); + +openDialog.addEventListener("click", () => { + dialog.showModal(); +}); + +copyButton.addEventListener("click", async () => { + await navigator.clipboard.writeText(commands.textContent.trim()); + copyButton.textContent = "已复制"; + setTimeout(() => { + copyButton.textContent = "复制命令"; + }, 1200); +}); diff --git a/public/demo/modern-web-guidance/index.html b/public/demo/modern-web-guidance/index.html new file mode 100644 index 0000000..a9a99c0 --- /dev/null +++ b/public/demo/modern-web-guidance/index.html @@ -0,0 +1,87 @@ + + + + + + Modern Web Guidance Demo + + + +
+
+

Official CLI demo

+

Modern Web Guidance

+

+ 这段演示直接使用 Chrome 官方 CLI:先搜索某个 Web 开发意图,再取回对应指南。 + 重点不是背 API,而是让开发者看到 AI coding agent 如何被现代 Web 知识和 Baseline 边界纠偏。 +

+
+ +
+
+
+ + terminal +
+
npx modern-web-guidance@latest search "animate a dialog modal backdrop"
+npx modern-web-guidance@latest retrieve "animate-to-from-top-layer"
+ +
+ +
+

Expected official output

+

搜索会返回 use case

+
    +
  • light-dismiss-a-dialog:用原生 dialog 处理 light dismiss。
  • +
  • animate-to-from-top-layer:用现代 CSS 动画进入/离开 top layer。
  • +
  • declarative-dialog-popover-control:用声明式控制 dialog / popover。
  • +
+
+
+ +
+
+

Guided implementation

+

指南会把 Agent 拉回现代 Web 写法

+

+ 这个页面用一个可运行的 dialog 动画做对应说明:优先使用原生 <dialog>、 + ::backdrop、@starting-style 和 transition-behavior, + 而不是用大量 JavaScript 重新造弹层。 +

+ +
+ +
dialog {
+  opacity: 0;
+  transform: translateY(16px) scale(0.96);
+  transition-property: opacity, transform, display, overlay;
+  transition-duration: 220ms;
+  transition-behavior: allow-discrete;
+}
+
+dialog[open] {
+  opacity: 1;
+  transform: translateY(0) scale(1);
+}
+
+@starting-style {
+  dialog[open] {
+    opacity: 0;
+    transform: translateY(16px) scale(0.96);
+  }
+}
+
+ + +
+

Native top layer

+

这是原生 dialog

+

演示重点:Modern Web Guidance 让 Agent 找到现代 HTML/CSS 能力,并提醒兼容和降级边界。

+ +
+
+
+ + + + diff --git a/public/demo/modern-web-guidance/styles.css b/public/demo/modern-web-guidance/styles.css new file mode 100644 index 0000000..7cc88d4 --- /dev/null +++ b/public/demo/modern-web-guidance/styles.css @@ -0,0 +1,222 @@ +:root { + color-scheme: light; + --ink: #17202a; + --muted: #5f6f78; + --paper: #fffef8; + --panel: #eff4ef; + --line: #cfd9cd; + --green: #0b6b4f; + --blue: #174ea6; + --amber: #a46400; + --code: #14202a; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-height: 100vh; + color: var(--ink); + background: + linear-gradient(90deg, rgb(23 32 42 / 0.05) 1px, transparent 1px), + linear-gradient(rgb(23 32 42 / 0.05) 1px, transparent 1px), + #edf4ef; + background-size: 30px 30px; + font-family: ui-sans-serif, "PingFang SC", "Microsoft YaHei", sans-serif; +} + +button { + min-height: 42px; + border: 1px solid var(--ink); + background: var(--ink); + color: #fff; + cursor: pointer; + padding: 10px 14px; + font: inherit; + font-weight: 900; +} + +.shell { + width: min(1180px, calc(100vw - 32px)); + margin: 0 auto; + padding: 40px 0 64px; +} + +.eyebrow { + margin: 0 0 10px; + color: var(--green); + font-size: 12px; + font-weight: 900; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +h1, +h2, +p { + margin-top: 0; +} + +h1 { + max-width: 780px; + margin-bottom: 14px; + font-family: Georgia, "Times New Roman", serif; + font-size: clamp(42px, 7vw, 86px); + line-height: 0.9; +} + +.hero p, +.demo p, +.result li { + color: var(--muted); + line-height: 1.72; +} + +.grid, +.demo { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(320px, 0.82fr); + gap: 20px; + margin-top: 24px; +} + +.terminal, +.result, +.demo { + border: 1px solid var(--line); + background: rgb(255 254 248 / 0.92); + box-shadow: 0 18px 58px rgb(23 32 42 / 0.12); +} + +.terminal, +.result { + padding: 20px; +} + +.terminal { + background: var(--code); + color: #dfe8ef; +} + +.terminal-head { + display: flex; + align-items: center; + gap: 7px; + margin-bottom: 14px; +} + +.terminal-head span { + width: 12px; + height: 12px; + border-radius: 999px; + background: #e15c4f; +} + +.terminal-head span:nth-child(2) { + background: #e5ad35; +} + +.terminal-head span:nth-child(3) { + background: #42aa6d; +} + +.terminal-head strong { + margin-left: 8px; + color: #9fb0be; + font-size: 12px; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +pre { + overflow: auto; + margin: 0 0 16px; + white-space: pre-wrap; + font-size: 14px; + line-height: 1.65; +} + +.result ul { + display: grid; + gap: 12px; + margin: 0; + padding-left: 18px; +} + +.demo { + align-items: start; + padding: 24px; +} + +code { + border: 1px solid rgb(23 32 42 / 0.13); + background: #fff; + padding: 1px 5px; +} + +.code { + border: 1px solid #2d3944; + background: var(--code); + color: #dfe8ef; + padding: 16px; +} + +dialog { + width: min(520px, calc(100vw - 36px)); + border: 1px solid var(--line); + padding: 0; + background: var(--paper); + color: var(--ink); + opacity: 0; + transform: translateY(16px) scale(0.96); + transition-property: opacity, transform, display, overlay; + transition-duration: 220ms; + transition-timing-function: ease-out; + transition-behavior: allow-discrete; +} + +dialog[open] { + opacity: 1; + transform: translateY(0) scale(1); +} + +@starting-style { + dialog[open] { + opacity: 0; + transform: translateY(16px) scale(0.96); + } +} + +dialog::backdrop { + background: rgb(12 18 24 / 0); + transition: + display 220ms allow-discrete, + overlay 220ms allow-discrete, + background-color 220ms ease-out; +} + +dialog[open]::backdrop { + background: rgb(12 18 24 / 0.42); +} + +dialog form { + display: grid; + gap: 12px; + padding: 24px; +} + +@media (prefers-reduced-motion: reduce) { + dialog { + transform: none; + transition-duration: 80ms; + } +} + +@media (max-width: 880px) { + .grid, + .demo { + grid-template-columns: 1fr; + } +} diff --git a/public/demo/prompt-api/index.html b/public/demo/prompt-api/index.html new file mode 100644 index 0000000..dfec466 --- /dev/null +++ b/public/demo/prompt-api/index.html @@ -0,0 +1,97 @@ + + + + + + Prompt API Demo + + + +
+ + +
+
+
+

window.LanguageModel

+

把浏览器内置模型当作页面能力,而不是远程服务。

+

+ 可用时,页面直接创建本地模型会话;不可用时,页面仍能展示同一套输入、输出和错误边界, + 让观众理解这个 API 的产品形态。 +

+
+ +
+ +
+
+

Task

+

把 Chrome 更新摘要改写成给业务同事看的说明

+ + +
+ + +
+
const availability = await LanguageModel.availability();
+const session = await LanguageModel.create({
+  monitor(m) {
+    m.addEventListener("downloadprogress", updateProgress);
+  },
+});
+const result = await session.prompt(prompt);
+
+ + +
+
+
+ + + + diff --git a/public/demo/prompt-api/prompt-api.js b/public/demo/prompt-api/prompt-api.js new file mode 100644 index 0000000..ad142e4 --- /dev/null +++ b/public/demo/prompt-api/prompt-api.js @@ -0,0 +1,74 @@ +import { logTo, setText } from "../shared.js"; + +const hasPromptApi = "LanguageModel" in window; + +document.querySelector("#check").addEventListener("click", checkAvailability); +document.querySelector("#run").addEventListener("click", runPrompt); + +await checkAvailability(); + +async function checkAvailability() { + setText("#support-status", hasPromptApi ? "支持" : "未暴露"); + setText("#mode", hasPromptApi ? "Native" : "Mock"); + + if (!hasPromptApi) { + setText("#availability", "unavailable"); + logTo("#log", "prompt-api.detect", { + supported: false, + fallback: "Use local mock response for the presentation.", + }); + return; + } + + try { + const availability = await LanguageModel.availability(); + setText("#availability", availability); + logTo("#log", "prompt-api.availability", { availability }); + } catch (error) { + setText("#availability", "error"); + logTo("#log", "prompt-api.error", { message: error.message }); + } +} + +async function runPrompt() { + const source = document.querySelector("#prompt-input").value.trim(); + const tone = document.querySelector("#tone").value; + const prompt = `请用${tone}风格,用三句话解释下面内容:\n\n${source}`; + + if (!hasPromptApi) { + const result = createMockSummary(source, tone); + logTo("#log", "prompt-api.mock-result", { prompt, result }); + return; + } + + try { + setText("#mode", "Native running"); + const session = await LanguageModel.create({ + monitor(monitor) { + monitor.addEventListener("downloadprogress", (event) => { + const percent = Math.round(event.loaded * 100); + setText("#availability", `downloading ${percent}%`); + }); + }, + }); + const result = await session.prompt(prompt); + setText("#mode", "Native complete"); + logTo("#log", "prompt-api.native-result", { prompt, result }); + } catch (error) { + setText("#mode", "Native error"); + logTo("#log", "prompt-api.native-error", { + prompt, + message: error.message, + fallback: createMockSummary(source, tone), + }); + } +} + +function createMockSummary(source, tone) { + const first = source.split(/[。.!?]/).find(Boolean)?.trim() || source; + return [ + `[${tone}] Chrome 的新能力可以理解为三条线同时推进。`, + `第一条是用户体验升级,第二条是开发者工具升级,第三条是让网页能被 AI Agent 结构化调用。`, + `这是一段 mock 输出,用来在当前浏览器没有 Prompt API 时保持演示可运行;输入摘要:${first}。`, + ].join(""); +} diff --git a/public/demo/shared.css b/public/demo/shared.css new file mode 100644 index 0000000..02490b4 --- /dev/null +++ b/public/demo/shared.css @@ -0,0 +1,463 @@ +:root { + color-scheme: light; + --ink: #121417; + --muted: #65727d; + --paper: #fffdf8; + --panel: #f4f1e8; + --line: #d7d0bf; + --blue: #164db3; + --green: #0c7356; + --amber: #a26000; + --red: #b03831; + --violet: #6747b8; + --code: #18222d; + --shadow: 0 20px 60px rgb(35 37 42 / 0.13); +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + color: var(--ink); + background: + linear-gradient(90deg, rgb(18 20 23 / 0.055) 1px, transparent 1px), + linear-gradient(rgb(18 20 23 / 0.055) 1px, transparent 1px), + #ece6d8; + background-size: 30px 30px; + font-family: Avenir Next, Trebuchet MS, "PingFang SC", "Microsoft YaHei", sans-serif; +} + +button, +input, +select, +textarea { + font: inherit; +} + +a { + color: inherit; + text-decoration: none; +} + +.shell { + display: grid; + grid-template-columns: minmax(260px, 350px) minmax(0, 1fr); + gap: 22px; + min-height: 100vh; + padding: 28px; +} + +.rail, +.stage, +.panel, +.card, +.console { + border: 1px solid var(--line); + background: rgb(255 253 248 / 0.92); + box-shadow: var(--shadow); +} + +.rail { + position: sticky; + top: 28px; + align-self: start; + min-height: calc(100vh - 56px); + padding: 24px; +} + +.stage { + padding: 26px; +} + +.eyebrow { + margin: 0 0 8px; + color: var(--blue); + font-size: 12px; + font-weight: 900; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +h1, +h2, +h3, +p { + margin-top: 0; +} + +h1 { + max-width: 11ch; + margin-bottom: 16px; + font-family: Georgia, "Times New Roman", serif; + font-size: clamp(40px, 5vw, 72px); + line-height: 0.94; + letter-spacing: 0; +} + +h2 { + margin-bottom: 12px; + font-size: clamp(24px, 3vw, 38px); + line-height: 1.05; + letter-spacing: 0; +} + +h3 { + margin-bottom: 8px; + font-size: 19px; + letter-spacing: 0; +} + +.lead, +.muted, +.card p, +.panel p, +li { + color: var(--muted); + line-height: 1.65; +} + +.nav { + display: grid; + gap: 10px; + margin: 26px 0; +} + +.nav a, +.nav button, +.link-button, +.primary, +.ghost { + display: inline-grid; + grid-template-columns: 28px minmax(0, 1fr); + align-items: center; + gap: 10px; + min-height: 44px; + border: 1px solid var(--line); + background: var(--paper); + color: var(--ink); + cursor: pointer; + padding: 10px 12px; + text-align: left; + font-weight: 850; +} + +.nav a:hover, +.nav a.active, +.nav button:hover, +.link-button:hover, +.ghost:hover { + border-color: var(--blue); + background: #eef4ff; +} + +.primary { + border-color: var(--ink); + background: var(--ink); + color: white; +} + +.ghost { + grid-template-columns: 1fr; +} + +.num, +.icon { + display: grid; + place-items: center; + width: 28px; + height: 28px; + background: var(--ink); + color: white; + font-size: 12px; + font-weight: 900; +} + +.note { + border-left: 4px solid var(--amber); + background: #fff3d6; + color: #6f4707; + padding: 12px 14px; + font-size: 13px; + line-height: 1.6; +} + +.hero { + display: grid; + grid-template-columns: minmax(0, 1.25fr) minmax(250px, 0.75fr); + gap: 18px; + margin-bottom: 22px; +} + +.statement { + min-height: 250px; + padding: 28px; + color: white; + background: + linear-gradient(135deg, rgb(18 20 23 / 0.96), rgb(22 77 179 / 0.83)), + radial-gradient(circle at 20% 18%, rgb(255 255 255 / 0.22), transparent 26%); +} + +.statement .eyebrow, +.statement p { + color: rgb(255 255 255 / 0.78); +} + +.statement h2 { + max-width: 760px; + margin-bottom: 16px; + font-size: clamp(30px, 5vw, 58px); +} + +.metrics, +.grid, +.two-col { + display: grid; + gap: 14px; +} + +.metrics { + grid-template-columns: 1fr; +} + +.metric, +.panel, +.card, +.console { + padding: 18px; +} + +.metric { + border: 1px solid var(--line); + background: var(--panel); +} + +.metric strong { + display: block; + margin-bottom: 4px; + font-size: 25px; +} + +.metric span { + color: var(--muted); + font-size: 13px; +} + +.grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.two-col { + grid-template-columns: minmax(0, 1fr) minmax(340px, 0.82fr); +} + +.section { + margin-top: 24px; +} + +.tag, +.pill { + display: inline-flex; + align-items: center; + min-height: 26px; + margin-bottom: 12px; + border: 1px solid transparent; + padding: 3px 9px; + color: white; + font-size: 12px; + font-weight: 900; +} + +.tag.blue, +.pill.blue { + background: var(--blue); +} + +.tag.green, +.pill.green { + background: var(--green); +} + +.tag.amber, +.pill.amber { + background: var(--amber); +} + +.tag.red, +.pill.red { + background: var(--red); +} + +.tag.violet, +.pill.violet { + background: var(--violet); +} + +.pill.neutral { + border-color: var(--line); + background: var(--paper); + color: var(--muted); +} + +.card { + min-height: 190px; + transition: + transform 160ms ease, + box-shadow 160ms ease; +} + +.card:hover { + box-shadow: 0 14px 34px rgb(22 77 179 / 0.16); + transform: translateY(-2px); +} + +.card ul { + margin: 14px 0 0; + padding-left: 18px; +} + +.controls { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 14px; +} + +.controls button { + min-height: 42px; + border: 1px solid var(--ink); + background: var(--ink); + color: white; + cursor: pointer; + padding: 9px 13px; + font-weight: 850; +} + +.controls button.secondary { + border-color: var(--line); + background: white; + color: var(--ink); +} + +.field { + display: grid; + gap: 7px; + margin-bottom: 12px; + color: var(--muted); + font-size: 13px; + font-weight: 850; +} + +input, +select, +textarea { + width: 100%; + border: 1px solid var(--line); + background: white; + color: var(--ink); + padding: 11px 12px; +} + +textarea { + min-height: 110px; + resize: vertical; +} + +.code, +.console pre { + overflow: auto; + border: 1px solid #303b48; + background: var(--code); + color: #d8e4ef; + padding: 14px; + font-size: 13px; + line-height: 1.5; +} + +.code { + margin: 14px 0 0; +} + +.console { + align-self: start; + position: sticky; + top: 28px; +} + +.console pre { + max-height: 460px; + margin: 0; + white-space: pre-wrap; +} + +.status-row, +.list { + display: grid; + gap: 10px; +} + +.status-row { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.status { + border: 1px solid var(--line); + background: var(--panel); + padding: 12px; +} + +.status span { + display: block; + color: var(--muted); + font-size: 12px; +} + +.status strong { + display: block; + margin-top: 4px; +} + +.table { + width: 100%; + border-collapse: collapse; + background: var(--paper); +} + +.table th, +.table td { + border: 1px solid var(--line); + padding: 12px; + text-align: left; + vertical-align: top; +} + +.table th { + background: var(--ink); + color: white; + font-size: 13px; +} + +.table td { + color: var(--muted); + line-height: 1.55; +} + +@media (max-width: 1020px) { + .shell, + .hero, + .two-col { + grid-template-columns: 1fr; + } + + .rail, + .console { + position: static; + min-height: auto; + } + + .grid, + .status-row { + grid-template-columns: 1fr; + } +} diff --git a/public/demo/shared.js b/public/demo/shared.js new file mode 100644 index 0000000..403ce5f --- /dev/null +++ b/public/demo/shared.js @@ -0,0 +1,24 @@ +export function logTo(target, title, payload) { + const el = typeof target === "string" ? document.querySelector(target) : target; + if (!el) return; + const stamp = new Date().toLocaleTimeString("zh-CN", { hour12: false }); + const entry = { + time: stamp, + event: title, + ...payload, + }; + el.textContent = `${JSON.stringify(entry, null, 2)}\n\n${el.textContent}`.trim(); +} + +export function setText(selector, value) { + const el = document.querySelector(selector); + if (el) el.textContent = value; +} + +export function escapeHtml(value) { + return String(value) + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """); +} diff --git a/public/demo/view-transitions/app.js b/public/demo/view-transitions/app.js new file mode 100644 index 0000000..67572f6 --- /dev/null +++ b/public/demo/view-transitions/app.js @@ -0,0 +1,56 @@ +const panel = document.querySelector("#panel"); +const buttons = Array.from(document.querySelectorAll("[data-view]")); + +const views = { + orders: { + title: "订单工作台", + body: "状态从列表切换到业务面板,浏览器负责把 DOM 更新过渡成连续动画。", + metrics: ["128 个待处理", "96.4% 准时率"], + }, + analytics: { + title: "实时分析", + body: "适合展示页面状态、卡片位置、详情视图之间的平滑切换。", + metrics: ["42ms INP", "3 条异常趋势"], + }, + settings: { + title: "权限设置", + body: "在支持的浏览器里使用 View Transitions;不支持时保留普通状态切换。", + metrics: ["7 个角色", "24 项权限"], + }, +}; + +function render(view) { + const data = views[view]; + panel.innerHTML = ` +
+

Current view

+

${data.title}

+

${data.body}

+
+
+ ${data.metrics + .map((metric) => `
${metric}View transition keeps the context visible.
`) + .join("")} +
+ `; +} + +function setActive(view) { + buttons.forEach((button) => { + button.classList.toggle("active", button.dataset.view === view); + }); + render(view); +} + +buttons.forEach((button) => { + button.addEventListener("click", () => { + const next = button.dataset.view; + if (document.startViewTransition) { + document.startViewTransition(() => setActive(next)); + return; + } + setActive(next); + }); +}); + +setActive("orders"); diff --git a/public/demo/view-transitions/index.html b/public/demo/view-transitions/index.html new file mode 100644 index 0000000..3dd05f5 --- /dev/null +++ b/public/demo/view-transitions/index.html @@ -0,0 +1,29 @@ + + + + + + View Transitions Demo + + + +
+
+

Chrome UI capability

+

View Transitions

+

点击不同状态,观察浏览器把 DOM 更新包装成一次连续过渡。没有支持时,页面仍然即时切换。

+
+ +
+ +
+
+
+ + + + diff --git a/public/demo/view-transitions/styles.css b/public/demo/view-transitions/styles.css new file mode 100644 index 0000000..2eb318e --- /dev/null +++ b/public/demo/view-transitions/styles.css @@ -0,0 +1,126 @@ +:root { + color-scheme: light; + --ink: #17202a; + --muted: #66717c; + --paper: #f3f5f7; + --panel: #ffffff; + --line: #d8dee6; + --blue: #1a5fb4; + --green: #16784e; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-height: 100vh; + color: var(--ink); + background: var(--paper); + font-family: ui-sans-serif, "PingFang SC", "Microsoft YaHei", sans-serif; +} + +.shell { + width: min(1040px, calc(100vw - 32px)); + margin: 0 auto; + padding: 42px 0 60px; +} + +.eyebrow { + margin: 0 0 10px; + color: var(--blue); + font-size: 12px; + font-weight: 900; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +h1, +p { + margin-top: 0; +} + +h1 { + margin-bottom: 14px; + font-family: Georgia, "Times New Roman", serif; + font-size: clamp(44px, 8vw, 96px); + line-height: 0.9; +} + +.hero p { + max-width: 680px; + color: var(--muted); + line-height: 1.75; +} + +.app { + margin-top: 26px; + border: 1px solid var(--line); + background: var(--panel); + box-shadow: 0 22px 70px rgb(23 32 42 / 0.14); +} + +.tabs { + display: flex; + gap: 8px; + border-bottom: 1px solid var(--line); + padding: 14px; +} + +button { + min-height: 40px; + border: 1px solid var(--line); + background: #fff; + color: var(--ink); + cursor: pointer; + padding: 9px 14px; + font: inherit; + font-weight: 900; +} + +button.active { + border-color: var(--blue); + background: #e8f0ff; + color: var(--blue); +} + +.panel { + display: grid; + gap: 18px; + min-height: 380px; + padding: 22px; + view-transition-name: demo-panel; +} + +.summary { + display: grid; + grid-template-columns: 1.2fr 0.8fr; + gap: 18px; +} + +.card { + border: 1px solid var(--line); + background: #fff; + padding: 18px; +} + +.card strong { + display: block; + margin-bottom: 8px; + font-size: 34px; +} + +::view-transition-old(demo-panel), +::view-transition-new(demo-panel) { + animation-duration: 320ms; + animation-timing-function: cubic-bezier(0.2, 0, 0, 1); +} + +@media (max-width: 760px) { + .tabs, + .summary { + display: grid; + grid-template-columns: 1fr; + } +} diff --git a/public/demo/webmcp/index.html b/public/demo/webmcp/index.html index 118e4f0..3544eac 100644 --- a/public/demo/webmcp/index.html +++ b/public/demo/webmcp/index.html @@ -14,6 +14,7 @@
检测中 + Demo 总览 Chrome 官方文档
diff --git a/public/demo/webmcp/styles.css b/public/demo/webmcp/styles.css index 1825ec7..3413a35 100644 --- a/public/demo/webmcp/styles.css +++ b/public/demo/webmcp/styles.css @@ -80,11 +80,24 @@ h2 { .runtime { display: flex; + flex-wrap: wrap; align-items: center; gap: 14px; margin-bottom: 8px; } +.demo-home { + border: 1px solid var(--line); + background: var(--paper); + padding: 6px 10px; + color: var(--ink); +} + +.demo-home:hover { + border-color: var(--blue); + background: #eef4ff; +} + .pill, .mode { display: inline-flex; diff --git a/public/html-in-canvas/app.js b/public/html-in-canvas/app.js new file mode 100644 index 0000000..2b8122f --- /dev/null +++ b/public/html-in-canvas/app.js @@ -0,0 +1,69 @@ +const canvas = document.getElementById('canvas'); +const ctx = canvas.getContext('2d'); +const drawElement = document.getElementById('draw-element'); +const statusPanel = document.getElementById('support-status'); +const demoForm = document.getElementById('demo-form'); + +const hasHtmlInCanvas = + typeof ctx.drawElementImage === 'function' && + typeof canvas.requestPaint === 'function'; + +function setStatus(state, message) { + statusPanel.dataset.state = state; + statusPanel.textContent = message; +} + +function resizeCanvas(entry) { + const dpcSize = entry.devicePixelContentBoxSize?.[0]; + const nextWidth = dpcSize?.inlineSize ?? Math.round(entry.contentRect.width * window.devicePixelRatio); + const nextHeight = dpcSize?.blockSize ?? Math.round(entry.contentRect.height * window.devicePixelRatio); + + if (canvas.width !== nextWidth) { + canvas.width = nextWidth; + } + + if (canvas.height !== nextHeight) { + canvas.height = nextHeight; + } +} + +function paint() { + ctx.reset(); + ctx.fillStyle = '#101820'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + const x = canvas.width / 25; + const y = canvas.height / 25; + const transform = ctx.drawElementImage(drawElement, x, y); + drawElement.style.transform = transform.toString(); +} + +demoForm.addEventListener('submit', (event) => { + event.preventDefault(); + setStatus('ok', '表单提交事件来自真实 DOM:发射指令已拦截,demo 仍停留在当前页面。'); +}); + +if (!hasHtmlInCanvas) { + document.body.dataset.api = 'fallback'; + setStatus( + 'fail', + '当前浏览器未暴露 drawElementImage/requestPaint。请使用 Chrome Canary 149+ 并开启 canvas-draw-element flag。' + ); +} else { + document.body.dataset.api = 'native'; + setStatus('ok', '已检测到 HTML-in-Canvas 实验 API,可以操作 canvas 中绘制的 DOM 表单。'); + canvas.onpaint = paint; + + const supportsDevicePixelContentBox = + typeof ResizeObserverEntry !== 'undefined' && + 'devicePixelContentBoxSize' in ResizeObserverEntry.prototype; + const resizeOptions = supportsDevicePixelContentBox ? { box: 'device-pixel-content-box' } : {}; + + const observer = new ResizeObserver(([entry]) => { + resizeCanvas(entry); + canvas.requestPaint(); + }); + + observer.observe(canvas, resizeOptions); + canvas.requestPaint(); +} diff --git a/public/html-in-canvas/index.html b/public/html-in-canvas/index.html new file mode 100644 index 0000000..19b514d --- /dev/null +++ b/public/html-in-canvas/index.html @@ -0,0 +1,115 @@ + + + + + + HTML-in-Canvas API Demo + + + +
+
+

Chrome Origin Trial Demo

+

HTML-in-Canvas API

+

+ 这个页面基于 WICG 官方示例改写,演示把真实 DOM 表单绘制进 + <canvas>,同时保留输入、选择、滑块和按钮的交互能力。 +

+ +
+ 正在检测当前浏览器是否支持实验 API... +
+
+ +
+
+ +
+
+
+ 飞船控制台 + + + + +
+ 目标星系 + + +
+ + + + +
+
+
+
+ +
+ + +
+
+ + + + diff --git a/public/html-in-canvas/styles.css b/public/html-in-canvas/styles.css new file mode 100644 index 0000000..5a123cb --- /dev/null +++ b/public/html-in-canvas/styles.css @@ -0,0 +1,292 @@ +:root { + color-scheme: light; + --ink: #17202a; + --muted: #5d6670; + --panel: #f7f4ec; + --canvas: #101820; + --accent: #ffb000; + --accent-strong: #e04f2f; + --line: #263241; + --ok: #1d7f4f; + --warn: #9a5b00; + font-family: "Avenir Next", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-height: 100vh; + color: var(--ink); + background: + linear-gradient(90deg, rgba(23, 32, 42, 0.06) 1px, transparent 1px), + linear-gradient(rgba(23, 32, 42, 0.05) 1px, transparent 1px), + #fffaf0; + background-size: 28px 28px; +} + +button, +input { + font: inherit; +} + +code { + border: 1px solid rgba(23, 32, 42, 0.16); + border-radius: 4px; + padding: 0.08rem 0.28rem; + background: rgba(255, 255, 255, 0.74); +} + +.page-shell { + width: min(1120px, calc(100vw - 32px)); + margin: 0 auto; + padding: 44px 0 56px; +} + +.intro { + display: grid; + gap: 12px; + margin-bottom: 28px; +} + +.eyebrow { + margin: 0; + color: var(--accent-strong); + font-size: 0.82rem; + font-weight: 800; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +h1, +h2, +p { + margin: 0; +} + +h1 { + max-width: 760px; + font-family: Georgia, "Times New Roman", serif; + font-size: clamp(3rem, 6vw, 5.8rem); + line-height: 0.92; + letter-spacing: 0; +} + +.lede { + max-width: 760px; + color: var(--muted); + font-size: 1.06rem; + line-height: 1.75; +} + +.status-panel { + width: fit-content; + max-width: 100%; + border: 1px solid rgba(23, 32, 42, 0.18); + border-left: 6px solid var(--warn); + border-radius: 6px; + padding: 10px 14px; + color: var(--warn); + background: rgba(255, 255, 255, 0.72); + font-weight: 700; +} + +.status-panel[data-state="ok"] { + border-left-color: var(--ok); + color: var(--ok); +} + +.status-panel[data-state="fail"] { + border-left-color: var(--accent-strong); + color: var(--accent-strong); +} + +.demo-grid { + display: grid; + grid-template-columns: minmax(0, 1.55fr) minmax(280px, 0.8fr); + gap: 24px; + align-items: stretch; +} + +.canvas-stage { + position: relative; + display: grid; + place-items: center; + min-height: 480px; + border: 1px solid rgba(23, 32, 42, 0.24); + border-radius: 8px; + padding: 22px; + background: + radial-gradient(circle at 24% 18%, rgba(255, 176, 0, 0.34), transparent 28%), + linear-gradient(135deg, #1a2633 0%, #0d141b 100%); + box-shadow: 0 24px 70px rgba(23, 32, 42, 0.18); +} + +canvas { + width: min(638px, 100%); + height: auto; + aspect-ratio: 638 / 318; + border: 2px solid rgba(255, 176, 0, 0.9); + border-radius: 6px; + background: var(--canvas); + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.16); +} + +.fallback-preview { + display: none; + position: absolute; + inset: 42px; + place-items: center; + gap: 14px; + border: 1px dashed rgba(255, 176, 0, 0.72); + border-radius: 6px; + padding: 22px; + background: rgba(16, 24, 32, 0.9); +} + +body[data-api="fallback"] .fallback-preview { + display: grid; +} + +body[data-api="fallback"] canvas { + opacity: 0.34; +} + +.fallback-label { + color: #ffd27c; + font-size: 0.86rem; + font-weight: 800; + text-align: center; +} + +.control-panel { + width: 578px; + color: #101820; +} + +.control-panel fieldset { + display: grid; + gap: 12px; + margin: 0; + border: 2px solid #17202a; + border-radius: 6px; + padding: 18px; + background: #fffaf0; +} + +.control-panel legend { + padding: 0 8px; + font-weight: 900; +} + +.field-row, +.check-row { + display: flex; + gap: 10px; + align-items: center; +} + +.field-row { + justify-content: space-between; +} + +.field-row input[type="text"] { + width: min(310px, 58%); + border: 1px solid #17202a; + border-radius: 4px; + padding: 7px 9px; + background: #ffffff; +} + +.field-row input[type="range"] { + width: min(300px, 58%); + accent-color: var(--accent-strong); +} + +.nested-fieldset { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.nested-fieldset legend { + grid-column: 1 / -1; +} + +.control-panel button { + justify-self: end; + border: 2px solid #17202a; + border-radius: 4px; + padding: 8px 18px; + color: #17202a; + background: var(--accent); + font-weight: 900; + cursor: pointer; +} + +.preview-panel { + width: min(578px, 100%); +} + +.preview-panel input:disabled, +.preview-panel button:disabled { + cursor: not-allowed; + opacity: 0.72; +} + +.notes { + display: grid; + align-content: start; + gap: 14px; + border-left: 4px solid var(--line); + padding: 20px 0 20px 22px; + color: var(--muted); + line-height: 1.7; +} + +.notes h2 { + color: var(--ink); + font-size: 1rem; +} + +.notes ul { + display: grid; + gap: 8px; + margin: 0; + padding-left: 1.2rem; +} + +@media (max-width: 820px) { + .page-shell { + width: min(100vw - 22px, 680px); + padding-top: 28px; + } + + h1 { + font-size: 3.1rem; + } + + .demo-grid { + grid-template-columns: 1fr; + } + + .canvas-stage { + min-height: 360px; + padding: 14px; + } + + .fallback-preview { + inset: 24px; + padding: 14px; + } + + .nested-fieldset { + grid-template-columns: 1fr; + } + + .notes { + border-left: 0; + border-top: 4px solid var(--line); + padding: 18px 0 0; + } +} diff --git a/scripts/demo-modern-web-guidance.sh b/scripts/demo-modern-web-guidance.sh new file mode 100755 index 0000000..49ad5a6 --- /dev/null +++ b/scripts/demo-modern-web-guidance.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail + +QUERY=${1:-"animate a dialog modal backdrop"} +GUIDE_ID=${2:-"animate-to-from-top-layer"} + +echo "== Modern Web Guidance: search ==" +echo "npx modern-web-guidance@latest search \"$QUERY\"" +npx modern-web-guidance@latest search "$QUERY" + +echo +echo "== Modern Web Guidance: retrieve ==" +echo "npx modern-web-guidance@latest retrieve \"$GUIDE_ID\"" +npx modern-web-guidance@latest retrieve "$GUIDE_ID" diff --git a/slides.md b/slides.md index f319743..58ac122 100644 --- a/slides.md +++ b/slides.md @@ -58,13 +58,29 @@ src: ./pages/demo/devtools-workflow.md --- --- -src: ./pages/demo/platform-api.md +src: ./pages/demo/modern-web-guidance.md +--- + +--- +src: ./pages/demo/devtools-agents.md +--- + +--- +src: ./pages/demo/html-in-canvas.md +--- + +--- +src: ./pages/demo/view-transitions.md --- --- src: ./pages/demo/adoption-plan.md --- +--- +src: ./pages/demo/platform-api.md +--- + --- src: ./pages/closing.md ---