feat: add bundled OpenCode skills and refine module flow
Some checks failed
Electron E2E / Electron E2E (macos-latest) (push) Has been cancelled
Electron E2E / Electron E2E (ubuntu-latest) (push) Has been cancelled
Electron E2E / Electron E2E (windows-latest) (push) Has been cancelled

This commit is contained in:
inman
2026-08-14 18:15:55 +08:00
parent 88f9ee8708
commit c809002180
31 changed files with 1153 additions and 241 deletions

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Zara Zhang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,93 @@
---
name: frontend-slides
description: 创建、更新并验证适合项目展示的固定 16:9 HTML 幻灯片;当用户说生成演示、项目汇报、结题展示、答辩、做 PPT 或演示文稿,或需要把当前项目整理成可播放作品时使用。只生成本地 HTML可选导出 PDF不生成 PPTX不部署到云端。
---
# Makelore 项目演示
把当前项目的真实内容整理成孩子可以讲清楚、也愿意展示的单文件 HTML 幻灯片。优先生成可在浏览器中播放的作品,而不是把项目说明堆成文档。
## 调用边界
- 当用户明确要求“生成演示、做汇报、结题展示、答辩、项目介绍、演示文稿”时主动使用本技能。
- 当项目已经完成、用户要准备展示,或项目交付清单包含“项目演示”时主动建议使用本技能。
- 只在展示意图明确时调用;普通写代码、修 Bug 或写 README 不要自动生成幻灯片。
- 产物是项目目录中的 HTML 文件和资源;不要生成 `.ppt``.pptx`,不要调用云部署或上传服务。
## 工作流程
### 1. 建立内容事实
开始前读取与项目相关的 README、主要页面或入口、用户提供的图片/素材、最近的实现结果和验证记录。只使用项目中能找到的事实,不要编造数据、用户数量、性能指标或“已经上线”等结论。
如果信息不足,一次性用孩子能理解的语言补问:
1. 这是给谁看的、想让大家记住什么;
2. 预计讲几分钟(默认 58 张);
3. 最想展示哪些功能或过程;
4. 更喜欢“活泼、清楚、科技感、故事感”中的哪一种。
有足够上下文时直接推断,不要为了形式重复提问。
### 2. 先做三张风格草图
读取 [style-presets.md](references/style-presets.md),生成三份只包含标题页的自包含 HTML 草图,保存到项目的 `.frontend-slides/slide-previews/`
- 一份稳妥、清楚的浅色方案;
- 一份更活泼、适合孩子项目的方案;
- 一份根据项目主题定制的方案。
草图必须是真实标题页,不要把“方案 A”“安全选项”“生成预览”或文件路径写到幻灯片里。通过当前项目的浏览器能力打开并检查草图让用户选择一个方向用户已经明确风格时可以跳过选择但仍要保持有意识的设计系统。
### 3. 生成完整演示
读取以下参考文件后再生成:
- [viewport-base.css](references/viewport-base.css):完整复制到每份演示的 `<style>` 中;
- [html-template.md](references/html-template.md)HTML 结构、导航和可访问性要求;
- [animation-patterns.md](references/animation-patterns.md):按情绪选择克制的动画。
生成规则:
- 使用一个自包含的 `index.html`CSS 和 JavaScript 内联;
- 所有幻灯片固定为 1920×1080通过整体缩放适配窗口保持 16:9不用响应式断点重排幻灯片内容
- 每张幻灯片使用 `.slide`,切换使用 `.active` / `.visible``visibility``opacity``pointer-events`,不要用 `display: none` 切换;
- 每页只讲一个核心意思。优先拆页,不要把文字缩小到孩子读不清;
- 使用真实项目截图、图片或 CSS 图形;素材放在演示目录的相对路径下,不使用绝对本机路径;
- 默认采用明亮、友好、可读的视觉方向。不要让 Makelore 的应用界面出现暗色主题;演示内容可以有少量主题色,但不能牺牲可读性;
- 不依赖运行时联网的字体、CDN、npm 包或远程图片;为字体和网络不可用准备本地回退;
- 支持键盘方向键、空格、PageUp/PageDown 和触摸滑动;加入 `prefers-reduced-motion`
- 为关键结构写清楚的 CSS 注释,保证后续 Agent 能安全修改;
- 如用户没有要求锁定,提供轻量的编辑入口或清晰的后续修改提示,不要让编辑控件遮挡展示内容。
建议的项目内输出结构:
```text
presentations/<slug>/
├── index.html
└── assets/
└── ...项目素材...
```
如果项目已有明确的成果目录,沿用项目目录约定。不要为了生成演示删除或覆盖用户已有文件。
### 4. 浏览器验收
使用 `agent-browser` 或当前项目提供的浏览器能力打开最终 HTML至少检查
- 标题页和每张幻灯片都能到达,只有当前页可见;
- 1920×1080 舞台在桌面窗口和窄窗口都保持比例并完整显示;
- 没有文字越界、卡片重叠、滚动条、空白页或被控件遮挡;
- 项目图片、图标和相对路径资源全部加载;
- Console 没有错误,方向键、空格和触摸/鼠标导航可用;
- 开启减少动态效果时内容仍然完整可读。
修改后重新打开或刷新页面再验收,不要只根据源码推断视觉结果。完成后清理 `.frontend-slides/slide-previews/`,除非用户希望保留风格草图。
## 交付说明
告诉用户HTML 文件位置、页数、使用的视觉方向和操作方式。说明这是本地可播放演示,不是 PPTX 文件;如果用户需要其他格式,先说明当前 Skill 的边界,不要自行引入云服务或新的远程依赖。
## 许可证
本 Skill 改造自 Zara Zhang 的 `frontend-slides` 项目,相关版权和 MIT 许可证保存在同目录的 `LICENSE` 文件中。不要移除该声明。

View File

@@ -0,0 +1,4 @@
interface:
display_name: "项目演示"
short_description: "将项目内容变成适合孩子展示的可播放 HTML 幻灯片"
default_prompt: "使用 $frontend-slides 把当前项目整理成一份适合孩子展示的 HTML 幻灯片。"

View File

@@ -0,0 +1,47 @@
# 动画参考
动画服务于讲解顺序,不应成为内容的前提。每页最多使用一到两种运动方式,并保证减少动态效果时仍然完整可读。
## 情绪与选择
| 情绪 | 推荐 | 避免 |
| --- | --- | --- |
| 活泼友好 | 轻微弹性、错峰出现、短距离上移 | 连续旋转、强闪烁 |
| 科学探索 | 线条绘制、步骤依次出现、数字轻微递进 | 粒子铺满屏幕 |
| 故事叙述 | 淡入、横向推进、图片轻微放大 | 每个元素都飞入 |
| 清楚正式 | 200350ms 淡入或位移 | 复杂转场、视差 |
| 安静专注 | 500800ms 淡入、很小的位移 | 夸张弹跳 |
## 通用 CSS
```css
.reveal {
opacity: 0;
transform: translateY(28px);
transition: opacity .55s ease, transform .55s ease;
}
.slide.visible .reveal {
opacity: 1;
transform: translateY(0);
}
.slide.visible .reveal:nth-child(2) { transition-delay: .08s; }
.slide.visible .reveal:nth-child(3) { transition-delay: .16s; }
@media (prefers-reduced-motion: reduce) {
.reveal,
.slide.visible .reveal {
opacity: 1;
transform: none;
transition: none;
}
}
```
## 验收重点
- 切页时先切换可见状态,再触发内容动画;
- 不用 `display: none` 作为幻灯片切换机制;
- 不在每页添加同样的装饰动画;
- 检查动画结束后文字仍在原位置,不能出现溢出或重叠。

View File

@@ -0,0 +1,132 @@
# HTML 幻灯片结构参考
每份演示都是一个可独立打开的 HTML 文件。把 [viewport-base.css](viewport-base.css) 的完整内容放在 `<style>` 中,再加入项目自己的变量和组件样式。
## 最小结构
```html
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>项目演示</title>
<style>
:root {
--stage-bg: #eef3ef;
--slide-bg: #fffdf7;
--ink: #17332a;
--muted: #5c6f66;
--accent: #f08a63;
--display-font: "Trebuchet MS", "PingFang SC", sans-serif;
--body-font: "Avenir Next", "PingFang SC", sans-serif;
}
* { box-sizing: border-box; }
/* 在此处粘贴 viewport-base.css 的完整内容。 */
.slide-inner {
width: 100%;
height: 100%;
padding: 92px;
color: var(--ink);
font-family: var(--body-font);
}
.reveal {
opacity: 0;
transform: translateY(28px);
transition: opacity .55s ease, transform .55s ease;
}
.slide.visible .reveal {
opacity: 1;
transform: translateY(0);
}
.reveal:nth-child(2) { transition-delay: .08s; }
.reveal:nth-child(3) { transition-delay: .16s; }
</style>
</head>
<body>
<div class="deck-viewport" aria-label="项目演示">
<main class="deck-stage" id="deckStage">
<section class="slide title-slide active visible" aria-label="第 1 页">
<div class="slide-inner">
<h1 class="reveal">项目标题</h1>
<p class="reveal">一句话说明项目解决了什么问题</p>
</div>
</section>
<!-- 其余页使用同样的 .slide 结构。 -->
</main>
</div>
<div class="deck-controls" aria-label="演示控制">
<span id="pageCount">1 / 1</span>
</div>
<script>
class SlidePresentation {
constructor() {
this.slides = [...document.querySelectorAll('.slide')];
this.stage = document.querySelector('#deckStage');
this.pageCount = document.querySelector('#pageCount');
this.index = 0;
this.startX = null;
this.scaleStage();
window.addEventListener('resize', () => this.scaleStage());
window.addEventListener('keydown', (event) => this.onKey(event));
window.addEventListener('wheel', (event) => {
if (Math.abs(event.deltaY) > 12) this.show(this.index + Math.sign(event.deltaY));
}, { passive: true });
window.addEventListener('touchstart', (event) => {
this.startX = event.touches[0]?.clientX ?? null;
}, { passive: true });
window.addEventListener('touchend', (event) => {
if (this.startX === null) return;
const endX = event.changedTouches[0]?.clientX ?? this.startX;
if (Math.abs(endX - this.startX) > 40) this.show(this.index + (endX < this.startX ? 1 : -1));
this.startX = null;
}, { passive: true });
this.show(0);
}
scaleStage() {
const factor = Math.min(window.innerWidth / 1920, window.innerHeight / 1080);
const x = (window.innerWidth - 1920 * factor) / 2;
const y = (window.innerHeight - 1080 * factor) / 2;
this.stage.style.transform = `translate(${x}px, ${y}px) scale(${factor})`;
}
onKey(event) {
if (['ArrowRight', 'ArrowDown', 'PageDown', ' '].includes(event.key)) {
event.preventDefault(); this.show(this.index + 1);
}
if (['ArrowLeft', 'ArrowUp', 'PageUp'].includes(event.key)) {
event.preventDefault(); this.show(this.index - 1);
}
}
show(index) {
this.index = Math.max(0, Math.min(index, this.slides.length - 1));
this.slides.forEach((slide, slideIndex) => {
const active = slideIndex === this.index;
slide.classList.toggle('active', active);
slide.classList.toggle('visible', active);
slide.setAttribute('aria-hidden', String(!active));
});
if (this.pageCount) this.pageCount.textContent = `${this.index + 1} / ${this.slides.length}`;
}
}
new SlidePresentation();
</script>
</body>
</html>
```
## 内容和可访问性
- 使用一个明确的 `<h1>`,后续页面使用有顺序的 `<h2>`;图片提供简短 `alt` 文本。
- 不把重要内容只放在动画、颜色或悬停状态里;对比度要足够。
- 为每个 `.slide``aria-label`,并在切换时同步 `aria-hidden`
- 不在幻灯片内放调试按钮、文件路径、模板名称、内部提示或生成过程文字。
- 通过相对路径引用 `assets/`,不要把用户机器路径写入 HTML。

View File

@@ -0,0 +1,48 @@
# 项目演示风格参考
先根据项目主题和观众选择一个清晰的视觉方向,再扩展到完整演示。默认优先浅色、明亮和高对比度方案;不要使用大量渐变、紫色科技卡片或无法解释的装饰。
## 适合孩子项目的方向
### 纸张与贴纸
- 感受:像项目手册,亲切、清楚、有一点手作感。
- 颜色:暖白底、墨绿文字、珊瑚和柠檬黄点缀。
- 结构:大标题、纸张边缘、少量标签和手绘感 CSS 线条。
- 适合:校园项目、生活观察、故事类作品。
### 彩色积木
- 感受:活泼、简单、适合快速讲解。
- 颜色:奶油白底,天蓝、草绿、橙色和粉色作为区块色。
- 结构:大色块、圆角但不过度、每页一个核心卡片或图形。
- 适合:小游戏、机器人、互动小程序。
### 小小实验室
- 感受:清楚、好奇、像科学展板。
- 颜色:浅灰白底、深蓝文字、青绿色和橙色高亮。
- 结构:步骤编号、观察结果、前后对比和简短结论。
- 适合实验、数据观察、AI 探索、制作过程。
### 故事地图
- 感受:有起点、过程和结果,帮助孩子按故事讲项目。
- 颜色:浅米色底、森林绿、砖红和天蓝。
- 结构:时间线、路线、角色或场景卡片;每页最多一个复杂图。
- 适合:问题解决、公益主题、创意产品。
### 清爽展板
- 感受:更正式但不严肃,适合学校汇报。
- 颜色:白底、深灰文字、单一品牌色和浅色辅助线。
- 结构:标题、证据、截图、结论,留出充足空白。
- 适合:阅读型报告、比赛答辩、作品集。
## 选择规则
- 主题和已有素材优先于个人偏好;有真实截图时围绕截图建立布局。
- 三个草图必须在字体、构图和色彩关系上真正不同,不能只是换背景色。
- 文字多时增加页数,不要压缩字号;标题要能在教室屏幕上读清楚。
- 每页最多 3 个重点;过程类项目优先使用“问题 → 尝试 → 结果 → 下一步”。
- 动画只用来强调顺序和变化,不能依赖动画才能读懂内容。

View File

@@ -0,0 +1,123 @@
/*
* Fixed 16:9 stage. Include this whole file in every generated deck.
* Slides are authored at 1920x1080 and scaled as one unit.
*/
html,
body {
width: 100%;
height: 100%;
margin: 0;
overflow: hidden;
background: var(--stage-bg, #f4f7f5);
}
.deck-viewport {
position: fixed;
inset: 0;
overflow: hidden;
background: var(--stage-bg, #f4f7f5);
}
.deck-stage {
position: absolute;
left: 0;
top: 0;
width: 1920px;
height: 1080px;
overflow: hidden;
transform-origin: 0 0;
background: var(--slide-bg, #fffdf7);
}
.slide {
position: absolute;
inset: 0;
width: 1920px;
height: 1080px;
overflow: hidden;
display: block;
visibility: hidden;
opacity: 0;
pointer-events: none;
background: var(--slide-bg, #fffdf7);
}
.slide.active,
.slide.visible {
visibility: visible;
opacity: 1;
pointer-events: auto;
z-index: 1;
}
img,
video,
canvas,
svg {
max-width: 100%;
max-height: 100%;
}
.deck-controls {
position: fixed;
left: 50%;
bottom: 22px;
transform: translateX(-50%);
z-index: 1000;
}
@media print {
html,
body {
width: 1920px;
height: auto;
overflow: visible;
background: #fff;
}
.deck-viewport {
position: static;
overflow: visible;
background: #fff;
}
.deck-stage {
position: static;
width: auto;
height: auto;
transform: none !important;
background: none;
}
.slide {
position: relative;
display: block !important;
visibility: visible !important;
opacity: 1 !important;
pointer-events: auto !important;
width: 1920px;
height: 1080px;
break-after: page;
page-break-after: always;
}
.slide:last-child {
break-after: auto;
page-break-after: auto;
}
.deck-controls {
display: none !important;
}
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}