feat: add bundled OpenCode skills and refine module flow
This commit is contained in:
21
.opencode/skills/frontend-slides/LICENSE
Normal file
21
.opencode/skills/frontend-slides/LICENSE
Normal 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.
|
||||
93
.opencode/skills/frontend-slides/SKILL.md
Normal file
93
.opencode/skills/frontend-slides/SKILL.md
Normal 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. 预计讲几分钟(默认 5–8 张);
|
||||
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` 文件中。不要移除该声明。
|
||||
4
.opencode/skills/frontend-slides/agents/openai.yaml
Normal file
4
.opencode/skills/frontend-slides/agents/openai.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "项目演示"
|
||||
short_description: "将项目内容变成适合孩子展示的可播放 HTML 幻灯片"
|
||||
default_prompt: "使用 $frontend-slides 把当前项目整理成一份适合孩子展示的 HTML 幻灯片。"
|
||||
@@ -0,0 +1,47 @@
|
||||
# 动画参考
|
||||
|
||||
动画服务于讲解顺序,不应成为内容的前提。每页最多使用一到两种运动方式,并保证减少动态效果时仍然完整可读。
|
||||
|
||||
## 情绪与选择
|
||||
|
||||
| 情绪 | 推荐 | 避免 |
|
||||
| --- | --- | --- |
|
||||
| 活泼友好 | 轻微弹性、错峰出现、短距离上移 | 连续旋转、强闪烁 |
|
||||
| 科学探索 | 线条绘制、步骤依次出现、数字轻微递进 | 粒子铺满屏幕 |
|
||||
| 故事叙述 | 淡入、横向推进、图片轻微放大 | 每个元素都飞入 |
|
||||
| 清楚正式 | 200–350ms 淡入或位移 | 复杂转场、视差 |
|
||||
| 安静专注 | 500–800ms 淡入、很小的位移 | 夸张弹跳 |
|
||||
|
||||
## 通用 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` 作为幻灯片切换机制;
|
||||
- 不在每页添加同样的装饰动画;
|
||||
- 检查动画结束后文字仍在原位置,不能出现溢出或重叠。
|
||||
132
.opencode/skills/frontend-slides/references/html-template.md
Normal file
132
.opencode/skills/frontend-slides/references/html-template.md
Normal 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。
|
||||
48
.opencode/skills/frontend-slides/references/style-presets.md
Normal file
48
.opencode/skills/frontend-slides/references/style-presets.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# 项目演示风格参考
|
||||
|
||||
先根据项目主题和观众选择一个清晰的视觉方向,再扩展到完整演示。默认优先浅色、明亮和高对比度方案;不要使用大量渐变、紫色科技卡片或无法解释的装饰。
|
||||
|
||||
## 适合孩子项目的方向
|
||||
|
||||
### 纸张与贴纸
|
||||
|
||||
- 感受:像项目手册,亲切、清楚、有一点手作感。
|
||||
- 颜色:暖白底、墨绿文字、珊瑚和柠檬黄点缀。
|
||||
- 结构:大标题、纸张边缘、少量标签和手绘感 CSS 线条。
|
||||
- 适合:校园项目、生活观察、故事类作品。
|
||||
|
||||
### 彩色积木
|
||||
|
||||
- 感受:活泼、简单、适合快速讲解。
|
||||
- 颜色:奶油白底,天蓝、草绿、橙色和粉色作为区块色。
|
||||
- 结构:大色块、圆角但不过度、每页一个核心卡片或图形。
|
||||
- 适合:小游戏、机器人、互动小程序。
|
||||
|
||||
### 小小实验室
|
||||
|
||||
- 感受:清楚、好奇、像科学展板。
|
||||
- 颜色:浅灰白底、深蓝文字、青绿色和橙色高亮。
|
||||
- 结构:步骤编号、观察结果、前后对比和简短结论。
|
||||
- 适合:实验、数据观察、AI 探索、制作过程。
|
||||
|
||||
### 故事地图
|
||||
|
||||
- 感受:有起点、过程和结果,帮助孩子按故事讲项目。
|
||||
- 颜色:浅米色底、森林绿、砖红和天蓝。
|
||||
- 结构:时间线、路线、角色或场景卡片;每页最多一个复杂图。
|
||||
- 适合:问题解决、公益主题、创意产品。
|
||||
|
||||
### 清爽展板
|
||||
|
||||
- 感受:更正式但不严肃,适合学校汇报。
|
||||
- 颜色:白底、深灰文字、单一品牌色和浅色辅助线。
|
||||
- 结构:标题、证据、截图、结论,留出充足空白。
|
||||
- 适合:阅读型报告、比赛答辩、作品集。
|
||||
|
||||
## 选择规则
|
||||
|
||||
- 主题和已有素材优先于个人偏好;有真实截图时围绕截图建立布局。
|
||||
- 三个草图必须在字体、构图和色彩关系上真正不同,不能只是换背景色。
|
||||
- 文字多时增加页数,不要压缩字号;标题要能在教室屏幕上读清楚。
|
||||
- 每页最多 3 个重点;过程类项目优先使用“问题 → 尝试 → 结果 → 下一步”。
|
||||
- 动画只用来强调顺序和变化,不能依赖动画才能读懂内容。
|
||||
123
.opencode/skills/frontend-slides/references/viewport-base.css
Normal file
123
.opencode/skills/frontend-slides/references/viewport-base.css
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user